diff --git a/.gitattributes b/.gitattributes index 590f276f50..a2047fd34c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,4 @@ /.github/ export-ignore /benchmark/ export-ignore /core-tests/ export-ignore -/.travis.yml export-ignore *.h linguist-language=cpp diff --git a/.github/ISSUE-CN.md b/.github/ISSUE-CN.md deleted file mode 100644 index c08b7485d9..0000000000 --- a/.github/ISSUE-CN.md +++ /dev/null @@ -1,89 +0,0 @@ -[English](./ISSUE.md) | 中文 - -错误报告 -=========== - -# 需知 - -当你觉得发现了一个Swoole内核的BUG时, 请提出报告. -Swoole的内核开发者们或许还不知道问题的存在, -除非你主动提出报告, 否则BUG也许将很难被发现并修复, -你可以在[Github的issue区](https://github.com/swoole/swoole-src/issues)提出错误报告(即点击右上角绿色的`New issue`按钮), 这里的错误报告将会被最优先解决. -请不要在邮件列表或私人信件中发送错误报告, GitHub的issue区同样可以提出对于Swoole的任何要求与建议. - -在你提交错误报告之前, 请先阅读以下的**如何提交错误报告**. - -## 新建问题 - -首先在创建issue的同时, 系统将会给出如下模板, 请你认真填写它, 否则issue由于缺乏信息可能会被忽略: - -```markdown - -Please answer these questions before submitting your issue. Thanks! -> 在提交Issue前请回答以下问题: - -1. What did you do? If possible, provide a simple script for reproducing the error. -> 请详细描述问题的产生过程,贴出相关的代码,最好能提供一份可稳定重现的简单脚本代码。 - -2. What did you expect to see? -> 期望的结果是什么? - -3. What did you see instead? -> 实际运行的结果是什么? - -4. What version of Swoole are you using (`php --ri swoole`)? -> 你的版本? 贴出 `php --ri swoole` 所打印的内容 - -5. What is your machine environment used (including the version of kernel & php & gcc)? -> 你使用的机器系统环境是什么(包括内核、PHP、gcc编译器版本信息)? -> 可以使用`uname -a`, `php -v`, `gcc -v` 命令打印 - -``` - -其中, 最为关键的是提供**可稳定重现的简单脚本代码**, 否则你必须提供尽可能多的其它信息来帮助开发者判断错误原因 - -## 内存分析 (强烈推荐) - -更多时候, Valgrind比gdb更能发现内存问题, 通过以下指令运行你的程序, 直到触发BUG - -```shell -USE_ZEND_ALLOC=0 valgrind --log-file=/tmp/valgrind.log php your_file.php -``` - -* 当程序发生错误时, 可以通过键入 `ctrl+c` 退出, 然后上传 `/tmp/valgrind.log` 文件以便于开发组定位BUG. - -## 关于段错误(核心转储) - -此外, 在一种特殊情况下你可以使用调试工具来帮助开发者定位问题 - -```shell -WARNING swManager_check_exit_status: worker#1 abnormal exit, status=0, signal=11 -``` - -当如上提示出现在Swoole日志中(signal11), 说明程序发生了`核心转储`, 你需要使用跟踪调试工具来确定其发生位置 - -> 使用`gdb`来跟踪`swoole`前, 需要在编译时添加`--enable-debug`参数以保留更多信息 - -开启核心转储文件 -```shell -ulimit -c unlimited -``` - -触发BUG, 核心转储文件会生成在 程序目录 或 系统根目录 或 `/cores` 目录下 (取决于你的系统配置), 键入以下命令进入gdb调试程序 -``` -gdb php core -gdb php /tmp/core.4596 -``` - -紧接着输入`bt`并回车, 就可以看到出现问题的调用栈 -``` -(gdb) bt -``` - -可以通过键入 `f 数字` 来查看指定的调用栈帧 -``` -(gdb)f 1 -(gdb)f 0 -``` - -将以上信息都贴在issue中 diff --git a/.github/ISSUE.md b/.github/ISSUE.md deleted file mode 100644 index 3cbe490867..0000000000 --- a/.github/ISSUE.md +++ /dev/null @@ -1,76 +0,0 @@ -English | [中文](./ISSUE-CN.md) - -# Bug reports - -## Instruction - -If you think you have found a bug in Swoole, please report it. -The Swoole developers probably don't know about it, -and unless you report it, chances are it won't be fixed. -You can report bugs at https://github.com/swoole/swoole-src/issues. -Please do not send bug reports in the mailing list or personal letters. -The issue page is also suitable to submit feature requests. - -Please read the **How to report a bug document** before submitting any bug reports. - -## New issue - -First, while creating an issue, the system will give the following template: - -```markdown -Please answer these questions before submitting your issue. Thanks! -1. What did you do? If possible, provide a simple script for reproducing the error. -2. What did you expect to see? -3. What did you see instead? -4. What version of Swoole are you using (`php --ri swoole`)? -5. What is your machine environment used (including the version of kernel & php & gcc)? -``` -The most important thing is to provide a simple script for reproducing the error, otherwise, you must provide as much information as possible. - -## Memory detection (recommended) - -In addition to using `gdb` analysis, you can use the `valgrind` tool to check if the program is working properly. - -```shell -USE_ZEND_ALLOC=0 valgrind --log-file=/tmp/valgrind.log php your_file.php -``` - -* After the program is executed to the wrong location, `ctrl+c` is interrupted, and upload the `/tmp/valgrind.log` file. - -## CoreDump - -Besides, In a special case, you can use debugging tools to help developers locate problems - -```shell -WARNING swManager_check_exit_status: worker#1 abnormal exit, status=0, signal=11 -``` - -When a segmentation error occurs with Swoole, You can use the `gdb` tool and use `bt` command. -> Using `gdb` to track the core file need to add the `--enable-debug` parameter when compiling `swoole`. - -Enable core dump -```shell -ulimit -c unlimited -``` - -Use `gdb` to view the `core dump` information. The `core` file is usually in the current directory. If the operating system does the processing, put the `core dump` file in another directory, please replace it with the corresponding path. -``` -gdb php core -gdb php /tmp/core.4596 -``` - -Enter bt under gdb to view the call stack information. -``` -(gdb) bt -``` -Use the f command in gdb to view the code segment corresponding to the ID. -``` -(gdb)f 1 -(gdb)f 0 -``` - -If there is no function call stack information, it may be that the compiler has removed the debug information. Please manually modify the `Makefile` file in the swoole source directory and modify CFLAGS to - -```shell -CFLAGS = -Wall -pthread -g -O0 -``` diff --git a/.github/workflows/alpine.Dockerfile b/.github/workflows/alpine.Dockerfile index 96945df3de..6eb9b2c9c4 100644 --- a/.github/workflows/alpine.Dockerfile +++ b/.github/workflows/alpine.Dockerfile @@ -1,23 +1,25 @@ ARG PHP_VERSION ARG ALPINE_VERSION -FROM hyperf/hyperf:${PHP_VERSION}-alpine-v${ALPINE_VERSION}-dev +FROM phpswoole/php:${PHP_VERSION}-alpine LABEL maintainer="Swoole Team " version="1.0" license="Apache2" ARG PHP_VERSION -COPY . /opt/www +COPY . /swoole -WORKDIR /opt/www +WORKDIR /swoole + +RUN apk add liburing-dev RUN set -ex \ && phpize \ - && ./configure --enable-openssl --enable-swoole-curl \ - && make -s -j$(nproc) && make install \ - && echo "extension=swoole.so" > /etc/php${PHP_VERSION%\.*}/conf.d/50_swoole.ini \ - # check - && php -v \ - && php -m \ - && php --ri swoole \ - && echo -e "\033[42;37m Build Completed :).\033[0m\n" + && ./configure --enable-swoole-curl --enable-iouring \ + && make -s -j$(nproc) && make install + +RUN echo "extension=swoole.so" > "/usr/local/etc/php/conf.d/swoole.ini" +RUN php -v +RUN php -m +RUN php --ri swoole +RUN echo -e "\033[42;37m Build Completed :).\033[0m\n" diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml new file mode 100644 index 0000000000..276109c0b6 --- /dev/null +++ b/.github/workflows/core.yml @@ -0,0 +1,72 @@ +name: Core Tests + +on: [ push, pull_request ] + +env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + +jobs: + ubuntu: + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[core]')" + timeout-minutes: 12 + services: + tinyproxy: + image: "vimagick/tinyproxy" + ports: + - 8888:8888 + nginx: + image: "nginx" + ports: + - "80:80" + env: + NGINX_PORT: "[::]:80" + socks5: + image: "xkuma/socks5" + ports: + - 8080:1080 + env: + PROXY_USER: user + PROXY_PASSWORD: password + PROXY_SERVER: 0.0.0.0:1080 + socks5-no-auth: + image: "xkuma/socks5" + ports: + - 8081:1080 + env: + PROXY_SERVER: 0.0.0.0:1080 + + steps: + - uses: actions/checkout@v6 + + - name: install dependencies + run: sudo apt update -y && sudo apt install -y googletest libgtest-dev libnghttp2-dev libboost-stacktrace-dev libbrotli-dev redis-server nodejs npm nghttp2-client + + - name: install liburing + run: sudo bash ./scripts/install-liburing.sh + + - name: configure + run: phpize && ./configure --enable-sockets --enable-mysqlnd --enable-iouring + + - name: build + run: | + cmake . -D CODE_COVERAGE=ON -D enable_thread=1 -D verbose=1 -D async_io=1 || exit 1 + make VERBOSE=1 -j $(nproc) core-tests || exit 1 + + - name: run tests + run: | + ./run-core-tests.sh + + - name: run coverage + shell: bash + run: sudo apt-get install lcov && + sudo lcov --directory . --capture --branch-coverage --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --output-file coverage.info && + sudo lcov --remove coverage.info '/usr/*' --output-file coverage.info && + sudo lcov --list coverage.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.info + fail_ci_if_error: true diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index c32c29cff0..d79ab2c715 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -16,14 +16,14 @@ env: jobs: coverity-scan: - if: github.repository_owner == 'swoole' + if: "github.repository_owner == 'swoole' && (!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[coverity]'))" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: "8.0" + php-version: "8.3" - name: Download and install Coverity Build Tool run: | @@ -36,13 +36,13 @@ jobs: - name: Run build steps run: | - sudo phpize && ./configure + sudo phpize && ./configure --enable-mysqlnd --enable-sockets --enable-swoole-curl - name: Run Coverity Scan Analysis Tool run: | export PATH=${COV_TOOLS_DIR}/bin:$PATH cd ${COV_BUILD_DIR} - cov-build --dir ${COV_RESULTS_DIR} make -j 4 + cov-build --dir ${COV_RESULTS_DIR} make -j $(nproc) - name: Upload Coverity Scan Analysis results run: | diff --git a/.github/workflows/ext.yml b/.github/workflows/ext.yml index 0edd0f6218..ba743f7336 100644 --- a/.github/workflows/ext.yml +++ b/.github/workflows/ext.yml @@ -1,81 +1,78 @@ -name: ext-swoole +name: Compile Tests -on: [push, pull_request] +on: [ push, pull_request ] + +env: + CPPFLAGS: "-I/opt/homebrew/opt/pcre2/include/" jobs: build-ubuntu-latest: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[ubuntu]')" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: install-deps - run: sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev - - name: phpize - run: phpize - - name: build1 - run: ./configure && - make clean && make -j$(nproc) - - name: build2 - run: ./configure --enable-sockets && - make clean && make -j$(nproc) - - name: build3 - run: ./configure --enable-sockets --enable-mysqlnd && - make clean && make -j$(nproc) - - name: build5 - run: ./configure --enable-sockets --enable-mysqlnd --enable-openssl && - make clean && make -j$(nproc) - - name: build6 - run: ./configure --enable-sockets --enable-mysqlnd --enable-openssl --enable-debug-log && - make clean && make -j$(nproc) - - name: build7 - run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-openssl --enable-debug-log && - make clean && make -j$(nproc) - - name: build8 - run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-openssl --enable-cares --enable-debug-log && - make clean && make -j$(nproc) + - uses: actions/checkout@v6 + - name: install-deps + run: sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev + - name: phpize + run: phpize + - name: build1 + run: ./configure && + make clean && make -j$(nproc) + - name: build2 + run: ./configure --enable-sockets && + make clean && make -j$(nproc) + - name: build5 + run: ./configure --enable-sockets --enable-mysqlnd && + make clean && make -j$(nproc) + - name: build6 + run: ./configure --enable-sockets --enable-mysqlnd --enable-debug-log && + make clean && make -j$(nproc) + - name: build7 + run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-debug-log && + make clean && make -j$(nproc) + - name: build8 + run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-debug-log && + make clean && make -j$(nproc) + - name: build with thread context + run: ./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-debug-log --enable-thread-context && + make clean && make -j$(nproc) build-macos-latest: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[macos]')" runs-on: macos-latest steps: - - name: install dependencies - run: brew install openssl && brew link openssl && brew install curl && brew link curl && brew install c-ares && brew link c-ares - - uses: actions/checkout@v3 - - name: phpize - run: phpize - - name: build1 - run: ./configure && make clean && make -j$(sysctl -n hw.ncpu) - - name: build2 - run: ./configure --enable-sockets && - make clean && make -j$(sysctl -n hw.ncpu) - - name: build3 - run: ./configure --enable-sockets --enable-mysqlnd && - make clean && make -j$(sysctl -n hw.ncpu) - - name: build5 - run: ./configure --enable-sockets --enable-mysqlnd --enable-openssl - --with-openssl-dir=/usr/local/opt/openssl@1.1 && - make clean && make -j$(sysctl -n hw.ncpu) - - name: build6 - run: ./configure --enable-sockets --enable-mysqlnd --enable-openssl --enable-swoole-curl --enable-debug-log - --with-openssl-dir=/usr/local/opt/openssl@1.1 && - make clean && make -j$(sysctl -n hw.ncpu) - - name: build7 - run: ./configure --enable-sockets --enable-mysqlnd --enable-openssl --enable-swoole-curl --enable-debug-log --enable-cares - --with-openssl-dir=/usr/local/opt/openssl@1.1 && - make clean && make -j$(sysctl -n hw.ncpu) + - name: install dependencies + run: brew reinstall php + - uses: actions/checkout@v6 + - name: phpize + run: phpize + - name: build1 + run: ./configure CPPFLAGS="${CPPFLAGS}" && make clean && make -j$(sysctl -n hw.ncpu) + - name: build2 + run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets && + make clean && make -j$(sysctl -n hw.ncpu) + - name: build5 + run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd && + make clean && make -j$(sysctl -n hw.ncpu) + - name: build6 + run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-debug-log && + make clean && make -j$(sysctl -n hw.ncpu) + - name: build7 + run: ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl + --enable-debug-log --enable-cares && + make clean && make -j$(sysctl -n hw.ncpu) build-alpine-latest: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[alpine]')" runs-on: ubuntu-latest strategy: matrix: - php-version: [ '8.0', '8.1' ] - alpine-version: [ '3.12', '3.13', '3.14', '3.15' ] - exclude: - - php-version: '8.1' - alpine-version: '3.15' + php-version: [ '8.2', '8.3', '8.4', '8.5' ] max-parallel: 8 fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: build run: | cp .github/workflows/alpine.Dockerfile alpine.Dockerfile - docker build -t swoole . -f alpine.Dockerfile --build-arg PHP_VERSION=${{ matrix.php-version }} --build-arg ALPINE_VERSION=${{ matrix.alpine-version }} + docker build -t swoole . -f alpine.Dockerfile --build-arg PHP_VERSION=${{ matrix.php-version }} diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index 881cee5dd1..8d781d762b 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -1,4 +1,4 @@ -name: Frameworks Tests +name: Framework Tests on: push: @@ -7,15 +7,16 @@ on: jobs: linux: runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[framework]')" strategy: fail-fast: false matrix: - php-version: [ '8.0', '8.1' ] + php-version: [ '8.2', '8.3', '8.4', '8.5' ] framework: [ 'Laravel Octane', 'Hyperf', 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -28,46 +29,48 @@ jobs: - name: Build Swoole run: | - sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev + sudo apt update -y && sudo apt install -y libcurl4-openssl-dev php-curl libc-ares-dev libpq-dev valgrind phpize - ./configure --enable-openssl --enable-mysqlnd --enable-swoole-curl --enable-cares + ./configure --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-swoole-pgsql make -j$(nproc) sudo make install + php -v + php -m + php --ini php --ri swoole - name: Laravel Octane Tests - if: matrix.framework == 'Laravel Octane' + if: matrix.framework == 'Laravel Octane' && matrix.php-version != '8.5' run: | git clone https://github.com/laravel/octane.git --depth=1 cd octane/ - composer require laravel/framework:"^8.35" --no-update composer update --prefer-dist --no-interaction --no-progress - vendor/bin/phpunit --verbose + vendor/bin/testbench package:sync-skeleton + vendor/bin/phpunit --display-deprecations --fail-on-deprecation - name: Hyperf Tests - if: matrix.framework == 'Hyperf' + if: matrix.framework == 'Hyperf' && matrix.php-version != '8.5' env: SW_VERSION: 'master' MYSQL_VERSION: '5.7' + PGSQL_VERSION: '14' run: | git clone https://github.com/hyperf/hyperf.git --depth=1 cd hyperf/ composer update -o - docker run --name mysql -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=true -d mysql:${MYSQL_VERSION} --bind-address=0.0.0.0 --default-authentication-plugin=mysql_native_password - docker run --name redis -p 6379:6379 -d redis - docker run -d --name dev-consul -e CONSUL_BIND_INTERFACE=eth0 -p 8500:8500 consul - docker run --name nsq -p 4150:4150 -p 4151:4151 -p 4160:4160 -p 4161:4161 -p 4170:4170 -p 4171:4171 --entrypoint /bin/nsqd -d nsqio/nsq:latest - docker run -d --restart=always --name rabbitmq -p 4369:4369 -p 5672:5672 -p 15672:15672 -p 25672:25672 rabbitmq:management-alpine - docker build --tag grpc-server:latest src/grpc-client/tests/Mock - docker run -d --name grpc-server -p 50051:50051 grpc-server:latest - docker build -t tcp-server:latest .travis/tcp_server - docker run -d --name tcp-server -p 10001:10001 tcp-server:latest + ./.travis/requirement.install.sh + ./.travis/setup.services.sh export TRAVIS_BUILD_DIR=$(pwd) && bash ./.travis/setup.mysql.sh + export TRAVIS_BUILD_DIR=$(pwd) && bash ./.travis/setup.pgsql.sh cp .travis/.env.example .env - composer analyse src - composer test -- --exclude-group NonCoroutine - vendor/bin/phpunit --group NonCoroutine - vendor/bin/phpunit src/filesystem --group NonCoroutine + export SWOOLE_BRANCH=${GITHUB_REF##*/} + if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then + USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' bin/co-phpunit --exclude-group NonCoroutine + USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' vendor/bin/phpunit --group NonCoroutine + USE_ZEND_ALLOC=0 valgrind php -dswoole.use_shortname='Off' vendor/bin/phpunit src/filesystem --group NonCoroutine + else + .travis/run.test.sh + fi - name: Simps Tests if: matrix.framework == 'Simps' @@ -78,16 +81,17 @@ jobs: composer test macos: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[framework]')" runs-on: macos-latest strategy: fail-fast: false matrix: - php-version: [ '8.0', '8.1' ] + php-version: [ '8.2', '8.3', '8.4', '8.5' ] framework: [ 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} - macOS steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -100,10 +104,10 @@ jobs: - name: Build Swoole run: | - brew install openssl && brew link openssl - brew install c-ares && brew link c-ares phpize - ./configure --enable-openssl --with-openssl-dir=/usr/local/opt/openssl@1.1 --enable-mysqlnd --enable-swoole-curl --enable-cares + export CPPFLAGS="${CPPFLAGS} -I/opt/homebrew/opt/pcre2/include/" + export CFLAGS="${CFLAGS} -I/opt/homebrew/opt/pcre2/include/" + ./configure --enable-mysqlnd --enable-swoole-curl --enable-cares make -j$(sysctl -n hw.ncpu) sudo make install php --ri swoole @@ -115,3 +119,4 @@ jobs: cd mqtt/ composer install -o composer test + diff --git a/.github/workflows/iouring.yml b/.github/workflows/iouring.yml new file mode 100644 index 0000000000..df286b8514 --- /dev/null +++ b/.github/workflows/iouring.yml @@ -0,0 +1,41 @@ +name: Linux io_uring Tests + +on: [push, pull_request] + +jobs: + linux: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[iouring]')" + strategy: + fail-fast: false + matrix: + php: [ '8.2', '8.3', '8.4', '8.5' ] + os: [ ubuntu-24.04, ubuntu-24.04-arm ] + name: ${{ matrix.php }}-${{ matrix.os }}-iouring + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + coverage: none + - name: Show machine information + run: | + date + env + uname -a + ulimit -a + php -v + php --ini + ls -al + pwd + echo "`git log -20 --pretty --oneline`" + echo "`git log -10 --stat --pretty --oneline`" + - name: Run Swoole test + run: | + export SWOOLE_CI_TYPE=IOURING + export SWOOLE_BRANCH=${GITHUB_REF##*/} + export SWOOLE_BUILD_DIR=$(realpath .) + export PHP_VERSION=${{ matrix.php }} + ${SWOOLE_BUILD_DIR}/scripts/route.sh + diff --git a/.github/workflows/lib.yml b/.github/workflows/lib.yml deleted file mode 100644 index fa248b01a2..0000000000 --- a/.github/workflows/lib.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: lib-swoole - -on: [ push, pull_request ] - -env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - -jobs: - build: - runs-on: ubuntu-latest - - services: - tinyproxy: - image: "vimagick/tinyproxy" - ports: - - 8888:8888 - socks5: - image: "xkuma/socks5" - ports: - - 1080:1080 - env: - PROXY_USER: user - PROXY_PASSWORD: password - PROXY_SERVER: 0.0.0.0:1080 - - steps: - - uses: actions/checkout@v3 - - - name: install dependencies - run: sudo apt update -y && sudo apt install -y googletest libgtest-dev redis-server libboost-stacktrace-dev libbrotli-dev - - - name: configure - run: phpize && ./configure --enable-sockets --enable-mysqlnd --enable-openssl - - - name: make - run: cmake . -DCODE_COVERAGE=ON && - make VERBOSE=1 -j && - sudo make install - - - name: make test - run: - cd core-tests && ./run.sh - - - name: run coverage - shell: bash - run: sudo apt-get install lcov && - sudo lcov --directory . --capture --output-file coverage.info && - sudo lcov --remove coverage.info "${{runner.workspace}}/swoole-src/include/*" '/usr/*' --output-file coverage.info && - sudo lcov --list coverage.info - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ./coverage.info - fail_ci_if_error: true diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml deleted file mode 100644 index dc42ad9031..0000000000 --- a/.github/workflows/mirror.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: mirror - -on: [ push, delete, create ] - -jobs: - gitee: - if: github.repository_owner == 'swoole' - runs-on: ubuntu-latest - steps: - - name: Configure Private Key - env: - SSH_PRIVATE_KEY: ${{ secrets.GITEE_PRIVATE_KEY }} - run: | - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - echo "StrictHostKeyChecking no" >> ~/.ssh/config - - - name: Push Mirror - env: - SOURCE_REPO: 'https://github.com/swoole/swoole-src.git' - DESTINATION_REPO: 'git@gitee.com:swoole/swoole.git' - run: | - git clone --mirror "$SOURCE_REPO" && cd `basename "$SOURCE_REPO"` - git remote set-url --push origin "$DESTINATION_REPO" - git fetch -p origin - git for-each-ref --format 'delete %(refname)' refs/pull | git update-ref --stdin - git push --mirror diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml deleted file mode 100644 index 4ef02f5dde..0000000000 --- a/.github/workflows/test-linux.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: test-linux - -on: [push, pull_request] - -jobs: - test-linux: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: [8.0, 8.1] - steps: - - uses: actions/checkout@v3 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: "${{ matrix.php }}" - - name: Show machine information - run: | - date - env - uname -a - ulimit -a - php -v - php --ini - ls -al - pwd - echo "`git log -20 --pretty --oneline`" - echo "`git log -10 --stat --pretty --oneline`" - - name: Run pecl-install.sh - run: | - sudo ${{runner.workspace}}/swoole-src/travis/pecl-install.sh - - name: Run Swoole test - run: | - export TRAVIS_BRANCH=${GITHUB_REF##*/} - ${{runner.workspace}}/swoole-src/travis/route.sh diff --git a/.github/workflows/thread.yml b/.github/workflows/thread.yml new file mode 100644 index 0000000000..5d86ef543e --- /dev/null +++ b/.github/workflows/thread.yml @@ -0,0 +1,43 @@ +name: Thread Support Tests + +on: [push, pull_request] + +jobs: + linux: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[thread]')" + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + php: ['8.2-zts', '8.3-zts', '8.4-zts', '8.5-zts'] + os: [ ubuntu-24.04, ubuntu-24.04-arm ] + name: ${{ matrix.php }}-thread-${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + coverage: none + env: + phpts: ts + - name: Show machine information + run: | + date + env + uname -a + ulimit -a + php -v + php --ini + ls -al + pwd + echo "`git log -20 --pretty --oneline`" + echo "`git log -10 --stat --pretty --oneline`" + - name: Run tests + run: | + export SWOOLE_CI_TYPE=THREAD + export SWOOLE_BRANCH=${GITHUB_REF##*/} + export SWOOLE_BUILD_DIR=$(realpath .) + export PHP_VERSION=${{ matrix.php }} + ${SWOOLE_BUILD_DIR}/scripts/route.sh diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000000..34abd39295 --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,97 @@ +name: Unit Tests + +on: [push, pull_request] + +jobs: + linux: + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[unit]')" + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + php: ['8.2', '8.3', '8.4', '8.5'] + os: [ ubuntu-24.04, ubuntu-24.04-arm ] + name: ${{ matrix.php }}-${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + coverage: none + - name: Show machine information + run: | + date + env + uname -a + ulimit -a + php -v + php --ini + ls -al + pwd + echo "`git log -20 --pretty --oneline`" + echo "`git log -10 --stat --pretty --oneline`" + - name: Run unit tests + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + export SWOOLE_CI_TYPE=NORMAL + export SWOOLE_BRANCH=${GITHUB_REF##*/} + export SWOOLE_BUILD_DIR=$(realpath .) + export PHP_VERSION=${{ matrix.php }} + ${{runner.workspace}}/swoole-src/scripts/route.sh + macos: + runs-on: macos-latest + if: "!contains(github.event.head_commit.message, '--filter=') || contains(github.event.head_commit.message, '[macos-unit]')" + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: +# php-version: [ '8.2', '8.3', '8.4', '8.5' ] + php-version: [ '8.3' ] + name: ${{ matrix.php-version }} - macOS + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: dom, curl, libxml, mbstring, zip, redis, pdo, pdo_mysql, bcmath, sockets + tools: phpize, composer:v2 + ini-values: extension=swoole + coverage: none + + - name: Install dependencies + run: | + brew install redis tinyproxy nginx md5sha1sum + brew services start redis + brew services start nginx + brew services start tinyproxy + + - name: Build Swoole + run: | + phpize + export PCRE2_INCLUDE_DIR="/opt/homebrew/opt/pcre2/include" + export CPPFLAGS="${CPPFLAGS} -I${PCRE2_INCLUDE_DIR}" + export CFLAGS="${CFLAGS} -I${PCRE2_INCLUDE_DIR}" + ./configure CPPFLAGS="${CPPFLAGS}" --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares --enable-zstd + make -j$(sysctl -n hw.ncpu) + sudo make install + php --ri swoole + uname -a + + - name: Run unit tests + run: | + export SWOOLE_CI_TYPE=NORMAL + export SWOOLE_BRANCH=${GITHUB_REF##*/} + export SWOOLE_BUILD_DIR=$(realpath .) + export PHP_VERSION=${{ matrix.php-version }} + export SWOOLE_CI_IN_MACOS=1 + cd ${{runner.workspace}}/swoole-src + ulimit -n 100000 + ./scripts/run-tests.sh diff --git a/.gitignore b/.gitignore index 59254b2a59..787a0403a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,13 +8,13 @@ *.loT *.pid *.dep -/Debug/* modules/* /.deps /.libs/ /core /examples/core /Debug +/Release /CMakeFiles /cmake_install.cmake /CMakeCache.txt @@ -80,11 +80,25 @@ cmake-build-debug/ /.vs /configure.in /configure.ac + +# core-tests /core-tests/CMakeCache.txt /core-tests/CMakeFiles/ /core-tests/Makefile /core-tests/bin/ /core-tests/cmake_install.cmake +/core-tests/samples/Makefile +/core-tests/samples/cmake_install\.cmake +/core-tests/samples/CMakeCache\.txt +/core-tests/samples/bin/ +/core-tests/samples/CMakeFiles/ +/core-tests/.project +/core-tests/.cproject +/core-tests/compile_commands.json +/core-tests/samples/.project +/core-tests/fuzz/fuzz_results/ +/core-tests/fuzz/bin/ + /tools/vendor /tools/composer.lock /examples/wrapper/CMakeFiles @@ -92,11 +106,6 @@ cmake-build-debug/ /examples/wrapper/Makefile /examples/wrapper/server /examples/wrapper/cmake_install.cmake -core-tests/samples/Makefile -core-tests/samples/cmake_install\.cmake -core-tests/samples/CMakeCache\.txt -core-tests/samples/bin/ -core-tests/samples/CMakeFiles/ /out /gcov_result @@ -108,5 +117,7 @@ core-tests/samples/CMakeFiles/ /html /tests/include/lib/vendor/ /tests/include/lib/composer.lock -/core-tests/fuzz/fuzz_results/ -/core-tests/fuzz/bin/ +/scripts/data +/.cmake/ +/Testing/ +build.ninja \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000000..f8489db2d4 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,67 @@ +setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PhpCsFixer' => true, + '@PSR2' => true, + '@Symfony' => true, + 'align_multiline_comment' => ['comment_type' => 'all_multiline'], + 'array_syntax' => ['syntax' => 'short'], + 'binary_operator_spaces' => ['operators' => ['=' => 'align', '=>' => 'align', ]], + 'blank_line_after_namespace' => true, + 'blank_line_before_statement' => ['statements' => ['declare']], + 'class_attributes_separation' => true, + 'concat_space' => ['spacing' => 'one'], + 'constant_case' => ['case' => 'lower'], + 'combine_consecutive_unsets' => true, + 'declare_strict_types' => true, + 'fully_qualified_strict_types' => ['phpdoc_tags' => []], + 'general_phpdoc_annotation_remove' => ['annotations' => ['author']], + 'header_comment' => ['comment_type' => 'PHPDoc', 'header' => $header, 'location' => 'after_open', 'separate' => 'bottom'], + 'increment_style' => ['style' => 'post'], + 'lambda_not_used_import' => false, + 'linebreak_after_opening_tag' => true, + 'list_syntax' => ['syntax' => 'short'], + 'lowercase_static_reference' => true, + 'multiline_comment_opening_closing' => true, + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'allow_unused_params' => true, 'remove_inheritdoc' => false], + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_return' => true, + 'not_operator_with_space' => false, + 'not_operator_with_successor_space' => false, + 'php_unit_strict' => false, + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_annotation_without_dot' => false, + 'phpdoc_no_empty_return' => false, + 'phpdoc_types_order' => ['sort_algorithm' => 'none', 'null_adjustment' => 'always_last'], + 'phpdoc_separation' => false, + 'phpdoc_summary' => false, + 'ordered_class_elements' => true, + 'ordered_imports' => ['imports_order' => ['class', 'function', 'const'], 'sort_algorithm' => 'alpha'], + 'ordered_types' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'single_line_comment_style' => ['comment_types' => []], + 'single_line_comment_spacing' => false, + 'single_line_empty_body' => false, + 'single_quote' => true, + 'standardize_increment' => false, + 'standardize_not_equals' => true, + 'yoda_style' => ['always_move_variable' => false, 'equal' => false, 'identical' => false], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->exclude(['html', 'vendor']) + ->in(__DIR__) + ) + ->setUsingCache(false); diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index c8bd1cf0b1..0000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Swoole Changelog - - -## 2022-07-22 v5.0.0 - -### Added -* Added `max_concurrency` option for `Server` -* Added `max_retries` option for `Coroutine\Http\Client` -* Added `name_resolver` global option -* Added `upload_max_filesize` option for `Server` -* Added `Coroutine::getExecuteTime()` -* Added `SWOOLE_DISPATCH_CONCURRENT_LB` dispatch_mode for `Server` - -### Changed -* Enhanced type system, added types for parameters and return values of all functions -* Optimized error handling, all constructors will throw exceptions when fail -* Adjusted the default mode of Server, the default is `SWOOLE_BASE` mode - -### Removed - -- Removed `PSR-0` style class names -- Removed the automatic addition of `Event::wait()` in shutdown function -- Removed `Server::tick/after/clearTimer/defer` aliases -- Removed `--enable-http`/`--enable-swoole-json`, adjusted to be enable by default - -### Deprecated -- Deprecated `Coroutine\Redis` and `Coroutine\MySQL` diff --git a/CMakeLists.txt b/CMakeLists.txt index e191098a33..6db962e215 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ -PROJECT(libswoole) +cmake_minimum_required(VERSION 3.10) +project(libswoole) -ENABLE_LANGUAGE(ASM) -set(SWOOLE_VERSION 5.0.0) +enable_language(ASM) +set(SWOOLE_VERSION 6.2.0) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") -cmake_minimum_required(VERSION 2.8) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -g") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -g") file(READ ./config.h SWOOLE_CONFIG_FILE) @@ -21,7 +22,21 @@ else() list(APPEND SWOOLE_LINK_LIBRARIES rt crypt) endif() -SET(CMAKE_BUILD_TYPE Debug) +find_package(PkgConfig REQUIRED) + +if (UNIX AND NOT APPLE) + find_library(URING_LIBRARIES uring) + if (URING_LIBRARIES) + message(STATUS "Found iouring") + list(APPEND SWOOLE_LINK_LIBRARIES ${URING_LIBRARIES}) + else() + message(WARNING "liburing not found.") + endif() +endif() + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif () # Code Coverage Configuration add_library(coverage_config INTERFACE) @@ -33,6 +48,7 @@ if(CODE_COVERAGE) target_compile_options(coverage_config INTERFACE -O0 -g + -fprofile-update=atomic --coverage ) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) @@ -50,14 +66,16 @@ file(GLOB_RECURSE SRC_LIST FOLLOW_SYMLINKS src/*.c src/*.cc thirdparty/hiredis/net.c thirdparty/hiredis/read.c thirdparty/hiredis/sds.c - thirdparty/swoole_http_parser.c + thirdparty/llhttp/api.c + thirdparty/llhttp/http.c + thirdparty/llhttp/llhttp.c thirdparty/multipart_parser.c ) file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS include/*.h) file(GLOB_RECURSE HEAD_WAPPER_FILES FOLLOW_SYMLINKS include/wrapper/*.hpp) -SET(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) -SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) #message(STATUS "source=${SRC_LIST}") #message(STATUS "header=${HEAD_FILES}") @@ -66,8 +84,7 @@ add_definitions(-DHAVE_CONFIG_H) # test #add_definitions(-DSW_USE_THREAD_CONTEXT) -include_directories(BEFORE ./include ./include/wrapper ext-src/ thirdparty/ ./) -SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) +include_directories(BEFORE ./include ./include/wrapper ext-src/ thirdparty/ thirdparty/llhttp ./) # find OpenSSL if (DEFINED openssl_dir) @@ -100,8 +117,44 @@ if (DEFINED enable_trace_log) add_definitions(-DSW_LOG_TRACE_OPEN) endif() -execute_process(COMMAND php-config --includes OUTPUT_VARIABLE PHP_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE) -execute_process(COMMAND php-config --extension-dir OUTPUT_VARIABLE PHP_EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) +if (DEFINED enable_asan) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + add_definitions(-DSW_USE_ASAN) +endif() + +if (DEFINED enable_thread) + add_definitions(-DSW_THREAD) +endif() + +if (DEFINED async_io) + # This macro is exclusively used for kernel unit testing to + # verify the asynchronous operation functionality of the file thread pool. + add_definitions(-DSW_USE_ASYNC) +endif() + +if (DEFINED verbose) + add_definitions(-DSW_VERBOSE) +endif() + +set(php_dir "" CACHE STRING "Set the root directory of PHP") + +if (DEFINED php_dir) + set(PHP_CONFIG "${php_dir}/bin/php-config") +else () + set(PHP_CONFIG "php-config") +endif() + +execute_process(COMMAND ${PHP_CONFIG} --includes OUTPUT_VARIABLE PHP_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE PHP_CONFIG_RESULT) +if (NOT PHP_CONFIG_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to execute php-config: ${PHP_CONFIG_RESULT}") +endif() + +execute_process(COMMAND ${PHP_CONFIG} --extension-dir OUTPUT_VARIABLE PHP_EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE PHP_CONFIG_RESULT) +if (NOT PHP_CONFIG_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to execute php-config: ${PHP_CONFIG_RESULT}") +endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PHP_INCLUDES}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PHP_INCLUDES}") @@ -122,7 +175,8 @@ link_directories(${LIBRARY_OUTPUT_PATH}) add_library(lib-swoole SHARED ${SRC_LIST}) set_target_properties(lib-swoole PROPERTIES OUTPUT_NAME "swoole" VERSION ${SWOOLE_VERSION}) target_link_libraries(lib-swoole ${SWOOLE_LINK_LIBRARIES}) -if(CODE_COVERAGE) + +if (CODE_COVERAGE) target_link_libraries(lib-swoole coverage_config gcov) endif(CODE_COVERAGE) @@ -143,12 +197,15 @@ file(GLOB ext_cxx_files ext-src/*.cc) set(ext_src_list ${ext_cxx_files} thirdparty/php/curl/interface.cc thirdparty/php/curl/multi.cc - thirdparty/php/sockets/multicast.cc + thirdparty/php84/curl/interface.cc + thirdparty/php84/curl/multi.cc + thirdparty/php/sockets/multicast.cc thirdparty/php/sockets/sendrecvmsg.cc thirdparty/php/sockets/conversions.cc thirdparty/php/sockets/sockaddr_conv.cc thirdparty/php/standard/proc_open.cc - thirdparty/nghttp2/nghttp2_hd.c + thirdparty/php/standard/var_decoder.cc + thirdparty/nghttp2/nghttp2_hd.c thirdparty/nghttp2/nghttp2_rcbuf.c thirdparty/nghttp2/nghttp2_helper.c thirdparty/nghttp2/nghttp2_buf.c @@ -161,7 +218,37 @@ set_target_properties(ext-swoole PROPERTIES PREFIX "") set_target_properties(ext-swoole PROPERTIES OUTPUT_NAME "swoole") add_dependencies(ext-swoole lib-swoole) -target_link_libraries(ext-swoole swoole) +# core-tests +pkg_check_modules(NGHTTP2 REQUIRED libnghttp2) +if (${NGHTTP2_FOUND}) + message(STATUS "Found nghttp2") +else() + message(STATUS "Not found nghttp2") +endif() + +# find GTest +find_package(GTest REQUIRED) +if (!${GTEST_FOUND}) + message(FATAL_ERROR "Not found GTest") +endif() +message(STATUS "Found GTest") + +file(GLOB_RECURSE core_test_files core-tests/src/*.cpp thirdparty/llhttp/*.c) +add_executable(core-tests ${core_test_files}) +add_dependencies(core-tests lib-swoole) +include_directories(BEFORE core-tests/include thirdparty thirdparty/hiredis thirdparty/llhttp/ ${GTEST_INCLUDE_DIRS} ${NGHTTP2_INCLUDE_DIR}) +target_link_libraries(core-tests swoole pthread ssl crypto ${GTEST_BOTH_LIBRARIES} ${NGHTTP2_LIBRARIES}) + +# find libpq +if (DEFINED libpq_dir) + include_directories(BEFORE ${libpq_dir}/include) + link_directories(${libpq_dir}/lib) +else() + pkg_check_modules(LIBPQ REQUIRED libpq) + target_include_directories(ext-swoole PRIVATE ${LIBPQ_INCLUDE_DIRS}) +endif() + +target_link_libraries(ext-swoole swoole pq curl) # install INSTALL(TARGETS ext-swoole LIBRARY DESTINATION ${PHP_EXTENSION_DIR}) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..424fb3d183 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# This Dockerfile is designed to create a debug version of the PHP and Swoole environment, + # enabling ASAN (`--enable-address-sanitizer`) to facilitate debugging and analysis of runtime crashes. +FROM ubuntu:22.04 +ARG PHP_VERSION=8.2.28 +RUN apt update +RUN apt install -y g++ cmake automake wget pkg-config git xz-utils +RUN apt install -y libssl-dev libcurl4-openssl-dev libxml2-dev libzip-dev libsqlite3-dev libreadline-dev libonig-dev \ + libbz2-dev libffi-dev libxslt-dev unixodbc-dev libpq-dev libbrotli-dev libc-ares-dev + +RUN mkdir /work +WORKDIR /work + +RUN wget https://www.php.net/distributions/php-${PHP_VERSION}.tar.xz +RUN tar -xvf php-${PHP_VERSION}.tar.xz + +COPY . /work/php-${PHP_VERSION}/ext/swoole + +RUN cd php-${PHP_VERSION} && ./buildconf --force && \ + ./configure --enable-mbstring --with-curl --with-openssl \ + --enable-soap --enable-intl --enable-bcmath --enable-sockets \ + --with-pear --with-webp --with-jpeg --with-ffi \ + --enable-sysvsem --enable-sysvshm --enable-sysvmsg --with-zlib --with-bz2 --with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-xsl \ + --without-pdo-sqlite \ + --enable-debug --enable-address-sanitizer \ + --enable-swoole \ + --enable-swoole-curl \ + --enable-swoole-pgsql \ + --enable-swoole-sqlite \ + --enable-mysqlnd \ + --enable-cares \ + --with-swoole-odbc=unixODBC,/usr \ + --enable-brotli && \ + make clean && make -j $(nproc) && make install + +RUN php -v +RUN php -m +RUN php --ri swoole +RUN php --ri curl +RUN php --ri openssl +RUN cd /work/php-${PHP_VERSION} && make clean +RUN cd /work && rm php-${PHP_VERSION}.tar.xz && rm -rf php-${PHP_VERSION}/ext/swoole/.git +RUN rm -rf /var/lib/apt/lists/* /usr/bin/qemu-*-static diff --git a/README-CN.md b/README-CN.md deleted file mode 100644 index 53f1a2992d..0000000000 --- a/README-CN.md +++ /dev/null @@ -1,551 +0,0 @@ -[English](./README.md) | 中文 - -

-Swoole Logo -

- -[![lib-swoole](https://github.com/swoole/swoole-src/workflows/lib-swoole/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Alib-swoole) -[![ext-swoole](https://github.com/swoole/swoole-src/workflows/ext-swoole/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Aext-swoole) -[![test-linux](https://github.com/swoole/swoole-src/workflows/test-linux/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Atest-linux) -[![Frameworks Tests](https://github.com/swoole/swoole-src/actions/workflows/framework.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/framework.yml) -[![codecov](https://codecov.io/gh/swoole/swoole-src/branch/master/graph/badge.svg)](https://codecov.io/gh/swoole/swoole-src) - -[![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/phpswoole) -[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.swoole.dev) -[![Latest Release](https://img.shields.io/github/release/swoole/swoole-src.svg)](https://github.com/swoole/swoole-src/releases/) -[![License](https://badgen.net/github/license/swoole/swoole-src)](https://github.com/swoole/swoole-src/blob/master/LICENSE) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/11654/badge.svg)](https://scan.coverity.com/projects/swoole-swoole-src) - -**Swoole是一个C++编写的基于异步事件驱动和协程的并行网络通信引擎,为PHP提供高性能网络编程支持** - -## ⚙️ 快速启动 - -可以直接使用 [Docker](https://github.com/swoole/docker-swoole) 来执行Swoole的代码,例如: - -```bash -docker run --rm phpswoole/swoole "php --ri swoole" -``` - -具体的使用方式可以查看:[如何使用此镜像](https://github.com/swoole/docker-swoole#how-to-use-this-image) 。 - -或者可以在Swoole官网提供的 [在线编程](https://www.swoole.com/coding) 页面运行代码以及官网提供的示例代码。 - -## ✨ 事件驱动 - -Swoole中的网络请求处理是基于事件的,并且充分利用了底层的 epoll/kqueue 实现,使得为数百万个请求提供服务变得非常容易。 - -Swoole4使用全新的协程内核引擎,现在它拥有一个全职的开发团队,因此我们正在进入PHP历史上前所未有的时期,为性能的高速提升提供了独一无二的可能性。 - -## ⚡️ 协程 - -Swoole4或更高版本拥有高可用性的内置协程,您可以使用完全同步的代码来实现异步性能,PHP代码没有任何额外的关键字,底层会自动进行协程调度。 - -开发者可以将协程理解为超轻量级的线程, 你可以非常容易地在一个进程中创建成千上万个协程。 - -### MySQL客户端 - -并发1万个请求从MySQL读取海量数据仅需要0.2秒 - -```php -$s = microtime(true); -Co\run(function() { - for ($c = 100; $c--;) { - go(function () { - $mysql = new Swoole\Coroutine\MySQL; - $mysql->connect([ - 'host' => '127.0.0.1', - 'user' => 'root', - 'password' => 'root', - 'database' => 'test' - ]); - $statement = $mysql->prepare('SELECT * FROM `user`'); - for ($n = 100; $n--;) { - $result = $statement->execute(); - assert(count($result) > 0); - } - }); - } -}); -echo 'use ' . (microtime(true) - $s) . ' s'; -``` - -### 混合服务器 - -你可以在一个事件循环上创建多个服务:TCP,HTTP,Websocket和HTTP2,并且能轻松承载上万请求。 - -```php -function tcp_pack(string $data): string -{ - return pack('N', strlen($data)) . $data; -} -function tcp_unpack(string $data): string -{ - return substr($data, 4, unpack('N', substr($data, 0, 4))[1]); -} -$tcp_options = [ - 'open_length_check' => true, - 'package_length_type' => 'N', - 'package_length_offset' => 0, - 'package_body_offset' => 4 -]; -``` - -```php -$server = new Swoole\WebSocket\Server('127.0.0.1', 9501, SWOOLE_BASE); -$server->set(['open_http2_protocol' => true]); -// http && http2 -$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { - $response->end('Hello ' . $request->rawcontent()); -}); -// websocket -$server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { - $server->push($frame->fd, 'Hello ' . $frame->data); -}); -// tcp -$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP); -$tcp_server->set($tcp_options); -$tcp_server->on('receive', function (Swoole\Server $server, int $fd, int $reactor_id, string $data) { - $server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); -}); -$server->start(); -``` -### 多种客户端 - -不管是DNS查询抑或是发送请求和接收响应,都是协程调度的,不会产生任何阻塞。 - -```php -go(function () { - // http - $http_client = new Swoole\Coroutine\Http\Client('127.0.0.1', 9501); - assert($http_client->post('/', 'Swoole Http')); - var_dump($http_client->body); - // websocket - $http_client->upgrade('/'); - $http_client->push('Swoole Websocket'); - var_dump($http_client->recv()->data); -}); -go(function () { - // http2 - $http2_client = new Swoole\Coroutine\Http2\Client('localhost', 9501); - $http2_client->connect(); - $http2_request = new Swoole\Http2\Request; - $http2_request->method = 'POST'; - $http2_request->data = 'Swoole Http2'; - $http2_client->send($http2_request); - $http2_response = $http2_client->recv(); - var_dump($http2_response->data); -}); -go(function () use ($tcp_options) { - // tcp - $tcp_client = new Swoole\Coroutine\Client(SWOOLE_TCP); - $tcp_client->set($tcp_options); - $tcp_client->connect('127.0.0.1', 9502); - $tcp_client->send(tcp_pack('Swoole Tcp')); - var_dump(tcp_unpack($tcp_client->recv())); -}); -``` - -### 通道 - -通道(Channel)是协程之间通信交换数据的唯一渠道, 而协程+通道的开发组合即为著名的CSP编程模型。 - -在Swoole开发中,Channel常用于连接池的实现和协程并发的调度。 - -#### 连接池最简示例 - -在以下示例中,我们并发了一千个redis请求,通常的情况下,这已经超过了Redis最大的连接数,将会抛出连接异常, 但基于Channel实现的连接池可以完美地调度请求,开发者就无需担心连接过载。 - -```php -class RedisPool -{ - /**@var \Swoole\Coroutine\Channel */ - protected $pool; - - /** - * RedisPool constructor. - * @param int $size max connections - */ - public function __construct(int $size = 100) - { - $this->pool = new \Swoole\Coroutine\Channel($size); - for ($i = 0; $i < $size; $i++) { - $redis = new \Swoole\Coroutine\Redis(); - $res = $redis->connect('127.0.0.1', 6379); - if ($res == false) { - throw new \RuntimeException("failed to connect redis server."); - } else { - $this->put($redis); - } - } - } - - public function get(): \Swoole\Coroutine\Redis - { - return $this->pool->pop(); - } - - public function put(\Swoole\Coroutine\Redis $redis) - { - $this->pool->push($redis); - } - - public function close(): void - { - $this->pool->close(); - $this->pool = null; - } -} - -go(function () { - $pool = new RedisPool(); - // max concurrency num is more than max connections - // but it's no problem, channel will help you with scheduling - for ($c = 0; $c < 1000; $c++) { - go(function () use ($pool, $c) { - for ($n = 0; $n < 100; $n++) { - $redis = $pool->get(); - assert($redis->set("awesome-{$c}-{$n}", 'swoole')); - assert($redis->get("awesome-{$c}-{$n}") === 'swoole'); - assert($redis->delete("awesome-{$c}-{$n}")); - $pool->put($redis); - } - }); - } -}); -``` - -#### 生产和消费 - -Swoole的部分客户端实现了defer机制来进行并发,但你依然可以用协程和通道的组合来灵活地实现它。 - -```php -go(function () { - // User: I need you to bring me some information back. - // Channel: OK! I will be responsible for scheduling. - $channel = new Swoole\Coroutine\Channel; - go(function () use ($channel) { - // Coroutine A: Ok! I will show you the github addr info - $addr_info = Co::getaddrinfo('github.com'); - $channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); - }); - go(function () use ($channel) { - // Coroutine B: Ok! I will show you what your code look like - $mirror = Co::readFile(__FILE__); - $channel->push(['B', $mirror]); - }); - go(function () use ($channel) { - // Coroutine C: Ok! I will show you the date - $channel->push(['C', date(DATE_W3C)]); - }); - for ($i = 3; $i--;) { - list($id, $data) = $channel->pop(); - echo "From {$id}:\n {$data}\n"; - } - // User: Amazing, I got every information at earliest time! -}); -``` - -### 定时器 - -```php -$id = Swoole\Timer::tick(100, function () { - echo "⚙️ Do something...\n"; -}); -Swoole\Timer::after(500, function () use ($id) { - Swoole\Timer::clear($id); - echo "⏰ Done\n"; -}); -Swoole\Timer::after(1000, function () use ($id) { - if (!Swoole\Timer::exists($id)) { - echo "✅ All right!\n"; - } -}); -``` - -#### 使用协程方式 - -```php -go(function () { - $i = 0; - while (true) { - Co::sleep(0.1); - echo "📝 Do something...\n"; - if (++$i === 5) { - echo "🛎 Done\n"; - break; - } - } - echo "🎉 All right!\n"; -}); -``` - -### 命名空间 - -Swoole提供了多种类命名规则以满足不同开发者的爱好 - -1. 符合PSR规范的命名空间风格 -2. 便于键入的下划线风格 -3. 协程类短名风格 - -## 🔥 强大的运行时钩子 - -在最新版本的Swoole中,我们添加了一项新功能,使PHP原生的同步网络库一键化成为协程库。 - -只需在脚本顶部调用`Swoole\Runtime::enableCoroutine()`方法并使用`php-redis`,并发1万个请求从Redis读取数据仅需0.1秒! - -```php -Swoole\Runtime::enableCoroutine(); -$s = microtime(true); -Co\run(function() { - for ($c = 100; $c--;) { - go(function () { - ($redis = new Redis)->connect('127.0.0.1', 6379); - for ($n = 100; $n--;) { - assert($redis->get('awesome') === 'swoole'); - } - }); - } -}); -echo 'use ' . (microtime(true) - $s) . ' s'; -``` - -调用它之后,Swoole内核将替换ZendVM中的Stream函数指针,如果使用基于`php_stream`的扩展,则所有套接字操作都可以在运行时动态转换为协程调度的异步IO。 - -### 你可以在一秒钟里做多少事? - -睡眠1万次,读取,写入,检查和删除文件1万次,使用PDO和MySQLi与数据库通信1万次,创建TCP服务器和多个客户端相互通信1万次,创建UDP服务器和多个客户端相互通信1万次......一切都在一个进程中完美完成! - -```php -Swoole\Runtime::enableCoroutine(); -$s = microtime(true); -Co\run(function() { - // i just want to sleep... - for ($c = 100; $c--;) { - go(function () { - for ($n = 100; $n--;) { - usleep(1000); - } - }); - } - - // 10k file read and write - for ($c = 100; $c--;) { - go(function () use ($c) { - $tmp_filename = "/tmp/test-{$c}.php"; - for ($n = 100; $n--;) { - $self = file_get_contents(__FILE__); - file_put_contents($tmp_filename, $self); - assert(file_get_contents($tmp_filename) === $self); - } - unlink($tmp_filename); - }); - } - - // 10k pdo and mysqli read - for ($c = 50; $c--;) { - go(function () { - $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root'); - $statement = $pdo->prepare('SELECT * FROM `user`'); - for ($n = 100; $n--;) { - $statement->execute(); - assert(count($statement->fetchAll()) > 0); - } - }); - } - for ($c = 50; $c--;) { - go(function () { - $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test'); - $statement = $mysqli->prepare('SELECT `id` FROM `user`'); - for ($n = 100; $n--;) { - $statement->bind_result($id); - $statement->execute(); - $statement->fetch(); - assert($id > 0); - } - }); - } - - // php_stream tcp server & client with 12.8k requests in single process - function tcp_pack(string $data): string - { - return pack('n', strlen($data)) . $data; - } - - function tcp_length(string $head): int - { - return unpack('n', $head)[1]; - } - - go(function () { - $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]); - $socket = stream_socket_server( - 'tcp://0.0.0.0:9502', - $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx - ); - if (!$socket) { - echo "$errstr ($errno)\n"; - } else { - $i = 0; - while ($conn = stream_socket_accept($socket, 1)) { - stream_set_timeout($conn, 5); - for ($n = 100; $n--;) { - $data = fread($conn, tcp_length(fread($conn, 2))); - assert($data === "Hello Swoole Server #{$n}!"); - fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!")); - } - if (++$i === 128) { - fclose($socket); - break; - } - } - } - }); - for ($c = 128; $c--;) { - go(function () { - $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1); - if (!$fp) { - echo "$errstr ($errno)\n"; - } else { - stream_set_timeout($fp, 5); - for ($n = 100; $n--;) { - fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!")); - $data = fread($fp, tcp_length(fread($fp, 2))); - assert($data === "Hello Swoole Client #{$n}!"); - } - fclose($fp); - } - }); - } - - // udp server & client with 12.8k requests in single process - go(function () { - $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); - $socket->bind('127.0.0.1', 9503); - $client_map = []; - for ($c = 128; $c--;) { - for ($n = 0; $n < 100; $n++) { - $recv = $socket->recvfrom($peer); - $client_uid = "{$peer['address']}:{$peer['port']}"; - $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1; - assert($recv === "Client: Hello #{$id}!"); - $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!"); - } - } - $socket->close(); - }); - for ($c = 128; $c--;) { - go(function () { - $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1); - if (!$fp) { - echo "$errstr ($errno)\n"; - } else { - for ($n = 0; $n < 100; $n++) { - fwrite($fp, "Client: Hello #{$n}!"); - $recv = fread($fp, 1024); - list($address, $port) = explode(':', (stream_socket_get_name($fp, true))); - assert($address === '127.0.0.1' && (int)$port === 9503); - assert($recv === "Server: Hello #{$n}!"); - } - fclose($fp); - } - }); - } -}); -echo 'use ' . (microtime(true) - $s) . ' s'; -``` - -## ⌛️ 安装 - -> 和任何开源项目一样, Swoole总是在**最新的发行版**提供最可靠的稳定性和最强的功能, 请尽量保证你使用的是最新版本 - -### 编译需求 - -+ Linux, OS X 系统 或 CygWin, WSL -+ PHP 7.2.0 或以上版本 (版本越高性能越好) -+ GCC 4.8 及以上 - -### 1. 使用PHP官方的PECL工具安装 (初学者) - -```shell -pecl install swoole -``` - -### 2. 从源码编译安装 (推荐) - -> 非内核开发研究之用途, 请下载[发布版本](https://github.com/swoole/swoole-src/releases)的源码编译 - -```shell -cd swoole-src && \ -phpize && \ -./configure && \ -make && sudo make install -``` - -#### 启用扩展 - -编译安装到系统成功后, 需要在`php.ini`中加入一行`extension=swoole.so`来启用Swoole扩展 - -#### 额外编译参数 - -> 使用例子: `./configure --enable-openssl --enable-sockets` - -+ `--enable-openssl` 或 `--with-openssl-dir=DIR` -+ `--enable-sockets` -+ `--enable-mysqlnd` (需要 mysqlnd, 只是为了支持`mysql->escape`方法) -+ `--enable-swoole-curl` - -### 升级 - -> ⚠️ 如果你要从源码升级, 别忘记在源码目录执行 `make clean` - -1. `pecl upgrade swoole` -2. `cd swoole-src && git pull && make clean && make && sudo make install` -3. 如果你改变了PHP版本, 请重新执行 `phpize clean && phpize`后重新编译 - -## 💎 框架 & 组件 - -+ [**Hyperf**](https://github.com/hyperf/hyperf) 是一个高性能、高灵活性的协程框架,存在丰富的可能性,如实现分布式中间件,微服务架构等 -+ [**Swoft**](https://github.com/swoft-cloud) 是一个现代化的面向切面的高性能协程全栈组件化框架 -+ [**Easyswoole**](https://www.easyswoole.com) 是一个极简的高性能的框架,让代码开发就好像写`echo "hello world"`一样简单 -+ [**MixPHP**](https://github.com/mix-php/mix) 是一个功能强大的单线程协程框架,轻量、简单而优雅 -+ [**imi**](https://github.com/Yurunsoft/imi) 是基于 PHP Swoole 的高性能协程应用开发框架,它支持 HttpApi、WebSocket、TCP、UDP 服务的开发。 -+ [**Saber**](https://github.com/swlib/saber) 是一个人性化的高性能HTTP客户端组件,几乎拥有一切你可以想象的强大功能 -+ [**One**](https://github.com/lizhichao/one) 是一个极简高性能php框架,支持[swoole | php-fpm ]环境 - -## 🛠 开发 & 讨论 - -+ __中文文档__: -+ __Document__: -+ __IDE Helper & API__: -+ __调试工具__: -+ __中文社区及QQ群__: -+ __Twitter__: -+ __Slack Group__: - -## 🍭 性能测试 - -+ 在开源的 [Techempower Web Framework benchmarks](https://www.techempower.com/benchmarks/#section=data-r17) 压测平台上,Swoole使用MySQL数据库压测的成绩一度位居首位, 所有IO性能测试都位列第一梯队。 -+ 你可以直接运行 [Benchmark Script](https://github.com/swoole/benchmark/blob/master/benchmark.php) 来快速地测试出Swoole提供的Http服务在你的机器上所能达到的最大QPS - -## 🔰️ 安全问题 - -安全问题应通过电子邮件私下报告给Swoole开发团队[team@swoole.com](mailto:team@swoole.com)。您将会在24小时内收到回复,若由于某些原因您没有收到回复,请再次通过电子邮件跟进以确保我们收到了您的原始消息。 - -## 🖊️ 如何贡献 - -非常欢迎您对Swoole的开发作出贡献! - -你可以选择以下方式向Swoole贡献: - -+ [发布issue进行问题反馈和建议](https://github.com/swoole/swoole-src/issues) -+ 通过Pull Request提交修复 -+ 完善我们的文档和例子 - -## ❤️ 贡献者 - -项目的发展离不开以下贡献者的努力! [[Contributor](https://github.com/swoole/swoole-src/graphs/contributors)]. - - -## 📃 开源协议 - -Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/README.md b/README.md index 0d716c8d0e..7c44331b4d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,21 @@ -English | [中文](./README-CN.md) - -

-Swoole Logo
+

+Swoole Logo
Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP. -

+ -[![lib-swoole](https://github.com/swoole/swoole-src/workflows/lib-swoole/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Alib-swoole) -[![ext-swoole](https://github.com/swoole/swoole-src/workflows/ext-swoole/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Aext-swoole) -[![test-linux](https://github.com/swoole/swoole-src/workflows/test-linux/badge.svg)](https://github.com/swoole/swoole-src/actions?query=workflow%3Atest-linux) +[![Compiler Tests](https://github.com/swoole/swoole-src/actions/workflows/ext.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/ext.yml) +[![Core Test](https://github.com/swoole/swoole-src/actions/workflows/core.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/core.yml) +[![Unit Tests](https://github.com/swoole/swoole-src/actions/workflows/unit.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/unit.yml) +[![Thread Support Tests](https://github.com/swoole/swoole-src/actions/workflows/thread.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/thread.yml) +[![Linux io_uring Tests](https://github.com/swoole/swoole-src/actions/workflows/iouring.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/iouring.yml) [![Frameworks Tests](https://github.com/swoole/swoole-src/actions/workflows/framework.yml/badge.svg)](https://github.com/swoole/swoole-src/actions/workflows/framework.yml) -[![codecov](https://codecov.io/gh/swoole/swoole-src/branch/master/graph/badge.svg)](https://codecov.io/gh/swoole/swoole-src) [![Twitter](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/phpswoole) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.swoole.dev) [![Latest Release](https://img.shields.io/github/release/swoole/swoole-src.svg)](https://github.com/swoole/swoole-src/releases/) [![License](https://badgen.net/github/license/swoole/swoole-src)](https://github.com/swoole/swoole-src/blob/master/LICENSE) [![Coverity Scan Build Status](https://scan.coverity.com/projects/11654/badge.svg)](https://scan.coverity.com/projects/swoole-swoole-src) - +[![Codecov](https://codecov.io/gh/swoole/swoole-src/branch/master/graph/badge.svg)](https://codecov.io/gh/swoole/swoole-src) ## ⚙️ Quick Start @@ -28,7 +27,10 @@ docker run --rm phpswoole/swoole "php --ri swoole" > For details on how to use it, see: [How to Use This Image](https://github.com/swoole/docker-swoole#how-to-use-this-image). -### Http Service +## Documentation + + +### HTTP Service ```php $http = new Swoole\Http\Server('127.0.0.1', 9501); $http->set(['hook_flags' => SWOOLE_HOOK_ALL]); @@ -59,7 +61,7 @@ Co\run(function() { echo fread($fp, 8192), PHP_EOL; } }); - + Co\go(function() { $fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); while(1) { @@ -67,7 +69,7 @@ Co\run(function() { fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); } }); - + Co\go(function() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); @@ -77,7 +79,7 @@ Co\run(function() { }); } }); - + Co\go(function() { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); @@ -96,17 +98,21 @@ Co\run(function() { ### Supported extension/functions +* `ext-curl` (Support `symfony` and `guzzle`) * `ext-redis` * `ext-mysqli` * `ext-pdo_mysql` -* `ext-curl` (Support `symfony` or `guzzle`) +* `ext-pdo_pgsql` +* `ext-pdo_sqlite` +* `ext-pdo_oracle` +* `ext-pdo_odbc` * `stream functions` (e.g. `stream_socket_client`/`stream_socket_server`), Supports `TCP`/`UDP`/`UDG`/`Unix`/`SSL/TLS`/`FileSystem API`/`Pipe` -* `ext-socket` +* `ext-sockets` * `ext-soap` * `sleep`/`usleep`/`time_sleep_until` * `proc_open` * `gethostbyname`/`shell_exec`/`exec` -* `fread`/`fopen`/`fsockopen`/`fwrite` +* `fread`/`fopen`/`fsockopen`/`fwrite`/`flock` ## 🛠 Develop & Discussion @@ -114,7 +120,6 @@ Co\run(function() { + __IDE Helper & API__: + __Twitter__: + __Discord__: -+ __中文文档__: + __中文社区__: ## 💎 Awesome Swoole @@ -552,7 +557,7 @@ echo 'use ' . (microtime(true) - $s) . ' s'; ### Compiling requirements + Linux, OS X or Cygwin, WSL -+ PHP 7.2.0 or later (The higher the version, the better the performance.) ++ PHP 8.1.0 or later (The higher the version, the better the performance.) + GCC 4.8 or later ### 1. Install via PECL (beginners) @@ -563,10 +568,11 @@ pecl install swoole ### 2. Install from source (recommended) -Please download the source packages from [Releases](https://github.com/swoole/swoole-src/releases) or: +Please download the source packages from [Releases](https://github.com/swoole/swoole-src/releases) or clone a specific version. Don't use `master` branch as it may be in development. +To clone the source code from git specify a tag: ```shell -git clone https://github.com/swoole/swoole-src.git && \ +git clone --branch v6.0.0 --single-branch https://github.com/swoole/swoole-src.git && \ cd swoole-src ``` diff --git a/SUPPORTED.md b/SUPPORTED.md deleted file mode 100644 index c77441f384..0000000000 --- a/SUPPORTED.md +++ /dev/null @@ -1,20 +0,0 @@ -## Supported Versions - -| Branch | PHP Version | Initialization | Active Support Until | Security Support Until | -| --------------------------------------------------------------- | ----------- | -------------- | -------------------- | ---------------------- | -| [v4.4.x](https://github.com/swoole/v4.4-lts) (LTS) | 7.2 - 7.4 | 2019-04-15 | 2020-04-30 | 2022-07-31 | -| [v4.8.x](https://github.com/swoole/swoole-src/tree/4.8.x) (LTS) | 7.2 - 8.1 | 2021-10-14 | 2023-10-14 | 2024-06-30 | -| [v5.0.x](https://github.com/swoole/swoole-src/tree/master) | 8.0 - 8.1 | 2022-01-20 | 2023-01-20 | 2023-07-20 | - -| Active support | A release that is being actively supported. Reported bugs and security issues are fixed and regular point releases are made. | -| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Security fixes only | A release that is supported for critical security issues only. Releases are only made on an as-needed basis. | - -## Unsupported Branches - -> These releases that are no longer supported. Users of this release should upgrade as soon as possible, as they may be exposed to unpatched security vulnerabilities. - -- `v1.x` (2012-7-1 ~ 2018-05-14) -- `v2.x` (2016-12-30 ~ 2018-05-23) -- `v4.0.x`, `v4.1.x`, `v4.2.x`, `v4.3.x` (2018-06-14 ~ 2019-12-31) -- `v4.5.x`,`v4.6.x`, `v4.7.x` (2019-12-20 ~ 2021-12-31) diff --git a/code_format.sh b/code_format.sh deleted file mode 100755 index dd655d9912..0000000000 --- a/code_format.sh +++ /dev/null @@ -1,35 +0,0 @@ -# core source file -clang-format -i src/core/*.cc -clang-format -i src/coroutine/*.cc -clang-format -i src/lock/*.cc -clang-format -i src/memory/*.cc -clang-format -i src/network/*.cc -clang-format -i src/os/*.cc -clang-format -i src/pipe/*.cc -clang-format -i src/protocol/*.cc -clang-format -i src/reactor/*.cc -clang-format -i src/server/*.cc -clang-format -i src/wrapper/*.cc -# core header file -clang-format -i include/*.h - -# ext source file -clang-format -i *.cc -clang-format -i *.h - -clang-format -i examples/cpp/*.cc - -# core-tests source file -clang-format -i core-tests/src/_lib/*.cpp -clang-format -i core-tests/src/client/*.cpp -clang-format -i core-tests/src/core/*.cpp -clang-format -i core-tests/src/coroutine/*.cpp -clang-format -i core-tests/src/lock/*.cpp -clang-format -i core-tests/src/memory/*.cpp -clang-format -i core-tests/src/network/*.cpp -clang-format -i core-tests/src/os/*.cpp -clang-format -i core-tests/src/process/*.cpp -clang-format -i core-tests/src/protocol/*.cpp -clang-format -i core-tests/src/reactor/*.cpp -clang-format -i core-tests/src/server/*.cpp -clang-format -i core-tests/src/main.cpp \ No newline at end of file diff --git a/code_stats.sh b/code_stats.sh deleted file mode 100755 index be5403e14c..0000000000 --- a/code_stats.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -cloc . --exclude-dir=thirdparty,Debug,CMakeFiles,build,CMakeFiles,.git diff --git a/codecov.yml b/codecov.yml index 3aaaf7fa7e..7645c081fa 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,7 @@ ignore: - "src/core/error.cc" - "thirdparty/hiredis/*" + - "thirdparty/llhttp/*" coverage: range: 60..80 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000000..f301f3da15 --- /dev/null +++ b/composer.json @@ -0,0 +1,106 @@ +{ + "name": "swoole/swoole", + "type": "php-ext", + "license": "Apache-2.0", + "description": "Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP.", + "require": { + "php": ">=8.2 <8.6", + "ext-pdo": "*" + }, + "suggest": { + "ext-pdo_mysql": "PDO_MYSQL is required for Swoole to work with MySQL", + "ext-pdo_pgsql": "PDO_PGSQL is required for Swoole to work with PostgreSQL", + "ext-curl": "CURL is required for Swoole to work with HTTP", + "ext-sockets": "Sockets is required for Swoole to work with Socket", + "ext-openssl": "OpenSSL is required for Swoole to work with HTTPS", + "ext-posix": "*" + }, + "php-ext": { + "extension-name": "swoole", + "configure-options": [ + { + "name": "enable-sockets", + "description": "Enable sockets support" + }, + { + "name": "with-openssl-dir", + "description": "Specify openssl installation directory (requires OpenSSL 1.1.0 or later)?", + "needs-value": true + }, + { + "name": "enable-mysqlnd", + "description": "Enable mysqlnd support" + }, + { + "name": "enable-swoole-curl", + "description": "Enable curl support" + }, + { + "name": "enable-cares", + "description": "Enable cares support" + }, + { + "name": "enable-brotli", + "description": "Enable brotli support" + }, + { + "name": "with-brotli-dir", + "description": "Specify brotli installation directory?", + "needs-value": true + }, + { + "name": "enable-swoole-pgsql", + "description": "Enable PostgreSQL database support" + }, + { + "name": "with-swoole-odbc", + "description": "Enable ODBC database support", + "needs-value": true + }, + { + "name": "with-swoole-oracle", + "description": "Enable Oracle database support", + "needs-value": true + }, + { + "name": "enable-swoole-sqlite", + "description": "Enable Sqlite database support" + }, + { + "name": "with-swoole-firebird", + "description": "Enable Firebird database support", + "needs-value": true + }, + { + "name": "enable-swoole-thread", + "description": "Enable swoole thread support (need php zts support)" + }, + { + "name": "enable-iouring", + "description": "Enable iouring for file async support" + }, + { + "name": "with-liburing-dir", + "description": "Specify liburing installation directory (requires liburing 2.8 or later)", + "needs-value": true + }, + { + "name": "enable-uring-socket", + "description": "Enable iouring for http coroutine server support" + }, + { + "name": "with-swoole-ssh2", + "description": "Enable async ssh2 client support", + "needs-value": true + }, + { + "name": "enable-swoole-ftp", + "description": "Enable async ssh2 client support" + }, + { + "name": "enable-zstd", + "description": "Enable zstd support (requires zstd 1.4.0 or later)" + } + ] + } +} diff --git a/config.m4 b/config.m4 index 2507b92220..d123062d3b 100644 --- a/config.m4 +++ b/config.m4 @@ -31,11 +31,6 @@ PHP_ARG_ENABLE([sockets], [AS_HELP_STRING([--enable-sockets], [Do you have sockets extension?])], [no], [no]) -PHP_ARG_ENABLE([openssl], - [enable openssl support], - [AS_HELP_STRING([--enable-openssl], - [Use openssl])], [no], [no]) - PHP_ARG_ENABLE([swoole], [swoole support], [AS_HELP_STRING([--enable-swoole], @@ -51,15 +46,45 @@ PHP_ARG_ENABLE([cares], [AS_HELP_STRING([--enable-cares], [Enable cares])], [no], [no]) +PHP_ARG_ENABLE([iouring], + [enable io-uring support], + [AS_HELP_STRING([--enable-iouring], + [Enable io-uring])], [no], [no]) + +PHP_ARG_WITH([liburing_dir], + [dir of liburing], + [AS_HELP_STRING([[--with-liburing-dir[=DIR]]], + [Include liburing support (requires liburing >= 2.13)])], [no], [no]) + +PHP_ARG_ENABLE([uring_socket], + [enable uring_socket support], + [AS_HELP_STRING([--enable-uring-socket], + [Enable iouring socket support])], [no], [no]) + PHP_ARG_WITH([openssl_dir], [dir of openssl], [AS_HELP_STRING([[--with-openssl-dir[=DIR]]], - [Include OpenSSL support (requires OpenSSL >= 1.0.2)])], [no], [no]) + [Include OpenSSL support (requires OpenSSL >= 1.1.0)])], [no], [no]) + +PHP_ARG_ENABLE([brotli], + [enable brotli support], + [AS_HELP_STRING([[--enable-brotli]], + [Use brotli])], [auto], [no]) + +PHP_ARG_WITH([brotli_dir], + [dir of brotli], + [AS_HELP_STRING([[--with-brotli-dir[=DIR]]], + [Include Brotli support])], [no], [no]) -PHP_ARG_WITH([jemalloc_dir], - [dir of jemalloc], - [AS_HELP_STRING([[--with-jemalloc-dir[=DIR]]], - [Include jemalloc support])], [no], [no]) +PHP_ARG_ENABLE([zstd], + [enable zstd support], + [AS_HELP_STRING([[--enable-zstd]], + [Use zstd])], [no], [no]) + +PHP_ARG_WITH([nghttp2_dir], + [dir of nghttp2], + [AS_HELP_STRING([[--with-nghttp2-dir[=DIR]]], + [Include nghttp2 support])], [no], [no]) PHP_ARG_ENABLE([asan], [enable asan], @@ -86,16 +111,70 @@ PHP_ARG_ENABLE([swoole-pgsql], [AS_HELP_STRING([--enable-swoole-pgsql], [Enable postgresql support])], [no], [no]) +PHP_ARG_WITH([swoole-firebird], + [whether to enable firebird build flags], + [AS_HELP_STRING([[--with-swoole-firebird[=DIR]]], + [PDO: Async Firebird support. DIR is the Firebird base install directory + [/opt/firebird]])], [no], [no]) + +PHP_ARG_WITH([swoole-ssh2], + [whether to enable ssh2 support], + [AS_HELP_STRING([[--with-swoole-ssh2[=DIR]]], + [Enable Async ssh2 support. DIR is the libssh2 base install directory + [/usr]])], [no], [no]) + +PHP_ARG_ENABLE([swoole-ftp], + [whether to enable Async FTP support], + [AS_HELP_STRING([--enable-swoole-ftp], + [Enable Async FTP support])], [no], [no]) + PHP_ARG_ENABLE([thread-context], [whether to enable thread context], [AS_HELP_STRING([--enable-thread-context], [Use thread context])], [no], [no]) +PHP_ARG_ENABLE([swoole-thread], + [whether to enable swoole thread support], + [AS_HELP_STRING([--enable-swoole-thread], + [Enable swoole thread support])], [no], [no]) + +PHP_ARG_ENABLE([swoole-stdext], + [whether to enable swoole stdext support], + [AS_HELP_STRING([--enable-swoole-stdext], + [Enable swoole stdext support("[Experimental] This module is only used for swoole-cli. + If you are unsure which feature you need, keep it disabled")])], [no], [no]) + PHP_ARG_ENABLE([swoole-coro-time], [whether to enable coroutine execution time ], [AS_HELP_STRING([--enable-swoole-coro-time], [Calculating coroutine execution time])], [no], [no]) +define([PDO_ODBC_HELP_TEXT],[[ + The include and lib dirs are looked for under 'dir'. The 'flavour' can be one + of: ibm-db2, iODBC, unixODBC, generic. If ',dir' part is omitted, default for + the flavour you have selected will be used. e.g.: --with-swoole-odbc=unixODBC + will check for unixODBC under /usr/local. You may attempt to use an otherwise + unsupported driver using the 'generic' flavour. The syntax for generic ODBC + support is: --with-swoole-odbc=generic,dir,libname,ldflags,cflags. When built as + 'shared' the extension filename is always pdo_odbc.so]]) + +PHP_ARG_WITH([swoole-odbc], + ["for ODBC v3 support for PDO"], + [AS_HELP_STRING([--with-swoole-odbc=flavour,dir], + ["PDO: Support for 'flavour' ODBC driver."]PDO_ODBC_HELP_TEXT)], [no], [no]) + +AC_DEFUN([PDO_ODBC_CHECK_HEADER],[ + AC_MSG_CHECKING([for $1 in $PDO_ODBC_INCDIR]) + if test -f "$PDO_ODBC_INCDIR/$1"; then + php_pdo_have_header=yes + AC_DEFINE_UNQUOTED(AS_TR_CPP([HAVE_$1]), [1], + [Define to 1 if you have the <$1> header file.]) + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi +]) + AC_DEFUN([SWOOLE_HAVE_PHP_EXT], [ extname=$1 haveext=$[PHP_]translit($1,a-z_-,A-Z__) @@ -132,6 +211,7 @@ AC_DEFUN([AC_SWOOLE_CPU_AFFINITY], #include typedef cpuset_t cpu_set_t; #else + #define _GNU_SOURCE 1 #include #endif ]], [[ @@ -170,8 +250,7 @@ AC_DEFUN([AC_SWOOLE_HAVE_FUTEX], #include ]], [[ int futex_addr; - int val1; - syscall(SYS_futex, &futex_addr, val1, NULL, NULL, 0); + syscall(SYS_futex, &futex_addr, FUTEX_WAIT, NULL, NULL, 0); ]])],[ AC_DEFINE([HAVE_FUTEX], 1, [have FUTEX?]) AC_MSG_RESULT([yes]) @@ -233,38 +312,63 @@ AC_DEFUN([AC_SWOOLE_HAVE_BOOST_STACKTRACE], AC_LANG_POP([C++]) ]) -AC_DEFUN([AC_SWOOLE_CHECK_SOCKETS], [ - dnl Check for struct cmsghdr - AC_CACHE_CHECK([for struct cmsghdr], ac_cv_cmsghdr, - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include -#include ]], [[struct cmsghdr s; s]])], [ac_cv_cmsghdr=yes], [ac_cv_cmsghdr=no]) +AC_DEFUN([AC_SWOOLE_HAVE_IOURING_FUTEX], +[ + AC_MSG_CHECKING([for io_uring futex]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define _GNU_SOURCE + #include + ]], [[ + int op = IORING_OP_FUTEX_WAIT; + ]])],[ + AC_DEFINE([HAVE_IOURING_FUTEX], 1, [have io_uring futex?]) + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) ]) +]) - if test "$ac_cv_cmsghdr" = yes; then - AC_DEFINE(HAVE_CMSGHDR,1,[Whether you have struct cmsghdr]) - fi +AC_DEFUN([AC_SWOOLE_HAVE_IOURING_STATX], +[ + AC_MSG_CHECKING([for io_uring statx]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define _GNU_SOURCE + #include + #include + #include + ]], [[ + struct statx _statxbuf; + memset(&_statxbuf, 0, sizeof(_statxbuf)); + int op = IORING_OP_STATX; + ]])],[ + AC_DEFINE([HAVE_IOURING_STATX], 1, [have io_uring statx?]) + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) + +AC_DEFUN([AC_SWOOLE_HAVE_IOURING_FTRUNCATE], +[ + AC_MSG_CHECKING([for io_uring ftruncate]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define _GNU_SOURCE + #include + ]], [[ + int op = IORING_OP_FTRUNCATE; + ]])],[ + AC_DEFINE([HAVE_IOURING_FTRUNCATE], 1, [have io_uring ftruncate?]) + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) +AC_DEFUN([AC_SWOOLE_CHECK_SOCKETS], [ AC_CHECK_FUNCS([hstrerror socketpair if_nametoindex if_indextoname]) AC_CHECK_HEADERS([netdb.h netinet/tcp.h sys/un.h sys/sockio.h]) AC_DEFINE([HAVE_SOCKETS], 1, [ ]) - dnl Check for fied ss_family in sockaddr_storage (missing in AIX until 5.3) - AC_CACHE_CHECK([for field ss_family in struct sockaddr_storage], ac_cv_ss_family, - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include -#include -#include - ]], [[struct sockaddr_storage sa_store; sa_store.ss_family = AF_INET6;]])], - [ac_cv_ss_family=yes], [ac_cv_ss_family=no]) - ]) - - if test "$ac_cv_ss_family" = yes; then - AC_DEFINE(HAVE_SA_SS_FAMILY,1,[Whether you have sockaddr_storage.ss_family]) - fi - dnl Check for AI_V4MAPPED flag AC_CACHE_CHECK([if getaddrinfo supports AI_V4MAPPED],[ac_cv_gai_ai_v4mapped], [ @@ -303,19 +407,22 @@ AC_DEFUN([AC_SWOOLE_CHECK_SOCKETS], [ if test "$ac_cv_gai_ai_idn" = yes; then AC_DEFINE(HAVE_AI_IDN,1,[Whether you have AI_IDN]) fi +]) - AC_CACHE_CHECK([if gethostbyname2_r is supported],[ac_cv_gethostbyname2_r], - [ - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ -#include -#include - ]], [[struct hostent s, *res; char ptr[256]; int err; gethostbyname2_r("example.com", AF_INET, &s, ptr, sizeof(ptr), &res, &err);]])], - [ac_cv_gethostbyname2_r=yes], [ac_cv_gethostbyname2_r=no]) +AC_DEFUN([AC_SWOOLE_HAVE_MSGQUEUE], +[ + AC_MSG_CHECKING([for sysv message queue]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include + #include + ]], [[ + msgget(0x9501, IPC_CREAT); + ]])],[ + AC_DEFINE([HAVE_MSGQUEUE], 1, [have sysv message queue?]) + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) ]) - - if test "$ac_cv_gethostbyname2_r" = yes; then - AC_DEFINE(HAVE_GETHOSTBYNAME2_R,1,[Whether you have gethostbyname2_r]) - fi ]) AC_MSG_CHECKING([if compiling with clang]) @@ -329,7 +436,8 @@ AC_COMPILE_IFELSE([ ) AC_MSG_RESULT([$CLANG]) -AC_PROG_CC_C99 +dnl AC_PROG_CC_C99 is obsolete with autoconf >= 2.70 yet necessary for <= 2.69. +m4_version_prereq([2.70],,[AC_PROG_CC_C99]) AC_CANONICAL_HOST @@ -339,7 +447,6 @@ if test "$PHP_SWOOLE" != "no"; then AC_CHECK_LIB(c, signalfd, AC_DEFINE(HAVE_SIGNALFD, 1, [have signalfd])) AC_CHECK_LIB(c, eventfd, AC_DEFINE(HAVE_EVENTFD, 1, [have eventfd])) AC_CHECK_LIB(c, epoll_create, AC_DEFINE(HAVE_EPOLL, 1, [have epoll])) - AC_CHECK_LIB(c, poll, AC_DEFINE(HAVE_POLL, 1, [have poll])) AC_CHECK_LIB(c, sendfile, AC_DEFINE(HAVE_SENDFILE, 1, [have sendfile])) AC_CHECK_LIB(c, kqueue, AC_DEFINE(HAVE_KQUEUE, 1, [have kqueue])) AC_CHECK_LIB(c, backtrace, AC_DEFINE(HAVE_EXECINFO, 1, [have execinfo])) @@ -348,6 +455,7 @@ if test "$PHP_SWOOLE" != "no"; then AC_CHECK_LIB(c, inotify_init, AC_DEFINE(HAVE_INOTIFY, 1, [have inotify])) AC_CHECK_LIB(c, malloc_trim, AC_DEFINE(HAVE_MALLOC_TRIM, 1, [have malloc_trim])) AC_CHECK_LIB(c, inotify_init1, AC_DEFINE(HAVE_INOTIFY_INIT1, 1, [have inotify_init1])) + AC_CHECK_LIB(c, gethostbyname2_r, AC_DEFINE(HAVE_GETHOSTBYNAME2_R, 1, [have gethostbyname2_r])) AC_CHECK_LIB(c, ptrace, AC_DEFINE(HAVE_PTRACE, 1, [have ptrace])) AC_CHECK_LIB(c, getrandom, AC_DEFINE(HAVE_GETRANDOM, 1, [have getrandom])) AC_CHECK_LIB(c, arc4random, AC_DEFINE(HAVE_ARC4RANDOM, 1, [have arc4random])) @@ -355,12 +463,12 @@ if test "$PHP_SWOOLE" != "no"; then AC_CHECK_LIB(pthread, pthread_rwlock_init, AC_DEFINE(HAVE_RWLOCK, 1, [have pthread_rwlock_init])) AC_CHECK_LIB(pthread, pthread_spin_lock, AC_DEFINE(HAVE_SPINLOCK, 1, [have pthread_spin_lock])) AC_CHECK_LIB(pthread, pthread_mutex_timedlock, AC_DEFINE(HAVE_MUTEX_TIMEDLOCK, 1, [have pthread_mutex_timedlock])) + AC_CHECK_LIB(pthread, pthread_rwlock_timedrdlock, AC_DEFINE(HAVE_RWLOCK_TIMEDRDLOCK, 1, [have pthread_rwlock_timedrdlock])) + AC_CHECK_LIB(pthread, pthread_rwlock_timedwrlock, AC_DEFINE(HAVE_RWLOCK_TIMEDWRLOCK, 1, [have pthread_rwlock_timedwrlock])) AC_CHECK_LIB(pthread, pthread_barrier_init, AC_DEFINE(HAVE_PTHREAD_BARRIER, 1, [have pthread_barrier_init])) AC_CHECK_LIB(pthread, pthread_mutexattr_setpshared, AC_DEFINE(HAVE_PTHREAD_MUTEXATTR_SETPSHARED, 1, [have pthread_mutexattr_setpshared])) AC_CHECK_LIB(pthread, pthread_mutexattr_setrobust, AC_DEFINE(HAVE_PTHREAD_MUTEXATTR_SETROBUST, 1, [have pthread_mutexattr_setrobust])) AC_CHECK_LIB(pthread, pthread_mutex_consistent, AC_DEFINE(HAVE_PTHREAD_MUTEX_CONSISTENT, 1, [have pthread_mutex_consistent])) - AC_CHECK_LIB(pcre, pcre_compile, AC_DEFINE(HAVE_PCRE, 1, [have pcre])) - AC_CHECK_LIB(cares, ares_gethostbyname, AC_DEFINE(HAVE_CARES, 1, [have c-ares])) if test "$PHP_SWOOLE_DEV" = "yes"; then AX_CHECK_COMPILE_FLAG(-Wbool-conversion, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wbool-conversion") @@ -377,6 +485,7 @@ if test "$PHP_SWOOLE" != "no"; then AX_CHECK_COMPILE_FLAG(-Wlogical-op-parentheses, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wlogical-op-parentheses") AX_CHECK_COMPILE_FLAG(-Wloop-analysis, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wloop-analysis") AX_CHECK_COMPILE_FLAG(-Wuninitialized, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wuninitialized") + AX_CHECK_COMPILE_FLAG(-Wno-date-time, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-date-time") AX_CHECK_COMPILE_FLAG(-Wno-missing-field-initializers, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-missing-field-initializers") AX_CHECK_COMPILE_FLAG(-Wno-sign-compare, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-sign-compare") AX_CHECK_COMPILE_FLAG(-Wno-unused-const-variable, _MAINTAINER_CFLAGS="$_MAINTAINER_CFLAGS -Wno-unused-const-variable") @@ -398,6 +507,9 @@ if test "$PHP_SWOOLE" != "no"; then fi if test "$PHP_SWOOLE_CURL" = "yes"; then + PKG_CHECK_MODULES([CURL], [libcurl >= 7.56.0]) + PHP_EVAL_LIBLINE($CURL_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($CURL_CFLAGS) AC_DEFINE(SW_USE_CURL, 1, [do we enable cURL native client]) fi @@ -405,9 +517,12 @@ if test "$PHP_SWOOLE" != "no"; then AC_DEFINE(SW_CORO_TIME, 1, [do we enable to calculate coroutine execution time]) fi + dnl pgsql begin + if test "$PHP_SWOOLE_PGSQL" != "no"; then dnl TODO macros below can be reused to find curl things dnl prepare pkg-config + if test -z "$PKG_CONFIG"; then AC_PATH_PROG(PKG_CONFIG, pkg-config, no) fi @@ -475,26 +590,535 @@ EOF AC_DEFINE(SW_USE_PGSQL, 1, [do we enable postgresql coro support]) fi + dnl pgsql end + + dnl odbc begin + + if test "$PHP_SWOOLE_ODBC" != "no"; then + PHP_CHECK_PDO_INCLUDES + + AC_MSG_CHECKING([for selected PDO ODBC flavour]) + + pdo_odbc_flavour="`echo $PHP_SWOOLE_ODBC | cut -d, -f1`" + pdo_odbc_dir="`echo $PHP_SWOOLE_ODBC | cut -d, -f2`" + + if test "$pdo_odbc_dir" = "$PHP_SWOOLE_ODBC" ; then + pdo_odbc_dir= + fi + + case $pdo_odbc_flavour in + ibm-db2) + pdo_odbc_def_libdir=/home/db2inst1/sqllib/lib + pdo_odbc_def_incdir=/home/db2inst1/sqllib/include + pdo_odbc_def_lib=db2 + ;; + + iODBC|iodbc) + pdo_odbc_def_libdir=/usr/local/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/local/include + pdo_odbc_def_lib=iodbc + ;; + + unixODBC|unixodbc) + pdo_odbc_def_libdir=/usr/local/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/local/include + pdo_odbc_def_lib=odbc + ;; + + ODBCRouter|odbcrouter) + pdo_odbc_def_libdir=/usr/$PHP_LIBDIR + pdo_odbc_def_incdir=/usr/include + pdo_odbc_def_lib=odbcsdk + ;; + + generic) + pdo_odbc_def_lib="`echo $PHP_SWOOLE_ODBC | cut -d, -f3`" + pdo_odbc_def_ldflags="`echo $PHP_SWOOLE_ODBC | cut -d, -f4`" + pdo_odbc_def_cflags="`echo $PHP_SWOOLE_ODBC | cut -d, -f5`" + pdo_odbc_flavour="generic-$pdo_odbc_def_lib" + ;; + + *) + AC_MSG_ERROR([Unknown ODBC flavour $pdo_odbc_flavour]PDO_ODBC_HELP_TEXT) + ;; + esac + + if test -n "$pdo_odbc_dir"; then + PDO_ODBC_INCDIR="$pdo_odbc_dir/include" + PDO_ODBC_LIBDIR="$pdo_odbc_dir/$PHP_LIBDIR" + else + PDO_ODBC_INCDIR="$pdo_odbc_def_incdir" + PDO_ODBC_LIBDIR="$pdo_odbc_def_libdir" + fi + + AC_MSG_RESULT([$pdo_odbc_flavour + libs $PDO_ODBC_LIBDIR, + headers $PDO_ODBC_INCDIR]) + + if test ! -d "$PDO_ODBC_LIBDIR" ; then + AC_MSG_WARN([library dir $PDO_ODBC_LIBDIR does not exist]) + fi + + PDO_ODBC_CHECK_HEADER(odbc.h) + PDO_ODBC_CHECK_HEADER(odbcsdk.h) + PDO_ODBC_CHECK_HEADER(iodbc.h) + PDO_ODBC_CHECK_HEADER(sqlunix.h) + PDO_ODBC_CHECK_HEADER(sqltypes.h) + PDO_ODBC_CHECK_HEADER(sqlucode.h) + PDO_ODBC_CHECK_HEADER(sql.h) + PDO_ODBC_CHECK_HEADER(isql.h) + PDO_ODBC_CHECK_HEADER(sqlext.h) + PDO_ODBC_CHECK_HEADER(isqlext.h) + PDO_ODBC_CHECK_HEADER(udbcext.h) + PDO_ODBC_CHECK_HEADER(sqlcli1.h) + PDO_ODBC_CHECK_HEADER(LibraryManager.h) + PDO_ODBC_CHECK_HEADER(cli0core.h) + PDO_ODBC_CHECK_HEADER(cli0ext.h) + PDO_ODBC_CHECK_HEADER(cli0cli.h) + PDO_ODBC_CHECK_HEADER(cli0defs.h) + PDO_ODBC_CHECK_HEADER(cli0env.h) + + if test "$php_pdo_have_header" != "yes"; then + AC_MSG_ERROR([Cannot find header file(s) for pdo_odbc]) + fi + + if test -n "$SWOOLE_ODBC_LIBS"; then + ODBC_LIBS="$SWOOLE_ODBC_LIBS" + else + ODBC_LIBS="-l$pdo_odbc_def_lib" + fi + + PDO_ODBC_INCLUDE="$pdo_odbc_def_cflags -I$PDO_ODBC_INCDIR -DPDO_ODBC_TYPE=\\\"$pdo_odbc_flavour\\\"" + PDO_ODBC_LDFLAGS="$pdo_odbc_def_ldflags -L$PDO_ODBC_LIBDIR $ODBC_LIBS" + + PHP_EVAL_LIBLINE([$PDO_ODBC_LDFLAGS], [SWOOLE_SHARED_LIBADD]) + + EXTRA_CFLAGS="$EXTRA_CFLAGS -I$pdo_cv_inc_path $PDO_ODBC_INCLUDE" + + dnl Check first for an ODBC 1.0 function to assert that the libraries work + SAVE_LIBS="$LIBS" + LIBS="$LIBS $PDO_ODBC_LDFLAGS" + + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + #include ]], + [[ + SQLLEN ind = 0; + char buf[1]; + SQLBindCol((SQLHSTMT)0, (SQLUSMALLINT)1, (SQLSMALLINT)SQL_C_CHAR, + (SQLPOINTER)buf, (SQLLEN)sizeof(buf), &ind); + return 0; + ]])], + [ + dnl And now check for an ODBC 3.0 function to assert that they are *good* + dnl libraries. + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + #include ]], + [[ + SQLHANDLE out = SQL_NULL_HANDLE; + SQLAllocHandle((SQLSMALLINT)SQL_HANDLE_ENV, + (SQLHANDLE)SQL_NULL_HANDLE, &out); + return 0; + ]])], + [], + [AC_MSG_ERROR([ + Your ODBC library does not appear to be ODBC 3 compatible. + You should consider using iODBC or unixODBC instead, and loading your + libraries as a driver in that environment; it will emulate the + functions required for PDO support. + ])] + ) + ], + [AC_MSG_ERROR([Your ODBC library does not exist or there was an error. Check config.log for more information])] + ) + + LIBS="$SAVE_LIBS" + + AC_DEFINE(SW_USE_ODBC, 1, [do we enable swoole-odbc coro support]) + fi + + dnl odbc end + + dnl SWOOLE_ORACLE start + + if test -z "$SED"; then + SWOOLE_PDO_OCI_SED="sed"; + else + SWOOLE_PDO_OCI_SED="$SED"; + fi + + SWOOLE_PDO_OCI_TAIL1=`echo a | tail -n1 2>/dev/null` + if test "$SWOOLE_PDO_OCI_TAIL1" = "a"; then + SWOOLE_PDO_OCI_TAIL1="tail -n1" + else + SWOOLE_PDO_OCI_TAIL1="tail -1" + fi + + AC_DEFUN([AC_PDO_OCI_VERSION],[ + AC_MSG_CHECKING([Oracle version]) + PDO_OCI_LCS_BASE=$PDO_OCI_LIB_DIR/libclntsh.$SHLIB_SUFFIX_NAME + dnl Oracle 10g, 11g, 12c etc + PDO_OCI_LCS=`ls $PDO_OCI_LCS_BASE.*.1 2> /dev/null | $SWOOLE_PDO_OCI_TAIL1` + if test -f "$PDO_OCI_LCS"; then + dnl Oracle 10g, 11g 12c etc. The x.2 version libraries are named x.1 for + dnl drop in compatibility + PDO_OCI_VERSION=`echo $PDO_OCI_LCS | $SWOOLE_PDO_OCI_SED -e 's/.*\.\(.*\)\.1$/\1.1/'` + elif test -f $PDO_OCI_LCS_BASE.9.0; then + dnl There is no case for Oracle 9.2. Oracle 9.2 libraries have a 9.0 suffix + dnl for drop-in compatibility with Oracle 9.0 + PDO_OCI_VERSION=9.0 + else + AC_MSG_ERROR(Oracle libclntsh.$SHLIB_SUFFIX_NAME client library not found or its version is lower than 9) + fi + AC_MSG_RESULT($PDO_OCI_VERSION) + ]) + + AC_DEFUN([AC_PDO_OCI_CHECK_LIB_DIR],[ + AC_CHECK_SIZEOF([long]) + AC_MSG_CHECKING([if we're at 64-bit platform]) + AS_IF([test "$ac_cv_sizeof_long" -eq 4],[ + AC_MSG_RESULT([no]) + TMP_PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib32" + ],[ + AC_MSG_RESULT([yes]) + TMP_PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib" + ]) + + AC_MSG_CHECKING([OCI8 libraries dir]) + if test -d "$PDO_OCI_DIR/lib" && test ! -d "$PDO_OCI_DIR/lib32"; then + PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib" + elif test ! -d "$PDO_OCI_DIR/lib" && test -d "$PDO_OCI_DIR/lib32"; then + PDO_OCI_LIB_DIR="$PDO_OCI_DIR/lib32" + elif test -d "$PDO_OCI_DIR/lib" && test -d "$PDO_OCI_DIR/lib32"; then + PDO_OCI_LIB_DIR=$TMP_PDO_OCI_LIB_DIR + else + AC_MSG_ERROR([Oracle required OCI8 libraries not found]) + fi + AC_MSG_RESULT($PDO_OCI_LIB_DIR) + ]) + + PHP_ARG_WITH([swoole-oracle], + [whether to enable oracle build flags], + [AS_HELP_STRING([[--with-swoole-oracle[=DIR]]], + ["PDO: Oracle OCI support. DIR defaults to ${ORACLE_HOME}. Use + --with-swoole-oracle=instantclient,/path/to/instant/client/lib for an Oracle + Instant Client installation."])], [no], [no]) + + if test "$PHP_SWOOLE_ORACLE" != "no"; then + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + AC_MSG_CHECKING([Oracle Install-Dir]) + if test "$PHP_SWOOLE_ORACLE" = "yes" || test -z "$PHP_SWOOLE_ORACLE"; then + PDO_OCI_DIR=$ORACLE_HOME + else + PDO_OCI_DIR=$PHP_SWOOLE_ORACLE + fi + AC_MSG_RESULT($PHP_SWOOLE_ORACLE) + + AC_MSG_CHECKING([if that is sane]) + if test -z "$PDO_OCI_DIR"; then + AC_MSG_ERROR([You need to tell me where to find your Oracle Instant Client SDK, or set ORACLE_HOME.]) + else + AC_MSG_RESULT([yes]) + fi + + if test "instantclient" = "`echo $PDO_OCI_DIR | cut -d, -f1`" ; then + AC_CHECK_SIZEOF([long]) + AC_MSG_CHECKING([if we're at 64-bit platform]) + AS_IF([test "$ac_cv_sizeof_long" -eq 4],[ + AC_MSG_RESULT([no]) + PDO_OCI_CLIENT_DIR="client" + ],[ + AC_MSG_RESULT([yes]) + PDO_OCI_CLIENT_DIR="client64" + ]) + + PDO_OCI_LIB_DIR="`echo $PDO_OCI_DIR | cut -d, -f2`" + AC_PDO_OCI_VERSION($PDO_OCI_LIB_DIR) + + AC_MSG_CHECKING([for oci.h]) + dnl Header directory for Instant Client SDK RPM install + OCISDKRPMINC=`echo "$PDO_OCI_LIB_DIR" | $SWOOLE_PDO_OCI_SED -e 's!^\(.*\)/lib/oracle/\(.*\)/\('${PDO_OCI_CLIENT_DIR}'\)/lib[/]*$!\1/include/oracle/\2/\3!'` + + dnl Header directory for manual installation + OCISDKMANINC=`echo "$PDO_OCI_LIB_DIR" | $SWOOLE_PDO_OCI_SED -e 's!^\(.*\)/lib[/]*$!\1/include!'` + + dnl Header directory for Instant Client SDK zip file install + OCISDKZIPINC=$PDO_OCI_LIB_DIR/sdk/include + + if test -f "$OCISDKRPMINC/oci.h" ; then + PHP_ADD_INCLUDE($OCISDKRPMINC) + AC_MSG_RESULT($OCISDKRPMINC) + elif test -f "$OCISDKMANINC/oci.h" ; then + PHP_ADD_INCLUDE($OCISDKMANINC) + AC_MSG_RESULT($OCISDKMANINC) + elif test -f "$OCISDKZIPINC/oci.h" ; then + PHP_ADD_INCLUDE($OCISDKZIPINC) + AC_MSG_RESULT($OCISDKZIPINC) + else + AC_MSG_ERROR([I'm too dumb to figure out where the include dir is in your Instant Client install]) + fi + else + AC_PDO_OCI_CHECK_LIB_DIR($PDO_OCI_DIR) + + if test -d "$PDO_OCI_DIR/rdbms/public"; then + PHP_ADD_INCLUDE($PDO_OCI_DIR/rdbms/public) + PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/rdbms/public" + fi + if test -d "$PDO_OCI_DIR/rdbms/demo"; then + PHP_ADD_INCLUDE($PDO_OCI_DIR/rdbms/demo) + PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/rdbms/demo" + fi + if test -d "$PDO_OCI_DIR/network/public"; then + PHP_ADD_INCLUDE($PDO_OCI_DIR/network/public) + PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/network/public" + fi + if test -d "$PDO_OCI_DIR/plsql/public"; then + PHP_ADD_INCLUDE($PDO_OCI_DIR/plsql/public) + PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/plsql/public" + fi + if test -d "$PDO_OCI_DIR/include"; then + PHP_ADD_INCLUDE($PDO_OCI_DIR/include) + PDO_OCI_INCLUDES="$PDO_OCI_INCLUDES -I$PDO_OCI_DIR/include" + fi + + if test -f "$PDO_OCI_LIB_DIR/sysliblist"; then + PHP_EVAL_LIBLINE(`cat $PDO_OCI_LIB_DIR/sysliblist`, SWOOLE_SHARED_LIBADD) + elif test -f "$PDO_OCI_DIR/rdbms/lib/sysliblist"; then + PHP_EVAL_LIBLINE(`cat $PDO_OCI_DIR/rdbms/lib/sysliblist`, SWOOLE_SHARED_LIBADD) + fi + AC_PDO_OCI_VERSION($PDO_OCI_LIB_DIR) + fi + + case $PDO_OCI_VERSION in + 7.3|8.0|8.1) + AC_MSG_ERROR([Oracle client libraries < 9 are not supported]) + ;; + esac + + PHP_ADD_LIBRARY(clntsh, 1, SWOOLE_SHARED_LIBADD) + PHP_ADD_LIBPATH($PDO_OCI_LIB_DIR, SWOOLE_SHARED_LIBADD) + + PHP_CHECK_LIBRARY(clntsh, OCIEnvCreate, + [ + AC_DEFINE(HAVE_OCIENVCREATE,1,[ ]) + ], [], [ + -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD + ]) + + PHP_CHECK_LIBRARY(clntsh, OCIEnvNlsCreate, + [ + AC_DEFINE(HAVE_OCIENVNLSCREATE,1,[ ]) + ], [], [ + -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD + ]) + + dnl Scrollable cursors? + PHP_CHECK_LIBRARY(clntsh, OCIStmtFetch2, + [ + AC_DEFINE(HAVE_OCISTMTFETCH2,1,[ ]) + ], [], [ + -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD + ]) + + dnl Can handle bytes vs. characters? + PHP_CHECK_LIBRARY(clntsh, OCILobRead2, + [ + AC_DEFINE(HAVE_OCILOBREAD2,1,[ ]) + ], [], [ + -L$PDO_OCI_LIB_DIR $SWOOLE_SHARED_LIBADD + ]) + + EXTRA_CFLAGS="$EXTRA_CFLAGS -I$pdo_cv_inc_path $PDO_OCI_INCLUDE" + PHP_CHECK_PDO_INCLUDES + AC_DEFINE_UNQUOTED(SWOOLE_PDO_OCI_CLIENT_VERSION, "$PDO_OCI_VERSION", [ ]) + AC_DEFINE(SW_USE_ORACLE, 1, [do we enable oracle coro support]) + fi + dnl SWOOLE_ORACLE stop + + dnl sqlite start + PHP_ARG_ENABLE([swoole-sqlite], + ["for sqlite 3 support for PDO"], + [AS_HELP_STRING([--enable-swoole-sqlite], + [PDO: sqlite 3 support.])], [no], [no]) + + if test "$PHP_SWOOLE_SQLITE" != "no"; then + + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + PHP_CHECK_PDO_INCLUDES + + PKG_CHECK_MODULES([SQLITE], [sqlite3 >= 3.7.7]) + + PHP_EVAL_INCLINE($SQLITE_CFLAGS) + PHP_EVAL_LIBLINE($SQLITE_LIBS, SWOOLE_SHARED_LIBADD) + AC_DEFINE(HAVE_SW_PDO_SQLITELIB, 1, [Define to 1 if you have the pdo_sqlite extension enabled.]) + + PHP_CHECK_LIBRARY(sqlite3, sqlite3_close_v2, [ + AC_DEFINE(HAVE_SW_SQLITE3_CLOSE_V2, 1, [have sqlite3_close_v2]) + ], [], [$SWOOLE_SHARED_LIBADD]) + + PHP_CHECK_LIBRARY(sqlite3, sqlite3_column_table_name, [ + AC_DEFINE(HAVE_SW_SQLITE3_COLUMN_TABLE_NAME, 1, [have sqlite3_column_table_name]) + ], [], [$SWOOLE_SHARED_LIBADD]) + + AC_DEFINE(SW_USE_SQLITE, 1, [do we enable sqlite coro support]) + fi + dnl sqlite stop + + dnl firebird start + + if test "$PHP_SWOOLE_FIREBIRD" != "no"; then + if test "$PHP_PDO" = "no" && test "$ext_shared" = "no"; then + AC_MSG_ERROR([PDO is not enabled! Add --enable-pdo to your configure line.]) + fi + + AC_PATH_PROG([FB_CONFIG], [fb_config], [no]) + + if test -x "$FB_CONFIG" && test "$PHP_PDO_FIREBIRD" = "yes"; then + AC_MSG_CHECKING([for libfbconfig]) + FB_CFLAGS=$($FB_CONFIG --cflags) + FB_LIBDIR=$($FB_CONFIG --libs) + FB_VERSION=$($FB_CONFIG --version) + AC_MSG_RESULT([version $FB_VERSION]) + AS_VERSION_COMPARE([$FB_VERSION], [3.0], + [AC_MSG_ERROR([Firebird required version is at least 3.0])]) + PHP_EVAL_LIBLINE([$FB_LIBDIR], [SWOOLE_SHARED_LIBADD]) + PHP_EVAL_INCLINE([$FB_CFLAGS]) + else + AS_VAR_IF([PHP_PDO_FIREBIRD], [yes], [ + FIREBIRD_INCDIR= + FIREBIRD_LIBDIR= + FIREBIRD_LIBDIR_FLAG= + ], [ + FIREBIRD_INCDIR=$PHP_PDO_FIREBIRD/include + FIREBIRD_LIBDIR=$PHP_PDO_FIREBIRD/$PHP_LIBDIR + FIREBIRD_LIBDIR_FLAG=-L$FIREBIRD_LIBDIR + ]) + + PHP_CHECK_LIBRARY([fbclient], [fb_get_master_interface], + [], + [AC_MSG_FAILURE([libfbclient not found.])], + [$FIREBIRD_LIBDIR_FLAG]) + PHP_ADD_LIBRARY_WITH_PATH([fbclient], + [$FIREBIRD_LIBDIR], + [SWOOLE_SHARED_LIBADD]) + PHP_ADD_INCLUDE([$FIREBIRD_INCDIR]) + fi + + PHP_CHECK_PDO_INCLUDES + AC_DEFINE(SW_USE_FIREBIRD, 1, [do we enable firebird coro support]) + fi + + dnl firebird stop + + + dnl ssh2 start + + if test "$PHP_SWOOLE_SSH2" != "no"; then + SEARCH_PATH="/usr/local /usr" + SEARCH_FOR="/include/libssh2.h" + if test -r $PHP_SWOOLE_SSH2/$SEARCH_FOR; then # path given as parameter + SSH2_DIR=$PHP_SWOOLE_SSH2 + else + AC_MSG_CHECKING([for ssh2 files in default path]) + for i in $SEARCH_PATH ; do + if test -r $i/$SEARCH_FOR; then + SSH2_DIR=$i + AC_MSG_RESULT(found in $i) + fi + done + fi + + if test -z "$SSH2_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_ERROR([The required libssh2 library was not found. + You can obtain that package from http://sourceforge.net/projects/libssh2/]) + fi + + PHP_ADD_INCLUDE($SSH2_DIR/include) + + PHP_CHECK_LIBRARY(ssh2, libssh2_session_hostkey, [ + PHP_ADD_LIBRARY_WITH_PATH(ssh2, $SSH2_DIR/lib, SWOOLE_SHARED_LIBADD) + AC_DEFINE(SW_HAVE_SSH2LIB, 1, [Have libssh2]) + ],[ + AC_MSG_ERROR([libssh2 version >= 1.2 not found]) + ],[ + -L$SSH2_DIR/lib -lm + ]) + + PHP_CHECK_LIBRARY(ssh2, libssh2_agent_init, [ + AC_DEFINE(PHP_SSH2_AGENT_AUTH, 1, [Have libssh2 with ssh-agent support]) + ],[ + AC_MSG_WARN([libssh2 <= 1.2.3, ssh-agent subsystem support not enabled]) + ],[ + -L$SSH2_DIR/lib -lm + ]) + + PHP_CHECK_LIBRARY(ssh2, libssh2_session_set_timeout, [ + AC_DEFINE(PHP_SSH2_SESSION_TIMEOUT, 1, [Have libssh2 with session timeout support]) + ],[ + AC_MSG_WARN([libssh2 < 1.2.9, session timeout support not enabled]) + ],[ + -L$SSH2_DIR/lib -lm + ]) + fi + + dnl ssh2 stop + AC_CHECK_LIB(z, gzgets, [ - AC_DEFINE(SW_HAVE_COMPRESSION, 1, [have compression]) AC_DEFINE(SW_HAVE_ZLIB, 1, [have zlib]) PHP_ADD_LIBRARY(z, 1, SWOOLE_SHARED_LIBADD) ]) - AC_CHECK_LIB(brotlienc, BrotliEncoderCreateInstance, [ - AC_CHECK_LIB(brotlidec, BrotliDecoderCreateInstance, [ - AC_DEFINE(SW_HAVE_COMPRESSION, 1, [have compression]) - AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli encoder]) - PHP_ADD_LIBRARY(brotlienc, 1, SWOOLE_SHARED_LIBADD) - PHP_ADD_LIBRARY(brotlidec, 1, SWOOLE_SHARED_LIBADD) - ]) - ]) + if test "$PHP_BROTLI_DIR" != "no"; then + AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) + PHP_ADD_INCLUDE("${PHP_BROTLI_DIR}/include") + PHP_ADD_LIBRARY_WITH_PATH(brotlienc, "${PHP_BROTLI_DIR}/${PHP_LIBDIR}") + PHP_ADD_LIBRARY_WITH_PATH(brotlidec, "${PHP_BROTLI_DIR}/${PHP_LIBDIR}") + elif test "$PHP_BROTLI" = "yes"; then + PKG_CHECK_MODULES([BROTLIENC], [libbrotlienc]) + PKG_CHECK_MODULES([BROTLIDEC], [libbrotlidec]) + AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) + PHP_EVAL_LIBLINE($BROTLIENC_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($BROTLIENC_CFLAGS) + PHP_EVAL_LIBLINE($BROTLIDEC_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($BROTLIDEC_CFLAGS) + elif test "$PHP_BROTLI" = "auto"; then + PKG_CHECK_MODULES([BROTLIENC], [libbrotlienc], [found_brotlienc=yes], [found_brotlienc=no]) + if test "$found_brotlienc" = "yes"; then + AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) + PHP_EVAL_LIBLINE($BROTLIENC_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($BROTLIENC_CFLAGS) + fi + + PKG_CHECK_MODULES([BROTLIDEC], [libbrotlidec], [found_brotlidec=yes], [found_brotlidec=no]) + if test "$found_brotlidec" = "yes"; then + AC_DEFINE(SW_HAVE_BROTLI, 1, [have brotli]) + PHP_EVAL_LIBLINE($BROTLIDEC_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($BROTLIDEC_CFLAGS) + fi + fi + + if test "$PHP_ZSTD" = "yes"; then + PKG_CHECK_MODULES([ZSTD], [libzstd >= 1.4.0]) + AC_DEFINE(SW_HAVE_ZSTD, 1, [have zstd]) + PHP_EVAL_LIBLINE($ZSTD_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($ZSTD_CFLAGS) + fi PHP_ADD_LIBRARY(pthread) PHP_SUBST(SWOOLE_SHARED_LIBADD) AC_ARG_ENABLE(debug, - [ --enable-debug, compile with debug symbols], + [ --enable-debug Compile with debug symbols], [PHP_DEBUG=$enableval], [PHP_DEBUG=0] ) @@ -514,6 +1138,14 @@ EOF AC_DEFINE(SW_LOG_TRACE_OPEN, 1, [enable trace log]) fi + if test "$PHP_SWOOLE_THREAD" != "no"; then + AC_DEFINE(SW_THREAD, 1, [enable swoole thread support]) + fi + + if test "$PHP_SWOOLE_STDEXT" != "no"; then + AC_DEFINE(SW_STDEXT, 1, [enable swoole stdext support]) + fi + if test "$PHP_SOCKETS" = "yes"; then AC_MSG_CHECKING([for php_sockets.h]) @@ -536,8 +1168,11 @@ EOF fi if test "$PHP_CARES" = "yes"; then + PKG_CHECK_MODULES([CARES], [libcares]) + PHP_EVAL_LIBLINE($CARES_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($CARES_CFLAGS) AC_DEFINE(SW_USE_CARES, 1, [do we enable c-ares support]) - PHP_ADD_LIBRARY(cares, 1, SWOOLE_SHARED_LIBADD) + AC_DEFINE(HAVE_CARES, 1, [have c-ares]) fi AC_SWOOLE_CPU_AFFINITY @@ -546,6 +1181,7 @@ EOF AC_SWOOLE_HAVE_UCONTEXT AC_SWOOLE_HAVE_VALGRIND AC_SWOOLE_CHECK_SOCKETS + AC_SWOOLE_HAVE_MSGQUEUE AC_SWOOLE_HAVE_BOOST_STACKTRACE AS_CASE([$host_os], @@ -560,29 +1196,102 @@ EOF CFLAGS="-Wall -pthread $CFLAGS" LDFLAGS="$LDFLAGS -lpthread" - if test "$SW_OS" != "MAC"; then + HAVE_CONFIG_IOURING_X="no" + if (test "$PHP_IOURING" = "yes" || test "$PHP_LIBURING_DIR" != "no") && test "$SW_OS" = "LINUX"; then + if test "$PHP_IOURING" != "no"; then + PKG_CHECK_MODULES([URING], [liburing >= 2.8]) + PHP_EVAL_LIBLINE($URING_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($URING_CFLAGS) + elif test "$PHP_LIBURING_DIR" != "no"; then + PHP_ADD_INCLUDE("${PHP_LIBURING_DIR}/include") + PHP_ADD_LIBRARY_WITH_PATH(uring, "${PHP_LIBURING_DIR}/${PHP_LIBDIR}") + PHP_ADD_LIBRARY(uring, 1, SWOOLE_SHARED_LIBADD) + fi + + AC_SWOOLE_HAVE_IOURING_STATX + + KERNEL_MAJOR=`uname -r | awk -F '.' '{print $1}'` + KERNEL_MINOR=`uname -r | awk -F '.' '{print $2}'` + + if (test $KERNEL_MAJOR -eq 6 && test $KERNEL_MINOR -ge 9); then + dnl IORING_OP_FTRUNCATE is available since 6.9 + AC_SWOOLE_HAVE_IOURING_FTRUNCATE + fi + + if (test $KERNEL_MAJOR -eq 6 && test $KERNEL_MINOR -ge 7); then + dnl IORING_OP_FUTEX_WAKE/IORING_OP_FUTEX_WAIT is available since 6.7 + AC_SWOOLE_HAVE_IOURING_FUTEX + fi + + HAVE_CONFIG_IOURING_X="yes" + AC_DEFINE(SW_USE_IOURING, 1, [have io_uring]) + fi + + if test "$PHP_URING_SOCKET" != "no"; then + if test "$HAVE_CONFIG_IOURING_X" != "no"; then + AC_DEFINE(SW_USE_URING_SOCKET, 1, [enable io_uring socket support]) + else + AC_MSG_ERROR([--enable-uring-socket requires io_uring. Please specify either --enable-iouring or --with-liburing-dir.]) + fi + fi + + dnl Check should we link to librt + + if test "$SW_OS" = "LINUX"; then + GLIBC_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}') + AC_MSG_NOTICE([glibc version: $GLIBC_VERSION]) + + GLIBC_MAJOR_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}' | cut -d '.' -f 1) + GLIBC_MINOR_VERSION=$(getconf GNU_LIBC_VERSION | awk '{print $2}' | cut -d '.' -f 2) + + if test $GLIBC_MAJOR_VERSION -lt 2 || (test $GLIBC_MAJOR_VERSION -eq 2 && test $GLIBC_MINOR_VERSION -lt 17); then + OS_SHOULD_HAVE_LIBRT=1 + else + AC_MSG_NOTICE([link with -lrt (only for glibc version before 2.17)]) + OS_SHOULD_HAVE_LIBRT=0 + fi + elif test "$SW_OS" = "MAC"; then + OS_SHOULD_HAVE_LIBRT=0 + else + AS_CASE([$host_os], + [openbsd*], [OS_SHOULD_HAVE_LIBRT=0] + [OS_SHOULD_HAVE_LIBRT=1] + ) + fi + + if test "x$OS_SHOULD_HAVE_LIBRT" = "x1"; then + AC_MSG_NOTICE([Librt is required on $host_os.]) + dnl Check for the existence of librt + AC_CHECK_LIB([rt], [clock_gettime], [], [ + AC_MSG_ERROR([We have to link to librt on your os, but librt not found.]) + ]) PHP_ADD_LIBRARY(rt, 1, SWOOLE_SHARED_LIBADD) + else + AC_MSG_NOTICE([$host_os doesn't have librt -- don't link to librt.]) fi if test "$SW_OS" = "LINUX"; then LDFLAGS="$LDFLAGS -z now" fi - if test "$PHP_OPENSSL" != "no" || test "$PHP_OPENSSL_DIR" != "no"; then - if test "$PHP_OPENSSL_DIR" != "no"; then - PHP_ADD_INCLUDE("${PHP_OPENSSL_DIR}/include") - PHP_ADD_LIBRARY_WITH_PATH(ssl, "${PHP_OPENSSL_DIR}/${PHP_LIBDIR}") - fi - AC_DEFINE(SW_USE_OPENSSL, 1, [enable openssl support]) - PHP_ADD_LIBRARY(ssl, 1, SWOOLE_SHARED_LIBADD) - PHP_ADD_LIBRARY(crypto, 1, SWOOLE_SHARED_LIBADD) + if test "$PHP_OPENSSL_DIR" != "no"; then + PHP_ADD_INCLUDE("${PHP_OPENSSL_DIR}/include") + PHP_ADD_LIBRARY_WITH_PATH(ssl, "${PHP_OPENSSL_DIR}/${PHP_LIBDIR}") + PHP_ADD_LIBRARY_WITH_PATH(crypto, "${PHP_OPENSSL_DIR}/${PHP_LIBDIR}") + else + PKG_CHECK_MODULES([SSL], [libssl]) + PHP_EVAL_LIBLINE($SSL_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($SSL_CFLAGS) + + PKG_CHECK_MODULES([CRYPTO], [libcrypto]) + PHP_EVAL_LIBLINE($CRYPTO_LIBS, SWOOLE_SHARED_LIBADD) + PHP_EVAL_INCLINE($CRYPTO_CFLAGS) fi - - if test "$PHP_JEMALLOC_DIR" != "no"; then - AC_DEFINE(SW_USE_JEMALLOC, 1, [use jemalloc]) - PHP_ADD_INCLUDE("${PHP_JEMALLOC_DIR}/include") - PHP_ADD_LIBRARY_WITH_PATH(jemalloc, "${PHP_JEMALLOC_DIR}/${PHP_LIBDIR}") - PHP_ADD_LIBRARY(jemalloc, 1, SWOOLE_SHARED_LIBADD) + + if test "$PHP_NGHTTP2_DIR" != "no"; then + PHP_ADD_INCLUDE("${PHP_NGHTTP2_DIR}/include") + PHP_ADD_LIBRARY_WITH_PATH(nghttp2, "${PHP_NGHTTP2_DIR}/${PHP_LIBDIR}") + PHP_ADD_LIBRARY(nghttp2, 1, SWOOLE_SHARED_LIBADD) fi PHP_ADD_LIBRARY(pthread, 1, SWOOLE_SHARED_LIBADD) @@ -592,118 +1301,22 @@ EOF AC_DEFINE(SW_USE_MYSQLND, 1, [use mysqlnd]) fi - swoole_source_file=" \ - ext-src/php_swoole.cc \ - ext-src/php_swoole_cxx.cc \ - ext-src/swoole_admin_server.cc \ - ext-src/swoole_async_coro.cc \ - ext-src/swoole_atomic.cc \ - ext-src/swoole_channel_coro.cc \ - ext-src/swoole_client.cc \ - ext-src/swoole_client_coro.cc \ - ext-src/swoole_coroutine.cc \ - ext-src/swoole_coroutine_scheduler.cc \ - ext-src/swoole_coroutine_system.cc \ - ext-src/swoole_curl.cc \ - ext-src/swoole_event.cc \ - ext-src/swoole_http2_client_coro.cc \ - ext-src/swoole_http2_server.cc \ - ext-src/swoole_http_client_coro.cc \ - ext-src/swoole_http_request.cc \ - ext-src/swoole_http_response.cc \ - ext-src/swoole_http_server.cc \ - ext-src/swoole_http_server_coro.cc \ - ext-src/swoole_lock.cc \ - ext-src/swoole_mysql_coro.cc \ - ext-src/swoole_mysql_proto.cc \ - ext-src/swoole_name_resolver.cc \ - ext-src/swoole_postgresql_coro.cc \ - ext-src/swoole_process.cc \ - ext-src/swoole_process_pool.cc \ - ext-src/swoole_redis_coro.cc \ - ext-src/swoole_redis_server.cc \ - ext-src/swoole_runtime.cc \ - ext-src/swoole_server.cc \ - ext-src/swoole_server_port.cc \ - ext-src/swoole_socket_coro.cc \ - ext-src/swoole_table.cc \ - ext-src/swoole_timer.cc \ - ext-src/swoole_websocket_server.cc \ - src/core/base.cc \ - src/core/channel.cc \ - src/core/crc32.cc \ - src/core/error.cc \ - src/core/heap.cc \ - src/core/log.cc \ - src/core/string.cc \ - src/core/timer.cc \ - src/coroutine/base.cc \ - src/coroutine/channel.cc \ - src/coroutine/context.cc \ - src/coroutine/file_lock.cc \ - src/coroutine/hook.cc \ - src/coroutine/socket.cc \ - src/coroutine/system.cc \ - src/coroutine/thread_context.cc \ - src/lock/mutex.cc \ - src/lock/rw_lock.cc \ - src/lock/spin_lock.cc \ - src/memory/buffer.cc \ - src/memory/fixed_pool.cc \ - src/memory/global_memory.cc \ - src/memory/ring_buffer.cc \ - src/memory/shared_memory.cc \ - src/memory/table.cc \ - src/network/address.cc \ - src/network/client.cc \ - src/network/dns.cc \ - src/network/socket.cc \ - src/network/stream.cc \ - src/os/async_thread.cc \ - src/os/base.cc \ - src/os/file.cc \ - src/os/msg_queue.cc \ - src/os/pipe.cc \ - src/os/process_pool.cc \ - src/os/sendfile.cc \ - src/os/signal.cc \ - src/os/timer.cc \ - src/os/unix_socket.cc \ - src/os/wait.cc \ - src/protocol/base.cc \ - src/protocol/base64.cc \ - src/protocol/dtls.cc \ - src/protocol/http.cc \ - src/protocol/http2.cc \ - src/protocol/mime_type.cc \ - src/protocol/mqtt.cc \ - src/protocol/redis.cc \ - src/protocol/socks5.cc \ - src/protocol/ssl.cc \ - src/protocol/websocket.cc \ - src/reactor/base.cc \ - src/reactor/epoll.cc \ - src/reactor/kqueue.cc \ - src/reactor/poll.cc \ - src/reactor/select.cc \ - src/server/base.cc \ - src/server/manager.cc \ - src/server/master.cc \ - src/server/message_bus.cc \ - src/server/port.cc \ - src/server/process.cc \ - src/server/reactor_process.cc \ - src/server/reactor_thread.cc \ - src/server/static_handler.cc \ - src/server/task_worker.cc \ - src/server/worker.cc \ - src/wrapper/event.cc \ - src/wrapper/http.cc \ - src/wrapper/timer.cc" + AC_MSG_CHECKING([for sources]) + if test -f "$abs_srcdir/ext-src/php_swoole.cc"; then + swoole_source_dir=$abs_srcdir + elif test -f "ext-src/php_swoole.cc"; then + swoole_source_dir=$(pwd) + else + swoole_source_dir="ext/swoole" + fi + AC_MSG_RESULT([$swoole_source_dir]) + + ext_src_files=$(cd $swoole_source_dir && find ext-src/ -name *.cc) + lib_src_files=$(cd $swoole_source_dir && find src/ -name *.cc) + + swoole_source_file="${ext_src_files} ${lib_src_files}" swoole_source_file="$swoole_source_file \ - thirdparty/php/curl/interface.cc \ - thirdparty/php/curl/multi.cc \ thirdparty/php/sockets/multicast.cc \ thirdparty/php/sockets/sendrecvmsg.cc \ thirdparty/php/sockets/conversions.cc \ @@ -712,24 +1325,113 @@ EOF thirdparty/php/standard/proc_open.cc" swoole_source_file="$swoole_source_file \ - thirdparty/swoole_http_parser.c \ + thirdparty/llhttp/api.c \ + thirdparty/llhttp/http.c \ + thirdparty/llhttp/llhttp.c \ thirdparty/multipart_parser.c" - swoole_source_file="$swoole_source_file \ - thirdparty/hiredis/hiredis.c \ - thirdparty/hiredis/alloc.c \ - thirdparty/hiredis/net.c \ - thirdparty/hiredis/read.c \ - thirdparty/hiredis/sds.c" + if test "$PHP_NGHTTP2_DIR" = "no"; then + swoole_source_file="$swoole_source_file \ + thirdparty/nghttp2/nghttp2_hd.c \ + thirdparty/nghttp2/nghttp2_rcbuf.c \ + thirdparty/nghttp2/nghttp2_helper.c \ + thirdparty/nghttp2/nghttp2_buf.c \ + thirdparty/nghttp2/nghttp2_mem.c \ + thirdparty/nghttp2/nghttp2_hd_huffman.c \ + thirdparty/nghttp2/nghttp2_hd_huffman_data.c" + fi - swoole_source_file="$swoole_source_file \ - thirdparty/nghttp2/nghttp2_hd.c \ - thirdparty/nghttp2/nghttp2_rcbuf.c \ - thirdparty/nghttp2/nghttp2_helper.c \ - thirdparty/nghttp2/nghttp2_buf.c \ - thirdparty/nghttp2/nghttp2_mem.c \ - thirdparty/nghttp2/nghttp2_hd_huffman.c \ - thirdparty/nghttp2/nghttp2_hd_huffman_data.c" + dnl During static compilation, there is no php-config variable, + dnl but the php-version variable is always present and is not affected by the shell environment variables. + dnl During dynamic compilation, the php-config variable is always available, whereas the php-version variable is absent. + + if test -z "$PHP_CONFIG"; then + if test -z "$PHP_VERSION"; then + AC_MSG_ERROR([the PHP_VERSION variable must be defined]) + else + SW_PHP_VERSION=$PHP_VERSION + fi + else + SW_PHP_VERSION=`$PHP_CONFIG --version` + fi + + SW_PHP_VERSION_ID=`echo "${SW_PHP_VERSION}" | $AWK 'BEGIN { FS = "."; } { printf "%d", ([$]1 * 10 + [$]2); }'` + SW_PHP_THIRDPARTY_DIR="thirdparty/php${SW_PHP_VERSION_ID}" + + AC_MSG_NOTICE([php version: $SW_PHP_VERSION, version_id: $SW_PHP_VERSION_ID, thirdparty_dir: $SW_PHP_THIRDPARTY_DIR]) + + if test "$PHP_SWOOLE_CURL" != "no"; then + if test "$SW_PHP_VERSION_ID" -ge "84"; then + swoole_source_file="$swoole_source_file \ + thirdparty/php84/curl/interface.cc \ + thirdparty/php84/curl/multi.cc" + else + swoole_source_file="$swoole_source_file \ + thirdparty/php/curl/interface.cc \ + thirdparty/php/curl/multi.cc" + fi + fi + + if test "$PHP_SWOOLE_PGSQL" != "no"; then + swoole_source_file="$swoole_source_file \ + ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_driver.c \ + ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_statement.c" + if test "$SW_PHP_VERSION_ID" -ge "84"; then + swoole_source_file="$swoole_source_file \ + ${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql/pgsql_sql_parser.c" + fi + fi + + if test "$PHP_SWOOLE_ORACLE" != "no"; then + swoole_source_file="$swoole_source_file \ + thirdparty/pdo_oci/oci_driver.c \ + thirdparty/pdo_oci/oci_statement.c" + fi + + if test "$PHP_SWOOLE_ODBC" != "no"; then + swoole_source_file="$swoole_source_file \ + ${SW_PHP_THIRDPARTY_DIR}/pdo_odbc/odbc_driver.c \ + ${SW_PHP_THIRDPARTY_DIR}/pdo_odbc/odbc_stmt.c" + fi + + if test "$PHP_SWOOLE_SQLITE" != "no"; then + swoole_source_file="$swoole_source_file \ + thirdparty/pdo_sqlite/sqlite_driver.c \ + thirdparty/pdo_sqlite/sqlite_statement.c" + if test "$SW_PHP_VERSION_ID" -ge "84"; then + swoole_source_file="$swoole_source_file \ + thirdparty/pdo_sqlite/sqlite_sql_parser.c" + fi + fi + + if test "$PHP_SWOOLE_FIREBIRD" != "no"; then + if test "$SW_PHP_VERSION_ID" -ge "85"; then + swoole_source_file="$swoole_source_file \ + thirdparty/php85/pdo_firebird/firebird_driver.c \ + thirdparty/php85/pdo_firebird/firebird_statement.c \ + thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp" + else + swoole_source_file="$swoole_source_file \ + thirdparty/php84/pdo_firebird/firebird_driver.c \ + thirdparty/php84/pdo_firebird/firebird_statement.c \ + thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp" + fi + fi + + if test "$PHP_SWOOLE_SSH2" != "no"; then + swoole_source_file="$swoole_source_file \ + thirdparty/php/ssh2/ssh2.cc \ + thirdparty/php/ssh2/ssh2_fopen_wrappers.cc \ + thirdparty/php/ssh2/ssh2_sftp.cc" + fi + + if test "$PHP_SWOOLE_FTP" != "no"; then + AC_DEFINE(SW_HAVE_FTP_SSL, 1, [have swoole-ftp with SSL]) + swoole_source_file="$swoole_source_file \ + thirdparty/php84/ftp/ftp.c \ + thirdparty/php84/ftp/php_ftp.c" + AC_DEFINE(SW_HAVE_FTP, 1, [have swoole-ftp]) + fi SW_ASM_DIR="thirdparty/boost/asm/" SW_USE_ASM_CONTEXT="yes" @@ -745,6 +1447,7 @@ EOF [mips64*], [SW_CPU="mips64"], [mips*], [SW_CPU="mips32"], [riscv64*], [SW_CPU="riscv64"], + [loongarch64*], [SW_CPU="loongarch64"], [ SW_USE_ASM_CONTEXT="no" ] @@ -806,13 +1509,19 @@ EOF else SW_USE_ASM_CONTEXT="no" fi + elif test "$SW_CPU" = "loongarch64"; then + if test "$SW_OS" = "LINUX"; then + SW_CONTEXT_ASM_FILE="loongarch64_sysv_elf_gas.S" + else + SW_USE_ASM_CONTEXT="no" + fi else SW_USE_ASM_CONTEXT="no" fi if test "$PHP_THREAD_CONTEXT" != "no"; then - AC_DEFINE(SW_USE_THREAD_CONTEXT, 1, [do we enable thread context]) - SW_USE_ASM_CONTEXT="no" + AC_DEFINE(SW_USE_THREAD_CONTEXT, 1, [do we enable thread context]) + SW_USE_ASM_CONTEXT="no" fi if test "$SW_USE_ASM_CONTEXT" = "yes"; then @@ -822,14 +1531,14 @@ EOF AC_DEFINE(SW_USE_ASM_CONTEXT, 1, [use boost asm context]) fi - PHP_NEW_EXTENSION(swoole, $swoole_source_file, $ext_shared,, "$EXTRA_CFLAGS -DENABLE_PHP_SWOOLE", cxx) + EXTRA_CFLAGS="$EXTRA_CFLAGS -DENABLE_PHP_SWOOLE" + + PHP_NEW_EXTENSION(swoole, $swoole_source_file, $ext_shared,, "$EXTRA_CFLAGS", cxx) PHP_ADD_INCLUDE([$ext_srcdir]) PHP_ADD_INCLUDE([$ext_srcdir/include]) - PHP_ADD_INCLUDE([$ext_srcdir/stubs]) PHP_ADD_INCLUDE([$ext_srcdir/ext-src]) PHP_ADD_INCLUDE([$ext_srcdir/thirdparty]) - PHP_ADD_INCLUDE([$ext_srcdir/thirdparty/hiredis]) AC_MSG_CHECKING([swoole coverage]) if test "$PHP_SWOOLE_COVERAGE" != "no"; then @@ -844,17 +1553,17 @@ EOF include/*.h \ stubs/*.h \ thirdparty/*.h \ - thirdparty/nghttp2/*.h \ - thirdparty/hiredis/*.h]) + thirdparty/llhttp/*.h \ + thirdparty/nghttp2/*.h]) PHP_REQUIRE_CXX() - CXXFLAGS="$CXXFLAGS -Wall -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations" + CXXFLAGS="$CXXFLAGS -Wall -Wno-date-time -Wno-unused-function -Wno-deprecated -Wno-deprecated-declarations" if test "$SW_OS" = "CYGWIN" || test "$SW_OS" = "MINGW"; then - CXXFLAGS="$CXXFLAGS -std=gnu++11" + CXXFLAGS="$CXXFLAGS -std=gnu++14" else - CXXFLAGS="$CXXFLAGS -std=c++11" + CXXFLAGS="$CXXFLAGS -std=c++14" fi if test "$SW_CPU" = "arm"; then @@ -874,9 +1583,27 @@ EOF PHP_ADD_BUILD_DIR($ext_builddir/src/wrapper) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/boost) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/boost/asm) - PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/hiredis) - PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/nghttp2) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/sockets) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/standard) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/curl) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/ssh2) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/pdo_oci) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/pdo_sqlite) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/curl) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_firebird) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/pdo_firebird) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/llhttp) + + if test "$PHP_NGHTTP2_DIR" = "no"; then + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/nghttp2) + fi + if test "$PHP_SWOOLE_PGSQL" != "no"; then + PHP_ADD_BUILD_DIR($ext_builddir/${SW_PHP_THIRDPARTY_DIR}/pdo_pgsql) + fi + if test "$PHP_SWOOLE_ODBC" != "no"; then + PHP_ADD_BUILD_DIR($ext_builddir/${SW_PHP_THIRDPARTY_DIR}/pdo_odbc) + fi + if test "$PHP_SWOOLE_FTP" != "no"; then + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/ftp) + fi fi diff --git a/core-tests/.gitignore b/core-tests/.gitignore new file mode 100644 index 0000000000..55971b213e --- /dev/null +++ b/core-tests/.gitignore @@ -0,0 +1,6 @@ +.idea/ +.cmake/ +Testing/ +build.ninja +.ninja_deps +.ninja_log \ No newline at end of file diff --git a/core-tests/CMakeLists.txt b/core-tests/CMakeLists.txt deleted file mode 100755 index b8f6478ed6..0000000000 --- a/core-tests/CMakeLists.txt +++ /dev/null @@ -1,70 +0,0 @@ -cmake_minimum_required(VERSION 2.8.1) - -project(core_tests) - -#set(CMAKE_BUILD_TYPE Released) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -g") - -file(GLOB_RECURSE SOURCE_FILES FOLLOW_SYMLINKS src/*.cpp deps/llhttp/src/*.c) - -add_definitions(-DHAVE_CONFIG_H) - -set(core_tests_includes ./include/ ../thirdparty/hiredis ./deps/llhttp/include) -set(core_tests_libraries) -set(core_tests_link_directories /usr/local/lib) - -list(APPEND core_tests_libraries pthread) - -# find GTest -find_package(GTest) -if (!${GTEST_FOUND}) - message(FATAL_ERROR "Not found GTest") -endif() -message(STATUS "Found GTest") - -list(APPEND core_tests_libraries ${GTEST_BOTH_LIBRARIES}) -list(APPEND core_tests_includes ${GTEST_INCLUDE_DIRS}) - -if (DEFINED swoole_dir) - add_definitions(-DHAVE_SWOOLE_DIR) - list(APPEND core_tests_includes ${swoole_dir} ${swoole_dir}/include) - list(APPEND core_tests_link_directories ${swoole_dir}/lib) -else() - list(APPEND core_tests_includes /usr/local/include/swoole /usr/include/swoole) -endif() - -list(APPEND core_tests_libraries swoole) - -# find OpenSSL -if (DEFINED openssl_dir) - list(APPEND core_tests_includes ${openssl_dir}/include) - list(APPEND core_tests_link_directories ${openssl_dir}/lib) - list(APPEND core_tests_libraries ssl crypto) -else() - find_package(OpenSSL) - if (${OPENSSL_FOUND}) - message(STATUS "Found OpenSSL") - list(APPEND core_tests_includes ${OPENSSL_INCLUDE_DIR}) - list(APPEND core_tests_libraries ${OPENSSL_LIBRARIES}) - else() - message(STATUS "Not found OpenSSL") - endif() -endif() - -if (DEFINED brotli_dir) - list(APPEND core_tests_includes ${brotli_dir}/include) - list(APPEND core_tests_link_directories ${brotli_dir}/lib) -endif() - -if (DEFINED enable_asan) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") -endif() - -# should execute before the add_executable command -link_directories(${core_tests_link_directories}) - -set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) -add_executable(core_tests ${SOURCE_FILES}) -target_include_directories(core_tests PRIVATE ${core_tests_includes}) -target_link_libraries(core_tests ${core_tests_libraries}) diff --git a/core-tests/README-CN.md b/core-tests/README-CN.md deleted file mode 100644 index c15bf2721b..0000000000 --- a/core-tests/README-CN.md +++ /dev/null @@ -1,39 +0,0 @@ -**Swoole 核心单元测试** -=========== -## **1. 编译googletest** -swoole单元测试依赖于googletest,因此第一步我们需要编译googletest。 - -这里要求gcc和和g++编译器的版本要大于4.8.5。 -```shell -wget https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz -tar zxf release-1.11.0.tar.gz -cd googletest-release-1.11.0 -mkdir ./build && cd ./build -cmake .. -make -j -make install -``` -如果你已经安装了更高版本的gcc和g++编译器,但是在执行cmake . 的过程中还是发现如下输出。 -```shell -- The C compiler identification is GNU 4.8.5 -- The CXX compiler identification is GNU 4.8.5 -``` -请执行下面的代码并重试。 -```shell -export CC=/usr/local/bin/gcc -export CXX=/usr/local/bin/g++ -``` - -## **2. 编译swoole.so** -```shell -export SWOOLE_DIR=/your-path/swoole-src/ -git clone https://github.com/swoole/swoole-src.git -cd /your-path/swoole-src/ -./make.sh cmake -``` - -## **3. 运行swoole单元测试** -```shell -cd /your-path/swoole-src/core-tests -./run.sh -``` \ No newline at end of file diff --git a/core-tests/README.md b/core-tests/README.md deleted file mode 100644 index 04eb7e7756..0000000000 --- a/core-tests/README.md +++ /dev/null @@ -1,40 +0,0 @@ -**Swoole core unit testing** -=========== -## **1. compiling googletest** -Since swoole core unit testing rely on googletest, we need compile googletest at first. - -gcc compiler version > 4.8.5 and gcc-c++ compiler version > 4.8.5. -```shell -wget https://github.com/google/googletest/archive/refs/tags/release-1.11.0.tar.gz -tar zxf release-1.11.0.tar.gz -cd googletest-release-1.11.0 -mkdir ./build && cd ./build -cmake .. -make -j -make install -``` -The output still contains the following messages after compiling higher version gcc and gcc-c++. - -```shell -- The C compiler identification is GNU 4.8.5 -- The CXX compiler identification is GNU 4.8.5 -``` -Please execute following code and retry. -```shell -export CC=/usr/local/bin/gcc -export CXX=/usr/local/bin/g++ -``` - -## **2. How to compile swoole.so** -```shell -export SWOOLE_DIR=/your-path/swoole-src/ -git clone https://github.com/swoole/swoole-src.git -cd /your-path/swoole-src/ -./make.sh cmake -``` - -## **3. Run swoole core testing** -```shell -cd /your-path/swoole-src/core-tests -./run.sh -``` diff --git a/core-tests/code-stats.sh b/core-tests/code-stats.sh new file mode 100755 index 0000000000..2924c740a4 --- /dev/null +++ b/core-tests/code-stats.sh @@ -0,0 +1,7 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +# enter the dir +cd "${__DIR__}" +cloc . --exclude-dir=js,Debug,CMakeFiles,build,CMakeFiles,deps diff --git a/core-tests/deps/llhttp/include/llhttp.h b/core-tests/deps/llhttp/include/llhttp.h deleted file mode 100644 index 024392ace3..0000000000 --- a/core-tests/deps/llhttp/include/llhttp.h +++ /dev/null @@ -1,379 +0,0 @@ -#ifndef INCLUDE_LLHTTP_H_ -#define INCLUDE_LLHTTP_H_ - -#define LLHTTP_VERSION_MAJOR 2 -#define LLHTTP_VERSION_MINOR 0 -#define LLHTTP_VERSION_PATCH 5 - -#ifndef INCLUDE_LLHTTP_ITSELF_H_ -#define INCLUDE_LLHTTP_ITSELF_H_ -#ifdef __cplusplus -extern "C" { -#endif - -#include - -typedef struct llhttp__internal_s llhttp__internal_t; -struct llhttp__internal_s { - int32_t _index; - void* _span_pos0; - void* _span_cb0; - int32_t error; - const char* reason; - const char* error_pos; - void* data; - void* _current; - uint64_t content_length; - uint8_t type; - uint8_t method; - uint8_t http_major; - uint8_t http_minor; - uint8_t header_state; - uint16_t flags; - uint8_t upgrade; - uint16_t status_code; - uint8_t finish; - void* settings; -}; - -int llhttp__internal_init(llhttp__internal_t* s); -int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif /* INCLUDE_LLHTTP_ITSELF_H_ */ - -#ifndef LLLLHTTP_C_HEADERS_ -#define LLLLHTTP_C_HEADERS_ -#ifdef __cplusplus -extern "C" { -#endif - -enum llhttp_errno { - HPE_OK = 0, - HPE_INTERNAL = 1, - HPE_STRICT = 2, - HPE_LF_EXPECTED = 3, - HPE_UNEXPECTED_CONTENT_LENGTH = 4, - HPE_CLOSED_CONNECTION = 5, - HPE_INVALID_METHOD = 6, - HPE_INVALID_URL = 7, - HPE_INVALID_CONSTANT = 8, - HPE_INVALID_VERSION = 9, - HPE_INVALID_HEADER_TOKEN = 10, - HPE_INVALID_CONTENT_LENGTH = 11, - HPE_INVALID_CHUNK_SIZE = 12, - HPE_INVALID_STATUS = 13, - HPE_INVALID_EOF_STATE = 14, - HPE_INVALID_TRANSFER_ENCODING = 15, - HPE_CB_MESSAGE_BEGIN = 16, - HPE_CB_HEADERS_COMPLETE = 17, - HPE_CB_MESSAGE_COMPLETE = 18, - HPE_CB_CHUNK_HEADER = 19, - HPE_CB_CHUNK_COMPLETE = 20, - HPE_PAUSED = 21, - HPE_PAUSED_UPGRADE = 22, - HPE_USER = 23 -}; -typedef enum llhttp_errno llhttp_errno_t; - -enum llhttp_flags { - F_CONNECTION_KEEP_ALIVE = 0x1, - F_CONNECTION_CLOSE = 0x2, - F_CONNECTION_UPGRADE = 0x4, - F_CHUNKED = 0x8, - F_UPGRADE = 0x10, - F_CONTENT_LENGTH = 0x20, - F_SKIPBODY = 0x40, - F_TRAILING = 0x80, - F_LENIENT = 0x100, - F_TRANSFER_ENCODING = 0x200 -}; -typedef enum llhttp_flags llhttp_flags_t; - -enum llhttp_type { - HTTP_BOTH = 0, - HTTP_REQUEST = 1, - HTTP_RESPONSE = 2 -}; -typedef enum llhttp_type llhttp_type_t; - -enum llhttp_finish { - HTTP_FINISH_SAFE = 0, - HTTP_FINISH_SAFE_WITH_CB = 1, - HTTP_FINISH_UNSAFE = 2 -}; -typedef enum llhttp_finish llhttp_finish_t; - -enum llhttp_method { - HTTP_DELETE = 0, - HTTP_GET = 1, - HTTP_HEAD = 2, - HTTP_POST = 3, - HTTP_PUT = 4, - HTTP_CONNECT = 5, - HTTP_OPTIONS = 6, - HTTP_TRACE = 7, - HTTP_COPY = 8, - HTTP_LOCK = 9, - HTTP_MKCOL = 10, - HTTP_MOVE = 11, - HTTP_PROPFIND = 12, - HTTP_PROPPATCH = 13, - HTTP_SEARCH = 14, - HTTP_UNLOCK = 15, - HTTP_BIND = 16, - HTTP_REBIND = 17, - HTTP_UNBIND = 18, - HTTP_ACL = 19, - HTTP_REPORT = 20, - HTTP_MKACTIVITY = 21, - HTTP_CHECKOUT = 22, - HTTP_MERGE = 23, - HTTP_MSEARCH = 24, - HTTP_NOTIFY = 25, - HTTP_SUBSCRIBE = 26, - HTTP_UNSUBSCRIBE = 27, - HTTP_PATCH = 28, - HTTP_PURGE = 29, - HTTP_MKCALENDAR = 30, - HTTP_LINK = 31, - HTTP_UNLINK = 32, - HTTP_SOURCE = 33, - HTTP_PRI = 34 -}; -typedef enum llhttp_method llhttp_method_t; - -#define HTTP_ERRNO_MAP(XX) \ - XX(0, OK, OK) \ - XX(1, INTERNAL, INTERNAL) \ - XX(2, STRICT, STRICT) \ - XX(3, LF_EXPECTED, LF_EXPECTED) \ - XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ - XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ - XX(6, INVALID_METHOD, INVALID_METHOD) \ - XX(7, INVALID_URL, INVALID_URL) \ - XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ - XX(9, INVALID_VERSION, INVALID_VERSION) \ - XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ - XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ - XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ - XX(13, INVALID_STATUS, INVALID_STATUS) \ - XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ - XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ - XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ - XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ - XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ - XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ - XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ - XX(21, PAUSED, PAUSED) \ - XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ - XX(23, USER, USER) \ - - -#define HTTP_METHOD_MAP(XX) \ - XX(0, DELETE, DELETE) \ - XX(1, GET, GET) \ - XX(2, HEAD, HEAD) \ - XX(3, POST, POST) \ - XX(4, PUT, PUT) \ - XX(5, CONNECT, CONNECT) \ - XX(6, OPTIONS, OPTIONS) \ - XX(7, TRACE, TRACE) \ - XX(8, COPY, COPY) \ - XX(9, LOCK, LOCK) \ - XX(10, MKCOL, MKCOL) \ - XX(11, MOVE, MOVE) \ - XX(12, PROPFIND, PROPFIND) \ - XX(13, PROPPATCH, PROPPATCH) \ - XX(14, SEARCH, SEARCH) \ - XX(15, UNLOCK, UNLOCK) \ - XX(16, BIND, BIND) \ - XX(17, REBIND, REBIND) \ - XX(18, UNBIND, UNBIND) \ - XX(19, ACL, ACL) \ - XX(20, REPORT, REPORT) \ - XX(21, MKACTIVITY, MKACTIVITY) \ - XX(22, CHECKOUT, CHECKOUT) \ - XX(23, MERGE, MERGE) \ - XX(24, MSEARCH, M-SEARCH) \ - XX(25, NOTIFY, NOTIFY) \ - XX(26, SUBSCRIBE, SUBSCRIBE) \ - XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ - XX(28, PATCH, PATCH) \ - XX(29, PURGE, PURGE) \ - XX(30, MKCALENDAR, MKCALENDAR) \ - XX(31, LINK, LINK) \ - XX(32, UNLINK, UNLINK) \ - XX(33, SOURCE, SOURCE) \ - XX(34, PRI, PRI) \ - - - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif /* LLLLHTTP_C_HEADERS_ */ - -#ifndef INCLUDE_LLHTTP_API_H_ -#define INCLUDE_LLHTTP_API_H_ -#ifdef __cplusplus -extern "C" { -#endif -#include - -typedef llhttp__internal_t llhttp_t; -typedef struct llhttp_settings_s llhttp_settings_t; - -typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); -typedef int (*llhttp_cb)(llhttp_t*); - -struct llhttp_settings_s { - /* Possible return values 0, -1, `HPE_PAUSED` */ - llhttp_cb on_message_begin; - - llhttp_data_cb on_url; - llhttp_data_cb on_status; - llhttp_data_cb on_header_field; - llhttp_data_cb on_header_value; - - /* Possible return values: - * 0 - Proceed normally - * 1 - Assume that request/response has no body, and proceed to parsing the - * next message - * 2 - Assume absence of body (as above) and make `llhttp_execute()` return - * `HPE_PAUSED_UPGRADE` - * -1 - Error - * `HPE_PAUSED` - */ - llhttp_cb on_headers_complete; - - llhttp_data_cb on_body; - - /* Possible return values 0, -1, `HPE_PAUSED` */ - llhttp_cb on_message_complete; - - /* When on_chunk_header is called, the current chunk length is stored - * in parser->content_length. - * Possible return values 0, -1, `HPE_PAUSED` - */ - llhttp_cb on_chunk_header; - llhttp_cb on_chunk_complete; -}; - -/* Initialize the parser with specific type and user settings */ -void llhttp_init(llhttp_t* parser, llhttp_type_t type, - const llhttp_settings_t* settings); - -/* Initialize the settings object */ -void llhttp_settings_init(llhttp_settings_t* settings); - -/* Parse full or partial request/response, invoking user callbacks along the - * way. - * - * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing - * interrupts, and such errno is returned from `llhttp_execute()`. If - * `HPE_PAUSED` was used as a errno, the execution can be resumed with - * `llhttp_resume()` call. - * - * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` - * is returned after fully parsing the request/response. If the user wishes to - * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. - * - * NOTE: if this function ever returns a non-pause type error, it will continue - * to return the same error upon each successive call up until `llhttp_init()` - * is called. - */ -llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); - -/* This method should be called when the other side has no further bytes to - * send (e.g. shutdown of readable side of the TCP connection.) - * - * Requests without `Content-Length` and other messages might require treating - * all incoming bytes as the part of the body, up to the last byte of the - * connection. This method will invoke `on_message_complete()` callback if the - * request was terminated safely. Otherwise a error code would be returned. - */ -llhttp_errno_t llhttp_finish(llhttp_t* parser); - -/* Returns `1` if the incoming message is parsed until the last byte, and has - * to be completed by calling `llhttp_finish()` on EOF - */ -int llhttp_message_needs_eof(const llhttp_t* parser); - -/* Returns `1` if there might be any other messages following the last that was - * successfully parsed. - */ -int llhttp_should_keep_alive(const llhttp_t* parser); - -/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set - * appropriate error reason. - * - * Important: do not call this from user callbacks! User callbacks must return - * `HPE_PAUSED` if pausing is required. - */ -void llhttp_pause(llhttp_t* parser); - -/* Might be called to resume the execution after the pause in user's callback. - * See `llhttp_execute()` above for details. - * - * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. - */ -void llhttp_resume(llhttp_t* parser); - -/* Might be called to resume the execution after the pause in user's callback. - * See `llhttp_execute()` above for details. - * - * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` - */ -void llhttp_resume_after_upgrade(llhttp_t* parser); - -/* Returns the latest return error */ -llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); - -/* Returns the verbal explanation of the latest returned error. - * - * Note: User callback should set error reason when returning the error. See - * `llhttp_set_error_reason()` for details. - */ -const char* llhttp_get_error_reason(const llhttp_t* parser); - -/* Assign verbal description to the returned error. Must be called in user - * callbacks right before returning the errno. - * - * Note: `HPE_USER` error code might be useful in user callbacks. - */ -void llhttp_set_error_reason(llhttp_t* parser, const char* reason); - -/* Returns the pointer to the last parsed byte before the returned error. The - * pointer is relative to the `data` argument of `llhttp_execute()`. - * - * Note: this method might be useful for counting the number of parsed bytes. - */ -const char* llhttp_get_error_pos(const llhttp_t* parser); - -/* Returns textual name of error code */ -const char* llhttp_errno_name(llhttp_errno_t err); - -/* Returns textual name of HTTP method */ -const char* llhttp_method_name(llhttp_method_t method); - - -/* Enables/disables lenient header value parsing (disabled by default). - * - * Lenient parsing disables header value token checks, extending llhttp's - * protocol support to highly non-compliant clients/server. No - * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when - * lenient parsing is "on". - * - * **(USE AT YOUR OWN RISK)** - */ -void llhttp_set_lenient(llhttp_t* parser, int enabled); - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif /* INCLUDE_LLHTTP_API_H_ */ - -#endif /* INCLUDE_LLHTTP_H_ */ diff --git a/core-tests/deps/llhttp/src/api.c b/core-tests/deps/llhttp/src/api.c deleted file mode 100644 index 6f7246546d..0000000000 --- a/core-tests/deps/llhttp/src/api.c +++ /dev/null @@ -1,224 +0,0 @@ -#include -#include -#include - -#include "llhttp.h" - -#define CALLBACK_MAYBE(PARSER, NAME, ...) \ - do { \ - llhttp_settings_t* settings; \ - settings = (llhttp_settings_t*) (PARSER)->settings; \ - if (settings == NULL || settings->NAME == NULL) { \ - err = 0; \ - break; \ - } \ - err = settings->NAME(__VA_ARGS__); \ - } while (0) - -void llhttp_init(llhttp_t* parser, llhttp_type_t type, - const llhttp_settings_t* settings) { - llhttp__internal_init(parser); - - parser->type = type; - parser->settings = (void*) settings; -} - - -llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { - return llhttp__internal_execute(parser, data, data + len); -} - - -void llhttp_settings_init(llhttp_settings_t* settings) { - memset(settings, 0, sizeof(*settings)); -} - - -llhttp_errno_t llhttp_finish(llhttp_t* parser) { - int err; - - /* We're in an error state. Don't bother doing anything. */ - if (parser->error != 0) { - return 0; - } - - switch (parser->finish) { - case HTTP_FINISH_SAFE_WITH_CB: - CALLBACK_MAYBE(parser, on_message_complete, parser); - if (err != HPE_OK) return err; - - /* FALLTHROUGH */ - case HTTP_FINISH_SAFE: - return HPE_OK; - case HTTP_FINISH_UNSAFE: - parser->reason = "Invalid EOF state"; - return HPE_INVALID_EOF_STATE; - default: - abort(); - } -} - - -void llhttp_pause(llhttp_t* parser) { - if (parser->error != HPE_OK) { - return; - } - - parser->error = HPE_PAUSED; - parser->reason = "Paused"; -} - - -void llhttp_resume(llhttp_t* parser) { - if (parser->error != HPE_PAUSED) { - return; - } - - parser->error = 0; -} - - -void llhttp_resume_after_upgrade(llhttp_t* parser) { - if (parser->error != HPE_PAUSED_UPGRADE) { - return; - } - - parser->error = 0; -} - - -llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { - return parser->error; -} - - -const char* llhttp_get_error_reason(const llhttp_t* parser) { - return parser->reason; -} - - -void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { - parser->reason = reason; -} - - -const char* llhttp_get_error_pos(const llhttp_t* parser) { - return parser->error_pos; -} - - -const char* llhttp_errno_name(llhttp_errno_t err) { -#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; - switch (err) { - HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) - default: abort(); - } -#undef HTTP_ERRNO_GEN -} - - -const char* llhttp_method_name(llhttp_method_t method) { -#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; - switch (method) { - HTTP_METHOD_MAP(HTTP_METHOD_GEN) - default: abort(); - } -#undef HTTP_METHOD_GEN -} - - -void llhttp_set_lenient(llhttp_t* parser, int enabled) { - if (enabled) { - parser->flags |= F_LENIENT; - } else { - parser->flags &= ~F_LENIENT; - } -} - - -/* Callbacks */ - - -int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_message_begin, s); - return err; -} - - -int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_url, s, p, endp - p); - return err; -} - - -int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_status, s, p, endp - p); - return err; -} - - -int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_header_field, s, p, endp - p); - return err; -} - - -int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_header_value, s, p, endp - p); - return err; -} - - -int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_headers_complete, s); - return err; -} - - -int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_message_complete, s); - return err; -} - - -int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_body, s, p, endp - p); - return err; -} - - -int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_chunk_header, s); - return err; -} - - -int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { - int err; - CALLBACK_MAYBE(s, on_chunk_complete, s); - return err; -} - - -/* Private */ - - -void llhttp__debug(llhttp_t* s, const char* p, const char* endp, - const char* msg) { - if (p == endp) { - fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, - s->flags, msg); - } else { - fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, - s->type, s->flags, *p, msg); - } -} diff --git a/core-tests/deps/llhttp/src/llhttp.c b/core-tests/deps/llhttp/src/llhttp.c deleted file mode 100644 index e146d2591b..0000000000 --- a/core-tests/deps/llhttp/src/llhttp.c +++ /dev/null @@ -1,6528 +0,0 @@ -#include -#include -#include - -#ifdef __SSE4_2__ - #ifdef _MSC_VER - #include - #else /* !_MSC_VER */ - #include - #endif /* _MSC_VER */ -#endif /* __SSE4_2__ */ - -#ifdef _MSC_VER - #define ALIGN(n) _declspec(align(n)) -#else /* !_MSC_VER */ - #define ALIGN(n) __attribute__((aligned(n))) -#endif /* _MSC_VER */ - -#include "llhttp.h" - -typedef int (*llhttp__internal__span_cb)( - llhttp__internal_t*, const char*, const char*); - -static const unsigned char llparse_blob0[] = { - 'C', 'L' -}; -#ifdef __SSE4_2__ -static const unsigned char ALIGN(16) llparse_blob1[] = { - 0x9, 0x9, 0xc, 0xc, '!', '"', '$', '>', '@', '~', 0x80, - 0xff, 0x0, 0x0, 0x0, 0x0 -}; -#endif /* __SSE4_2__ */ -static const unsigned char llparse_blob2[] = { - 'o', 'n' -}; -static const unsigned char llparse_blob3[] = { - 'e', 'c', 't', 'i', 'o', 'n' -}; -static const unsigned char llparse_blob4[] = { - 'l', 'o', 's', 'e' -}; -static const unsigned char llparse_blob5[] = { - 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e' -}; -static const unsigned char llparse_blob6[] = { - 'p', 'g', 'r', 'a', 'd', 'e' -}; -#ifdef __SSE4_2__ -static const unsigned char ALIGN(16) llparse_blob7[] = { - 0x9, 0x9, ' ', '~', 0x80, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0 -}; -#endif /* __SSE4_2__ */ -static const unsigned char llparse_blob8[] = { - 'c', 'h', 'u', 'n', 'k', 'e', 'd' -}; -#ifdef __SSE4_2__ -static const unsigned char ALIGN(16) llparse_blob9[] = { - ' ', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A', - 'Z', '^', 'z', '|', '|' -}; -#endif /* __SSE4_2__ */ -#ifdef __SSE4_2__ -static const unsigned char ALIGN(16) llparse_blob10[] = { - '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0 -}; -#endif /* __SSE4_2__ */ -static const unsigned char llparse_blob11[] = { - 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h' -}; -static const unsigned char llparse_blob12[] = { - 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c', - 't', 'i', 'o', 'n' -}; -static const unsigned char llparse_blob13[] = { - 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', - 'o', 'd', 'i', 'n', 'g' -}; -static const unsigned char llparse_blob14[] = { - 'p', 'g', 'r', 'a', 'd', 'e' -}; -static const unsigned char llparse_blob15[] = { - 0xd, 0xa -}; -static const unsigned char llparse_blob16[] = { - 'T', 'T', 'P', '/' -}; -static const unsigned char llparse_blob17[] = { - 'C', 'E', '/' -}; -static const unsigned char llparse_blob18[] = { - 'I', 'N', 'D' -}; -static const unsigned char llparse_blob19[] = { - 'E', 'C', 'K', 'O', 'U', 'T' -}; -static const unsigned char llparse_blob20[] = { - 'N', 'E', 'C', 'T' -}; -static const unsigned char llparse_blob21[] = { - 'E', 'L', 'E', 'T', 'E' -}; -static const unsigned char llparse_blob22[] = { - 'E', 'T' -}; -static const unsigned char llparse_blob23[] = { - 'E', 'A', 'D' -}; -static const unsigned char llparse_blob24[] = { - 'N', 'K' -}; -static const unsigned char llparse_blob25[] = { - 'C', 'K' -}; -static const unsigned char llparse_blob26[] = { - 'S', 'E', 'A', 'R', 'C', 'H' -}; -static const unsigned char llparse_blob27[] = { - 'R', 'G', 'E' -}; -static const unsigned char llparse_blob28[] = { - 'C', 'T', 'I', 'V', 'I', 'T', 'Y' -}; -static const unsigned char llparse_blob29[] = { - 'L', 'E', 'N', 'D', 'A', 'R' -}; -static const unsigned char llparse_blob30[] = { - 'V', 'E' -}; -static const unsigned char llparse_blob31[] = { - 'O', 'T', 'I', 'F', 'Y' -}; -static const unsigned char llparse_blob32[] = { - 'P', 'T', 'I', 'O', 'N', 'S' -}; -static const unsigned char llparse_blob33[] = { - 'T', 'C', 'H' -}; -static const unsigned char llparse_blob34[] = { - 'S', 'T' -}; -static const unsigned char llparse_blob35[] = { - 'I', 'N', 'D' -}; -static const unsigned char llparse_blob36[] = { - 'A', 'T', 'C', 'H' -}; -static const unsigned char llparse_blob37[] = { - 'G', 'E' -}; -static const unsigned char llparse_blob38[] = { - 'I', 'N', 'D' -}; -static const unsigned char llparse_blob39[] = { - 'O', 'R', 'T' -}; -static const unsigned char llparse_blob40[] = { - 'A', 'R', 'C', 'H' -}; -static const unsigned char llparse_blob41[] = { - 'U', 'R', 'C', 'E' -}; -static const unsigned char llparse_blob42[] = { - 'B', 'S', 'C', 'R', 'I', 'B', 'E' -}; -static const unsigned char llparse_blob43[] = { - 'R', 'A', 'C', 'E' -}; -static const unsigned char llparse_blob44[] = { - 'I', 'N', 'D' -}; -static const unsigned char llparse_blob45[] = { - 'N', 'K' -}; -static const unsigned char llparse_blob46[] = { - 'C', 'K' -}; -static const unsigned char llparse_blob47[] = { - 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E' -}; -static const unsigned char llparse_blob48[] = { - 'H', 'T', 'T', 'P', '/' -}; -static const unsigned char llparse_blob49[] = { - 'A', 'D' -}; -static const unsigned char llparse_blob50[] = { - 'T', 'P', '/' -}; - -enum llparse_match_status_e { - kMatchComplete, - kMatchPause, - kMatchMismatch -}; -typedef enum llparse_match_status_e llparse_match_status_t; - -struct llparse_match_s { - llparse_match_status_t status; - const unsigned char* current; -}; -typedef struct llparse_match_s llparse_match_t; - -static llparse_match_t llparse__match_sequence_id( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp, - const unsigned char* seq, uint32_t seq_len) { - uint32_t index; - llparse_match_t res; - - index = s->_index; - for (; p != endp; p++) { - unsigned char current; - - current = *p; - if (current == seq[index]) { - if (++index == seq_len) { - res.status = kMatchComplete; - goto reset; - } - } else { - res.status = kMatchMismatch; - goto reset; - } - } - s->_index = index; - res.status = kMatchPause; - res.current = p; - return res; -reset: - s->_index = 0; - res.current = p; - return res; -} - -static llparse_match_t llparse__match_sequence_to_lower_unsafe( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp, - const unsigned char* seq, uint32_t seq_len) { - uint32_t index; - llparse_match_t res; - - index = s->_index; - for (; p != endp; p++) { - unsigned char current; - - current = ((*p) | 0x20); - if (current == seq[index]) { - if (++index == seq_len) { - res.status = kMatchComplete; - goto reset; - } - } else { - res.status = kMatchMismatch; - goto reset; - } - } - s->_index = index; - res.status = kMatchPause; - res.current = p; - return res; -reset: - s->_index = 0; - res.current = p; - return res; -} - -enum llparse_state_e { - s_error, - s_n_llhttp__internal__n_invoke_llhttp__after_message_complete, - s_n_llhttp__internal__n_pause_1, - s_n_llhttp__internal__n_invoke_is_equal_upgrade, - s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2, - s_n_llhttp__internal__n_chunk_data_almost_done_skip, - s_n_llhttp__internal__n_chunk_data_almost_done, - s_n_llhttp__internal__n_consume_content_length, - s_n_llhttp__internal__n_span_start_llhttp__on_body, - s_n_llhttp__internal__n_invoke_is_equal_content_length, - s_n_llhttp__internal__n_chunk_size_almost_done, - s_n_llhttp__internal__n_chunk_parameters, - s_n_llhttp__internal__n_chunk_size_otherwise, - s_n_llhttp__internal__n_chunk_size, - s_n_llhttp__internal__n_chunk_size_digit, - s_n_llhttp__internal__n_invoke_update_content_length, - s_n_llhttp__internal__n_consume_content_length_1, - s_n_llhttp__internal__n_span_start_llhttp__on_body_1, - s_n_llhttp__internal__n_eof, - s_n_llhttp__internal__n_span_start_llhttp__on_body_2, - s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete, - s_n_llhttp__internal__n_headers_almost_done, - s_n_llhttp__internal__n_span_start_llhttp__on_header_value, - s_n_llhttp__internal__n_header_value_discard_lws, - s_n_llhttp__internal__n_header_value_discard_ws_almost_done, - s_n_llhttp__internal__n_header_value_lws, - s_n_llhttp__internal__n_header_value_almost_done, - s_n_llhttp__internal__n_header_value_lenient, - s_n_llhttp__internal__n_header_value_otherwise, - s_n_llhttp__internal__n_header_value_connection_token, - s_n_llhttp__internal__n_header_value_connection_ws, - s_n_llhttp__internal__n_header_value_connection_1, - s_n_llhttp__internal__n_header_value_connection_2, - s_n_llhttp__internal__n_header_value_connection_3, - s_n_llhttp__internal__n_header_value_connection, - s_n_llhttp__internal__n_error_18, - s_n_llhttp__internal__n_header_value, - s_n_llhttp__internal__n_header_value_discard_rws, - s_n_llhttp__internal__n_error_19, - s_n_llhttp__internal__n_header_value_content_length_ws, - s_n_llhttp__internal__n_header_value_content_length, - s_n_llhttp__internal__n_header_value_te_chunked_last, - s_n_llhttp__internal__n_header_value_te_token_ows, - s_n_llhttp__internal__n_header_value_te_token, - s_n_llhttp__internal__n_header_value_te_chunked, - s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1, - s_n_llhttp__internal__n_header_value_discard_ws, - s_n_llhttp__internal__n_header_field_general_otherwise, - s_n_llhttp__internal__n_header_field_general, - s_n_llhttp__internal__n_header_field_colon, - s_n_llhttp__internal__n_header_field_3, - s_n_llhttp__internal__n_header_field_4, - s_n_llhttp__internal__n_header_field_2, - s_n_llhttp__internal__n_header_field_1, - s_n_llhttp__internal__n_header_field_5, - s_n_llhttp__internal__n_header_field_6, - s_n_llhttp__internal__n_header_field_7, - s_n_llhttp__internal__n_header_field, - s_n_llhttp__internal__n_span_start_llhttp__on_header_field, - s_n_llhttp__internal__n_header_field_start, - s_n_llhttp__internal__n_url_skip_to_http09, - s_n_llhttp__internal__n_url_skip_lf_to_http09, - s_n_llhttp__internal__n_req_http_end_1, - s_n_llhttp__internal__n_req_http_end, - s_n_llhttp__internal__n_req_http_minor, - s_n_llhttp__internal__n_req_http_dot, - s_n_llhttp__internal__n_req_http_major, - s_n_llhttp__internal__n_req_http_start_1, - s_n_llhttp__internal__n_req_http_start_2, - s_n_llhttp__internal__n_req_http_start, - s_n_llhttp__internal__n_url_skip_to_http, - s_n_llhttp__internal__n_url_fragment, - s_n_llhttp__internal__n_span_end_stub_query_3, - s_n_llhttp__internal__n_url_query, - s_n_llhttp__internal__n_url_query_or_fragment, - s_n_llhttp__internal__n_url_path, - s_n_llhttp__internal__n_span_start_stub_path_2, - s_n_llhttp__internal__n_span_start_stub_path, - s_n_llhttp__internal__n_span_start_stub_path_1, - s_n_llhttp__internal__n_url_server_with_at, - s_n_llhttp__internal__n_url_server, - s_n_llhttp__internal__n_url_schema_delim_1, - s_n_llhttp__internal__n_url_schema_delim, - s_n_llhttp__internal__n_span_end_stub_schema, - s_n_llhttp__internal__n_url_schema, - s_n_llhttp__internal__n_url_start, - s_n_llhttp__internal__n_span_start_llhttp__on_url_1, - s_n_llhttp__internal__n_span_start_llhttp__on_url, - s_n_llhttp__internal__n_req_spaces_before_url, - s_n_llhttp__internal__n_req_first_space_before_url, - s_n_llhttp__internal__n_start_req_1, - s_n_llhttp__internal__n_start_req_2, - s_n_llhttp__internal__n_start_req_4, - s_n_llhttp__internal__n_start_req_6, - s_n_llhttp__internal__n_start_req_7, - s_n_llhttp__internal__n_start_req_5, - s_n_llhttp__internal__n_start_req_3, - s_n_llhttp__internal__n_start_req_8, - s_n_llhttp__internal__n_start_req_9, - s_n_llhttp__internal__n_start_req_10, - s_n_llhttp__internal__n_start_req_12, - s_n_llhttp__internal__n_start_req_13, - s_n_llhttp__internal__n_start_req_11, - s_n_llhttp__internal__n_start_req_15, - s_n_llhttp__internal__n_start_req_16, - s_n_llhttp__internal__n_start_req_18, - s_n_llhttp__internal__n_start_req_20, - s_n_llhttp__internal__n_start_req_21, - s_n_llhttp__internal__n_start_req_19, - s_n_llhttp__internal__n_start_req_17, - s_n_llhttp__internal__n_start_req_22, - s_n_llhttp__internal__n_start_req_14, - s_n_llhttp__internal__n_start_req_23, - s_n_llhttp__internal__n_start_req_24, - s_n_llhttp__internal__n_start_req_26, - s_n_llhttp__internal__n_start_req_27, - s_n_llhttp__internal__n_start_req_31, - s_n_llhttp__internal__n_start_req_32, - s_n_llhttp__internal__n_start_req_30, - s_n_llhttp__internal__n_start_req_29, - s_n_llhttp__internal__n_start_req_28, - s_n_llhttp__internal__n_start_req_34, - s_n_llhttp__internal__n_start_req_33, - s_n_llhttp__internal__n_start_req_25, - s_n_llhttp__internal__n_start_req_37, - s_n_llhttp__internal__n_start_req_38, - s_n_llhttp__internal__n_start_req_36, - s_n_llhttp__internal__n_start_req_35, - s_n_llhttp__internal__n_start_req_40, - s_n_llhttp__internal__n_start_req_41, - s_n_llhttp__internal__n_start_req_42, - s_n_llhttp__internal__n_start_req_39, - s_n_llhttp__internal__n_start_req_43, - s_n_llhttp__internal__n_start_req_46, - s_n_llhttp__internal__n_start_req_48, - s_n_llhttp__internal__n_start_req_49, - s_n_llhttp__internal__n_start_req_47, - s_n_llhttp__internal__n_start_req_50, - s_n_llhttp__internal__n_start_req_45, - s_n_llhttp__internal__n_start_req_44, - s_n_llhttp__internal__n_start_req, - s_n_llhttp__internal__n_res_line_almost_done, - s_n_llhttp__internal__n_res_status, - s_n_llhttp__internal__n_span_start_llhttp__on_status, - s_n_llhttp__internal__n_res_status_start, - s_n_llhttp__internal__n_res_status_code_otherwise, - s_n_llhttp__internal__n_res_status_code, - s_n_llhttp__internal__n_res_http_end, - s_n_llhttp__internal__n_res_http_minor, - s_n_llhttp__internal__n_res_http_dot, - s_n_llhttp__internal__n_res_http_major, - s_n_llhttp__internal__n_start_res, - s_n_llhttp__internal__n_req_or_res_method_2, - s_n_llhttp__internal__n_req_or_res_method_3, - s_n_llhttp__internal__n_req_or_res_method_1, - s_n_llhttp__internal__n_req_or_res_method, - s_n_llhttp__internal__n_start_req_or_res, - s_n_llhttp__internal__n_invoke_load_type, - s_n_llhttp__internal__n_start, -}; -typedef enum llparse_state_e llparse_state_t; - -int llhttp__on_url( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_header_field( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_header_value( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_body( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_status( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_update_finish( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->finish = 2; - return 0; -} - -int llhttp__on_message_begin( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_load_type( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->type; -} - -int llhttp__internal__c_store_method( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - state->method = match; - return 0; -} - -int llhttp__internal__c_is_equal_method( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->method == 5; -} - -int llhttp__internal__c_update_http_major( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->http_major = 0; - return 0; -} - -int llhttp__internal__c_update_http_minor( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->http_minor = 9; - return 0; -} - -int llhttp__internal__c_test_flags( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return (state->flags & 128) == 128; -} - -int llhttp__on_chunk_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_message_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_is_equal_upgrade( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->upgrade == 1; -} - -int llhttp__after_message_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_update_finish_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->finish = 0; - return 0; -} - -int llhttp__internal__c_test_flags_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return (state->flags & 544) == 544; -} - -int llhttp__internal__c_test_flags_2( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return (state->flags & 256) == 256; -} - -int llhttp__internal__c_test_flags_3( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return (state->flags & 40) == 40; -} - -int llhttp__before_headers_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__on_headers_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__after_headers_complete( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_update_content_length( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->content_length = 0; - return 0; -} - -int llhttp__internal__c_mul_add_content_length( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - /* Multiplication overflow */ - if (state->content_length > 0xffffffffffffffffULL / 16) { - return 1; - } - - state->content_length *= 16; - - /* Addition overflow */ - if (match >= 0) { - if (state->content_length > 0xffffffffffffffffULL - match) { - return 1; - } - } else { - if (state->content_length < 0ULL - match) { - return 1; - } - } - state->content_length += match; - return 0; -} - -int llhttp__on_chunk_header( - llhttp__internal_t* s, const unsigned char* p, - const unsigned char* endp); - -int llhttp__internal__c_is_equal_content_length( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->content_length == 0; -} - -int llhttp__internal__c_or_flags( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 128; - return 0; -} - -int llhttp__internal__c_update_finish_2( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->finish = 1; - return 0; -} - -int llhttp__internal__c_or_flags_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 64; - return 0; -} - -int llhttp__internal__c_update_upgrade( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->upgrade = 1; - return 0; -} - -int llhttp__internal__c_store_header_state( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - state->header_state = match; - return 0; -} - -int llhttp__internal__c_load_header_state( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->header_state; -} - -int llhttp__internal__c_or_flags_3( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 1; - return 0; -} - -int llhttp__internal__c_update_header_state( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 1; - return 0; -} - -int llhttp__internal__c_or_flags_4( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 2; - return 0; -} - -int llhttp__internal__c_or_flags_5( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 4; - return 0; -} - -int llhttp__internal__c_or_flags_6( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 8; - return 0; -} - -int llhttp__internal__c_update_header_state_2( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 6; - return 0; -} - -int llhttp__internal__c_update_header_state_4( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 0; - return 0; -} - -int llhttp__internal__c_update_header_state_5( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 5; - return 0; -} - -int llhttp__internal__c_update_header_state_6( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 7; - return 0; -} - -int llhttp__internal__c_test_flags_5( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return (state->flags & 32) == 32; -} - -int llhttp__internal__c_mul_add_content_length_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - /* Multiplication overflow */ - if (state->content_length > 0xffffffffffffffffULL / 10) { - return 1; - } - - state->content_length *= 10; - - /* Addition overflow */ - if (match >= 0) { - if (state->content_length > 0xffffffffffffffffULL - match) { - return 1; - } - } else { - if (state->content_length < 0ULL - match) { - return 1; - } - } - state->content_length += match; - return 0; -} - -int llhttp__internal__c_or_flags_15( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 32; - return 0; -} - -int llhttp__internal__c_or_flags_16( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 512; - return 0; -} - -int llhttp__internal__c_update_header_state_8( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->header_state = 8; - return 0; -} - -int llhttp__internal__c_or_flags_17( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->flags |= 16; - return 0; -} - -int llhttp__internal__c_store_http_major( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - state->http_major = match; - return 0; -} - -int llhttp__internal__c_store_http_minor( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - state->http_minor = match; - return 0; -} - -int llhttp__internal__c_is_equal_method_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - return state->method == 33; -} - -int llhttp__internal__c_update_status_code( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->status_code = 0; - return 0; -} - -int llhttp__internal__c_mul_add_status_code( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp, - int match) { - /* Multiplication overflow */ - if (state->status_code > 0xffff / 10) { - return 1; - } - - state->status_code *= 10; - - /* Addition overflow */ - if (match >= 0) { - if (state->status_code > 0xffff - match) { - return 1; - } - } else { - if (state->status_code < 0 - match) { - return 1; - } - } - state->status_code += match; - - /* Enforce maximum */ - if (state->status_code > 999) { - return 1; - } - return 0; -} - -int llhttp__internal__c_update_type( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->type = 1; - return 0; -} - -int llhttp__internal__c_update_type_1( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - state->type = 2; - return 0; -} - -int llhttp__internal_init(llhttp__internal_t* state) { - memset(state, 0, sizeof(*state)); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start; - return 0; -} - -static llparse_state_t llhttp__internal__run( - llhttp__internal_t* state, - const unsigned char* p, - const unsigned char* endp) { - int match; - switch ((llparse_state_t) (intptr_t) state->_current) { - case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: - s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: { - switch (llhttp__after_message_complete(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_finish_1; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_pause_1: - s_n_llhttp__internal__n_pause_1: { - state->error = 0x16; - state->reason = "Pause on CONNECT/Upgrade"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; - return s_error; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_is_equal_upgrade: - s_n_llhttp__internal__n_invoke_is_equal_upgrade: { - switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; - default: - goto s_n_llhttp__internal__n_pause_1; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: - s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: { - switch (llhttp__on_message_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_is_equal_upgrade; - case 21: - goto s_n_llhttp__internal__n_pause_5; - default: - goto s_n_llhttp__internal__n_error_9; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_data_almost_done_skip: - s_n_llhttp__internal__n_chunk_data_almost_done_skip: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_data_almost_done_skip; - } - p++; - goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_data_almost_done: - s_n_llhttp__internal__n_chunk_data_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_data_almost_done; - } - p++; - goto s_n_llhttp__internal__n_chunk_data_almost_done_skip; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_consume_content_length: - s_n_llhttp__internal__n_consume_content_length: { - size_t avail; - size_t need; - - avail = endp - p; - need = state->content_length; - if (avail >= need) { - p += need; - state->content_length = 0; - goto s_n_llhttp__internal__n_span_end_llhttp__on_body; - } - - state->content_length -= avail; - return s_n_llhttp__internal__n_consume_content_length; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_body: - s_n_llhttp__internal__n_span_start_llhttp__on_body: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_body; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_body; - goto s_n_llhttp__internal__n_consume_content_length; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_is_equal_content_length: - s_n_llhttp__internal__n_invoke_is_equal_content_length: { - switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_span_start_llhttp__on_body; - default: - goto s_n_llhttp__internal__n_invoke_or_flags; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_size_almost_done: - s_n_llhttp__internal__n_chunk_size_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_size_almost_done; - } - p++; - goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_parameters: - s_n_llhttp__internal__n_chunk_parameters: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_parameters; - } - switch (*p) { - case 13: { - p++; - goto s_n_llhttp__internal__n_chunk_size_almost_done; - } - default: { - p++; - goto s_n_llhttp__internal__n_chunk_parameters; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_size_otherwise: - s_n_llhttp__internal__n_chunk_size_otherwise: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_size_otherwise; - } - switch (*p) { - case 13: { - p++; - goto s_n_llhttp__internal__n_chunk_size_almost_done; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_chunk_parameters; - } - case ';': { - p++; - goto s_n_llhttp__internal__n_chunk_parameters; - } - default: { - goto s_n_llhttp__internal__n_error_6; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_size: - s_n_llhttp__internal__n_chunk_size: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_size; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'A': { - p++; - match = 10; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'B': { - p++; - match = 11; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'C': { - p++; - match = 12; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'D': { - p++; - match = 13; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'E': { - p++; - match = 14; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'F': { - p++; - match = 15; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'a': { - p++; - match = 10; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'b': { - p++; - match = 11; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'c': { - p++; - match = 12; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'd': { - p++; - match = 13; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'e': { - p++; - match = 14; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'f': { - p++; - match = 15; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - default: { - goto s_n_llhttp__internal__n_chunk_size_otherwise; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_chunk_size_digit: - s_n_llhttp__internal__n_chunk_size_digit: { - if (p == endp) { - return s_n_llhttp__internal__n_chunk_size_digit; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'A': { - p++; - match = 10; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'B': { - p++; - match = 11; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'C': { - p++; - match = 12; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'D': { - p++; - match = 13; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'E': { - p++; - match = 14; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'F': { - p++; - match = 15; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'a': { - p++; - match = 10; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'b': { - p++; - match = 11; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'c': { - p++; - match = 12; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'd': { - p++; - match = 13; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'e': { - p++; - match = 14; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - case 'f': { - p++; - match = 15; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length; - } - default: { - goto s_n_llhttp__internal__n_error_8; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_update_content_length: - s_n_llhttp__internal__n_invoke_update_content_length: { - switch (llhttp__internal__c_update_content_length(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_chunk_size_digit; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_consume_content_length_1: - s_n_llhttp__internal__n_consume_content_length_1: { - size_t avail; - size_t need; - - avail = endp - p; - need = state->content_length; - if (avail >= need) { - p += need; - state->content_length = 0; - goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1; - } - - state->content_length -= avail; - return s_n_llhttp__internal__n_consume_content_length_1; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_body_1: - s_n_llhttp__internal__n_span_start_llhttp__on_body_1: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_body_1; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_body; - goto s_n_llhttp__internal__n_consume_content_length_1; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_eof: - s_n_llhttp__internal__n_eof: { - if (p == endp) { - return s_n_llhttp__internal__n_eof; - } - p++; - goto s_n_llhttp__internal__n_eof; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_body_2: - s_n_llhttp__internal__n_span_start_llhttp__on_body_2: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_body_2; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_body; - goto s_n_llhttp__internal__n_eof; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: - s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: { - switch (llhttp__after_headers_complete(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1; - case 2: - goto s_n_llhttp__internal__n_invoke_update_content_length; - case 3: - goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1; - case 4: - goto s_n_llhttp__internal__n_invoke_update_finish_2; - case 5: - goto s_n_llhttp__internal__n_error_10; - default: - goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_headers_almost_done: - s_n_llhttp__internal__n_headers_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_headers_almost_done; - } - p++; - goto s_n_llhttp__internal__n_invoke_test_flags; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_header_value: - s_n_llhttp__internal__n_span_start_llhttp__on_header_value: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_header_value; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_header_value; - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_discard_lws: - s_n_llhttp__internal__n_header_value_discard_lws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_discard_lws; - } - switch (*p) { - case 9: { - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - } - default: { - goto s_n_llhttp__internal__n_invoke_load_header_state; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_discard_ws_almost_done: - s_n_llhttp__internal__n_header_value_discard_ws_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_discard_ws_almost_done; - } - p++; - goto s_n_llhttp__internal__n_header_value_discard_lws; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_lws: - s_n_llhttp__internal__n_header_value_lws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_lws; - } - switch (*p) { - case 9: { - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; - } - case ' ': { - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; - } - default: { - goto s_n_llhttp__internal__n_invoke_load_header_state_3; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_almost_done: - s_n_llhttp__internal__n_header_value_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_almost_done; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_header_value_lws; - } - default: { - goto s_n_llhttp__internal__n_error_15; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_lenient: - s_n_llhttp__internal__n_header_value_lenient: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_lenient; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; - } - case 13: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3; - } - default: { - p++; - goto s_n_llhttp__internal__n_header_value_lenient; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_otherwise: - s_n_llhttp__internal__n_header_value_otherwise: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_otherwise; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; - } - case 13: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2; - } - default: { - goto s_n_llhttp__internal__n_invoke_test_flags_4; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection_token: - s_n_llhttp__internal__n_header_value_connection_token: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection_token; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_header_value_connection_token; - } - case 2: { - p++; - goto s_n_llhttp__internal__n_header_value_connection; - } - default: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection_ws: - s_n_llhttp__internal__n_header_value_connection_ws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection_ws; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - case 13: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_connection_ws; - } - case ',': { - p++; - goto s_n_llhttp__internal__n_invoke_load_header_state_4; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_4; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection_1: - s_n_llhttp__internal__n_header_value_connection_1: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection_1; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob4, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_update_header_state_2; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_value_connection_1; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_header_value_connection_token; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection_2: - s_n_llhttp__internal__n_header_value_connection_2: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection_2; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob5, 9); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_update_header_state_5; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_value_connection_2; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_header_value_connection_token; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection_3: - s_n_llhttp__internal__n_header_value_connection_3: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection_3; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob6, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_update_header_state_6; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_value_connection_3; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_header_value_connection_token; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_connection: - s_n_llhttp__internal__n_header_value_connection: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_connection; - } - switch (((*p) | 0x20)) { - case 9: { - p++; - goto s_n_llhttp__internal__n_header_value_connection; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_connection; - } - case 'c': { - p++; - goto s_n_llhttp__internal__n_header_value_connection_1; - } - case 'k': { - p++; - goto s_n_llhttp__internal__n_header_value_connection_2; - } - case 'u': { - p++; - goto s_n_llhttp__internal__n_header_value_connection_3; - } - default: { - goto s_n_llhttp__internal__n_header_value_connection_token; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_error_18: - s_n_llhttp__internal__n_error_18: { - state->error = 0xb; - state->reason = "Content-Length overflow"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value: - s_n_llhttp__internal__n_header_value: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_header_value; - } - #ifdef __SSE4_2__ - if (endp - p >= 16) { - __m128i ranges; - __m128i input; - int avail; - int match_len; - - /* Load input */ - input = _mm_loadu_si128((__m128i const*) p); - ranges = _mm_loadu_si128((__m128i const*) llparse_blob7); - - /* Find first character that does not match `ranges` */ - match_len = _mm_cmpestri(ranges, 6, - input, 16, - _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | - _SIDD_NEGATIVE_POLARITY); - - if (match_len != 0) { - p += match_len; - goto s_n_llhttp__internal__n_header_value; - } - goto s_n_llhttp__internal__n_header_value_otherwise; - } - #endif /* __SSE4_2__ */ - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_header_value; - } - default: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_discard_rws: - s_n_llhttp__internal__n_header_value_discard_rws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_discard_rws; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - case 13: { - goto s_n_llhttp__internal__n_header_value_otherwise; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_discard_rws; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_7; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_error_19: - s_n_llhttp__internal__n_error_19: { - state->error = 0xb; - state->reason = "Invalid character in Content-Length"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_content_length_ws: - s_n_llhttp__internal__n_header_value_content_length_ws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_content_length_ws; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_invoke_or_flags_15; - } - case 13: { - goto s_n_llhttp__internal__n_invoke_or_flags_15; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_content_length_ws; - } - default: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_content_length: - s_n_llhttp__internal__n_header_value_content_length: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_content_length; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; - } - default: { - goto s_n_llhttp__internal__n_header_value_content_length_ws; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_te_chunked_last: - s_n_llhttp__internal__n_header_value_te_chunked_last: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_te_chunked_last; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_invoke_update_header_state_8; - } - case 13: { - goto s_n_llhttp__internal__n_invoke_update_header_state_8; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_te_chunked_last; - } - default: { - goto s_n_llhttp__internal__n_header_value_te_chunked; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_te_token_ows: - s_n_llhttp__internal__n_header_value_te_token_ows: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_te_token_ows; - } - switch (*p) { - case 9: { - p++; - goto s_n_llhttp__internal__n_header_value_te_token_ows; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_te_token_ows; - } - default: { - goto s_n_llhttp__internal__n_header_value_te_chunked; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_te_token: - s_n_llhttp__internal__n_header_value_te_token: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_header_value_te_token; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_header_value_te_token; - } - case 2: { - p++; - goto s_n_llhttp__internal__n_header_value_te_token_ows; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_7; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_te_chunked: - s_n_llhttp__internal__n_header_value_te_chunked: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_value_te_chunked; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob8, 7); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_header_value_te_chunked_last; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_value_te_chunked; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_header_value_te_token; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: - s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_header_value; - goto s_n_llhttp__internal__n_invoke_load_header_state_2; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_value_discard_ws: - s_n_llhttp__internal__n_header_value_discard_ws: { - if (p == endp) { - return s_n_llhttp__internal__n_header_value_discard_ws; - } - switch (*p) { - case 9: { - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - } - case 10: { - p++; - goto s_n_llhttp__internal__n_header_value_discard_lws; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - } - default: { - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_general_otherwise: - s_n_llhttp__internal__n_header_field_general_otherwise: { - if (p == endp) { - return s_n_llhttp__internal__n_header_field_general_otherwise; - } - switch (*p) { - case ':': { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1; - } - default: { - goto s_n_llhttp__internal__n_error_20; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_general: - s_n_llhttp__internal__n_header_field_general: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_header_field_general; - } - #ifdef __SSE4_2__ - if (endp - p >= 16) { - __m128i ranges; - __m128i input; - int avail; - int match_len; - - /* Load input */ - input = _mm_loadu_si128((__m128i const*) p); - ranges = _mm_loadu_si128((__m128i const*) llparse_blob9); - - /* Find first character that does not match `ranges` */ - match_len = _mm_cmpestri(ranges, 16, - input, 16, - _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | - _SIDD_NEGATIVE_POLARITY); - - if (match_len != 0) { - p += match_len; - goto s_n_llhttp__internal__n_header_field_general; - } - ranges = _mm_loadu_si128((__m128i const*) llparse_blob10); - - /* Find first character that does not match `ranges` */ - match_len = _mm_cmpestri(ranges, 2, - input, 16, - _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | - _SIDD_NEGATIVE_POLARITY); - - if (match_len != 0) { - p += match_len; - goto s_n_llhttp__internal__n_header_field_general; - } - goto s_n_llhttp__internal__n_header_field_general_otherwise; - } - #endif /* __SSE4_2__ */ - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_header_field_general; - } - default: { - goto s_n_llhttp__internal__n_header_field_general_otherwise; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_colon: - s_n_llhttp__internal__n_header_field_colon: { - if (p == endp) { - return s_n_llhttp__internal__n_header_field_colon; - } - switch (*p) { - case ' ': { - p++; - goto s_n_llhttp__internal__n_header_field_colon; - } - case ':': { - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_9; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_3: - s_n_llhttp__internal__n_header_field_3: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_3; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob3, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_header_state; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_3; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_4: - s_n_llhttp__internal__n_header_field_4: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_4; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob11, 10); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_header_state; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_4; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_2: - s_n_llhttp__internal__n_header_field_2: { - if (p == endp) { - return s_n_llhttp__internal__n_header_field_2; - } - switch (((*p) | 0x20)) { - case 'n': { - p++; - goto s_n_llhttp__internal__n_header_field_3; - } - case 't': { - p++; - goto s_n_llhttp__internal__n_header_field_4; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_1: - s_n_llhttp__internal__n_header_field_1: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_1; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob2, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_header_field_2; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_1; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_5: - s_n_llhttp__internal__n_header_field_5: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_5; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob12, 15); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_header_state; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_5; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_6: - s_n_llhttp__internal__n_header_field_6: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_6; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob13, 16); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_header_state; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_6; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_7: - s_n_llhttp__internal__n_header_field_7: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_header_field_7; - } - match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob14, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_header_state; - } - case kMatchPause: { - return s_n_llhttp__internal__n_header_field_7; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field: - s_n_llhttp__internal__n_header_field: { - if (p == endp) { - return s_n_llhttp__internal__n_header_field; - } - switch (((*p) | 0x20)) { - case 'c': { - p++; - goto s_n_llhttp__internal__n_header_field_1; - } - case 'p': { - p++; - goto s_n_llhttp__internal__n_header_field_5; - } - case 't': { - p++; - goto s_n_llhttp__internal__n_header_field_6; - } - case 'u': { - p++; - goto s_n_llhttp__internal__n_header_field_7; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_header_state_10; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_header_field: - s_n_llhttp__internal__n_span_start_llhttp__on_header_field: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_header_field; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_header_field; - goto s_n_llhttp__internal__n_header_field; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_header_field_start: - s_n_llhttp__internal__n_header_field_start: { - if (p == endp) { - return s_n_llhttp__internal__n_header_field_start; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_headers_almost_done; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_headers_almost_done; - } - default: { - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_skip_to_http09: - s_n_llhttp__internal__n_url_skip_to_http09: { - if (p == endp) { - return s_n_llhttp__internal__n_url_skip_to_http09; - } - p++; - goto s_n_llhttp__internal__n_invoke_update_http_major; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_skip_lf_to_http09: - s_n_llhttp__internal__n_url_skip_lf_to_http09: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_url_skip_lf_to_http09; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_update_http_major; - } - case kMatchPause: { - return s_n_llhttp__internal__n_url_skip_lf_to_http09; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_21; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_end_1: - s_n_llhttp__internal__n_req_http_end_1: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_end_1; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_header_field_start; - } - default: { - goto s_n_llhttp__internal__n_error_22; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_end: - s_n_llhttp__internal__n_req_http_end: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_end; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_header_field_start; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_req_http_end_1; - } - default: { - goto s_n_llhttp__internal__n_error_22; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_minor: - s_n_llhttp__internal__n_req_http_minor: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_minor; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_store_http_minor; - } - default: { - goto s_n_llhttp__internal__n_error_23; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_dot: - s_n_llhttp__internal__n_req_http_dot: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_dot; - } - switch (*p) { - case '.': { - p++; - goto s_n_llhttp__internal__n_req_http_minor; - } - default: { - goto s_n_llhttp__internal__n_error_24; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_major: - s_n_llhttp__internal__n_req_http_major: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_major; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_store_http_major; - } - default: { - goto s_n_llhttp__internal__n_error_25; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_start_1: - s_n_llhttp__internal__n_req_http_start_1: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_req_http_start_1; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_req_http_major; - } - case kMatchPause: { - return s_n_llhttp__internal__n_req_http_start_1; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_27; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_start_2: - s_n_llhttp__internal__n_req_http_start_2: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_req_http_start_2; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_is_equal_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_req_http_start_2; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_27; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_http_start: - s_n_llhttp__internal__n_req_http_start: { - if (p == endp) { - return s_n_llhttp__internal__n_req_http_start; - } - switch (*p) { - case ' ': { - p++; - goto s_n_llhttp__internal__n_req_http_start; - } - case 'H': { - p++; - goto s_n_llhttp__internal__n_req_http_start_1; - } - case 'I': { - p++; - goto s_n_llhttp__internal__n_req_http_start_2; - } - default: { - goto s_n_llhttp__internal__n_error_27; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_skip_to_http: - s_n_llhttp__internal__n_url_skip_to_http: { - if (p == endp) { - return s_n_llhttp__internal__n_url_skip_to_http; - } - p++; - goto s_n_llhttp__internal__n_req_http_start; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_fragment: - s_n_llhttp__internal__n_url_fragment: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_fragment; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_url_fragment; - } - case 2: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6; - } - case 3: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7; - } - case 4: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8; - } - default: { - goto s_n_llhttp__internal__n_error_28; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_end_stub_query_3: - s_n_llhttp__internal__n_span_end_stub_query_3: { - if (p == endp) { - return s_n_llhttp__internal__n_span_end_stub_query_3; - } - p++; - goto s_n_llhttp__internal__n_url_fragment; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_query: - s_n_llhttp__internal__n_url_query: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_query; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_url_query; - } - case 2: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9; - } - case 3: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10; - } - case 4: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11; - } - case 5: { - goto s_n_llhttp__internal__n_span_end_stub_query_3; - } - default: { - goto s_n_llhttp__internal__n_error_29; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_query_or_fragment: - s_n_llhttp__internal__n_url_query_or_fragment: { - if (p == endp) { - return s_n_llhttp__internal__n_url_query_or_fragment; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3; - } - case 13: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4; - } - case ' ': { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5; - } - case '#': { - p++; - goto s_n_llhttp__internal__n_url_fragment; - } - case '?': { - p++; - goto s_n_llhttp__internal__n_url_query; - } - default: { - goto s_n_llhttp__internal__n_error_30; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_path: - s_n_llhttp__internal__n_url_path: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_path; - } - #ifdef __SSE4_2__ - if (endp - p >= 16) { - __m128i ranges; - __m128i input; - int avail; - int match_len; - - /* Load input */ - input = _mm_loadu_si128((__m128i const*) p); - ranges = _mm_loadu_si128((__m128i const*) llparse_blob1); - - /* Find first character that does not match `ranges` */ - match_len = _mm_cmpestri(ranges, 12, - input, 16, - _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | - _SIDD_NEGATIVE_POLARITY); - - if (match_len != 0) { - p += match_len; - goto s_n_llhttp__internal__n_url_path; - } - goto s_n_llhttp__internal__n_url_query_or_fragment; - } - #endif /* __SSE4_2__ */ - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_url_path; - } - default: { - goto s_n_llhttp__internal__n_url_query_or_fragment; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_stub_path_2: - s_n_llhttp__internal__n_span_start_stub_path_2: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_stub_path_2; - } - p++; - goto s_n_llhttp__internal__n_url_path; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_stub_path: - s_n_llhttp__internal__n_span_start_stub_path: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_stub_path; - } - p++; - goto s_n_llhttp__internal__n_url_path; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_stub_path_1: - s_n_llhttp__internal__n_span_start_stub_path_1: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_stub_path_1; - } - p++; - goto s_n_llhttp__internal__n_url_path; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_server_with_at: - s_n_llhttp__internal__n_url_server_with_at: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 3, 4, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 0, 6, - 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 0, 4, - 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_server_with_at; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12; - } - case 2: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13; - } - case 3: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14; - } - case 4: { - p++; - goto s_n_llhttp__internal__n_url_server; - } - case 5: { - goto s_n_llhttp__internal__n_span_start_stub_path_1; - } - case 6: { - p++; - goto s_n_llhttp__internal__n_url_query; - } - case 7: { - p++; - goto s_n_llhttp__internal__n_error_31; - } - default: { - goto s_n_llhttp__internal__n_error_32; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_server: - s_n_llhttp__internal__n_url_server: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 3, 4, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 0, 6, - 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 4, 0, 4, - 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 4, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_server; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url; - } - case 2: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1; - } - case 3: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2; - } - case 4: { - p++; - goto s_n_llhttp__internal__n_url_server; - } - case 5: { - goto s_n_llhttp__internal__n_span_start_stub_path; - } - case 6: { - p++; - goto s_n_llhttp__internal__n_url_query; - } - case 7: { - p++; - goto s_n_llhttp__internal__n_url_server_with_at; - } - default: { - goto s_n_llhttp__internal__n_error_33; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_schema_delim_1: - s_n_llhttp__internal__n_url_schema_delim_1: { - if (p == endp) { - return s_n_llhttp__internal__n_url_schema_delim_1; - } - switch (*p) { - case '/': { - p++; - goto s_n_llhttp__internal__n_url_server; - } - default: { - goto s_n_llhttp__internal__n_error_35; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_schema_delim: - s_n_llhttp__internal__n_url_schema_delim: { - if (p == endp) { - return s_n_llhttp__internal__n_url_schema_delim; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_error_34; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_error_34; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_error_34; - } - case '/': { - p++; - goto s_n_llhttp__internal__n_url_schema_delim_1; - } - default: { - goto s_n_llhttp__internal__n_error_35; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_end_stub_schema: - s_n_llhttp__internal__n_span_end_stub_schema: { - if (p == endp) { - return s_n_llhttp__internal__n_span_end_stub_schema; - } - p++; - goto s_n_llhttp__internal__n_url_schema_delim; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_schema: - s_n_llhttp__internal__n_url_schema: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, - 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, - 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_schema; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_error_34; - } - case 2: { - goto s_n_llhttp__internal__n_span_end_stub_schema; - } - case 3: { - p++; - goto s_n_llhttp__internal__n_url_schema; - } - default: { - goto s_n_llhttp__internal__n_error_36; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_url_start: - s_n_llhttp__internal__n_url_start: { - static uint8_t lookup_table[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, - 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - if (p == endp) { - return s_n_llhttp__internal__n_url_start; - } - switch (lookup_table[(uint8_t) *p]) { - case 1: { - p++; - goto s_n_llhttp__internal__n_error_34; - } - case 2: { - goto s_n_llhttp__internal__n_span_start_stub_path_2; - } - case 3: { - goto s_n_llhttp__internal__n_url_schema; - } - default: { - goto s_n_llhttp__internal__n_error_37; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_url_1: - s_n_llhttp__internal__n_span_start_llhttp__on_url_1: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_url_1; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_url; - goto s_n_llhttp__internal__n_url_start; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_url: - s_n_llhttp__internal__n_span_start_llhttp__on_url: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_url; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_url; - goto s_n_llhttp__internal__n_url_server; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_spaces_before_url: - s_n_llhttp__internal__n_req_spaces_before_url: { - if (p == endp) { - return s_n_llhttp__internal__n_req_spaces_before_url; - } - switch (*p) { - case ' ': { - p++; - goto s_n_llhttp__internal__n_req_spaces_before_url; - } - default: { - goto s_n_llhttp__internal__n_invoke_is_equal_method; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_first_space_before_url: - s_n_llhttp__internal__n_req_first_space_before_url: { - if (p == endp) { - return s_n_llhttp__internal__n_req_first_space_before_url; - } - switch (*p) { - case ' ': { - p++; - goto s_n_llhttp__internal__n_req_spaces_before_url; - } - default: { - goto s_n_llhttp__internal__n_error_38; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_1: - s_n_llhttp__internal__n_start_req_1: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_1; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob0, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 19; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_1; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_2: - s_n_llhttp__internal__n_start_req_2: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_2; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 16; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_2; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_4: - s_n_llhttp__internal__n_start_req_4: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_4; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 22; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_4; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_6: - s_n_llhttp__internal__n_start_req_6: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_6; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_6; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_7: - s_n_llhttp__internal__n_start_req_7: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_7; - } - switch (*p) { - case 'Y': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_5: - s_n_llhttp__internal__n_start_req_5: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_5; - } - switch (*p) { - case 'N': { - p++; - goto s_n_llhttp__internal__n_start_req_6; - } - case 'P': { - p++; - goto s_n_llhttp__internal__n_start_req_7; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_3: - s_n_llhttp__internal__n_start_req_3: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_3; - } - switch (*p) { - case 'H': { - p++; - goto s_n_llhttp__internal__n_start_req_4; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_5; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_8: - s_n_llhttp__internal__n_start_req_8: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_8; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 5); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_8; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_9: - s_n_llhttp__internal__n_start_req_9: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_9; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_9; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_10: - s_n_llhttp__internal__n_start_req_10: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_10; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_10; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_12: - s_n_llhttp__internal__n_start_req_12: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_12; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 31; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_12; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_13: - s_n_llhttp__internal__n_start_req_13: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_13; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_13; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_11: - s_n_llhttp__internal__n_start_req_11: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_11; - } - switch (*p) { - case 'I': { - p++; - goto s_n_llhttp__internal__n_start_req_12; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_13; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_15: - s_n_llhttp__internal__n_start_req_15: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_15; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 24; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_15; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_16: - s_n_llhttp__internal__n_start_req_16: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_16; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 23; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_16; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_18: - s_n_llhttp__internal__n_start_req_18: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_18; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 7); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 21; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_18; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_20: - s_n_llhttp__internal__n_start_req_20: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_20; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 30; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_20; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_21: - s_n_llhttp__internal__n_start_req_21: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_21; - } - switch (*p) { - case 'L': { - p++; - match = 10; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_19: - s_n_llhttp__internal__n_start_req_19: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_19; - } - switch (*p) { - case 'A': { - p++; - goto s_n_llhttp__internal__n_start_req_20; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_21; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_17: - s_n_llhttp__internal__n_start_req_17: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_17; - } - switch (*p) { - case 'A': { - p++; - goto s_n_llhttp__internal__n_start_req_18; - } - case 'C': { - p++; - goto s_n_llhttp__internal__n_start_req_19; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_22: - s_n_llhttp__internal__n_start_req_22: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_22; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 11; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_22; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_14: - s_n_llhttp__internal__n_start_req_14: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_14; - } - switch (*p) { - case '-': { - p++; - goto s_n_llhttp__internal__n_start_req_15; - } - case 'E': { - p++; - goto s_n_llhttp__internal__n_start_req_16; - } - case 'K': { - p++; - goto s_n_llhttp__internal__n_start_req_17; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_22; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_23: - s_n_llhttp__internal__n_start_req_23: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_23; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 5); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 25; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_23; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_24: - s_n_llhttp__internal__n_start_req_24: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_24; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 6); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_24; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_26: - s_n_llhttp__internal__n_start_req_26: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_26; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 28; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_26; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_27: - s_n_llhttp__internal__n_start_req_27: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_27; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_27; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_31: - s_n_llhttp__internal__n_start_req_31: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_31; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 12; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_31; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_32: - s_n_llhttp__internal__n_start_req_32: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_32; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 13; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_32; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_30: - s_n_llhttp__internal__n_start_req_30: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_30; - } - switch (*p) { - case 'F': { - p++; - goto s_n_llhttp__internal__n_start_req_31; - } - case 'P': { - p++; - goto s_n_llhttp__internal__n_start_req_32; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_29: - s_n_llhttp__internal__n_start_req_29: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_29; - } - switch (*p) { - case 'P': { - p++; - goto s_n_llhttp__internal__n_start_req_30; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_28: - s_n_llhttp__internal__n_start_req_28: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_28; - } - switch (*p) { - case 'I': { - p++; - match = 34; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_29; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_34: - s_n_llhttp__internal__n_start_req_34: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_34; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 29; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_34; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_33: - s_n_llhttp__internal__n_start_req_33: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_33; - } - switch (*p) { - case 'R': { - p++; - goto s_n_llhttp__internal__n_start_req_34; - } - case 'T': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_25: - s_n_llhttp__internal__n_start_req_25: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_25; - } - switch (*p) { - case 'A': { - p++; - goto s_n_llhttp__internal__n_start_req_26; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_27; - } - case 'R': { - p++; - goto s_n_llhttp__internal__n_start_req_28; - } - case 'U': { - p++; - goto s_n_llhttp__internal__n_start_req_33; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_37: - s_n_llhttp__internal__n_start_req_37: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_37; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 17; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_37; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_38: - s_n_llhttp__internal__n_start_req_38: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_38; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 20; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_38; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_36: - s_n_llhttp__internal__n_start_req_36: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_36; - } - switch (*p) { - case 'B': { - p++; - goto s_n_llhttp__internal__n_start_req_37; - } - case 'P': { - p++; - goto s_n_llhttp__internal__n_start_req_38; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_35: - s_n_llhttp__internal__n_start_req_35: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_35; - } - switch (*p) { - case 'E': { - p++; - goto s_n_llhttp__internal__n_start_req_36; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_40: - s_n_llhttp__internal__n_start_req_40: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_40; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 14; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_40; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_41: - s_n_llhttp__internal__n_start_req_41: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_41; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 33; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_41; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_42: - s_n_llhttp__internal__n_start_req_42: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_42; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 7); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 26; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_42; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_39: - s_n_llhttp__internal__n_start_req_39: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_39; - } - switch (*p) { - case 'E': { - p++; - goto s_n_llhttp__internal__n_start_req_40; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_41; - } - case 'U': { - p++; - goto s_n_llhttp__internal__n_start_req_42; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_43: - s_n_llhttp__internal__n_start_req_43: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_43; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 4); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_43; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_46: - s_n_llhttp__internal__n_start_req_46: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_46; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 18; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_46; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_48: - s_n_llhttp__internal__n_start_req_48: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_48; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 32; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_48; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_49: - s_n_llhttp__internal__n_start_req_49: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_49; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 15; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_49; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_47: - s_n_llhttp__internal__n_start_req_47: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_47; - } - switch (*p) { - case 'I': { - p++; - goto s_n_llhttp__internal__n_start_req_48; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_49; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_50: - s_n_llhttp__internal__n_start_req_50: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_req_50; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 8); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 27; - goto s_n_llhttp__internal__n_invoke_store_method_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_req_50; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_45: - s_n_llhttp__internal__n_start_req_45: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_45; - } - switch (*p) { - case 'B': { - p++; - goto s_n_llhttp__internal__n_start_req_46; - } - case 'L': { - p++; - goto s_n_llhttp__internal__n_start_req_47; - } - case 'S': { - p++; - goto s_n_llhttp__internal__n_start_req_50; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_44: - s_n_llhttp__internal__n_start_req_44: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_44; - } - switch (*p) { - case 'N': { - p++; - goto s_n_llhttp__internal__n_start_req_45; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req: - s_n_llhttp__internal__n_start_req: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req; - } - switch (*p) { - case 'A': { - p++; - goto s_n_llhttp__internal__n_start_req_1; - } - case 'B': { - p++; - goto s_n_llhttp__internal__n_start_req_2; - } - case 'C': { - p++; - goto s_n_llhttp__internal__n_start_req_3; - } - case 'D': { - p++; - goto s_n_llhttp__internal__n_start_req_8; - } - case 'G': { - p++; - goto s_n_llhttp__internal__n_start_req_9; - } - case 'H': { - p++; - goto s_n_llhttp__internal__n_start_req_10; - } - case 'L': { - p++; - goto s_n_llhttp__internal__n_start_req_11; - } - case 'M': { - p++; - goto s_n_llhttp__internal__n_start_req_14; - } - case 'N': { - p++; - goto s_n_llhttp__internal__n_start_req_23; - } - case 'O': { - p++; - goto s_n_llhttp__internal__n_start_req_24; - } - case 'P': { - p++; - goto s_n_llhttp__internal__n_start_req_25; - } - case 'R': { - p++; - goto s_n_llhttp__internal__n_start_req_35; - } - case 'S': { - p++; - goto s_n_llhttp__internal__n_start_req_39; - } - case 'T': { - p++; - goto s_n_llhttp__internal__n_start_req_43; - } - case 'U': { - p++; - goto s_n_llhttp__internal__n_start_req_44; - } - default: { - goto s_n_llhttp__internal__n_error_46; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_line_almost_done: - s_n_llhttp__internal__n_res_line_almost_done: { - if (p == endp) { - return s_n_llhttp__internal__n_res_line_almost_done; - } - p++; - goto s_n_llhttp__internal__n_header_field_start; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_status: - s_n_llhttp__internal__n_res_status: { - if (p == endp) { - return s_n_llhttp__internal__n_res_status; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_status; - } - case 13: { - goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1; - } - default: { - p++; - goto s_n_llhttp__internal__n_res_status; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_span_start_llhttp__on_status: - s_n_llhttp__internal__n_span_start_llhttp__on_status: { - if (p == endp) { - return s_n_llhttp__internal__n_span_start_llhttp__on_status; - } - state->_span_pos0 = (void*) p; - state->_span_cb0 = llhttp__on_status; - goto s_n_llhttp__internal__n_res_status; - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_status_start: - s_n_llhttp__internal__n_res_status_start: { - if (p == endp) { - return s_n_llhttp__internal__n_res_status_start; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_header_field_start; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_res_line_almost_done; - } - default: { - goto s_n_llhttp__internal__n_span_start_llhttp__on_status; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_status_code_otherwise: - s_n_llhttp__internal__n_res_status_code_otherwise: { - if (p == endp) { - return s_n_llhttp__internal__n_res_status_code_otherwise; - } - switch (*p) { - case 10: { - goto s_n_llhttp__internal__n_res_status_start; - } - case 13: { - goto s_n_llhttp__internal__n_res_status_start; - } - case ' ': { - p++; - goto s_n_llhttp__internal__n_res_status_start; - } - default: { - goto s_n_llhttp__internal__n_error_40; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_status_code: - s_n_llhttp__internal__n_res_status_code: { - if (p == endp) { - return s_n_llhttp__internal__n_res_status_code; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_mul_add_status_code; - } - default: { - goto s_n_llhttp__internal__n_res_status_code_otherwise; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_http_end: - s_n_llhttp__internal__n_res_http_end: { - if (p == endp) { - return s_n_llhttp__internal__n_res_http_end; - } - switch (*p) { - case ' ': { - p++; - goto s_n_llhttp__internal__n_invoke_update_status_code; - } - default: { - goto s_n_llhttp__internal__n_error_41; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_http_minor: - s_n_llhttp__internal__n_res_http_minor: { - if (p == endp) { - return s_n_llhttp__internal__n_res_http_minor; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_store_http_minor_1; - } - default: { - goto s_n_llhttp__internal__n_error_42; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_http_dot: - s_n_llhttp__internal__n_res_http_dot: { - if (p == endp) { - return s_n_llhttp__internal__n_res_http_dot; - } - switch (*p) { - case '.': { - p++; - goto s_n_llhttp__internal__n_res_http_minor; - } - default: { - goto s_n_llhttp__internal__n_error_43; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_res_http_major: - s_n_llhttp__internal__n_res_http_major: { - if (p == endp) { - return s_n_llhttp__internal__n_res_http_major; - } - switch (*p) { - case '0': { - p++; - match = 0; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '1': { - p++; - match = 1; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '2': { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '3': { - p++; - match = 3; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '4': { - p++; - match = 4; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '5': { - p++; - match = 5; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '6': { - p++; - match = 6; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '7': { - p++; - match = 7; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '8': { - p++; - match = 8; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - case '9': { - p++; - match = 9; - goto s_n_llhttp__internal__n_invoke_store_http_major_1; - } - default: { - goto s_n_llhttp__internal__n_error_44; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_res: - s_n_llhttp__internal__n_start_res: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_start_res; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 5); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_res_http_major; - } - case kMatchPause: { - return s_n_llhttp__internal__n_start_res; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_47; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_or_res_method_2: - s_n_llhttp__internal__n_req_or_res_method_2: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_req_or_res_method_2; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 2); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - match = 2; - goto s_n_llhttp__internal__n_invoke_store_method; - } - case kMatchPause: { - return s_n_llhttp__internal__n_req_or_res_method_2; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_45; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_or_res_method_3: - s_n_llhttp__internal__n_req_or_res_method_3: { - llparse_match_t match_seq; - - if (p == endp) { - return s_n_llhttp__internal__n_req_or_res_method_3; - } - match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 3); - p = match_seq.current; - switch (match_seq.status) { - case kMatchComplete: { - p++; - goto s_n_llhttp__internal__n_invoke_update_type_1; - } - case kMatchPause: { - return s_n_llhttp__internal__n_req_or_res_method_3; - } - case kMatchMismatch: { - goto s_n_llhttp__internal__n_error_45; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_or_res_method_1: - s_n_llhttp__internal__n_req_or_res_method_1: { - if (p == endp) { - return s_n_llhttp__internal__n_req_or_res_method_1; - } - switch (*p) { - case 'E': { - p++; - goto s_n_llhttp__internal__n_req_or_res_method_2; - } - case 'T': { - p++; - goto s_n_llhttp__internal__n_req_or_res_method_3; - } - default: { - goto s_n_llhttp__internal__n_error_45; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_req_or_res_method: - s_n_llhttp__internal__n_req_or_res_method: { - if (p == endp) { - return s_n_llhttp__internal__n_req_or_res_method; - } - switch (*p) { - case 'H': { - p++; - goto s_n_llhttp__internal__n_req_or_res_method_1; - } - default: { - goto s_n_llhttp__internal__n_error_45; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start_req_or_res: - s_n_llhttp__internal__n_start_req_or_res: { - if (p == endp) { - return s_n_llhttp__internal__n_start_req_or_res; - } - switch (*p) { - case 'H': { - goto s_n_llhttp__internal__n_req_or_res_method; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_type_2; - } - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_invoke_load_type: - s_n_llhttp__internal__n_invoke_load_type: { - switch (llhttp__internal__c_load_type(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_start_req; - case 2: - goto s_n_llhttp__internal__n_start_res; - default: - goto s_n_llhttp__internal__n_start_req_or_res; - } - /* UNREACHABLE */; - abort(); - } - case s_n_llhttp__internal__n_start: - s_n_llhttp__internal__n_start: { - if (p == endp) { - return s_n_llhttp__internal__n_start; - } - switch (*p) { - case 10: { - p++; - goto s_n_llhttp__internal__n_start; - } - case 13: { - p++; - goto s_n_llhttp__internal__n_start; - } - default: { - goto s_n_llhttp__internal__n_invoke_update_finish; - } - } - /* UNREACHABLE */; - abort(); - } - default: - /* UNREACHABLE */ - abort(); - } - s_n_llhttp__internal__n_error_34: { - state->error = 0x7; - state->reason = "Invalid characters in url"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_finish_1: { - switch (llhttp__internal__c_update_finish_1(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_5: { - state->error = 0x15; - state->reason = "on_message_complete pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_9: { - state->error = 0x12; - state->reason = "`on_message_complete` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_7: { - state->error = 0x15; - state->reason = "on_chunk_complete pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_13: { - state->error = 0x14; - state->reason = "`on_chunk_complete` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: { - switch (llhttp__on_chunk_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; - case 21: - goto s_n_llhttp__internal__n_pause_7; - default: - goto s_n_llhttp__internal__n_error_13; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_11: { - state->error = 0x4; - state->reason = "Content-Length can't be present with Transfer-Encoding"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_12: { - state->error = 0x4; - state->reason = "Content-Length can't be present with chunked encoding"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_2: { - state->error = 0x15; - state->reason = "on_message_complete pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_3: { - state->error = 0x12; - state->reason = "`on_message_complete` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: { - switch (llhttp__on_message_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_pause_1; - case 21: - goto s_n_llhttp__internal__n_pause_2; - default: - goto s_n_llhttp__internal__n_error_3; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_7: { - state->error = 0xc; - state->reason = "Chunk size overflow"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_3: { - state->error = 0x15; - state->reason = "on_chunk_complete pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_5: { - state->error = 0x14; - state->reason = "`on_chunk_complete` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: { - switch (llhttp__on_chunk_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_update_content_length; - case 21: - goto s_n_llhttp__internal__n_pause_3; - default: - goto s_n_llhttp__internal__n_error_5; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_body: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_body(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done; - return s_error; - } - goto s_n_llhttp__internal__n_chunk_data_almost_done; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags: { - switch (llhttp__internal__c_or_flags(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_4: { - state->error = 0x15; - state->reason = "on_chunk_header pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_4: { - state->error = 0x13; - state->reason = "`on_chunk_header` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: { - switch (llhttp__on_chunk_header(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_is_equal_content_length; - case 21: - goto s_n_llhttp__internal__n_pause_4; - default: - goto s_n_llhttp__internal__n_error_4; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_6: { - state->error = 0xc; - state->reason = "Invalid character in chunk size"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_mul_add_content_length: { - switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) { - case 1: - goto s_n_llhttp__internal__n_error_7; - default: - goto s_n_llhttp__internal__n_chunk_size; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_8: { - state->error = 0xc; - state->reason = "Invalid character in chunk size"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_body_1: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_body(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; - return s_error; - } - goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_finish_2: { - switch (llhttp__internal__c_update_finish_2(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_10: { - state->error = 0xf; - state->reason = "Request has invalid `Transfer-Encoding`"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause: { - state->error = 0x15; - state->reason = "on_message_complete pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_2: { - state->error = 0x12; - state->reason = "`on_message_complete` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: { - switch (llhttp__on_message_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; - case 21: - goto s_n_llhttp__internal__n_pause; - default: - goto s_n_llhttp__internal__n_error_2; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_1: { - switch (llhttp__internal__c_or_flags_1(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_2: { - switch (llhttp__internal__c_or_flags_1(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_upgrade: { - switch (llhttp__internal__c_update_upgrade(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_or_flags_2; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_6: { - state->error = 0x15; - state->reason = "Paused by on_headers_complete"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_1: { - state->error = 0x11; - state->reason = "User callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: { - switch (llhttp__on_headers_complete(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; - case 1: - goto s_n_llhttp__internal__n_invoke_or_flags_1; - case 2: - goto s_n_llhttp__internal__n_invoke_update_upgrade; - case 21: - goto s_n_llhttp__internal__n_pause_6; - default: - goto s_n_llhttp__internal__n_error_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: { - switch (llhttp__before_headers_complete(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags_3: { - switch (llhttp__internal__c_test_flags_3(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_error_12; - default: - goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags_2: { - switch (llhttp__internal__c_test_flags_2(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_error_11; - case 1: - goto s_n_llhttp__internal__n_invoke_test_flags_3; - default: - goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags_1: { - switch (llhttp__internal__c_test_flags_1(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_invoke_test_flags_2; - default: - goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags: { - switch (llhttp__internal__c_test_flags(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1; - default: - goto s_n_llhttp__internal__n_invoke_test_flags_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_14: { - state->error = 0xb; - state->reason = "Empty Content-Length"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; - return s_error; - } - goto s_n_llhttp__internal__n_header_field_start; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state: { - switch (llhttp__internal__c_update_header_state(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_3: { - switch (llhttp__internal__c_or_flags_3(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_4: { - switch (llhttp__internal__c_or_flags_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_5: { - switch (llhttp__internal__c_or_flags_5(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_6: { - switch (llhttp__internal__c_or_flags_6(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_load_header_state_1: { - switch (llhttp__internal__c_load_header_state(state, p, endp)) { - case 5: - goto s_n_llhttp__internal__n_invoke_or_flags_3; - case 6: - goto s_n_llhttp__internal__n_invoke_or_flags_4; - case 7: - goto s_n_llhttp__internal__n_invoke_or_flags_5; - case 8: - goto s_n_llhttp__internal__n_invoke_or_flags_6; - default: - goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_load_header_state: { - switch (llhttp__internal__c_load_header_state(state, p, endp)) { - case 2: - goto s_n_llhttp__internal__n_error_14; - default: - goto s_n_llhttp__internal__n_invoke_load_header_state_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_1: { - switch (llhttp__internal__c_update_header_state(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_7: { - switch (llhttp__internal__c_or_flags_3(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_8: { - switch (llhttp__internal__c_or_flags_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_9: { - switch (llhttp__internal__c_or_flags_5(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_1; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_10: { - switch (llhttp__internal__c_or_flags_6(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_load_header_state_3: { - switch (llhttp__internal__c_load_header_state(state, p, endp)) { - case 5: - goto s_n_llhttp__internal__n_invoke_or_flags_7; - case 6: - goto s_n_llhttp__internal__n_invoke_or_flags_8; - case 7: - goto s_n_llhttp__internal__n_invoke_or_flags_9; - case 8: - goto s_n_llhttp__internal__n_invoke_or_flags_10; - default: - goto s_n_llhttp__internal__n_header_field_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_15: { - state->error = 0x3; - state->reason = "Missing expected LF after header value"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; - return s_error; - } - goto s_n_llhttp__internal__n_header_value_almost_done; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_header_value_almost_done; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_header_value_almost_done; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_16: { - state->error = 0xa; - state->reason = "Invalid header value char"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags_4: { - switch (llhttp__internal__c_test_flags_2(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_header_value_lenient; - default: - goto s_n_llhttp__internal__n_error_16; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_3: { - switch (llhttp__internal__c_update_header_state(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_11: { - switch (llhttp__internal__c_or_flags_3(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_3; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_12: { - switch (llhttp__internal__c_or_flags_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_3; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_13: { - switch (llhttp__internal__c_or_flags_5(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_3; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_14: { - switch (llhttp__internal__c_or_flags_6(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_load_header_state_4: { - switch (llhttp__internal__c_load_header_state(state, p, endp)) { - case 5: - goto s_n_llhttp__internal__n_invoke_or_flags_11; - case 6: - goto s_n_llhttp__internal__n_invoke_or_flags_12; - case 7: - goto s_n_llhttp__internal__n_invoke_or_flags_13; - case 8: - goto s_n_llhttp__internal__n_invoke_or_flags_14; - default: - goto s_n_llhttp__internal__n_header_value_connection; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_4: { - switch (llhttp__internal__c_update_header_state_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection_token; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_2: { - switch (llhttp__internal__c_update_header_state_2(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection_ws; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_5: { - switch (llhttp__internal__c_update_header_state_5(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection_ws; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_6: { - switch (llhttp__internal__c_update_header_state_6(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_connection_ws; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_18; - return s_error; - } - goto s_n_llhttp__internal__n_error_18; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_mul_add_content_length_1: { - switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) { - case 1: - goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4; - default: - goto s_n_llhttp__internal__n_header_value_content_length; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_7: { - switch (llhttp__internal__c_update_header_state_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_15: { - switch (llhttp__internal__c_or_flags_15(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_discard_rws; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_value(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_19; - return s_error; - } - goto s_n_llhttp__internal__n_error_19; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_17: { - state->error = 0x4; - state->reason = "Duplicate Content-Length"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_test_flags_5: { - switch (llhttp__internal__c_test_flags_5(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_header_value_content_length; - default: - goto s_n_llhttp__internal__n_error_17; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_8: { - switch (llhttp__internal__c_update_header_state_8(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_otherwise; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_16: { - switch (llhttp__internal__c_or_flags_16(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_value_te_chunked; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_or_flags_17: { - switch (llhttp__internal__c_or_flags_17(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_header_state_7; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_load_header_state_2: { - switch (llhttp__internal__c_load_header_state(state, p, endp)) { - case 1: - goto s_n_llhttp__internal__n_header_value_connection; - case 2: - goto s_n_llhttp__internal__n_invoke_test_flags_5; - case 3: - goto s_n_llhttp__internal__n_invoke_or_flags_16; - case 4: - goto s_n_llhttp__internal__n_invoke_or_flags_17; - default: - goto s_n_llhttp__internal__n_header_value; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_field: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_field(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_discard_ws; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_header_field(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_discard_ws; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_header_value_discard_ws; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_20: { - state->error = 0xa; - state->reason = "Invalid header token"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_9: { - switch (llhttp__internal__c_update_header_state_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_general; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_header_state: { - switch (llhttp__internal__c_store_header_state(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_header_field_colon; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_header_state_10: { - switch (llhttp__internal__c_update_header_state_4(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_general; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_http_minor: { - switch (llhttp__internal__c_update_http_minor(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_header_field_start; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_http_major: { - switch (llhttp__internal__c_update_http_major(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_update_http_minor; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_3: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_21: { - state->error = 0x7; - state->reason = "Expected CRLF"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_4: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_lf_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_22: { - state->error = 0x9; - state->reason = "Expected CRLF after version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_http_minor: { - switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_req_http_end; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_23: { - state->error = 0x9; - state->reason = "Invalid minor version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_24: { - state->error = 0x9; - state->reason = "Expected dot"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_http_major: { - switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_req_http_dot; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_25: { - state->error = 0x9; - state->reason = "Invalid major version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_27: { - state->error = 0x8; - state->reason = "Expected HTTP/"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_26: { - state->error = 0x8; - state->reason = "Expected SOURCE method for ICE/x.x request"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_is_equal_method_1: { - switch (llhttp__internal__c_is_equal_method_1(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_error_26; - default: - goto s_n_llhttp__internal__n_req_http_major; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_5: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_6: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_7: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_lf_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_8: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_28: { - state->error = 0x7; - state->reason = "Invalid char in url fragment start"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_9: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_10: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_lf_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_11: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_29: { - state->error = 0x7; - state->reason = "Invalid char in url query"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_30: { - state->error = 0x7; - state->reason = "Invalid char in url path"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_1: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_lf_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_2: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_12: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_13: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_lf_to_http09; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_url_14: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_url(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; - return s_error; - } - goto s_n_llhttp__internal__n_url_skip_to_http; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_31: { - state->error = 0x7; - state->reason = "Double @ in url"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_32: { - state->error = 0x7; - state->reason = "Unexpected char in url server"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_33: { - state->error = 0x7; - state->reason = "Unexpected char in url server"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_35: { - state->error = 0x7; - state->reason = "Unexpected char in url schema"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_36: { - state->error = 0x7; - state->reason = "Unexpected char in url schema"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_37: { - state->error = 0x7; - state->reason = "Unexpected start char in url"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_is_equal_method: { - switch (llhttp__internal__c_is_equal_method(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1; - default: - goto s_n_llhttp__internal__n_span_start_llhttp__on_url; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_38: { - state->error = 0x6; - state->reason = "Expected space after method"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_method_1: { - switch (llhttp__internal__c_store_method(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_req_first_space_before_url; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_46: { - state->error = 0x6; - state->reason = "Invalid method encountered"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_39: { - state->error = 0xd; - state->reason = "Response overflow"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_mul_add_status_code: { - switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { - case 1: - goto s_n_llhttp__internal__n_error_39; - default: - goto s_n_llhttp__internal__n_res_status_code; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_status: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_status(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_header_field_start; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_span_end_llhttp__on_status_1: { - const unsigned char* start; - int err; - - start = state->_span_pos0; - state->_span_pos0 = NULL; - err = llhttp__on_status(state, start, p); - if (err != 0) { - state->error = err; - state->error_pos = (const char*) (p + 1); - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; - return s_error; - } - p++; - goto s_n_llhttp__internal__n_res_line_almost_done; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_40: { - state->error = 0xd; - state->reason = "Invalid response status"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_status_code: { - switch (llhttp__internal__c_update_status_code(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_res_status_code; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_41: { - state->error = 0x9; - state->reason = "Expected space after version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_http_minor_1: { - switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_res_http_end; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_42: { - state->error = 0x9; - state->reason = "Invalid minor version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_43: { - state->error = 0x9; - state->reason = "Expected dot"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_http_major_1: { - switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_res_http_dot; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_44: { - state->error = 0x9; - state->reason = "Invalid major version"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_47: { - state->error = 0x8; - state->reason = "Expected HTTP/"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_type: { - switch (llhttp__internal__c_update_type(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_req_first_space_before_url; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_store_method: { - switch (llhttp__internal__c_store_method(state, p, endp, match)) { - default: - goto s_n_llhttp__internal__n_invoke_update_type; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error_45: { - state->error = 0x8; - state->reason = "Invalid word encountered"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_type_1: { - switch (llhttp__internal__c_update_type_1(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_res_http_major; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_type_2: { - switch (llhttp__internal__c_update_type(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_start_req; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_pause_8: { - state->error = 0x15; - state->reason = "on_message_begin pause"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_error: { - state->error = 0x10; - state->reason = "`on_message_begin` callback error"; - state->error_pos = (const char*) p; - state->_current = (void*) (intptr_t) s_error; - return s_error; - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: { - switch (llhttp__on_message_begin(state, p, endp)) { - case 0: - goto s_n_llhttp__internal__n_invoke_load_type; - case 21: - goto s_n_llhttp__internal__n_pause_8; - default: - goto s_n_llhttp__internal__n_error; - } - /* UNREACHABLE */; - abort(); - } - s_n_llhttp__internal__n_invoke_update_finish: { - switch (llhttp__internal__c_update_finish(state, p, endp)) { - default: - goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin; - } - /* UNREACHABLE */; - abort(); - } -} - -int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) { - llparse_state_t next; - - /* check lingering errors */ - if (state->error != 0) { - return state->error; - } - - /* restart spans */ - if (state->_span_pos0 != NULL) { - state->_span_pos0 = (void*) p; - } - - next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); - if (next == s_error) { - return state->error; - } - state->_current = (void*) (intptr_t) next; - - /* execute spans */ - if (state->_span_pos0 != NULL) { - int error; - - error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); - if (error != 0) { - state->error = error; - state->error_pos = endp; - return error; - } - } - - return 0; -} \ No newline at end of file diff --git a/core-tests/docker-compose.yml b/core-tests/docker-compose.yml new file mode 100755 index 0000000000..c295256ae1 --- /dev/null +++ b/core-tests/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.4' +services: + httpbin: + container_name: "httpbin" + image: "kennethreitz/httpbin" + tinyproxy: + container_name: "tinyproxy" + image: "vimagick/tinyproxy" + ports: + - 8888:8888 + socks5: + container_name: "socks5" + image: "xkuma/socks5" + ports: + - "8080:1080" + environment: + - PROXY_USER=user + - PROXY_PASSWORD=password + - PROXY_SERVER=0.0.0.0:1080 + socks5-no-auth: + image: "xkuma/socks5" + ports: + - 8081:1080 + environment: + PROXY_SERVER: 0.0.0.0:1080 diff --git a/core-tests/fuzz/cases/req1.bin b/core-tests/fuzz/cases/req1.bin index fc9b786372..0043fab2a1 100644 --- a/core-tests/fuzz/cases/req1.bin +++ b/core-tests/fuzz/cases/req1.bin @@ -1,4 +1,4 @@ -POST / HTTP/1.1 +POST /index.html?test=hello&str=world HTTP/1.1 Host: 127.0.0.1:35347 Accept: */* Content-Length: 11199 diff --git a/core-tests/include/httplib_client.h b/core-tests/include/httplib_client.h index aa22c03a87..8b1289ecb3 100644 --- a/core-tests/include/httplib_client.h +++ b/core-tests/include/httplib_client.h @@ -375,9 +375,11 @@ struct Response { void set_content(const char *s, size_t n, const char *content_type); void set_content(std::string s, const char *content_type); - void set_content_provider(size_t length, ContentProvider provider, std::function resource_releaser = [] {}); + void set_content_provider( + size_t length, ContentProvider provider, std::function resource_releaser = [] {}); - void set_chunked_content_provider(ChunkedContentProvider provider, std::function resource_releaser = [] {}); + void set_chunked_content_provider( + ChunkedContentProvider provider, std::function resource_releaser = [] {}); Response() = default; Response(const Response &) = default; @@ -417,7 +419,7 @@ class Stream { virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; template - ssize_t write_format(const char *fmt, const Args &... args); + ssize_t write_format(const char *fmt, const Args &...args); ssize_t write(const char *ptr); ssize_t write(const std::string &s); }; @@ -611,6 +613,8 @@ class Client { void set_interface(const char *intf); + void set_websocket_mask(bool on); + void set_proxy(const char *host, int port); void set_proxy_basic_auth(const char *username, const char *password); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -678,6 +682,8 @@ class Client { std::string proxy_host_; int proxy_port_ = 80; + bool websocket_mask_ = false; + std::string proxy_basic_auth_username_; std::string proxy_basic_auth_password_; #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -716,6 +722,7 @@ class Client { proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; #endif + websocket_mask_ = rhs.websocket_mask_; logger_ = rhs.logger_; } @@ -2965,16 +2972,17 @@ inline std::string make_multipart_ranges_data(const Request &req, const std::string &content_type) { std::string data; - process_multipart_ranges_data(req, - res, - boundary, - content_type, - [&](const std::string &token) { data += token; }, - [&](const char *token) { data += token; }, - [&](size_t offset, size_t length) { - data += res.body.substr(offset, length); - return true; - }); + process_multipart_ranges_data( + req, + res, + boundary, + content_type, + [&](const std::string &token) { data += token; }, + [&](const char *token) { data += token; }, + [&](size_t offset, size_t length) { + data += res.body.substr(offset, length); + return true; + }); return data; } @@ -2985,16 +2993,17 @@ inline size_t get_multipart_ranges_data_length(const Request &req, const std::string &content_type) { size_t data_length = 0; - process_multipart_ranges_data(req, - res, - boundary, - content_type, - [&](const std::string &token) { data_length += token.size(); }, - [&](const char *token) { data_length += strlen(token); }, - [&](size_t /*offset*/, size_t length) { - data_length += length; - return true; - }); + process_multipart_ranges_data( + req, + res, + boundary, + content_type, + [&](const std::string &token) { data_length += token.size(); }, + [&](const char *token) { data_length += strlen(token); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); return data_length; } @@ -3357,8 +3366,8 @@ inline ssize_t Stream::write(const std::string &s) { } template -inline ssize_t Stream::write_format(const char *fmt, const Args &... args) { - std::array buf; +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + std::array buf{}; #if defined(_MSC_VER) && _MSC_VER < 1900 auto sn = _snprintf_s(buf, bufsiz, buf.size() - 1, fmt, args...); @@ -3821,54 +3830,55 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, bool &s } inline bool SSLClient::initialize_ssl(Socket &socket) { - auto ssl = detail::ssl_new(socket.sock, - ctx_, - ctx_mutex_, - [&](SSL *ssl) { - if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) { - SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); - } else if (!ca_cert_file_path_.empty()) { - if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { - return false; - } - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); - } else if (ca_cert_store_ != nullptr) { - if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { - SSL_CTX_set_cert_store(ctx_, ca_cert_store_); - } - SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); - } - - if (SSL_connect(ssl) != 1) { - return false; - } - - if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl); - - if (verify_result_ != X509_V_OK) { - return false; - } - - auto server_cert = SSL_get_peer_certificate(ssl); - - if (server_cert == nullptr) { - return false; - } - - if (!verify_host(server_cert)) { - X509_free(server_cert); - return false; - } - X509_free(server_cert); - } - - return true; - }, - [&](SSL *ssl) { - SSL_set_tlsext_host_name(ssl, host_.c_str()); - return true; - }); + auto ssl = detail::ssl_new( + socket.sock, + ctx_, + ctx_mutex_, + [&](SSL *ssl) { + if (ca_cert_file_path_.empty() && ca_cert_store_ == nullptr) { + SSL_CTX_set_verify(ctx_, SSL_VERIFY_NONE, nullptr); + } else if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), nullptr)) { + return false; + } + SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); + } else if (ca_cert_store_ != nullptr) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store_) { + SSL_CTX_set_cert_store(ctx_, ca_cert_store_); + } + SSL_CTX_set_verify(ctx_, SSL_VERIFY_PEER, nullptr); + } + + if (SSL_connect(ssl) != 1) { + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl); + + if (verify_result_ != X509_V_OK) { + return false; + } + + auto server_cert = SSL_get_peer_certificate(ssl); + + if (server_cert == nullptr) { + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl) { + SSL_set_tlsext_host_name(ssl, host_.c_str()); + return true; + }); if (ssl) { socket.ssl = ssl; diff --git a/core-tests/include/httplib_server.h b/core-tests/include/httplib_server.h index 489893f283..1ef1157b3f 100644 --- a/core-tests/include/httplib_server.h +++ b/core-tests/include/httplib_server.h @@ -9,11 +9,10 @@ #pragma once #include "swoole_coroutine_system.h" -#include "swoole_coroutine_socket.h" +#include "swoole_socket_impl.h" #include "httplib_client.h" using swoole::Coroutine; -using swoole::coroutine::Socket; using swoole::coroutine::System; using swoole::network::Address; @@ -102,7 +101,7 @@ class Server { bool &connection_closed, const std::function &setup_request); - Socket *svr_sock_; + SocketImpl *svr_sock_; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; @@ -118,7 +117,7 @@ class Server { using Handlers = std::vector>; using HandlersForContentReader = std::vector>; - Socket *create_server_socket(const char *host, int port, int socket_flags, SocketOptions socket_options) const; + SocketImpl *create_server_socket(const char *host, int port, int socket_flags, SocketOptions socket_options) const; int bind_internal(const char *host, int port, int socket_flags); bool listen_internal(); @@ -148,7 +147,7 @@ class Server { MultipartContentHeader mulitpart_header, ContentReceiver multipart_receiver); - virtual bool process_and_close_socket(Socket *sock); + virtual bool process_and_close_socket(SocketImpl *sock); std::atomic is_running_; std::vector> base_dirs_; @@ -517,30 +516,31 @@ inline bool Server::write_content_with_provider( inline bool Server::read_content(Stream &strm, Request &req, Response &res) { MultipartFormDataMap::iterator cur; - if (read_content_core(strm, - req, - res, - // Regular - [&](const char *buf, size_t n) { - if (req.body.size() + n > req.body.max_size()) { - return false; - } - req.body.append(buf, n); - return true; - }, - // Multipart - [&](const MultipartFormData &file) { - cur = req.files.emplace(file.name, file); - return true; - }, - [&](const char *buf, size_t n) { - auto &content = cur->second.content; - if (content.size() + n > content.max_size()) { - return false; - } - content.append(buf, n); - return true; - })) { + if (read_content_core( + strm, + req, + res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { + return false; + } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { + return false; + } + content.append(buf, n); + return true; + })) { const auto &content_type = req.get_header_value("Content-Type"); if (!content_type.find("application/x-www-form-urlencoded")) { detail::parse_query_text(req.body, req.params); @@ -641,10 +641,10 @@ inline bool Server::handle_file_request(Request &req, Response &res, bool head) return false; } -inline Socket *Server::create_server_socket(const char *host, - int port, - int socket_flags, - SocketOptions socket_options) const { +inline SocketImpl *Server::create_server_socket(const char *host, + int port, + int socket_flags, + SocketOptions socket_options) const { struct addrinfo hints; struct addrinfo *result; @@ -660,9 +660,9 @@ inline Socket *Server::create_server_socket(const char *host, return nullptr; } - Socket *sock = nullptr; + SocketImpl *sock = nullptr; for (auto rp = result; rp; rp = rp->ai_next) { - sock = new Socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + sock = new SocketImpl(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sock->get_fd() == INVALID_SOCKET) { delete sock; continue; @@ -720,7 +720,7 @@ inline int Server::bind_internal(const char *host, int port, int socket_flags) { } struct CoroutineArg { - Socket *client_socket; + SocketImpl *client_socket; Server *this_; }; @@ -991,7 +991,7 @@ inline bool process_server_socket(socket_t sock, class CoSocketStream : public detail::SocketStream { public: - CoSocketStream(Socket *sock, + CoSocketStream(SocketImpl *sock, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, @@ -999,8 +999,8 @@ class CoSocketStream : public detail::SocketStream { : detail::SocketStream( sock->get_fd(), read_timeout_sec, read_timeout_usec, write_timeout_sec, write_timeout_usec) { sock_ = sock; - sock->set_timeout((double) read_timeout_sec + ((double) read_timeout_usec / 1000000), Socket::TIMEOUT_READ); - sock->set_timeout((double) write_timeout_sec + ((double) write_timeout_usec / 1000000), Socket::TIMEOUT_WRITE); + sock->set_timeout((double) read_timeout_sec + ((double) read_timeout_usec / 1000000), SW_TIMEOUT_READ); + sock->set_timeout((double) write_timeout_sec + ((double) write_timeout_usec / 1000000), SW_TIMEOUT_WRITE); } ~CoSocketStream() {} bool is_readable() const { @@ -1018,15 +1018,15 @@ class CoSocketStream : public detail::SocketStream { void get_remote_ip_and_port(std::string &ip, int &port) const { Address sa; sock_->getpeername(&sa); - ip = std::string(sa.get_ip()); + ip = std::string(sa.get_addr()); port = sa.get_port(); } private: - Socket *sock_; + SocketImpl *sock_; }; -inline bool Server::process_and_close_socket(Socket *sock) { +inline bool Server::process_and_close_socket(SocketImpl *sock) { size_t keep_alive_max_count = keep_alive_max_count_; time_t read_timeout_sec = read_timeout_sec_; time_t read_timeout_usec = read_timeout_usec_; diff --git a/core-tests/include/redis_client.h b/core-tests/include/redis_client.h index fdcbf6a450..da1ff41178 100644 --- a/core-tests/include/redis_client.h +++ b/core-tests/include/redis_client.h @@ -62,6 +62,9 @@ class RedisClient { bool Connect(const std::string &host = "127.0.0.1", int port = 6379, struct timeval timeout = {}); std::string Get(const std::string &key); bool Set(const std::string &key, const std::string &value); + long Ttl(const std::string &key); + bool Select(int db); + std::string Role(); }; } // namespace swoole diff --git a/core-tests/include/test_core.h b/core-tests/include/test_core.h index c56deb9ee5..c716a214ed 100644 --- a/core-tests/include/test_core.h +++ b/core-tests/include/test_core.h @@ -1,7 +1,7 @@ #pragma once -#include "swoole_api.h" #include "swoole_client.h" +#include "swoole_socket_impl.h" #include @@ -16,21 +16,117 @@ #include #include - #define TEST_HOST "127.0.0.1" +#define TEST_HOST6 "::1" #define TEST_PORT 9501 #define TEST_TMP_FILE "/tmp/swoole_core_test_file" -#define TEST_TMP_DIR "/tmp/swoole_core_test_dir" +#define TEST_TMP_DIR "/tmp/swoole_core_test_dir" #define TEST_JPG_FILE "/examples/test.jpg" -#define TEST_JPG_MD5SUM "64a42b4c0f3c65a14c23b60d3880a917" -#define TEST_HTTP_PROXY_PORT 8888 +#define TEST_JPG_MD5SUM "64a42b4c0f3c65a14c23b60d3880a917" + #define TEST_HTTP_PROXY_HOST "127.0.0.1" +#define TEST_HTTP_PROXY_PORT 8888 +#define TEST_HTTP_PROXY_USER "user" +#define TEST_HTTP_PROXY_PASSWORD "password" + +#define TEST_SOCKS5_PROXY_HOST "127.0.0.1" +#define TEST_SOCKS5_PROXY_PORT 8080 +#define TEST_SOCKS5_PROXY_NO_AUTH_PORT 8081 +#define TEST_SOCKS5_PROXY_USER "user" +#define TEST_SOCKS5_PROXY_PASSWORD "password" + +#define TEST_DOMAIN_BAIDU "www.baidu.com" + +#define TEST_HTTP_DOMAIN "www.gov.cn" +#define TEST_HTTP_EXPECT "Location: https://www.gov.cn/" +#define TEST_HTTPS_EXPECT "中国政府网" + +#define TEST_STR "hello world, hello swoole\n" +#define TEST_STR2 "I am Rango\n" + +#define TEST_LOG_FILE "/tmp/swoole.log" +#define TEST_SOCK_FILE "/tmp/swoole-core-tests.sock" + +#define TEST_WRITEV_OFFSET 87 +#define TEST_READV_OFFSET 1949 + +#define TEST_COUNTER_NUM 32 + +#define TEST_REQUEST_BAIDU \ + "GET / HTTP/1.1\r\n" \ + "Host: www.baidu.com\r\n" \ + "Connection: close\r\n" \ + "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " \ + "Chrome/51.0.2704.106 Safari/537.36" \ + "\r\n\r\n" -#define ASSERT_MEMEQ(x,y,n) ASSERT_EQ(memcmp((x), (y), n), 0) -#define EXPECT_MEMEQ(x,y,n) EXPECT_EQ(memcmp((x), (y), n), 0) +#define CRLF "\r\n" +#define EOF_PACKET "hello world" CRLF +#define EOF_PACKET_2 "php&swoole, java&golang" CRLF +#define RECV_TIMEOUT 10.0 -namespace swoole { namespace test { +#define ASSERT_MEMEQ(x, y, n) ASSERT_EQ(memcmp((x), (y), n), 0) +#define EXPECT_MEMEQ(x, y, n) EXPECT_EQ(memcmp((x), (y), n), 0) +#define ASSERT_ERREQ(x) ASSERT_EQ(swoole_get_last_error(), x) +#define EXPECT_ERREQ(x) EXPECT_EQ(swoole_get_last_error(), x) + +#define TEST_WRITE(fd, s) ASSERT_EQ(write(fd, s, strlen(s)), strlen(s)) + +#define TIMER_PARAMS swoole::Timer *timer, swoole::TimerNode *tnode + +#ifdef SW_VERBOSE +#define DEBUG() swoole::test::debug_output.get() +#define debug_info printf +#else +#define DEBUG() swoole::test::null_stream +#define debug_info(...) +#endif + +#ifdef __ANDROID__ +#define sysv_signal signal +#endif + +namespace swoole { +struct HttpProxy; +struct Socks5Proxy; +namespace test { +class NullStream : public std::ostream { + public: + NullStream() : std::ostream(nullptr) {} +}; + +extern NullStream null_stream; +extern std::reference_wrapper debug_output; const std::string &get_root_path(); +std::string get_ssl_dir(); std::string get_jpg_file(); bool is_github_ci(); -}}; +int exec_js_script(const std::string &file, const std::string &args); +std::string http_get_request(const std::string &domain, const std::string &path); +int get_random_port(); +int has_threads(); +int has_child_processes(); +int wait_all_child_processes(bool verbose = false); +bool is_valid_fd(int fd); + +pid_t spawn_exec(const std::function &fn); +int spawn_exec_and_wait(const std::function &fn); + +void counter_init(); +int *counter_ptr(); +int counter_incr(int index, int add = 1); +int counter_get(int index); +void counter_set(int index, int value); +void counter_incr_and_put_log(int index, const char *msg); + +int dump_cert_info(const char *data, size_t len); +int recursive_rmdir(const char *path); + +std::pair, std::shared_ptr> create_socket_pair(); + +static inline int dump_cert_info(const String *str) { + return dump_cert_info(str->str, str->length); +} + +} // namespace test +}; // namespace swoole diff --git a/core-tests/include/test_coroutine.h b/core-tests/include/test_coroutine.h index baf6a4933b..cae90c62b0 100644 --- a/core-tests/include/test_coroutine.h +++ b/core-tests/include/test_coroutine.h @@ -6,70 +6,68 @@ #include "swoole_coroutine_channel.h" #include "swoole_coroutine_system.h" #include "swoole_coroutine_socket.h" -#include "swoole_coroutine_c_api.h" +#include "swoole_coroutine_api.h" -namespace swoole { namespace test { +namespace swoole { +namespace test { -class coroutine -{ -public: - coroutine(const CoroutineFunc &_fn, void *_arg, int *_complete_num) : - fn(_fn), arg(_arg), complete_num(_complete_num) { } +class coroutine { + public: + coroutine(const CoroutineFunc &_fn, void *_arg, int *_complete_num) + : fn(_fn), arg(_arg), complete_num(_complete_num) {} - void start() - { + void start() { fn(arg); (*complete_num)++; } - inline static void create(const CoroutineFunc &fn, void *arg, int *complete_num) - { + inline static void create(const CoroutineFunc &fn, void *arg, int *complete_num) { auto test = new coroutine(fn, arg, complete_num); - long cid = swoole::Coroutine::create([](void *arg) - { - ((coroutine *) arg)->start(); - delete (coroutine *) arg; - }, test); + long cid = swoole::Coroutine::create( + [](void *arg) { + ((coroutine *) arg)->start(); + delete (coroutine *) arg; + }, + test); ASSERT_GT(cid, 0); } - inline static void run(std::initializer_list> args) - { + inline static void run(std::initializer_list> args) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - for (const auto &arg : args) - { + Coroutine::activate(); + for (const auto &arg : args) { create(arg.first, arg.second, &complete_num); } swoole_event_wait(); + Coroutine::deactivate(); } - inline static void run(std::initializer_list fns) - { + inline static void run(std::initializer_list fns) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - for (const auto &fn : fns) - { + Coroutine::activate(); + for (const auto &fn : fns) { create(fn, nullptr, &complete_num); } swoole_event_wait(); + Coroutine::deactivate(); } - inline static void run(const CoroutineFunc &fn, void *arg = nullptr) - { + inline static void run(const CoroutineFunc &fn, void *arg = nullptr) { int complete_num = 0; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + Coroutine::activate(); create(fn, arg, &complete_num); swoole_event_wait(); + Coroutine::deactivate(); } -private: + private: CoroutineFunc fn; void *arg; int *complete_num; }; -} -} - - +} // namespace test +} // namespace swoole diff --git a/core-tests/include/test_server.h b/core-tests/include/test_server.h index bd1234f250..4c54474b33 100644 --- a/core-tests/include/test_server.h +++ b/core-tests/include/test_server.h @@ -5,29 +5,16 @@ #define SERVER_THIS ((swoole::test::Server *) serv->private_data_2) -#define ON_WORKERSTART_PARAMS swServer *serv, int worker_id -#define ON_PACKET_PARAMS swServer *serv, swRecvData *req -#define ON_RECEIVE_PARAMS swServer *serv, swRecvData *req +#define ON_START_PARAMS swoole::Server *serv +#define ON_WORKER_START_PARAMS swoole::Server *serv, swoole::Worker *worker +#define ON_PACKET_PARAMS swoole::Server *serv, swoole::RecvData *req +#define ON_RECEIVE_PARAMS swoole::Server *serv, swoole::RecvData *req -typedef void (*_onStart)(swServer *serv); -typedef void (*_onShutdown)(swServer *serv); -typedef void (*_onPipeMessage)(swServer *, swEventData *data); -typedef void (*_onWorkerStart)(swServer *serv, int worker_id); -typedef void (*_onWorkerStop)(swServer *serv, int worker_id); -typedef int (*_onReceive)(swServer *, swRecvData *); -typedef int (*_onPacket)(swServer *, swRecvData *); -typedef void (*_onClose)(swServer *serv, swDataHead *); -typedef void (*_onConnect)(swServer *serv, swDataHead *); - -using on_workerstart_lambda_type = void (*)(ON_WORKERSTART_PARAMS); -using on_receive_lambda_type = void (*)(ON_RECEIVE_PARAMS); -using on_packet_lambda_type = void (*)(ON_PACKET_PARAMS); - -namespace swoole { namespace test { +namespace swoole { +namespace test { //-------------------------------------------------------------------------------------------------------- -class Server -{ -private: +class Server { + private: swoole::Server serv; std::vector ports; std::unordered_map private_data; @@ -36,35 +23,40 @@ class Server int mode; int type; -public: + std::string tolower(const std::string &str); + + public: DgramPacket *packet = nullptr; Server(std::string _host, int _port, swoole::Server::Mode _mode, int _type); ~Server(); - void on(std::string event, void *fn); + + void on(const std::string &event, const std::function &fn); + void on(const std::string &event, const std::function &fn); + void on(const std::string &event, const std::function &fn); + void on(const std::string &event, const std::function &fn); + void on(const std::string &event, const std::function &fn); + void on(const std::string &event, const std::function &fn); + bool start(); - bool listen(std::string host, int port, enum swSocketType type); + bool listen(const std::string &host, int port, enum swSocketType type); int send(int session_id, const void *data, uint32_t length); ssize_t sendto(const swoole::network::Address &address, const char *__buf, size_t __n, int server_socket = -1); int close(int session_id, int reset); - inline void* get_private_data(const std::string &key) - { + inline void *get_private_data(const std::string &key) { auto it = private_data.find(key); - if (it == private_data.end()) - { + if (it == private_data.end()) { return nullptr; - } - else - { + } else { return it->second; } } - inline void set_private_data(const std::string &key, void *data) - { + inline void set_private_data(const std::string &key, void *data) { private_data[key] = data; } }; //-------------------------------------------------------------------------------------------------------- -}} +} // namespace test +} // namespace swoole diff --git a/core-tests/js/.gitignore b/core-tests/js/.gitignore new file mode 100644 index 0000000000..d5f19d89b3 --- /dev/null +++ b/core-tests/js/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json diff --git a/core-tests/js/mqtt.js b/core-tests/js/mqtt.js new file mode 100644 index 0000000000..82d5acf975 --- /dev/null +++ b/core-tests/js/mqtt.js @@ -0,0 +1,33 @@ +const mqtt = require('mqtt'); +const port = process.argv[2]; +const pino = require('pino'); +const logger = pino(pino.destination('/tmp/swoole.log')); + +const client = mqtt.connect(`mqtt://localhost:${port}`); + +client.on('connect', () => { + logger.info('the client is connected'); + + client.subscribe('test/topic', (err) => { + if (err) { + console.error('subscribe fail:', err); + return; + } + logger.info('subscribed: test/topic'); + + client.publish('test/topic', 'Hello MQTT from Node.js!'); + }); +}); + +client.on('disconnect', () => { + logger.info('the client is disconnected'); + client.end() +}) + +client.on('message', (topic, message) => { + logger.info(`received message, topic: ${topic}, content: ${message.toString()}`); +}); + +client.on('error', (err) => { + console.error('error:', err); +}); diff --git a/core-tests/js/package.json b/core-tests/js/package.json new file mode 100644 index 0000000000..2dfe706777 --- /dev/null +++ b/core-tests/js/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "mqtt": "^4.3.8", + "pino": "^6.14.0", + "ws": "^8.18.2" + } +} diff --git a/core-tests/js/ws_1.js b/core-tests/js/ws_1.js new file mode 100644 index 0000000000..dea85165ea --- /dev/null +++ b/core-tests/js/ws_1.js @@ -0,0 +1,34 @@ +const WebSocket = require('ws'); +const pino = require('pino'); +const port = process.argv[2]; +const logger = pino(pino.destination('/tmp/swoole.log')); + +const ws_1 = new WebSocket(`ws://127.0.0.1:${port}/`); + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +ws_1.on('error', console.error); + +ws_1.on('close', function () { + logger.info('the node websocket client is closed'); +}) + +ws_1.on('open', async () => { + ws_1.send('hello', {fin: false}); + await delay(50); + ws_1.send(' ', {fin: false}); + await delay(50); + ws_1.send('world', {fin: true}); + + await delay(200); + ws_1.ping("keep alive") + + await delay(200); + ws_1.close() +}); + +ws_1.on('message', function message(data) { + logger.info('received: ' + data); +}); diff --git a/core-tests/js/ws_2.js b/core-tests/js/ws_2.js new file mode 100644 index 0000000000..9630dee782 --- /dev/null +++ b/core-tests/js/ws_2.js @@ -0,0 +1,22 @@ +const WebSocket = require('ws'); +const pino = require('pino'); +const port = process.argv[2]; +const logger = pino(pino.destination('/tmp/swoole.log')); + +const ws = new WebSocket(`ws://127.0.0.1:${port}/ws/close`); + +function delay(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +ws.on('error', console.error); + +ws.on('close', function (code, reason) { + logger.info('the node websocket client is closed, code: ' + code + ', reason: ' + reason.toString()); +}) + +ws.on('open', async () => { +}); + +ws.on('message', function message(data) { +}); diff --git a/core-tests/run.sh b/core-tests/run.sh deleted file mode 100755 index 7c9dd0eb82..0000000000 --- a/core-tests/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -cmake . -make -j8 -ipcs -q -./bin/core_tests diff --git a/core-tests/samples/src/s1.cc b/core-tests/samples/src/s1.cc index e8865cc48c..45279e7c40 100644 --- a/core-tests/samples/src/s1.cc +++ b/core-tests/samples/src/s1.cc @@ -1,17 +1,14 @@ #include "swoole.h" -#include "swoole_api.h" #include "swoole_client.h" #include "swoole_server.h" #include "swoole_coroutine.h" -#include "swoole_coroutine_socket.h" +#include "swoole_socket_impl.h" #include "swoole_coroutine_system.h" #include using swoole::Coroutine; -using swoole::coroutine::Socket; using swoole::coroutine::System; -using namespace std; struct A { int x; @@ -32,9 +29,9 @@ int main(int argc, char **argv) { // no longer as expected System::sleep(1); // output 100 - cout << "X=" << G_a.x << endl; + std::cout << "X=" << G_a.x << std::endl; // read invalid point - cout << "Y=" << *G_a.y << endl; + std::cout << "Y=" << *G_a.y << std::endl; }); // coroutine 2 diff --git a/core-tests/src/_lib/http.cpp b/core-tests/src/_lib/http.cpp index 3f6f3fe139..748f62e878 100644 --- a/core-tests/src/_lib/http.cpp +++ b/core-tests/src/_lib/http.cpp @@ -4,6 +4,9 @@ namespace websocket = swoole::websocket; +using swoole::Protocol; +using swoole::String; + namespace httplib { bool Client::Upgrade(const char *_path, Headers &_headers) { @@ -26,22 +29,36 @@ bool Client::Push(const char *data, size_t length, int opcode) { return false; } return process_socket(socket_, [&](Stream &strm) { - swString buffer = {}; + String buffer = {}; char buf[32]; buffer.size = sizeof(buf); buffer.str = buf; - websocket::encode(&buffer, data, length, opcode, websocket::FLAG_FIN | websocket::FLAG_ENCODE_HEADER_ONLY); - strm.write(buffer.str, buffer.length); - strm.write(data, length); - return true; + auto flags = websocket::FLAG_FIN | websocket::FLAG_ENCODE_HEADER_ONLY; + if (websocket_mask_) { + flags |= websocket::FLAG_MASK; + } + + websocket::encode(&buffer, data, length, opcode, flags); + if (strm.write(buffer.str, buffer.length) != (ssize_t) buffer.length) { + return false; + } + + if (websocket_mask_) { + std::unique_ptr marked = std::make_unique(length + 1); + memcpy(marked.get(), data, length); + websocket::mask(marked.get(), length, buffer.str + buffer.length - SW_WEBSOCKET_MASK_LEN); + return strm.write(marked.get(), length) == (ssize_t) length; + } else { + return strm.write(data, length) == (ssize_t) length; + } }); } std::shared_ptr Client::Recv() { auto msg = std::make_shared(); auto retval = process_socket(socket_, [&](Stream &strm) { - swProtocol proto = {}; + Protocol proto = {}; proto.package_length_size = SW_WEBSOCKET_HEADER_LEN; proto.get_package_length = websocket::get_package_length; proto.package_max_length = SW_INPUT_BUFFER_SIZE; @@ -52,7 +69,7 @@ std::shared_ptr Client::Recv() { if (strm.read(buf, SW_WEBSOCKET_HEADER_LEN) <= 0) { return false; } - swoole::PacketLength pl { + swoole::PacketLength pl{ buf, SW_WEBSOCKET_HEADER_LEN, }; @@ -156,7 +173,7 @@ void Client::close_socket(Socket &socket, bool /*process_socket_ret*/) { } bool Client::read_response_line(Stream &strm, Response &res) { - std::array buf; + std::array buf{}; detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); @@ -907,6 +924,10 @@ void Client::set_proxy_basic_auth(const char *username, const char *password) { proxy_basic_auth_password_ = password; } +void Client::set_websocket_mask(bool on) { + websocket_mask_ = on; +} + #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void Client::set_proxy_digest_auth(const char *username, const char *password) { proxy_digest_auth_username_ = username; diff --git a/core-tests/src/_lib/process.cpp b/core-tests/src/_lib/process.cpp index f81b4fec29..6d971985b6 100644 --- a/core-tests/src/_lib/process.cpp +++ b/core-tests/src/_lib/process.cpp @@ -1,7 +1,7 @@ #include "test_process.h" -using swoole::test::Process; using swoole::UnixSocket; +using swoole::test::Process; Process::Process(std::function fn, int pipe_type) : handler(fn) { if (pipe_type > 0) { diff --git a/core-tests/src/_lib/redis.cpp b/core-tests/src/_lib/redis.cpp index b64a6794fa..45b74d283d 100644 --- a/core-tests/src/_lib/redis.cpp +++ b/core-tests/src/_lib/redis.cpp @@ -35,6 +35,43 @@ string RedisClient::Get(const string &key) { } } +long RedisClient::Ttl(const std::string &key) { + const char *argv[] = {"TTL", key.c_str()}; + size_t argvlen[] = {strlen(argv[0]), key.length()}; + + auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); + if (!reply.empty() && reply->integer) { + return reply->integer; + } else { + return 0; + } +} + +bool RedisClient::Select(int db) { + auto _db = std::to_string(db); + const char *argv[] = {"SELECT", _db.c_str()}; + size_t argvlen[] = {strlen(argv[0]), _db.length()}; + + auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); + if (!reply.empty() && reply->type == REDIS_REPLY_STATUS && strncmp(reply->str, "OK", 2) == 0) { + return true; + } else { + return false; + } +} + +std::string RedisClient::Role() { + const char *argv[] = {"ROLE"}; + size_t argvlen[] = {strlen(argv[0])}; + + auto reply = Request(SW_ARRAY_SIZE(argv), argv, argvlen); + if (!reply.empty() && reply->str) { + return string(reply->str, reply->len); + } else { + return ""; + } +} + bool RedisClient::Set(const string &key, const string &value) { const char *argv[] = {"SET", key.c_str(), value.c_str()}; size_t argvlen[] = {strlen(argv[0]), key.length(), value.length()}; diff --git a/core-tests/src/_lib/server.cpp b/core-tests/src/_lib/server.cpp index dac4afe909..ad52841bbe 100644 --- a/core-tests/src/_lib/server.cpp +++ b/core-tests/src/_lib/server.cpp @@ -21,6 +21,9 @@ #include "test_server.h" #include "swoole_memory.h" +#include +#include + using namespace swoole::test; using swoole::network::Address; @@ -37,37 +40,73 @@ Server::Server(std::string _host, int _port, swoole::Server::Mode _mode, int _ty serv.private_data_2 = this; if (!listen(host, port, (swSocketType) type)) { - swoole_warning("listen(%s:%d) fail[error=%d].", host.c_str(), port, errno); + swoole_sys_warning("listen(%s:%d) failed", host.c_str(), port); exit(0); } if (serv.create() < 0) { - swoole_warning("create server fail[error=%d].", errno); + swoole_sys_warning("create server failed"); exit(0); } } Server::~Server() {} -void Server::on(std::string event, void *fn) { - if (event == "Start") { - serv.onStart = (_onStart) fn; - } else if (event == "onShutdown") { - serv.onShutdown = (_onShutdown) fn; - } else if (event == "onPipeMessage") { - serv.onPipeMessage = (_onPipeMessage) fn; - } else if (event == "onWorkerStart") { - serv.onWorkerStart = (_onWorkerStart) fn; - } else if (event == "onWorkerStop") { - serv.onWorkerStop = (_onWorkerStop) fn; - } else if (event == "onReceive") { - serv.onReceive = (_onReceive) fn; - } else if (event == "onPacket") { - serv.onPacket = (_onPacket) fn; - } else if (event == "onClose") { - serv.onClose = (_onClose) fn; - } else { - serv.onConnect = (_onConnect) fn; +std::string Server::tolower(const std::string &str) { + std::string str_copy = str; + std::transform(str_copy.begin(), str_copy.end(), str_copy.begin(), [](unsigned char c) { return std::tolower(c); }); + return str_copy; +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "workerstart") { + serv.onWorkerStart = fn; + } else if (event == "workerstop") { + serv.onWorkerStop = fn; + } +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "start") { + serv.onStart = fn; + } else if (event == "shutdown") { + serv.onShutdown = fn; + } +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "pipemessage") { + serv.onPipeMessage = fn; + } +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "task") { + serv.onTask = fn; + } else if (event == "finish") { + serv.onFinish = fn; + } +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "packet") { + serv.onPacket = fn; + } else if (event == "receive") { + serv.onReceive = fn; + } +} + +void Server::on(const std::string &_event, const std::function &fn) { + auto event = tolower(_event); + if (event == "connect") { + serv.onConnect = fn; + } else if (event == "close") { + serv.onClose = fn; } } @@ -75,7 +114,7 @@ bool Server::start() { return serv.start() == 0; } -bool Server::listen(std::string host, int port, enum swSocketType type) { +bool Server::listen(const std::string &host, int port, enum swSocketType type) { ListenPort *ls = serv.add_port(type, (char *) host.c_str(), port); if (ls == nullptr) { return false; diff --git a/core-tests/src/_lib/ssl.cpp b/core-tests/src/_lib/ssl.cpp new file mode 100644 index 0000000000..21943a4b02 --- /dev/null +++ b/core-tests/src/_lib/ssl.cpp @@ -0,0 +1,94 @@ +#include "test_core.h" + +namespace swoole { +namespace test { +void printAllSubjectEntries(X509_NAME *name) { + if (!name) return; + + int entry_count = X509_NAME_entry_count(name); + + for (int i = 0; i < entry_count; ++i) { + X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, i); + ASN1_OBJECT *obj = X509_NAME_ENTRY_get_object(entry); + ASN1_STRING *data = X509_NAME_ENTRY_get_data(entry); + + // 获取字段的短名称(如 CN、O、ST 等) + char obj_txt[80] = {0}; + OBJ_obj2txt(obj_txt, sizeof(obj_txt), obj, 0); + + // 获取字段的值 + unsigned char *utf8 = nullptr; + int length = ASN1_STRING_to_UTF8(&utf8, data); + if (length >= 0 && utf8) { + sw_printf("%s: %.*s\n", obj_txt, length, utf8); + OPENSSL_free(utf8); + } + } +} + +void printX509Info(X509 *cert) { + X509_NAME *subject_name = X509_get_subject_name(cert); + printAllSubjectEntries(subject_name); + + char *subject = X509_NAME_oneline(subject_name, 0, 0); + if (subject) { + sw_printf("Peer certificate subject: %s\n", subject); + OPENSSL_free(subject); + } + + X509_NAME *issuer_name = X509_get_issuer_name(cert); + printAllSubjectEntries(issuer_name); + + // 获取证书有效期 + ASN1_TIME *not_before = X509_get_notBefore(cert); + ASN1_TIME *not_after = X509_get_notAfter(cert); + + BIO *bio = BIO_new(BIO_s_mem()); + ASN1_TIME_print(bio, not_before); + char buf[256] = {0}; + int len = BIO_read(bio, buf, sizeof(buf) - 1); + buf[len] = 0; + sw_printf("Validity Not Before: %s\n", buf); + + ASN1_TIME_print(bio, not_after); + len = BIO_read(bio, buf, sizeof(buf) - 1); + buf[len] = 0; + sw_printf("Validity Not After: %s\n", buf); + + BIO_free(bio); + + // 获取公钥 + EVP_PKEY *pubkey = X509_get_pubkey(cert); + if (pubkey) { + sw_printf("Public key type: %d\n", EVP_PKEY_id(pubkey)); + EVP_PKEY_free(pubkey); + } +} + +int dump_cert_info(const char *data, size_t len) { + BIO *bio = BIO_new_mem_buf(data, (int) len); + if (!bio) { + std::cerr << "Failed to create BIO" << std::endl; + return 1; + } + + // 从 BIO 中读取证书 + X509 *cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr); + if (!cert) { + std::cerr << "Failed to parse X509 certificate" << std::endl; + BIO_free(bio); + return 1; + } + + // 打印证书信息 + printX509Info(cert); + + // 释放资源 + X509_free(cert); + BIO_free(bio); + EVP_cleanup(); + + return 0; +} +} // namespace test +} // namespace swoole diff --git a/core-tests/src/core/base.cpp b/core-tests/src/core/base.cpp index 1c9528d882..12b72310ce 100644 --- a/core-tests/src/core/base.cpp +++ b/core-tests/src/core/base.cpp @@ -24,6 +24,10 @@ #include "swoole.h" #include "swoole_config.h" +#include "swoole_api.h" + +#include + using namespace swoole; using namespace std; @@ -61,6 +65,14 @@ TEST(base, random_string) { ASSERT_EQ(strlen(buf), sizeof(buf) - 1); } +static size_t test_sw_vsnprintf(char *buf, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + size_t result = sw_vsnprintf(buf, size, format, args); + va_end(args); + return result; +} + TEST(base, file_put_contents) { char buf[65536]; swoole_random_string(buf, sizeof(buf) - 1); @@ -76,7 +88,7 @@ TEST(base, file_get_size) { ASSERT_TRUE(f.ready()); f.truncate(0); - f.set_offest(0); + f.set_offset(0); f.write(buf, sizeof(buf) - 1); f.close(); @@ -185,23 +197,31 @@ TEST(base, dirname) { ASSERT_EQ(dirname("/"), "/"); } +TEST(base, mkdir_recursive) { + String dir(PATH_MAX + 2); + dir.append_random_bytes(PATH_MAX, true); + ASSERT_FALSE(swoole_mkdir_recursive(dir.to_std_string())); +} + TEST(base, set_task_tmpdir) { + auto ori_tmpdir = swoole_get_task_tmpdir(); ASSERT_FALSE(swoole_set_task_tmpdir("aaa")); size_t length = SW_TASK_TMP_PATH_SIZE + 1; - char too_long_dir[length] = {}; + char too_long_dir[SW_TASK_TMP_PATH_SIZE + 2] = {}; swoole_random_string(too_long_dir + 1, length - 1); too_long_dir[0] = '/'; ASSERT_FALSE(swoole_set_task_tmpdir(too_long_dir)); const char *tmpdir = "/tmp/swoole/core_tests/base"; ASSERT_TRUE(swoole_set_task_tmpdir(tmpdir)); - File fp = swoole::make_tmpfile(); + File fp = make_tmpfile(); ASSERT_TRUE(fp.ready()); char buf[128]; swoole_random_string(buf, sizeof(buf) - 2); buf[sizeof(buf) - 2] = '\n'; + buf[sizeof(buf) - 1] = '\0'; fp.write(buf, sizeof(buf) - 1); fp.close(); @@ -211,6 +231,20 @@ TEST(base, set_task_tmpdir) { unlink(fp.get_path().c_str()); rmdir(tmpdir); + + char buf2[264]; + swoole_random_string(buf2, sizeof(buf2) - 1); + memcpy(buf2, "/tmp/", 5); + buf2[64] = '/'; + buf2[128] = '/'; + buf2[192] = '/'; + buf2[256] = '/'; + std::string dir(buf2); + ASSERT_FALSE(swoole_set_task_tmpdir(dir)); + + test::recursive_rmdir(dir.c_str()); + + ASSERT_TRUE(swoole_set_task_tmpdir(ori_tmpdir)); } TEST(base, version) { @@ -219,21 +253,6 @@ TEST(base, version) { ASSERT_EQ(swoole_api_version_id(), SWOOLE_API_VERSION_ID); } -static std::string test_func(std::string test_data_2) { - return test_data + test_data_2; -} - -TEST(base, add_function) { - typedef std::string (*_func_t)(std::string); - swoole_add_function("test_func", (void *) test_func); - ASSERT_EQ(swoole_add_function("test_func", (void *) test_func), SW_ERR); - _func_t _func = (_func_t) swoole_get_function(SW_STRL("test_func")); - std::string b = ", swoole is best"; - auto rs = _func(", swoole is best"); - ASSERT_EQ(rs, test_data + b); - ASSERT_EQ(swoole_get_function(SW_STRL("test_func31")), nullptr); -} - TEST(base, hook) { int count = 0; swoole_add_hook( @@ -279,7 +298,9 @@ TEST(base, type_size) { ASSERT_EQ(swoole_type_size('c'), 1); ASSERT_EQ(swoole_type_size('s'), 2); ASSERT_EQ(swoole_type_size('l'), 4); - ASSERT_EQ(swoole_type_size('b'), 0); // default value + ASSERT_EQ(swoole_type_size('b'), 0); + ASSERT_EQ(swoole_type_size('q'), 8); + ASSERT_EQ(swoole_type_size('P'), 8); } size_t swoole_fatal_error_impl(const char *format, ...) { @@ -295,6 +316,38 @@ size_t swoole_fatal_error_impl(const char *format, ...) { TEST(base, vsnprintf) { ASSERT_GT(swoole_fatal_error_impl("Hello %s", "World!!!"), 0); + + char buffer[10]; + { + // The 9th byte will be set to \ 0, discarding one character + size_t result = test_sw_vsnprintf(buffer, 9, "Test %d", 1234); + EXPECT_STREQ(buffer, "Test 123"); + EXPECT_EQ(result, 8); + } + + { + size_t result = test_sw_vsnprintf(buffer, sizeof(buffer), "Test %d is too long", 12345); + EXPECT_EQ(buffer[sizeof(buffer) - 1], '\0'); + EXPECT_EQ(result, sizeof(buffer) - 1); + EXPECT_STREQ(buffer, "Test 1234"); + } +} + +TEST(base, snprintf) { + char buffer[10]; + { + // The 9th byte will be set to \ 0, discarding one character + size_t result = sw_snprintf(buffer, 9, "Test %d", 1234); + EXPECT_STREQ(buffer, "Test 123"); + EXPECT_EQ(result, 8); + } + + { + size_t result = sw_snprintf(buffer, sizeof(buffer), "Test %d is too long", 12345); + EXPECT_EQ(buffer[sizeof(buffer) - 1], '\0'); + EXPECT_EQ(result, sizeof(buffer) - 1); + EXPECT_STREQ(buffer, "Test 1234"); + } } TEST(base, log_level) { @@ -316,7 +369,196 @@ TEST(base, only_dump) { // just dump something std::string data = "hello world"; swoole_dump_ascii(data.c_str(), data.length()); - swoole_dump_bin(data.c_str(), 'C', data.length()); - swoole_dump_hex(data.c_str(), data.length()); + swoole_dump_bin((uchar *) data.c_str(), 'C', data.length()); + swoole_dump_hex((uchar *) data.c_str(), data.length()); ASSERT_TRUE(true); } + +TEST(base, redirect_stdout) { + auto file = TEST_LOG_FILE; + auto out_1 = "hello world, hello swoole!\n"; + auto out_2 = "write to /dev/null\n"; + auto status = test::spawn_exec_and_wait([&]() { + swoole_redirect_stdout(file); + printf("%s\n", out_1); + fflush(stdout); + + swoole_redirect_stdout("/dev/null"); + printf("%s\n", out_2); + fflush(stdout); + + swoole_clear_last_error(); + swoole_redirect_stdout("/tmp/not-exists/test.log"); + ASSERT_ERREQ(ENOTDIR); + }); + ASSERT_EQ(status, 0); + + auto rs = swoole::file_get_contents(file); + ASSERT_NE(rs, nullptr); + ASSERT_TRUE(rs->contains(out_1)); + ASSERT_FALSE(rs->contains(out_2)); + unlink(file); +} + +TEST(base, fatal_error) { + const char *msg = "core tests fatal error"; + auto status = test::spawn_exec_and_wait([msg]() { + swoole_set_log_file(TEST_LOG_FILE); + swoole_fatal_error(9999, msg); + }); + ASSERT_EQ(WEXITSTATUS(status), 1); + + auto rs = file_get_contents(TEST_LOG_FILE); + ASSERT_NE(rs, nullptr); + ASSERT_TRUE(rs->contains(msg)); + ASSERT_TRUE(rs->contains("(ERROR 9999)")); + File::remove(TEST_LOG_FILE); +} + +TEST(base, spinlock) { + test::counter_init(); + auto counter = test::counter_ptr(); + int n = 4096; + + auto test_fn = [counter, n]() { + SW_LOOP_N(n) { + sw_spinlock((sw_atomic_t *) &counter[0]); + counter[1]++; + if (i % 100 == 0) { + usleep(5); + } + sw_spinlock_release((sw_atomic_t *) &counter[0]); + } + }; + + std::thread t1(test_fn); + std::thread t2(test_fn); + + t1.join(); + t2.join(); + + ASSERT_EQ(counter[1], n * 2); +} + +TEST(base, futex) { + sw_atomic_t value = 1; + + std::thread t1([&value] { + DEBUG() << "wait 1\n"; + ASSERT_EQ(sw_atomic_futex_wait(&value, -1), SW_OK); // no wait + value = 0; + + DEBUG() << "wait 2\n"; + + ASSERT_EQ(sw_atomic_futex_wait(&value, 0.05), SW_ERR); // timed out + ASSERT_EQ(sw_atomic_futex_wait(&value, 0.5), SW_OK); // success + + DEBUG() << "wait 3\n"; + + value = 0; + ASSERT_EQ(sw_atomic_futex_wait(&value, -1), SW_OK); // no timeout + }); + + std::thread t2([&value] { + usleep(100000); + DEBUG() << "wakeup 1\n"; + ASSERT_EQ(sw_atomic_futex_wakeup(&value, 1), 1); + + DEBUG() << "wakeup 2\n"; + usleep(100000); + ASSERT_EQ(sw_atomic_futex_wakeup(&value, 1), 1); + }); + + t1.join(); + t2.join(); +} + +static int test_fork_fail(const std::function &after_fork_fail = nullptr) { + rlimit rl{}; + rlim_t ori_nproc_max; + int count = 0; + rlim_t nproc_max = 32; + + // 获取当前 NPROC 限制 + if (getrlimit(RLIMIT_NPROC, &rl) != 0) { + perror("getrlimit failed"); + return 1; + } + + printf("Current NPROC limit: soft=%lu, hard=%lu\n", rl.rlim_cur, rl.rlim_max); + + ori_nproc_max = rl.rlim_max; + rl.rlim_cur = nproc_max; + if (setrlimit(RLIMIT_NPROC, &rl) != 0) { + perror("setrlimit failed"); + return 1; + } + + printf("New NPROC limit: soft=%lu\n", rl.rlim_cur); + + std::vector children; + + // 循环创建子进程直到失败 + while (true) { + pid_t pid = fork(); + if (pid < 0) { + // fork 失败 + printf("fork() failed after %d processes: %s\n", count, strerror(errno)); + break; + } else if (pid == 0) { + sleep(30); + exit(0); + } else { + // 父进程 + count++; + children.push_back(pid); + printf("Created child process #%d (PID: %d)\n", count, pid); + } + } + + if (after_fork_fail) { + after_fork_fail(); + } + + printf("Cleaning up child processes...\n"); + for (const int i : children) { + kill(i, SIGKILL); + } + test::wait_all_child_processes(); + + rl.rlim_cur = ori_nproc_max; + // 恢复 NPROC 限制 + if (setrlimit(RLIMIT_NPROC, &rl) != 0) { + perror("setrlimit failed"); + return 1; + } + + return 0; +} + +#if 0 +TEST(base, fork_fail) { + auto status = test::spawn_exec_and_wait([]() { + if (geteuid() == 0) { + Server::worker_set_isolation("nobody", "nobody", ""); + } + ASSERT_EQ(test_fork_fail([]() { + pid_t pid; + auto pipe_fd = swoole_shell_exec("sleep 10", &pid, 0); + ASSERT_EQ(pipe_fd, -1); + }), + 0); + ASSERT_ERREQ(EAGAIN); + }); + + ASSERT_EQ(status, 0); +} +#endif + +TEST(base, undefined_behavior) { + swoole_init(); // no effect + delete SwooleG.logger; + SwooleG.logger = nullptr; // avoid double free in swoole_shutdown() + ASSERT_EQ(swoole_get_log_level(), SW_LOG_NONE); + SwooleG.logger = new Logger(); +} diff --git a/core-tests/src/core/hash.cpp b/core-tests/src/core/hash.cpp index 7d7f1cd5eb..b0d51f6ca9 100644 --- a/core-tests/src/core/hash.cpp +++ b/core-tests/src/core/hash.cpp @@ -20,20 +20,44 @@ #include "test_core.h" #include "swoole_hash.h" +static const int hash_count = 8192; +static const int str_max_len = 1024; static const char *data = "hello world, PHP the best."; TEST(hash, crc32) { ASSERT_EQ(swoole_crc32(data, strlen(data)), 2962796788); } +static void test_hash_func(uint64_t (*hash_fn)(const char *key, size_t len), int n) { + std::vector hashes; + std::vector data; + hashes.resize(n); + data.resize(n); + + SW_LOOP_N(n) { + size_t len = 1 + swoole_random_int() % str_max_len; + char buf[str_max_len]; + ASSERT_EQ(swoole_random_bytes(buf, len), len); + hashes[i] = hash_fn(buf, len); + data[i] = std::string(buf, len); + } + + usleep(100); + + SW_LOOP_N(n) { + auto &s = data.at(i); + ASSERT_EQ(hashes[i], hash_fn(s.c_str(), s.length())); + } +} + TEST(hash, php) { - ASSERT_GT(swoole_hash_php(data, strlen(data)), 0); + test_hash_func(swoole_hash_php, hash_count); } TEST(hash, jenkins) { - ASSERT_GT(swoole_hash_jenkins(data, strlen(data)), 0); + test_hash_func(swoole_hash_jenkins, hash_count); } TEST(hash, austin) { - ASSERT_GT(swoole_hash_austin(data, strlen(data)), 0); + test_hash_func(swoole_hash_austin, hash_count); } diff --git a/core-tests/src/core/heap.cpp b/core-tests/src/core/heap.cpp index 226c983803..62623bc7bd 100644 --- a/core-tests/src/core/heap.cpp +++ b/core-tests/src/core/heap.cpp @@ -26,7 +26,7 @@ TEST(heap, random) { _map[i] = pri; if (0 == i) { - pq.print(); // print once + pq.print(); // print once } } diff --git a/core-tests/src/core/log.cpp b/core-tests/src/core/log.cpp index 63a7d47246..172b367828 100644 --- a/core-tests/src/core/log.cpp +++ b/core-tests/src/core/log.cpp @@ -1,5 +1,6 @@ #include "test_core.h" #include "swoole_file.h" +#include "swoole_process_pool.h" #include #include @@ -8,29 +9,50 @@ using namespace swoole; const char *file = "/tmp/swoole_log_test.log"; TEST(log, level) { - std::vector processTypes = {SW_PROCESS_MASTER, SW_PROCESS_MANAGER, SW_PROCESS_WORKER, SW_PROCESS_TASKWORKER}; + std::vector processTypes = {SW_MASTER, SW_MANAGER, SW_WORKER, SW_TASK_WORKER}; - int originType = swoole_get_process_type(); + int originType = swoole_get_worker_type(); for (auto iter = processTypes.begin(); iter != processTypes.end(); iter++) { - SwooleG.process_type = *iter; + swoole_set_worker_type(*iter); sw_logger()->reset(); + + ASSERT_FALSE(sw_logger()->is_opened()); + + sw_logger()->set_level(999); + ASSERT_EQ(sw_logger()->get_level(), SW_LOG_NONE); + + sw_logger()->set_level(SW_LOG_DEBUG - 10); + ASSERT_EQ(sw_logger()->get_level(), SW_LOG_DEBUG); + sw_logger()->set_level(SW_LOG_NOTICE); sw_logger()->open(file); + ASSERT_TRUE(sw_logger()->is_opened()); + + sw_logger()->put(SW_LOG_DEBUG, SW_STRL("hello no debug")); + sw_logger()->put(SW_LOG_TRACE, SW_STRL("hello no trace")); sw_logger()->put(SW_LOG_INFO, SW_STRL("hello info")); sw_logger()->put(SW_LOG_NOTICE, SW_STRL("hello notice")); sw_logger()->put(SW_LOG_WARNING, SW_STRL("hello warning")); + sw_logger()->set_level(SW_LOG_DEBUG); + sw_logger()->put(SW_LOG_DEBUG, SW_STRL("hello debug")); + sw_logger()->put(SW_LOG_TRACE, SW_STRL("hello trace")); + auto content = file_get_contents(file); sw_logger()->close(); unlink(file); + ASSERT_FALSE(content->contains(SW_STRL("hello no debug"))); + ASSERT_FALSE(content->contains(SW_STRL("hello no trace"))); + ASSERT_TRUE(content->contains(SW_STRL("hello debug"))); + ASSERT_TRUE(content->contains(SW_STRL("hello trace"))); ASSERT_FALSE(content->contains(SW_STRL("hello info"))); ASSERT_TRUE(content->contains(SW_STRL("hello notice"))); ASSERT_TRUE(content->contains(SW_STRL("hello warning"))); - SwooleG.process_type = originType; + swoole_set_worker_type(originType); } } @@ -89,7 +111,7 @@ TEST(log, date_with_microseconds) { sw_logger()->close(); unlink(file); - std::regex e("\\[\\S+\\s\\d{2}:\\d{2}:\\d{2}\\<\\.(\\d+)\\>\\s@\\d+\\.\\d+\\]\tWARNING\thello world"); + std::regex e("\\[\\S+\\s\\d{2}:\\d{2}:\\d{2}\\<\\.(\\d+)\\>\\s%\\d+\\.\\d+\\]\tWARNING\thello world"); ASSERT_TRUE(std::regex_search(content->value(), e)); } @@ -117,28 +139,53 @@ TEST(log, rotation) { } } -TEST(log, redirect) { - char *p = getenv("GITHUB_ACTIONS"); - if (p) { - return; - } - sw_logger()->reset(); - ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(1)); // no log file opened - ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(0)); // no redirected +TEST(log, redirect_1) { + auto status = test::spawn_exec_and_wait([]() { + sw_logger()->reset(); + ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(true)); // no log file opened + ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(false)); // no redirected - ASSERT_TRUE(sw_logger()->open(file)); - ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(1)); - ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(1)); // has been redirected + ASSERT_TRUE(sw_logger()->open(file)); + ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(true)); + ASSERT_FALSE(sw_logger()->redirect_stdout_and_stderr(true)); // has been redirected - printf("hello world\n"); - auto content = file_get_contents(file); - ASSERT_NE(content.get(), nullptr); + printf("hello world\n"); + auto content = file_get_contents(file); + ASSERT_NE(content.get(), nullptr); - sw_logger()->close(); - ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(0)); - unlink(sw_logger()->get_real_file()); + sw_logger()->close(); + ASSERT_TRUE(sw_logger()->redirect_stdout_and_stderr(false)); + unlink(sw_logger()->get_real_file()); + + ASSERT_TRUE(content->contains(SW_STRL("hello world\n"))); + }); - ASSERT_TRUE(content->contains(SW_STRL("hello world\n"))); + ASSERT_EQ(status, 0); +} + +TEST(log, redirect_2) { + auto status = test::spawn_exec_and_wait([]() { + auto file = TEST_LOG_FILE; + auto str = "hello world, hello swoole\n"; + + sw_logger()->reset(); + sw_logger()->open(file); + sw_logger()->redirect_stdout_and_stderr(true); + + printf("%s\n", str); + + File f(file, File::READ); + auto rs = f.read_content(); + + ASSERT_TRUE(rs->contains(str)); + sw_logger()->redirect_stdout_and_stderr(false); + printf("%s\n", str); + + sw_logger()->close(); + unlink(sw_logger()->get_real_file()); + }); + + ASSERT_EQ(status, 0); } namespace TestA { @@ -214,3 +261,27 @@ TEST(log, ignore_error) { ASSERT_FALSE(content->contains(SW_STRL("error 1"))); ASSERT_TRUE(content->contains(SW_STRL("error 2"))); } + +TEST(log, open_fail) { + sw_logger()->reset(); + sw_logger()->set_level(SW_LOG_NOTICE); + sw_logger()->open("/tmp/not-exists/swoole.log"); + sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world\n")); +} + +TEST(log, set_stream) { + sw_logger()->reset(); + char *buffer = NULL; + size_t size = 0; + FILE *stream = open_memstream(&buffer, &size); + + sw_logger()->set_stream(stream); + sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world")); + + sw_logger()->set_stream(stdout); + sw_logger()->put(SW_LOG_ERROR, SW_STRL("hello world")); + + ASSERT_NE(strstr(buffer, "ERROR\thello world"), nullptr); + fclose(stream); + free(buffer); +} diff --git a/core-tests/src/core/string.cpp b/core-tests/src/core/string.cpp index b7087c3d11..4cdbd30eae 100644 --- a/core-tests/src/core/string.cpp +++ b/core-tests/src/core/string.cpp @@ -4,6 +4,31 @@ using namespace std; using swoole::String; +TEST(string, ltrim) { + char buf[1024]; + char *ptr_buf; + strcpy(buf, " hello world"); + ptr_buf = buf; + swoole::ltrim(&ptr_buf, strlen(buf)); + ASSERT_EQ(strcmp("hello world", ptr_buf), 0); + ASSERT_NE(strcmp(" hello world", ptr_buf), 0); + + strcpy(buf, " "); + ptr_buf = buf; + swoole::ltrim(&ptr_buf, strlen(buf)); + ASSERT_EQ(strlen(ptr_buf), 0); + + memcpy(buf, " a\0b\0", 6); + ptr_buf = buf; + swoole::ltrim(&ptr_buf, strlen(buf)); + ASSERT_EQ(strcmp("a", ptr_buf), 0); + + buf[0] = '\0'; + ptr_buf = buf; + swoole::ltrim(&ptr_buf, strlen(buf)); + ASSERT_EQ(strcmp("", ptr_buf), 0); +} + TEST(string, rtrim) { char buf[1024]; strcpy(buf, "hello world "); @@ -14,6 +39,100 @@ TEST(string, rtrim) { strcpy(buf, " "); swoole::rtrim(buf, strlen(buf)); ASSERT_EQ(strlen(buf), 0); + + buf[0] = '\0'; + swoole::rtrim(buf, strlen(buf)); + ASSERT_EQ(strcmp("", buf), 0); +} + +TEST(string, move_and_copy) { + String s1(TEST_STR); + ASSERT_MEMEQ(s1.str, TEST_STR, s1.length); + + String s2(s1); + ASSERT_MEMEQ(s2.str, TEST_STR, s2.length); + ASSERT_NE(s1.str, nullptr); + + String s3(std::move(s1)); + ASSERT_MEMEQ(s3.str, TEST_STR, s3.length); + ASSERT_EQ(s1.str, nullptr); + + String s4; + s4 = s3; + ASSERT_MEMEQ(s4.str, TEST_STR, s4.length); + ASSERT_NE(s3.str, nullptr); + + String s5; + s5 = std::move(s3); + ASSERT_MEMEQ(s5.str, TEST_STR, s5.length); + ASSERT_EQ(s3.str, nullptr); + + String s6(SW_STRL(TEST_STR)); + ASSERT_MEMEQ(s6.str, TEST_STR, s6.length); +} + +TEST(string, append) { + String s1(TEST_STR); + s1.append(12345678); + + String s2(TEST_STR2); + s1.append(s2); + + ASSERT_MEMEQ(s1.str, TEST_STR "12345678" TEST_STR2, s1.length); +} + +TEST(string, write) { + String s1; + s1.reserve(32); + + String s2(TEST_STR); + s1.repeat(" ", 1, 30); + s1.write(30, s2); + + auto s3 = s1.substr(30, s2.length); + ASSERT_MEMEQ(s3.str, TEST_STR, s3.length); +} + +TEST(string, repeat) { + auto end_str = "[end]"; + String s1; + s1.repeat(SW_STRL("hello\r\n"), 5); + s1.append(end_str); + + int count = 0; + auto offset = s1.split(SW_STRL("\r\n"), [&](const char *data, size_t length) -> bool { + count++; + EXPECT_MEMEQ(data, "hello\r\n", 7); + return true; + }); + + ASSERT_EQ(offset, s1.length - strlen(end_str)); + ASSERT_MEMEQ(s1.str + offset, end_str, strlen(end_str)); + + ASSERT_EQ(count, 5); +} + +TEST(string, release) { + String s1(TEST_STR); + ASSERT_MEMEQ(s1.str, TEST_STR, s1.length); + + auto s2 = s1.release(); + ASSERT_EQ(s1.str, nullptr); + ASSERT_EQ(s1.length, 0); + + ASSERT_MEMEQ(s2, TEST_STR, strlen(TEST_STR)); + sw_free(s2); +} + +TEST(string, ub) { + String s1(TEST_STR); + String s2(TEST_STR2); + + s1 = s2; + s1 = s1; + + auto rs = s1.substr(s1.length, 10); + ASSERT_EQ(rs.str, nullptr); } TEST(string, strnpos) { @@ -233,12 +352,14 @@ TEST(string, ends_with) { TEST(string, append_number) { string data = "hello"; - auto str = swoole::make_string(data.length()); + auto str = swoole::make_string(data.length() + 32); str->append(data.c_str(), data.length()); str->append(123); - str->str[str->length] = '\0'; + str->set_null_terminated(); EXPECT_STREQ(str->str, data.append("123").c_str()); str->print(true); str->print(false); + + delete str; } diff --git a/core-tests/src/core/time.cpp b/core-tests/src/core/time.cpp index bfeb44607a..80107f62d2 100644 --- a/core-tests/src/core/time.cpp +++ b/core-tests/src/core/time.cpp @@ -22,3 +22,7 @@ TEST(time, get_seconds) { time_t sec2 = time(NULL); ASSERT_TRUE(sec1 == sec2 or sec1 == sec2 - 1); } + +TEST(time, get_timezone) { + ASSERT_GE(swoole::get_timezone(), 0); +} diff --git a/core-tests/src/core/util.cpp b/core-tests/src/core/util.cpp new file mode 100644 index 0000000000..d756d54232 --- /dev/null +++ b/core-tests/src/core/util.cpp @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "swoole_util.h" + +TEST(util, bitmap) { + swoole::BitMap m(4096); + + m.set(199); + m.set(1234); + m.set(3048); + + ASSERT_EQ(m.get(199), true); + ASSERT_EQ(m.get(1234), true); + ASSERT_EQ(m.get(3048), true); + + ASSERT_EQ(m.get(2048), false); + ASSERT_EQ(m.get(128), false); + + m.unset(1234); + ASSERT_EQ(m.get(1234), false); + + m.clear(); +} diff --git a/core-tests/src/core/wheel_timer.cpp b/core-tests/src/core/wheel_timer.cpp deleted file mode 100644 index 1fa688a02e..0000000000 --- a/core-tests/src/core/wheel_timer.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "test_core.h" -#include "swoole_wheel_timer.h" - -using namespace swoole; -using namespace std; - - -TEST(wheel_timer, next) { - constexpr int size = 5; - WheelTimer wt(size); - - auto t1 = wt.add([&](WheelTimerNode *){ - ASSERT_EQ(wt.get_round(), 6); - }); - ASSERT_EQ(t1->index_, size - 1); - wt.next(); - - auto t2 = wt.add([&](WheelTimerNode *){ - ASSERT_EQ(wt.get_round(), 9); - }); - ASSERT_EQ(t2->index_, 0); - wt.update(t1); - - for (int i = 0; i < 10; i++) { - if (i < 4) { - wt.update(t2); - } - wt.next(); - } -} - diff --git a/core-tests/src/coroutine/accept.cpp b/core-tests/src/coroutine/accept.cpp new file mode 100644 index 0000000000..249d62bf99 --- /dev/null +++ b/core-tests/src/coroutine/accept.cpp @@ -0,0 +1,91 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_coroutine.h" +#include "swoole_coroutine_socket.h" +#include "swoole_coroutine_system.h" + +using namespace swoole::test; +using swoole::Coroutine; + +TEST(coroutine_hook, accept) { + coroutine::run([](void *arg) { + // Create a TCP socket using coroutine API + int server_sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(server_sock, 0); + + // Bind the socket to localhost with port 0 (auto-assign) + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + server_addr.sin_port = 0; + + int retval = ::bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); + ASSERT_EQ(retval, 0); + + // Listen on the socket + retval = ::listen(server_sock, 128); + ASSERT_EQ(retval, 0); + + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + // Test that swoole_coroutine_accept works correctly + Coroutine::create([&](void *arg) { + // Give the server time to start listening + usleep(10000); + + // Connect to the server using coroutine API + int client_sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(client_sock, 0); + + // Get the actual server port + struct sockaddr_in actual_server_addr; + socklen_t addr_len = sizeof(actual_server_addr); + ASSERT_EQ(getsockname(server_sock, (struct sockaddr *) &actual_server_addr, &addr_len), 0); + + // Connect to the server + retval = swoole_coroutine_connect(client_sock, (struct sockaddr *) &actual_server_addr, addr_len); + ASSERT_EQ(retval, 0); + + // Send a test message + const char *test_message = "test_data"; + ssize_t sent_bytes = swoole_coroutine_send(client_sock, test_message, strlen(test_message), 0); + ASSERT_EQ(sent_bytes, (ssize_t) strlen(test_message)); + + // Close the client socket + swoole_coroutine_close(client_sock); + }); + + // Accept the connection using coroutine API + int client_sock = swoole_coroutine_accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len); + ASSERT_GT(client_sock, 0); + + // Receive data from client + char buffer[256] = {}; + ssize_t received_bytes = swoole_coroutine_recv(client_sock, buffer, sizeof(buffer) - 1, 0); + ASSERT_GT(received_bytes, 0); + ASSERT_STREQ(buffer, "test_data"); + + // Close the client socket + swoole_coroutine_close(client_sock); + swoole_coroutine_close(server_sock); + }); +} diff --git a/core-tests/src/coroutine/async.cpp b/core-tests/src/coroutine/async.cpp index 8e46f3a1ed..3e0ebf7d48 100644 --- a/core-tests/src/coroutine/async.cpp +++ b/core-tests/src/coroutine/async.cpp @@ -1,9 +1,6 @@ #include "test_coroutine.h" -#ifdef HAVE_SWOOLE_DIR #include "swoole_async.h" -#else -#include "swoole/swoole_async.h" -#endif + #include #include @@ -57,8 +54,45 @@ TEST(coroutine_async, error) { coroutine::run([](void *arg) { int retval = 0x7009501; const char *test_file = "/tmp/swoole_core_test_file_not_exists"; - swoole::coroutine::async([&](void) { retval = open(test_file, O_RDONLY); }, -1); + swoole::coroutine::async([&](void) { retval = open(test_file, O_RDONLY); }); ASSERT_EQ(retval, -1); ASSERT_EQ(errno, ENOENT); }); } + +TEST(coroutine_async, cancel) { + coroutine::run([](void *arg) { + AsyncEvent ev = {}; + auto co = swoole::Coroutine::get_current(); + swoole_timer_after(50, [co](TIMER_PARAMS) { co->cancel(); }); + + bool retval = swoole::coroutine::async( + [](AsyncEvent *event) { + usleep(200000); + event->retval = magic_code; + }, + ev); + + ASSERT_EQ(retval, false); + ASSERT_EQ(ev.error, SW_ERROR_CO_CANCELED); + DEBUG() << "done\n"; + }); +} + +TEST(coroutine_async, timeout) { + coroutine::run([](void *arg) { + AsyncEvent ev = {}; + + bool retval = swoole::coroutine::async( + [](AsyncEvent *event) { + usleep(200000); + event->retval = magic_code; + }, + ev, + 0.1); + + ASSERT_EQ(retval, false); + ASSERT_EQ(ev.error, SW_ERROR_CO_TIMEDOUT); + DEBUG() << "done\n"; + }); +} diff --git a/core-tests/src/coroutine/base.cpp b/core-tests/src/coroutine/base.cpp index 442bb477d6..bf39ee3297 100644 --- a/core-tests/src/coroutine/base.cpp +++ b/core-tests/src/coroutine/base.cpp @@ -35,8 +35,9 @@ TEST(coroutine_base, get_init_msec) { TEST(coroutine_base, yield_resume) { Coroutine::set_on_yield([](void *arg) { - long task = *(long *) Coroutine::get_current_task(); - ASSERT_EQ(task, Coroutine::get_current_cid()); + auto task = static_cast(Coroutine::get_current_task()); + ASSERT_NE(task, nullptr); + ASSERT_EQ(*task, Coroutine::get_current_cid()); }); Coroutine::set_on_resume([](void *arg) { @@ -45,18 +46,19 @@ TEST(coroutine_base, yield_resume) { }); Coroutine::set_on_close([](void *arg) { - long task = *(long *) Coroutine::get_current_task(); - ASSERT_EQ(task, Coroutine::get_current_cid()); + auto task = static_cast(Coroutine::get_current_task()); + ASSERT_NE(task, nullptr); + ASSERT_EQ(*task, Coroutine::get_current_cid()); }); - long _cid; + long _cid, _cid2; long cid = Coroutine::create( - [](void *arg) { - long cid = Coroutine::get_current_cid(); - Coroutine *co = Coroutine::get_by_cid(cid); - co->set_task((void *) &cid); + [&_cid2](void *arg) { + _cid2 = Coroutine::get_current_cid(); + Coroutine *co = Coroutine::get_by_cid(_cid2); + co->set_task(&_cid2); co->yield(); - *(long *) arg = Coroutine::get_current_cid(); + *static_cast(arg) = Coroutine::get_current_cid(); }, &_cid); @@ -136,7 +138,7 @@ TEST(coroutine_base, get_current_cid) { auto co = Coroutine::get_current(); auto actual = co->get_cid(); ASSERT_EQ(actual, Coroutine::get_current_cid()); - ASSERT_EQ(actual, swoole_coroutine_get_current_id()); + ASSERT_EQ(actual, swoole_coroutine_get_id()); }); } @@ -210,6 +212,44 @@ TEST(coroutine_base, cancel) { }); } +TEST(coroutine_base, noncancelable) { + std::unordered_map flags; + coroutine::run([&flags](void *arg) { + auto cid = Coroutine::create([&flags](void *_arg) { + Coroutine *current = Coroutine::get_current(); + flags["yield_1"] = true; + current->yield(); + ASSERT_FALSE(current->is_canceled()); + + flags["yield_2"] = true; + current->yield_ex(-1); + ASSERT_TRUE(current->is_canceled()); + }); + + auto co = Coroutine::get_by_cid(cid); + + flags["cancel_1"] = true; + ASSERT_FALSE(co->cancel()); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_CO_CANNOT_CANCEL); + flags["resume_1"] = true; + co->resume(); + + flags["cancel_2"] = true; + ASSERT_TRUE(co->cancel()); + flags["resume_2"] = true; + + flags["done"] = true; + }); + + ASSERT_TRUE(flags["yield_1"]); + ASSERT_TRUE(flags["yield_2"]); + ASSERT_TRUE(flags["cancel_1"]); + ASSERT_TRUE(flags["resume_1"]); + ASSERT_TRUE(flags["cancel_2"]); + ASSERT_TRUE(flags["resume_2"]); + ASSERT_TRUE(flags["done"]); +} + TEST(coroutine_base, timeout) { coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); @@ -224,6 +264,7 @@ TEST(coroutine_base, gdb) { long cid = current->get_cid(); ASSERT_EQ(swoole_coroutine_count(), 1); ASSERT_EQ(swoole_coroutine_get(cid), current); + ASSERT_EQ(swoole_coroutine_get(999999), nullptr); swoole_coroutine_iterator_reset(); ASSERT_EQ(swoole_coroutine_iterator_each(), current); @@ -234,3 +275,88 @@ TEST(coroutine_base, gdb) { Coroutine::print_list(); }); } + +TEST(coroutine_base, bailout) { + int status; + + status = test::spawn_exec_and_wait([]() { + std::unordered_map flags; + coroutine::run([&flags](void *arg) { + Coroutine::create([&flags](void *_arg) { + Coroutine *current = Coroutine::get_current(); + current->bailout([&flags]() { flags["exit"] = true; }); + flags["end"] = true; + }); + }); + + ASSERT_TRUE(flags["exit"]); + ASSERT_FALSE(flags["end"]); + }); + ASSERT_EQ(status, 0); + + status = test::spawn_exec_and_wait([]() { + std::unordered_map flags; + coroutine::run([&flags](void *arg) { + Coroutine *current = Coroutine::get_current(); + current->bailout(nullptr); + flags["end"] = true; + }); + + ASSERT_TRUE(flags["exit"]); + ASSERT_FALSE(flags["end"]); + }); + ASSERT_EQ(WEXITSTATUS(status), 1); + + status = test::spawn_exec_and_wait([]() { + std::unordered_map flags; + coroutine::run([&flags](void *arg) { + Coroutine *current = Coroutine::get_current(); + swoole_event_defer( + [current, &flags](void *args) { + flags["bailout"] = true; + current->bailout(nullptr); + flags["end"] = true; + }, + nullptr); + flags["exit"] = true; + }); + + ASSERT_TRUE(flags["exit"]); + ASSERT_TRUE(flags["end"]); + }); + ASSERT_EQ(WEXITSTATUS(status), 0); +} + +TEST(coroutine_base, undefined_behavior) { + int status; + status = test::spawn_exec_and_wait([]() { test::coroutine::run([](void *) { swoole_fork(0); }); }); + ASSERT_EQ(1, WEXITSTATUS(status)); + + status = test::spawn_exec_and_wait([]() { + std::atomic handle_count(0); + AsyncEvent event = {}; + event.object = &handle_count; + event.callback = [](AsyncEvent *event) {}; + event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; + + swoole_event_init(0); + auto ret = async::dispatch(&event); + ASSERT_NE(ret, nullptr); + swoole_fork(0); + }); + ASSERT_EQ(1, WEXITSTATUS(status)); + + ASSERT_EQ(0, swoole_fork(SW_FORK_PRECHECK)); +} + +TEST(coroutine_base, c_api) { + int code = 0x9501; + auto c1 = swoole_coroutine_create( + [](void *_arg) { + ASSERT_EQ(*(int *) _arg, 0x9501); + swoole_coroutine_create([](void *_arg) { swoole_coroutine_usleep(100000); }, nullptr); + }, + &code); + + ASSERT_GE(c1, 1); +} diff --git a/core-tests/src/coroutine/channel.cpp b/core-tests/src/coroutine/channel.cpp index cb24673e5c..55498ee5aa 100644 --- a/core-tests/src/coroutine/channel.cpp +++ b/core-tests/src/coroutine/channel.cpp @@ -1,5 +1,6 @@ #include "test_coroutine.h" +using swoole::Coroutine; using swoole::coroutine::Channel; using namespace std; @@ -125,3 +126,24 @@ TEST(coroutine_channel, close) { ASSERT_TRUE(chan2.close()); } + +TEST(coroutine_channel, cancel) { + Channel chan(1); + + coroutine::run( + [](void *arg) { + auto chan = (Channel *) arg; + auto cid = Coroutine::create([chan](void *args) { + ASSERT_EQ(chan->pop(), nullptr); + ASSERT_EQ(chan->get_error(), Channel::ERROR_CANCELED); + }); + + auto co = Coroutine::get_by_cid(cid); + ASSERT_TRUE(co->cancel()); + ASSERT_TRUE(chan->close()); + + ASSERT_EQ(chan->pop(), nullptr); + ASSERT_EQ(chan->get_error(), Channel::ERROR_CLOSED); + }, + &chan); +} diff --git a/core-tests/src/coroutine/file.cpp b/core-tests/src/coroutine/file.cpp new file mode 100644 index 0000000000..171fad34b5 --- /dev/null +++ b/core-tests/src/coroutine/file.cpp @@ -0,0 +1,54 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ + +#include +#include +#include "test_coroutine.h" +#include "swoole_file.h" + +using namespace std; +using namespace swoole; +using swoole::coroutine::System; + +TEST(coroutine_async_file, async_file) { + coroutine::run([](void *arg) { + string filename = "/tmp/file.txt"; + auto file = new AsyncFile(filename, O_CREAT | O_RDWR, 0666); + ON_SCOPE_EXIT { + delete file; + }; + ASSERT_EQ(file->ready(), true); + ASSERT_EQ(file->truncate(0), true); + ASSERT_EQ(file->set_offset(0), 0); + ASSERT_EQ(file->get_offset(), 0); + + const char *data = "Hello World!"; + size_t length = strlen(data); + ASSERT_EQ(file->write((void *) data, length), static_cast(length)); + + ASSERT_EQ(file->sync(), true); + ASSERT_EQ(file->set_offset(0), 0); + + char buf[1024]; + ASSERT_EQ(file->read((void *) buf, 1024), static_cast(length)); + buf[length] = '\0'; + ASSERT_STREQ(data, buf); + + struct stat statbuf {}; + ASSERT_EQ(file->stat(&statbuf), true); + ASSERT_TRUE(statbuf.st_size > 0); + }); +} diff --git a/core-tests/src/coroutine/gethostbyname.cpp b/core-tests/src/coroutine/gethostbyname.cpp index 042999d61a..e25e988fd7 100644 --- a/core-tests/src/coroutine/gethostbyname.cpp +++ b/core-tests/src/coroutine/gethostbyname.cpp @@ -5,36 +5,29 @@ using swoole::Timer; using swoole::coroutine::System; using swoole::test::coroutine; -const char *domain_baidu = "www.baidu.com"; -const char *domain_tencent = "www.tencent.com"; - TEST(coroutine_gethostbyname, resolve_cache) { coroutine::run([](void *arg) { System::set_dns_cache_capacity(10); - std::string addr1 = System::gethostbyname(domain_baidu, AF_INET); + std::string addr1 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_NE(addr1, ""); - int64_t with_cache = Timer::get_absolute_msec(); for (int i = 0; i < 100; ++i) { - std::string addr2 = System::gethostbyname(domain_baidu, AF_INET); + std::string addr2 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_EQ(addr1, addr2); } - with_cache = Timer::get_absolute_msec() - with_cache; + ASSERT_GT(System::get_dns_cache_hit_ratio(), 0.99); System::set_dns_cache_capacity(0); - int64_t without_cache = Timer::get_absolute_msec(); for (int i = 0; i < 5; ++i) { - std::string addr2 = System::gethostbyname(domain_baidu, AF_INET); + std::string addr2 = System::gethostbyname(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_NE(addr2, ""); } - without_cache = Timer::get_absolute_msec() - without_cache; - - ASSERT_GT(without_cache, with_cache); + ASSERT_LT(System::get_dns_cache_hit_ratio(), 0.01); }); } TEST(coroutine_gethostbyname, impl_async) { coroutine::run([](void *arg) { - auto result = swoole::coroutine::gethostbyname_impl_with_async(domain_baidu, AF_INET); + auto result = swoole::coroutine::gethostbyname_impl_with_async(TEST_DOMAIN_BAIDU, AF_INET); ASSERT_EQ(result.empty(), false); }); } @@ -67,20 +60,18 @@ TEST(coroutine_gethostbyname, resolve_cache_inet4_and_inet6) { TEST(coroutine_gethostbyname, dns_expire) { coroutine::run([](void *arg) { - time_t expire = 0.2; - System::set_dns_cache_expire(expire); - System::gethostbyname(domain_tencent, AF_INET); + System::set_dns_cache_expire(1); + System::clear_dns_cache(); - int64_t with_cache = Timer::get_absolute_msec(); - System::gethostbyname(domain_tencent, AF_INET); - with_cache = Timer::get_absolute_msec() - with_cache; + System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); + System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); + ASSERT_GE(System::get_dns_cache_hit_ratio(), 0.5); - sleep(0.3); - int64_t without_cache = Timer::get_absolute_msec(); - System::gethostbyname(domain_tencent, AF_INET); - without_cache = Timer::get_absolute_msec() - without_cache; + sleep(2); + System::gethostbyname(TEST_HTTP_DOMAIN, AF_INET); + ASSERT_LT(System::get_dns_cache_hit_ratio(), 0.35); - ASSERT_GE(without_cache, with_cache); System::clear_dns_cache(); + System::set_dns_cache_expire(60); }); } diff --git a/core-tests/src/coroutine/hook.cpp b/core-tests/src/coroutine/hook.cpp index 8bc78392db..425fda6556 100644 --- a/core-tests/src/coroutine/hook.cpp +++ b/core-tests/src/coroutine/hook.cpp @@ -24,8 +24,8 @@ using namespace swoole::test; using swoole::Coroutine; +using swoole::File; using swoole::String; -using swoole::coroutine::Socket; using swoole::coroutine::System; using swoole::test::coroutine; @@ -33,24 +33,67 @@ const char *host_1 = "www.baidu.com"; const char *host_2 = "www.xxxxxxxxxxxxxxxxxxxxx00000xxxxxxxxx----not_found.com"; static const char *test_file = "/tmp/swoole-core-test"; -TEST(coroutine_hook, file) { - coroutine::run([](void *arg) { - char buf[8192]; - size_t n_buf = sizeof(buf); - ASSERT_EQ(swoole_random_bytes(buf, n_buf), n_buf); +void static test_file_hook() { + char buf[8192]; + size_t n_buf = sizeof(buf); + ASSERT_EQ(swoole_random_bytes(buf, n_buf), n_buf); - int fd = swoole_coroutine_open(test_file, O_WRONLY | O_TRUNC | O_CREAT, 0666); - ASSERT_EQ(swoole_coroutine_write(fd, buf, n_buf), n_buf); - swoole_coroutine_close(fd); + int fd = swoole_coroutine_open(test_file, O_WRONLY | O_TRUNC | O_CREAT, 0666); + ASSERT_EQ(swoole_coroutine_write(fd, buf, n_buf), n_buf); - fd = swoole_coroutine_open(test_file, O_RDONLY, 0); - char data[8192]; - ASSERT_EQ(swoole_coroutine_read(fd, data, n_buf), n_buf); - ASSERT_EQ(std::string(buf, n_buf), std::string(data, n_buf)); - swoole_coroutine_close(fd); + ASSERT_EQ(swoole_coroutine_fsync(fd), 0); + ASSERT_EQ(swoole_coroutine_fdatasync(fd), 0); - ASSERT_EQ(swoole_coroutine_unlink(test_file), 0); - }); + swoole_coroutine_close(fd); + + fd = swoole_coroutine_open(test_file, O_RDONLY, 0); + char data[8192]; + ASSERT_EQ(swoole_coroutine_read(fd, data, n_buf), n_buf); + ASSERT_EQ(std::string(buf, n_buf), std::string(data, n_buf)); + swoole_coroutine_close(fd); + + struct stat statbuf; + swoole_coroutine_stat(test_file, &statbuf); + + File f1(test_file, File::RW); + ASSERT_EQ(statbuf.st_size, f1.get_size()); + + struct stat statbuf2; + swoole_coroutine_lstat(test_file, &statbuf2); + ASSERT_EQ(statbuf2.st_size, f1.get_size()); + f1.close(); + + ASSERT_EQ(swoole_coroutine_unlink(test_file), 0); + + File f2(test_file, File::RW | File::CREATE); + swoole_coroutine_lseek(f2.get_fd(), 0, SEEK_SET); + + auto fp2 = swoole_coroutine_fdopen(f2.get_fd(), "w+"); + ASSERT_NE(fp2, nullptr); + + swoole_coroutine_fputs("hello\n", fp2); + swoole_coroutine_fputs("world\n", fp2); + ASSERT_EQ(swoole_coroutine_fflush(fp2), 0); + + swoole_coroutine_lseek(f2.get_fd(), 0, SEEK_SET); + auto fp3 = swoole_coroutine_fdopen(f2.get_fd(), "r+"); + ASSERT_NE(fp3, nullptr); + + char rbuf[2048] = {}; + ASSERT_EQ(swoole_coroutine_fread(rbuf, sizeof(rbuf), 1, fp3), 0); + ASSERT_STREQ(rbuf, "hello\nworld\n"); + + f2.close(); + swoole_coroutine_fclose(fp2); + swoole_coroutine_fclose(fp3); + + ASSERT_EQ(swoole_coroutine_unlink(test_file), 0); +} + +TEST(coroutine_hook, file) { + coroutine::run([](void *arg) { test_file_hook(); }); + + test_file_hook(); } TEST(coroutine_hook, gethostbyname) { @@ -96,72 +139,86 @@ TEST(coroutine_hook, getaddrinfo) { }); } +static void test_fstat() { + int fd = swoole_coroutine_open(TEST_TMP_FILE, O_RDONLY, 0); + struct stat statbuf_1; + swoole_coroutine_fstat(fd, &statbuf_1); + + struct stat statbuf_2; + fstat(fd, &statbuf_2); + + ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); + + swoole_coroutine_close(fd); +} + TEST(coroutine_hook, fstat) { - coroutine::run([](void *arg) { - int fd = swoole_coroutine_open(TEST_TMP_FILE, O_RDONLY, 0); - struct stat statbuf_1; - swoole_coroutine_fstat(fd, &statbuf_1); + coroutine::run([](void *arg) { test_fstat(); }); + test_fstat(); +} - struct stat statbuf_2; - fstat(fd, &statbuf_2); +static void test_statvfs() { + struct statvfs statbuf_1; + swoole_coroutine_statvfs("/tmp", &statbuf_1); - ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); + struct statvfs statbuf_2; + statvfs("/tmp", &statbuf_2); - swoole_coroutine_close(fd); - }); + ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); } TEST(coroutine_hook, statvfs) { - coroutine::run([](void *arg) { - struct statvfs statbuf_1; - swoole_coroutine_statvfs("/tmp", &statbuf_1); - - struct statvfs statbuf_2; - statvfs("/tmp", &statbuf_2); + coroutine::run([](void *arg) { test_statvfs(); }); + test_statvfs(); +} - ASSERT_EQ(memcmp(&statbuf_1, &statbuf_2, sizeof(statbuf_2)), 0); - }); +static void test_hook_dir() { + ASSERT_EQ(swoole_coroutine_mkdir(TEST_TMP_DIR, 0666), 0); + ASSERT_EQ(swoole_coroutine_access(TEST_TMP_DIR, R_OK), 0); + ASSERT_EQ(swoole_coroutine_rmdir(TEST_TMP_DIR), 0); + ASSERT_EQ(access(TEST_TMP_DIR, R_OK), -1); } TEST(coroutine_hook, dir) { - coroutine::run([](void *arg) { - ASSERT_EQ(swoole_coroutine_mkdir(TEST_TMP_DIR, 0666), 0); - ASSERT_EQ(swoole_coroutine_access(TEST_TMP_DIR, R_OK), 0); - ASSERT_EQ(swoole_coroutine_rmdir(TEST_TMP_DIR), 0); - ASSERT_EQ(access(TEST_TMP_DIR, R_OK), -1); - }); + coroutine::run([](void *arg) { test_hook_dir(); }); + + test_hook_dir(); } -TEST(coroutine_hook, socket) { - coroutine::run([](void *arg) { - int sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); - ASSERT_GT(sock, 0); - swoole::network::Address sa; - std::string ip = System::gethostbyname("www.baidu.com", AF_INET, 10); - sa.assign(SW_SOCK_TCP, ip, 80); - ASSERT_EQ(swoole_coroutine_connect(sock, &sa.addr.ss, sa.len), 0); - ASSERT_EQ(swoole_coroutine_socket_wait_event(sock, SW_EVENT_WRITE, 5), SW_OK); - - const char req[] = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nKeepAlive: off\r\n\r\n"; - ASSERT_EQ(swoole_coroutine_send(sock, req, strlen(req), 0), strlen(req)); - - swoole::String resp(1024); - - while (1) { - ssize_t n = swoole_coroutine_recv(sock, resp.value() + resp.length, resp.size - resp.length, 0); - if (n <= 0) { - break; - } - resp.length += n; - if (resp.length == resp.size) { - resp.reserve(resp.size * 2); - } +static void test_hook_socket() { + int sock = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(sock, 0); + swoole::network::Address sa; + std::string ip = System::gethostbyname("www.baidu.com", AF_INET, 10); + sa.assign(SW_SOCK_TCP, ip, 80); + ASSERT_EQ(swoole_coroutine_connect(sock, &sa.addr.ss, sa.len), 0); + ASSERT_EQ(swoole_coroutine_socket_wait_event(sock, SW_EVENT_WRITE, 5), SW_OK); + + const char req[] = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nKeepAlive: off\r\n\r\n"; + ASSERT_EQ(swoole_coroutine_send(sock, req, strlen(req), 0), strlen(req)); + + swoole::String resp(1024); + + while (1) { + ssize_t n = swoole_coroutine_recv(sock, resp.value() + resp.length, resp.size - resp.length, 0); + if (n <= 0) { + break; } + resp.length += n; + if (resp.length == resp.size) { + resp.reserve(resp.size * 2); + } + } - ASSERT_GT(resp.length, 100); - ASSERT_TRUE(resp.contains("baidu.com")); - swoole_coroutine_close(sock); - }); + ASSERT_GT(resp.length, 100); + ASSERT_TRUE(resp.contains("baidu.com")); + swoole_coroutine_close(sock); +} + +TEST(coroutine_hook, socket) { + coroutine::run([](void *arg) { test_hook_socket(); }); + + test_hook_socket(); } TEST(coroutine_hook, rename) { @@ -174,7 +231,7 @@ TEST(coroutine_hook, rename) { ASSERT_EQ(swoole_coroutine_write(fd, buf, n_buf), n_buf); swoole_coroutine_close(fd); - std::string to_file_name = std::string(test_file, ".bak"); + std::string to_file_name = std::string(test_file) + ".bak"; ASSERT_EQ(swoole_coroutine_rename(test_file, to_file_name.c_str()), 0); ASSERT_EQ(access(TEST_TMP_DIR, F_OK), -1); ASSERT_EQ(access(to_file_name.c_str(), F_OK), 0); @@ -186,8 +243,12 @@ TEST(coroutine_hook, rename) { TEST(coroutine_hook, flock) { long start_time = swoole::time(); coroutine::run([&](void *arg) { - swoole::Coroutine::create([&](void *arg) { + Coroutine::create([&](void *arg) { int fd = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); + + ASSERT_EQ(swoole_coroutine_flock(fd, 16), SW_ERR); + ASSERT_ERREQ(EINVAL); + ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX), 0); System::sleep(0.1); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); @@ -197,10 +258,10 @@ TEST(coroutine_hook, flock) { ASSERT_LE(swoole::time() - start_time, 1000); swoole_coroutine_close(fd); }); - swoole::Coroutine::create([&](void *arg) { + Coroutine::create([&](void *arg) { int fd = swoole_coroutine_open(TEST_TMP_FILE, O_WRONLY, 0); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_SH), 0); - System::sleep(2); + System::sleep(0.5); ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); swoole_coroutine_close(fd); }); @@ -314,17 +375,18 @@ TEST(coroutine_hook, stdio_2) { TEST(coroutine_hook, sleep) { coroutine::run([&](void *arg) { - const int sec = 1; - long sec_1 = swoole::time(); - swoole_coroutine_sleep(sec); - long sec_2 = swoole::time(); - ASSERT_LE(sec_2 - sec_1, sec); - - const int us = 2000; - long us_1 = swoole::time(); - swoole_coroutine_usleep(us); - long us_2 = swoole::time(); - ASSERT_LE(us_2 - us_1, us / 1000); + { + long ms1 = swoole::time(); + swoole_coroutine_sleep(2); + long ms2 = swoole::time(); + ASSERT_GE(ms2 - ms1, 1900); + } + { + long us_1 = swoole::time(); + swoole_coroutine_usleep(50000); + long us_2 = swoole::time(); + ASSERT_GE(us_2 - us_1, 45000); + } }); } @@ -333,8 +395,9 @@ TEST(coroutine_hook, exists) { const int fd = 100; // fake fd ASSERT_EQ(swoole_coroutine_socket_create(fd), 0); ASSERT_TRUE(swoole_coroutine_socket_exists(fd)); - Socket *sock = swoole_coroutine_get_socket_object(fd); + auto sock = swoole_coroutine_get_socket_object(fd); ASSERT_EQ(sock->get_fd(), fd); + swoole_coroutine_close(fd); }); } @@ -346,7 +409,13 @@ TEST(coroutine_hook, timeout) { size_t length = text.length(); // unregister fd + errno = 0; ASSERT_EQ(swoole_coroutine_socket_set_timeout(pairs[0], SO_SNDTIMEO, 0.05), -1); + ASSERT_EQ(errno, EINVAL); + + errno = 0; + ASSERT_EQ(swoole_coroutine_socket_set_connect_timeout(pairs[0], 0.05), -1); + ASSERT_EQ(errno, EINVAL); swoole::Coroutine::create([&](void *) { ASSERT_EQ(swoole_coroutine_socket_create(pairs[0]), 0); @@ -428,3 +497,281 @@ TEST(coroutine_hook, lseek) { swoole_coroutine_close(fd); ASSERT_EQ(offset, 0); } + +TEST(coroutine_hook, socket_close) { + coroutine::run([&](void *arg) { + auto pair = create_socket_pair(); + + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + std::map results; + auto _sock = pair.first; + auto _fd = _sock->move_fd(); + swoole_coroutine_socket_create(_fd); + + // write co + Coroutine::create([&](void *) { + SW_LOOP_N(32) { + ssize_t result = swoole_coroutine_write(_fd, buffer->value(), buffer->get_length()); + if (result < 0 && errno == ECANCELED) { + ASSERT_EQ(swoole_coroutine_close(_fd), -1); + ASSERT_EQ(errno, SW_ERROR_CO_SOCKET_CLOSE_WAIT); + results["write"] = true; + break; + } + } + }); + + // read co + Coroutine::create([&](void *) { + SW_LOOP_N(32) { + char buf[4096]; + ssize_t result = swoole_coroutine_read(_fd, buf, sizeof(buf)); + if (result < 0 && errno == ECANCELED) { + ASSERT_EQ(swoole_coroutine_close(_fd), 0); + results["read"] = true; + break; + } + } + }); + + System::sleep(0.1); + ASSERT_EQ(swoole_coroutine_close(_fd), -1); + ASSERT_EQ(errno, SW_ERROR_CO_SOCKET_CLOSE_WAIT); + ASSERT_TRUE(results["write"]); + ASSERT_TRUE(results["read"]); + }); +} + +TEST(coroutine_hook, poll) { + coroutine::run([&](void *arg) { + auto pair = create_socket_pair(); + + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + std::map results; + auto _sock0 = pair.first; + auto _fd0 = _sock0->move_fd(); + swoole_coroutine_socket_create(_fd0); + + auto _sock1 = pair.second; + auto _fd1 = _sock1->move_fd(); + swoole_coroutine_socket_create(_fd1); + + Coroutine::create([&](void *) { + ssize_t result; + result = swoole_coroutine_write(_fd0, buffer->value(), buffer->get_length()); + ASSERT_GT(result, 0); + System::sleep(0.01); + result = swoole_coroutine_write(_fd1, buffer->value(), 16 * 1024); + ASSERT_GT(result, 0); + }); + + struct pollfd fds[2]; + char buf[4096]; + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN; + fds[1].fd = _fd1; + fds[1].events = POLLIN; + + ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 1); + ASSERT_TRUE(fds[1].revents & POLLIN); + + ssize_t result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + + System::sleep(0.02); + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN; + fds[1].fd = _fd1; + fds[1].events = POLLIN; + + ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 2); + ASSERT_TRUE(fds[0].revents & POLLIN); + ASSERT_TRUE(fds[1].revents & POLLIN); + result = swoole_coroutine_read(_fd0, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + + System::sleep(0.02); + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN | POLLOUT; + fds[1].fd = _fd1; + fds[1].events = POLLIN | POLLOUT; + + ASSERT_EQ(swoole_coroutine_poll(fds, 2, 1000), 2); + ASSERT_TRUE(fds[0].revents & POLLIN); + ASSERT_TRUE(fds[1].revents & POLLIN); + ASSERT_FALSE(fds[0].revents & POLLOUT); // not writable + ASSERT_TRUE(fds[1].revents & POLLOUT); + result = swoole_coroutine_read(_fd0, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + }); +} + +TEST(coroutine_hook, poll_fake) { + coroutine::run([&](void *arg) { + auto pair = create_socket_pair(); + + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + std::map results; + auto _sock0 = pair.first; + auto _fd0 = _sock0->move_fd(); + swoole_coroutine_socket_create(_fd0); + + auto _sock1 = pair.second; + auto _fd1 = _sock1->move_fd(); + swoole_coroutine_socket_create(_fd1); + + Coroutine::create([&](void *) { + ssize_t result; + result = swoole_coroutine_write(_fd0, buffer->value(), buffer->get_length()); + ASSERT_GT(result, 0); + System::sleep(0.01); + result = swoole_coroutine_write(_fd1, buffer->value(), 16 * 1024); + ASSERT_GT(result, 0); + }); + + struct pollfd fds[2]; + char buf[4096]; + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd1; + fds[0].events = POLLIN; + + ASSERT_EQ(swoole_coroutine_poll_fake(fds, 1, 1000), 1); + ASSERT_TRUE(fds[0].revents & POLLIN); + + ssize_t result = swoole_coroutine_read(_fd1, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + + bzero(fds, sizeof(pollfd)); + ASSERT_EQ(swoole_coroutine_poll_fake(fds, 2, 1000), -1); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); + + System::sleep(0.02); + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN | POLLOUT; + ASSERT_EQ(swoole_coroutine_poll_fake(fds, 1, 1000), 1); + ASSERT_TRUE(fds[0].revents & POLLIN); + ASSERT_TRUE(fds[0].revents & POLLOUT); + }); +} + +TEST(coroutine_hook, unwrap) { + auto pair = create_socket_pair(); + auto _sock0 = pair.first; + auto _sock1 = pair.second; + + ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), -1); + + coroutine::run([&](void *arg) { + ASSERT_EQ(swoole_coroutine_socket_unwrap(999999), -1); + ASSERT_EQ(swoole_coroutine_socket_create(_sock0->get_fd()), 0); + ASSERT_GT(swoole_coroutine_write(_sock0->get_fd(), SW_STRL(TEST_STR)), 0); + + // blocking, wrap-socket not exists + char buf[128]; + ASSERT_EQ(swoole_coroutine_read(_sock1->get_fd(), buf, sizeof(buf)), strlen(TEST_STR)); + ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock1->get_fd()), -1); + // unwrap + ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), 0); + // fail to unwrap + ASSERT_EQ(swoole_coroutine_socket_unwrap(_sock0->get_fd()), -1); + }); +} + +static void test_freopen() { + auto output_file = "/tmp/output.txt"; + unlink(output_file); + unlink(TEST_LOG_FILE); + + auto fp = swoole_coroutine_fopen(TEST_LOG_FILE, "w"); + if (fp == NULL) { + fprintf(stderr, "Failed to open '%s': %s (errno=%d)\n", TEST_LOG_FILE, strerror(errno), errno); + } + ASSERT_NE(fp, nullptr); + swoole_coroutine_fputs("hello\n", fp); + + ASSERT_NE(swoole_coroutine_freopen(output_file, "w", fp), nullptr); + swoole_coroutine_fputs("world\n", fp); + + swoole_coroutine_fclose(fp); + + auto rs1 = swoole::file_get_contents(output_file); + ASSERT_FALSE(rs1->contains("hello\n")); + ASSERT_TRUE(rs1->contains("world\n")); + + auto rs2 = swoole::file_get_contents(TEST_LOG_FILE); + ASSERT_TRUE(rs2->contains("hello\n")); + ASSERT_FALSE(rs2->contains("world\n")); + + unlink(TEST_LOG_FILE); + unlink(output_file); +} + +TEST(coroutine_hook, freopen) { + coroutine::run([&](void *arg) { test_freopen(); }); + test_freopen(); +} + +TEST(coroutine_hook, ftruncate) { + coroutine::run([&](void *arg) { + int fd = swoole_coroutine_open("/tmp/123.txt", O_CREAT | O_RDWR, 0); + ASSERT_TRUE(fd > 0); + + const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; + size_t length = strlen(data); + ssize_t write_length = swoole_coroutine_write(fd, (const void *) data, length); + ASSERT_TRUE(write_length == static_cast(length)); + ASSERT_TRUE(swoole_coroutine_ftruncate(fd, 0) == 0); + swoole_coroutine_close(fd); + }); +} + +TEST(coroutine_hook, get_socket_fail) { + { + auto rs = swoole_coroutine_get_socket_object(999); + ASSERT_EQ(errno, ENOTSOCK); + ASSERT_EQ(rs, nullptr); + } + + { + int fd; + coroutine::run([&](void *arg) { + fd = swoole_coroutine_socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(errno, 0); + }); + + auto rs = swoole_coroutine_get_socket_object_ex(fd); + ASSERT_EQ(errno, EWOULDBLOCK); + ASSERT_EQ(rs, nullptr); + swoole_coroutine_close(fd); + } +} + +TEST(coroutine_hook, create_socket_fail) { + coroutine::run([&](void *arg) { + int fd = swoole_coroutine_socket(AF_INET + 99, SOCK_CLOEXEC, 0); + ASSERT_EQ(fd, -1); + ASSERT_EQ(errno, EAFNOSUPPORT); + }); +} diff --git a/core-tests/src/coroutine/http_server.cpp b/core-tests/src/coroutine/http_server.cpp index 68ddae7642..373d2d8afb 100644 --- a/core-tests/src/coroutine/http_server.cpp +++ b/core-tests/src/coroutine/http_server.cpp @@ -27,11 +27,12 @@ using namespace std; TEST(coroutine_http_server, get) { Server svr; mutex lock; + int port = swoole::test::get_random_port(); lock.lock(); - thread t1([&lock]() { + thread t1([&lock, port]() { lock.lock(); - Client cli(TEST_HOST, 8080); + Client cli(TEST_HOST, port); auto resp1 = cli.Get("/hi"); EXPECT_EQ(resp1->status, 200); EXPECT_EQ(resp1->body, string("Hello World!")); @@ -41,7 +42,7 @@ TEST(coroutine_http_server, get) { EXPECT_EQ(resp2->body, string("Stop Server!")); }); - coroutine::run([&lock, &svr](void *arg) { + coroutine::run([&lock, &svr, port](void *arg) { svr.Get("/hi", [](const Request &req, Response &res) { res.set_content("Hello World!", "text/plain"); }); svr.Get("/stop", [&svr](const Request &req, Response &res) { @@ -53,7 +54,7 @@ TEST(coroutine_http_server, get) { svr.BeforeListen([&lock]() { lock.unlock(); }); - ASSERT_TRUE(svr.listen(TEST_HOST, 8080)); + ASSERT_TRUE(svr.listen(TEST_HOST, port)); }); t1.join(); @@ -62,12 +63,13 @@ TEST(coroutine_http_server, get) { TEST(coroutine_http_server, post) { Server svr; mutex lock; + int port = swoole::test::get_random_port(); lock.lock(); - std::thread t1([&lock]() { + std::thread t1([&lock, port]() { lock.lock(); - Client cli(TEST_HOST, 8080); + Client cli(TEST_HOST, port); httplib::Params params; params.emplace("name", "john"); @@ -82,7 +84,7 @@ TEST(coroutine_http_server, post) { EXPECT_EQ(resp2->body, string("Stop Server!")); }); - coroutine::run([&lock, &svr](void *arg) { + coroutine::run([&lock, &svr, port](void *arg) { svr.Get("/stop", [&svr](const Request &req, Response &res) { res.set_content("Stop Server!", "text/plain"); svr.stop(); @@ -92,7 +94,7 @@ TEST(coroutine_http_server, post) { svr.BeforeListen([&lock]() { lock.unlock(); }); - svr.listen(TEST_HOST, 8080); + svr.listen(TEST_HOST, port); }); t1.join(); diff --git a/core-tests/src/coroutine/iouring.cpp b/core-tests/src/coroutine/iouring.cpp new file mode 100644 index 0000000000..3a553c19c9 --- /dev/null +++ b/core-tests/src/coroutine/iouring.cpp @@ -0,0 +1,468 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "test_coroutine.h" +#include "swoole_iouring.h" + +#include +#include + +#ifdef SW_USE_IOURING +using swoole::Iouring; +using swoole::Reactor; +using swoole::coroutine::System; +using swoole::test::coroutine; +using swoole::test::create_socket_pair; + +TEST(iouring, create) { + coroutine::run([](void *arg) { + SwooleG.iouring_entries = 4; + SwooleG.iouring_workers = 65536; + auto fd = Iouring::open(TEST_TMP_FILE, O_CREAT, 0666); + ASSERT_GE(fd, 0); + ASSERT_NE(Iouring::close(fd), -1); + }); +} + +TEST(iouring, list_all_opcode) { + auto list = Iouring::list_all_opcode(); + for (auto &item : list) { + DEBUG() << "opcode: " << item.first << ", value: " << item.second << "\n"; + } + ASSERT_TRUE(list.size() > 0); +} + +TEST(iouring, open_and_close) { + coroutine::run([](void *arg) { + const char *test_file = "/tmp/file_1"; + int fd = Iouring::open(test_file, O_CREAT, 0666); + ASSERT_TRUE(fd > 0); + + int result = Iouring::close(fd); + ASSERT_TRUE(result == 0); + + result = Iouring::unlink(test_file); + ASSERT_TRUE(result == 0); + }); +} + +TEST(iouring, mkdir_and_rmdir) { + coroutine::run([](void *arg) { + const char *directory = "/tmp/aaaa"; + int rv; + + rv = Iouring::mkdir(directory, 0755); + ASSERT_EQ(rv, 0); + + rv = Iouring::rmdir(directory); + ASSERT_EQ(rv, 0); + }); +} + +TEST(iouring, write_and_read) { + coroutine::run([](void *arg) { + const char *test_file = "/tmp/file_2"; + int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); + ASSERT_TRUE(fd > 0); + + const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; + size_t length = strlen(data); + ssize_t result = Iouring::write(fd, (const void *) data, length); + ASSERT_TRUE(result > 0); + ASSERT_TRUE(result == static_cast(length)); + + lseek(fd, 0, SEEK_SET); + + char buf[128]; + result = Iouring::read(fd, (void *) buf, 128); + ASSERT_TRUE(result > 0); + ASSERT_TRUE(result == static_cast(length)); + buf[result] = '\0'; + ASSERT_STREQ(data, buf); + + result = Iouring::close(fd); + ASSERT_TRUE(result == 0); + + result = Iouring::unlink(test_file); + ASSERT_TRUE(result == 0); + }); +} + +TEST(iouring, rename) { + coroutine::run([](void *arg) { + const char *oldpath = "/tmp/file_2"; + const char *newpath = "/tmp/file_3"; + int fd = Iouring::open(oldpath, O_CREAT | O_RDWR, 0666); + ASSERT_TRUE(fd > 0); + + int result = Iouring::close(fd); + ASSERT_TRUE(result == 0); + + result = Iouring::rename(oldpath, newpath); + ASSERT_TRUE(result == 0); + + result = Iouring::unlink(newpath); + ASSERT_TRUE(result == 0); + }); +} + +#ifdef HAVE_IOURING_STATX +TEST(iouring, fstat_and_stat) { + coroutine::run([](void *arg) { + struct stat statbuf {}; + int fd = Iouring::open(TEST_TMP_FILE, O_CREAT | O_RDWR, 0666); + ASSERT_TRUE(fd > 0); + + ASSERT_EQ(Iouring::write(fd, TEST_STR, strlen(TEST_STR)), strlen(TEST_STR)); + + int result = Iouring::fstat(fd, &statbuf); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(statbuf.st_size > 0); + + result = Iouring::close(fd); + ASSERT_TRUE(result == 0); + + statbuf = {}; + result = Iouring::stat(TEST_TMP_FILE, &statbuf); + ASSERT_TRUE(result == 0); + ASSERT_TRUE(statbuf.st_size > 0); + }); +} +#endif + +TEST(iouring, fsync_and_fdatasync) { + coroutine::run([](void *arg) { + const char *test_file = "/tmp/file_2"; + int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); + ASSERT_TRUE(fd > 0); + + const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; + size_t length = strlen(data); + ssize_t write_length = Iouring::write(fd, (const void *) data, length); + ASSERT_TRUE(write_length == static_cast(length)); + + int result = Iouring::fsync(fd); + ASSERT_TRUE(result == 0); + + write_length = Iouring::write(fd, (const void *) data, length); + ASSERT_TRUE(write_length == static_cast(length)); + + result = Iouring::fdatasync(fd); + ASSERT_TRUE(result == 0); + + result = Iouring::close(fd); + ASSERT_TRUE(result == 0); + + result = Iouring::unlink(test_file); + ASSERT_TRUE(result == 0); + }); +} + +#ifdef HAVE_IOURING_FTRUNCATE +TEST(iouring, ftruncate) { + coroutine::run([&](void *arg) { + const char *test_file = "/tmp/file_3"; + int fd = Iouring::open(test_file, O_CREAT | O_RDWR, 0666); + ASSERT_TRUE(fd > 0); + + const char *data = "aaaaaaaaaaaaaaaaaaaaaaa"; + size_t length = strlen(data); + ssize_t write_length = Iouring::write(fd, (const void *) data, length); + ASSERT_TRUE(write_length == static_cast(length)); + + int result = Iouring::ftruncate(fd, 0); + ASSERT_TRUE(result == 0); + Iouring::close(fd); + }); +} +#endif +#endif + +TEST(iouring, connect) { + signal(SIGPIPE, SIG_IGN); + coroutine::run([](void *arg) { + int fd = Iouring::socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(fd, -1); + + swoole::network::Address addr{}; + ASSERT_TRUE(addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80, true)); + + int rv = Iouring::connect(fd, &addr.addr.ss, addr.len); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + + rv = Iouring::write(fd, req.c_str(), req.length()); + ASSERT_EQ(rv, req.length()); + + char buf[4096]; + + rv = Iouring::read(fd, buf, sizeof(buf)); + ASSERT_GT(rv, 100); + + std::string s{buf}; + ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); + + Iouring::close(fd); + }); +} + +TEST(iouring, send_recv) { + signal(SIGPIPE, SIG_IGN); + coroutine::run([](void *arg) { + int fd = Iouring::socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(fd, -1); + + swoole::network::Address addr{}; + ASSERT_TRUE(addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80, true)); + + int rv = Iouring::connect(fd, &addr.addr.ss, addr.len); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + rv = Iouring::send(fd, req.c_str(), req.length(), 0); + ASSERT_EQ(rv, req.length()); + + char buf[4096]; + + rv = Iouring::recv(fd, buf, sizeof(buf), 0); + ASSERT_GT(rv, 100); + + std::string s{buf}; + ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); + + Iouring::close(fd); + }); +} + +TEST(iouring, sendfile) { + swoole::File file(swoole::test::get_jpg_file(), O_RDONLY); + + coroutine::run([&](void *arg) { + int pairs[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + Coroutine::create([&](void *) { + off_t offset = 0; + auto rv = Iouring::sendfile(pairs[0], file.get_fd(), &offset, file.get_size()); + ASSERT_EQ(rv, file.get_size()); + }); + + char data[250000]; + CoSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + ssize_t result = sock.read(data, 250000); + data[result] = '\0'; + sock.close(); + ASSERT_EQ(result, file.get_size()); + }); +} + +static int uring_create_server_socket(struct sockaddr_in *actual_server_addr, socklen_t *actual_server_addr_len) { + // Create a TCP socket using coroutine API + int server_sock = Iouring::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + EXPECT_GT(server_sock, 0); + + // Bind the socket to localhost with port 0 (auto-assign) + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + server_addr.sin_port = 0; + + int retval = Iouring::bind(server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr)); + EXPECT_EQ(retval, 0); + + // Listen on the socket + retval = Iouring::listen(server_sock, 128); + EXPECT_EQ(retval, 0); + + // Get the actual server port + EXPECT_EQ(getsockname(server_sock, (struct sockaddr *) actual_server_addr, actual_server_addr_len), 0); + + return server_sock; +} + +TEST(iouring, accept) { + coroutine::run([](void *arg) { + struct sockaddr_in actual_server_addr; + socklen_t actual_server_addr_len = sizeof(actual_server_addr); + auto server_sock = uring_create_server_socket(&actual_server_addr, &actual_server_addr_len); + + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + // Test that swoole_coroutine_accept works correctly + Coroutine::create([&](void *arg) { + // Give the server time to start listening + System::sleep(0.01); + + // Connect to the server using coroutine API + int client_sock = Iouring::socket(AF_INET, SOCK_STREAM, 0); + ASSERT_GT(client_sock, 0); + + // Connect to the server + auto retval = + Iouring::connect(client_sock, (struct sockaddr *) &actual_server_addr, actual_server_addr_len); + ASSERT_EQ(retval, 0); + + // Send a test message + const char *test_message = "test_data"; + ssize_t sent_bytes = Iouring::send(client_sock, test_message, strlen(test_message), 0); + ASSERT_EQ(sent_bytes, (ssize_t) strlen(test_message)); + + // Close the client socket + Iouring::close(client_sock); + }); + + // Accept the connection using coroutine API + int client_sock = Iouring::accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len); + ASSERT_GT(client_sock, 0); + + // Receive data from client + char buffer[256] = {}; + ssize_t received_bytes = Iouring::recv(client_sock, buffer, sizeof(buffer) - 1, 0); + ASSERT_GT(received_bytes, 0); + ASSERT_STREQ(buffer, "test_data"); + + // Close the client socket + Iouring::close(client_sock); + Iouring::close(server_sock); + }); +} + +TEST(iouring, sleep) { + coroutine::run([](void *arg) { + { + auto begin = swoole::microtime(); + ASSERT_EQ(Iouring::sleep(1, 200 * SW_NUM_MILLION), 0); + auto end = swoole::microtime(); + ASSERT_GE(end - begin, 1.2); + } + + { + auto begin = swoole::microtime(); + ASSERT_EQ(Iouring::sleep(0, 300 * SW_NUM_MILLION), 0); + auto end = swoole::microtime(); + ASSERT_GE(end - begin, 0.3); + } + }); +} + +TEST(iouring, wait_success) { + auto pid = swoole::test::spawn_exec([]() { sleep(1); }); + + coroutine::run([pid](void *arg) { + int status; + ASSERT_EQ(Iouring::wait(&status, 5), pid); + ASSERT_EQ(status, 0); + }); +} + +TEST(iouring, wait_timeout) { + auto pid = swoole::test::spawn_exec([]() { sleep(2000); }); + + coroutine::run([pid](void *arg) { + int status = 0x9501; + ASSERT_EQ(Iouring::wait(&status, 0.5), -1); + ASSERT_EQ(errno, ETIMEDOUT); + ASSERT_EQ(status, 0x9501); // After the timeout, the status will not be set. + }); + + kill(pid, SIGKILL); +} + +TEST(iouring, waitpid) { + auto pid = swoole::test::spawn_exec([]() { sleep(2000); }); + + coroutine::run([pid](void *arg) { + int status; + ASSERT_EQ(Iouring::waitpid(pid, &status, WNOHANG, -1), 0); + ASSERT_EQ(Iouring::waitpid(pid, &status, 0, 0.1), -1); + ASSERT_EQ(errno, ETIMEDOUT); + + kill(pid, SIGKILL); + System::sleep(0.3); + ASSERT_EQ(Iouring::waitpid(pid, &status, 0, 0.1), pid); + }); +} + +TEST(iouring, poll) { + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + coroutine::run([=](void *arg) { + int pair[2]; + struct pollfd fds[2]; + char buf[4096]; + socketpair(AF_UNIX, SOCK_STREAM, 0, pair); + + std::map results; + auto _fd0 = pair[0]; + auto _fd1 = pair[1]; + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN | POLLOUT; + ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); + ASSERT_TRUE(fds[0].revents & POLLOUT); + + Coroutine::create([=](void *) { + ssize_t result; + result = Iouring::write(_fd0, buffer->value(), buffer->get_length()); + ASSERT_GT(result, 0); + Iouring::sleep(0.1); + result = Iouring::write(_fd1, buffer->value(), 16 * 1024); + ASSERT_GT(result, 0); + }); + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd1; + fds[0].events = POLLIN; + + ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); + ASSERT_TRUE(fds[0].revents & POLLIN); + + ssize_t result = Iouring::read(_fd1, buf, sizeof(buf)); + ASSERT_GT(result, 1024); + + bzero(fds, sizeof(pollfd)); + ASSERT_EQ(Iouring::poll(fds, 2, 1000), -1); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); + + System::sleep(0.02); + + bzero(fds, sizeof(pollfd)); + fds[0].fd = _fd0; + fds[0].events = POLLIN | POLLOUT; + ASSERT_EQ(Iouring::poll(fds, 1, 1000), 1); + ASSERT_TRUE(fds[0].revents & POLLIN); + }); +} + +TEST(iouring, accept_timeout) { + coroutine::run([](void *arg) { + struct sockaddr_in actual_server_addr; + socklen_t actual_server_addr_len = sizeof(actual_server_addr); + auto server_sock = uring_create_server_socket(&actual_server_addr, &actual_server_addr_len); + + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + int client_sock = Iouring::accept(server_sock, (struct sockaddr *) &client_addr, &client_addr_len, 0, 0.1); + ASSERT_EQ(client_sock, -1); + + Iouring::close(server_sock); + }); +} diff --git a/core-tests/src/coroutine/socket.cpp b/core-tests/src/coroutine/socket.cpp index 690924b5ad..824f061cc5 100644 --- a/core-tests/src/coroutine/socket.cpp +++ b/core-tests/src/coroutine/socket.cpp @@ -21,16 +21,19 @@ #include "test_coroutine.h" #include "test_server.h" -using namespace swoole::test; - +using swoole::Coroutine; using swoole::HttpProxy; using swoole::Protocol; +using swoole::RecvData; using swoole::Socks5Proxy; using swoole::String; using swoole::coroutine::Socket; using swoole::coroutine::System; using swoole::network::Address; using swoole::network::IOVector; +using swoole::test::coroutine; +using swoole::test::create_socket_pair; +using swoole::test::Process; using swoole::test::Server; const std::string host = "www.baidu.com"; @@ -47,13 +50,95 @@ TEST(coroutine_socket, connect_refused) { TEST(coroutine_socket, connect_timeout) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); + sock.set_timeout(0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_DNS), 0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_CONNECT), 0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_READ), 0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_WRITE), 0.5); + + sock.set_timeout(1.5, SW_TIMEOUT_RDWR); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_DNS), 0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_CONNECT), 0.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_READ), 1.5); + ASSERT_EQ(sock.get_timeout(SW_TIMEOUT_WRITE), 1.5); + bool retval = sock.connect("192.0.0.1", 9801); ASSERT_EQ(retval, false); ASSERT_EQ(sock.errCode, ETIMEDOUT); }); } +TEST(coroutine_socket, timeout_controller) { + coroutine::run([](void *arg) { + const int port = __LINE__ + TEST_PORT; + Coroutine::create([](void *arg) { + Socket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.listen(128), true); + + Socket *conn = sock.accept(); + conn->send(TEST_STR); + System::sleep(1); + delete conn; + }); + + Socket sock(SW_SOCK_TCP); + Socket::TimeoutController tc(&sock, 0.5, SW_TIMEOUT_ALL); + ASSERT_TRUE(sock.connect("127.0.0.1", port)); + + char buf[128]; + off_t offset = 0; + sock.errCode = 0; + while (true) { + if (sw_unlikely(tc.has_timedout(SW_TIMEOUT_READ))) { + break; + } + auto rv = sock.recv(buf + offset, sizeof(buf) - offset); + if (rv <= 0) { + break; + } + offset += rv; + } + ASSERT_TRUE(tc.has_timedout(SW_TIMEOUT_READ)); + ASSERT_EQ(sock.errCode, ETIMEDOUT); + }); +} + +TEST(coroutine_socket, timeout_setter) { + coroutine::run([](void *arg) { + const int port = __LINE__ + TEST_PORT; + Coroutine::create([](void *arg) { + Socket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.listen(128), true); + + Socket *conn = sock.accept(); + conn->send(TEST_STR); + System::sleep(1); + delete conn; + }); + + Socket sock(SW_SOCK_TCP); + Socket::TimeoutSetter ts(&sock, 0.5, SW_TIMEOUT_ALL); + ASSERT_TRUE(sock.connect("127.0.0.1", port)); + + char buf[128]; + off_t offset = 0; + sock.errCode = 0; + while (true) { + auto rv = sock.recv(buf + offset, sizeof(buf) - offset); + if (rv <= 0) { + break; + } + offset += rv; + } + ASSERT_EQ(sock.errCode, ETIMEDOUT); + }); +} + TEST(coroutine_socket, connect_with_dns) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); @@ -63,16 +148,34 @@ TEST(coroutine_socket, connect_with_dns) { }); } +TEST(coroutine_socket, tcp6) { + coroutine::run([](void *arg) { + Socket sock(SW_SOCK_TCP6); + bool retval = sock.connect("::1", 80); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + }); +} + +TEST(coroutine_socket, unixsock_fail) { + coroutine::run([](void *arg) { + Socket sock(SW_SOCK_UNIX_STREAM); + bool retval = sock.connect("/tmp/unix.sock"); + ASSERT_EQ(retval, false); + ASSERT_EQ(sock.errCode, ENOENT); + }); +} + TEST(coroutine_socket, recv_success) { pid_t pid; + int port = swoole::test::get_random_port(); - Process proc([](Process *proc) { - on_receive_lambda_type receive_fn = [](ON_RECEIVE_PARAMS) { + Process proc([port](Process *proc) { + Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); + serv.on("Receive", [](ON_RECEIVE_PARAMS) { SERVER_THIS->send(req->info.fd, req->data, req->info.len); - }; - - Server serv(TEST_HOST, TEST_PORT, swoole::Server::MODE_BASE, SW_SOCK_TCP); - serv.on("onReceive", (void *) receive_fn); + return 0; + }); serv.start(); }); @@ -80,9 +183,9 @@ TEST(coroutine_socket, recv_success) { sleep(1); // wait for the test server to start - coroutine::run([](void *arg) { + coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect(TEST_HOST, TEST_PORT, -1); + bool retval = sock.connect(TEST_HOST, port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send(SW_STRS("hello world\n")); @@ -99,12 +202,14 @@ TEST(coroutine_socket, recv_success) { TEST(coroutine_socket, recv_fail) { pid_t pid; + int port = swoole::test::get_random_port(); - Process proc([](Process *proc) { - on_receive_lambda_type receive_fn = [](ON_RECEIVE_PARAMS) { SERVER_THIS->close(req->info.fd, 0); }; - - Server serv(TEST_HOST, TEST_PORT, swoole::Server::MODE_BASE, SW_SOCK_TCP); - serv.on("onReceive", (void *) receive_fn); + Process proc([port](Process *proc) { + Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); + serv.on("Receive", [](ON_PACKET_PARAMS) -> int { + serv->close(req->info.fd, 0); + return 0; + }); serv.start(); }); @@ -112,9 +217,9 @@ TEST(coroutine_socket, recv_fail) { sleep(1); // wait for the test server to start - coroutine::run([](void *arg) { + coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect(TEST_HOST, TEST_PORT, -1); + bool retval = sock.connect(TEST_HOST, port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("close", 6); @@ -129,13 +234,14 @@ TEST(coroutine_socket, recv_fail) { } TEST(coroutine_socket, bind_success) { - coroutine::run([](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); Socket sock_1(SW_SOCK_UNIX_DGRAM); - retval = sock_1.bind("127.0.0.1", 9909); + retval = sock_1.bind("/tmp/swoole-core-tests.sock"); ASSERT_EQ(retval, true); }); } @@ -154,39 +260,37 @@ TEST(coroutine_socket, bind_fail) { } TEST(coroutine_socket, listen) { - coroutine::run([](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run([port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); }); } TEST(coroutine_socket, accept) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); Socket *conn = sock.accept(); ASSERT_NE(conn, nullptr); + delete conn; }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.close(); }}); } -#define CRLF "\r\n" -#define EOF_PACKET "hello world" CRLF -#define EOF_PACKET_2 "php&swoole, java&golang" CRLF -#define RECV_TIMEOUT 10.0 - static void socket_set_eof_protocol(Socket &sock) { memcpy(sock.protocol.package_eof, SW_STRL(CRLF)); sock.protocol.package_eof_len = 2; @@ -194,9 +298,10 @@ static void socket_set_eof_protocol(Socket &sock) { } TEST(coroutine_socket, eof_1) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -207,9 +312,9 @@ TEST(coroutine_socket, eof_1) { conn->send(EOF_PACKET); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); @@ -228,11 +333,11 @@ TEST(coroutine_socket, eof_1) { } TEST(coroutine_socket, eof_2) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); - ASSERT_EQ(retval, true); - ASSERT_EQ(sock.listen(128), true); + ASSERT_TRUE(sock.bind("127.0.0.1", port)); + ASSERT_TRUE(sock.listen(128)); Socket *conn = sock.accept(); char buf[1024]; @@ -241,9 +346,9 @@ TEST(coroutine_socket, eof_2) { conn->send(EOF_PACKET EOF_PACKET_2); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); @@ -276,9 +381,10 @@ TEST(coroutine_socket, eof_2) { } TEST(coroutine_socket, eof_3) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -289,9 +395,9 @@ TEST(coroutine_socket, eof_3) { conn->shutdown(); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); @@ -304,9 +410,10 @@ TEST(coroutine_socket, eof_3) { } TEST(coroutine_socket, eof_4) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -318,9 +425,9 @@ TEST(coroutine_socket, eof_4) { conn->shutdown(); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); @@ -336,9 +443,11 @@ TEST(coroutine_socket, eof_4) { } TEST(coroutine_socket, eof_5) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + size_t pkt_len = 512 * 1024; + coroutine::run({[pkt_len, port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -347,31 +456,34 @@ TEST(coroutine_socket, eof_5) { ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); - swString *s = swoole::make_string(128 * 1024); - s->repeat("A", 1, 128 * 1024 - 16); + String *s = swoole::make_string(pkt_len); + s->repeat("A", 1, pkt_len - 16); s->append(SW_STRL(CRLF)); + conn->get_socket()->set_send_buffer_size(65536); conn->send_all(s->str, s->length); }, - [](void *arg) { + [pkt_len, port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); socket_set_eof_protocol(sock); + sock.get_socket()->set_recv_buffer_size(65536); ssize_t l = sock.recv_packet(RECV_TIMEOUT); - ASSERT_EQ(l, 128 * 1024 - 14); + ASSERT_EQ(l, pkt_len - 14); }}); } TEST(coroutine_socket, eof_6) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -380,16 +492,16 @@ TEST(coroutine_socket, eof_6) { ssize_t l = conn->recv(buf, sizeof(buf)); EXPECT_EQ(string(buf, l), string("start\r\n")); - swString s(128 * 1024); + String s(128 * 1024); s.repeat("A", 1, 128 * 1024 - 16); s.append(SW_STRL(CRLF)); conn->send_all(s.value(), s.get_length()); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); sock.send("start\r\n"); @@ -428,9 +540,10 @@ static void socket_set_length_protocol_2(Socket &sock) { } TEST(coroutine_socket, length_1) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -442,9 +555,9 @@ TEST(coroutine_socket, length_1) { conn->send(buf, l + 2); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -460,9 +573,10 @@ TEST(coroutine_socket, length_1) { } TEST(coroutine_socket, length_2) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -473,9 +587,9 @@ TEST(coroutine_socket, length_2) { conn->send(buf, 2); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -491,9 +605,10 @@ TEST(coroutine_socket, length_2) { } TEST(coroutine_socket, length_3) { + const int port = __LINE__ + TEST_PORT; coroutine::run({[](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -507,7 +622,7 @@ TEST(coroutine_socket, length_3) { [](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -523,9 +638,9 @@ TEST(coroutine_socket, length_3) { static string pkt_1; static string pkt_2; -static void length_protocol_server_func(void *arg) { +static void length_protocol_server_func(void *arg, int port) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -552,11 +667,12 @@ static void length_protocol_server_func(void *arg) { } TEST(coroutine_socket, length_4) { - coroutine::run({length_protocol_server_func, + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { length_protocol_server_func(arg, port); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -584,11 +700,12 @@ TEST(coroutine_socket, length_4) { } TEST(coroutine_socket, length_5) { - coroutine::run({length_protocol_server_func, + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { length_protocol_server_func(arg, port); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -613,9 +730,10 @@ TEST(coroutine_socket, length_5) { } TEST(coroutine_socket, length_7) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -628,9 +746,9 @@ TEST(coroutine_socket, length_7) { conn->send(buf + 2, 2); }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -646,9 +764,10 @@ TEST(coroutine_socket, length_7) { } TEST(coroutine_socket, event_hup) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9502); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -660,14 +779,14 @@ TEST(coroutine_socket, event_hup) { delete conn; }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9502, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); auto buf = sock.get_read_buffer(); - swoole::Coroutine::create([&sock](void *args) { + Coroutine::create([&sock](void *args) { System::sleep(0.01); sock.shutdown(SHUT_RDWR); }); @@ -678,9 +797,10 @@ TEST(coroutine_socket, event_hup) { } TEST(coroutine_socket, recv_line) { - coroutine::run({[](void *arg) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.bind("127.0.0.1", 9909); + bool retval = sock.bind("127.0.0.1", port); ASSERT_EQ(retval, true); ASSERT_EQ(sock.listen(128), true); @@ -694,9 +814,9 @@ TEST(coroutine_socket, recv_line) { delete conn; }, - [](void *arg) { + [port](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect("127.0.0.1", 9909, -1); + bool retval = sock.connect("127.0.0.1", port, -1); ASSERT_EQ(retval, true); ASSERT_EQ(sock.errCode, 0); @@ -731,16 +851,33 @@ TEST(coroutine_socket, recv_line) { TEST(coroutine_socket, getsockname) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); - bool retval = sock.connect(host, 80); - ASSERT_EQ(retval, true); - - Address sa; - bool result = sock.getsockname(&sa); + ASSERT_TRUE(sock.connect(host, 80)); + ASSERT_TRUE(sock.getsockname()); sock.close(); - ASSERT_EQ(result, true); }); } +TEST(coroutine_socket, buffer) { + Socket sock(SW_SOCK_TCP); + auto rbuf = sock.get_read_buffer(); + auto wbuf = sock.get_write_buffer(); + + auto rbuf_pop = sock.pop_read_buffer(); + auto wbuf_pop = sock.pop_write_buffer(); + + ASSERT_EQ(rbuf, rbuf_pop); + ASSERT_EQ(wbuf, wbuf_pop); + + auto rbuf2 = sock.get_read_buffer(); + auto wbuf2 = sock.get_write_buffer(); + + ASSERT_NE(rbuf2, rbuf); + ASSERT_NE(wbuf2, wbuf); + + delete rbuf_pop; + delete wbuf_pop; +} + TEST(coroutine_socket, check_liveness) { coroutine::run([](void *arg) { Socket sock(SW_SOCK_TCP); @@ -762,7 +899,7 @@ TEST(coroutine_socket, write_and_read) { std::string text = "Hello World"; size_t length = text.length(); - swoole::Coroutine::create([&](void *) { + Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.write(text.c_str(), length); sock.close(); @@ -787,7 +924,7 @@ TEST(coroutine_socket, write_and_read_2) { std::string text = "Hello World"; size_t length = text.length(); - swoole::Coroutine::create([&](void *) { + Coroutine::create([&](void *) { Socket sock(pairs[0], AF_UNIX, SOCK_STREAM, 0); ssize_t result = sock.write(text.c_str(), length); sock.close(); @@ -812,7 +949,7 @@ TEST(coroutine_socket, writev_and_readv) { size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); - swoole::Coroutine::create([&](void *) { + Coroutine::create([&](void *) { std::unique_ptr iov(new iovec[iovcnt]); for (int i = 0; i < iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); @@ -846,44 +983,88 @@ TEST(coroutine_socket, writev_and_readv) { }); } +TEST(coroutine_socket, send_and_recv_all) { + coroutine::run([&](void *arg) { + int pairs[2]; + + String wbuf; + wbuf.append_random_bytes(4 * 1024 * 1024, false); + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + Coroutine::create([&](void *) { + Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_send_buffer_size(65536); + + ASSERT_EQ(sock.send_all(wbuf.str, wbuf.length), wbuf.length); + + System::sleep(0.1); + + sock.close(); + }); + + Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_recv_buffer_size(65536); + + String rbuf(wbuf.length); + ssize_t result = sock.recv_all(rbuf.str, wbuf.length); + ASSERT_EQ(result, wbuf.length); + ASSERT_MEMEQ(wbuf.str, rbuf.str, wbuf.length); + System::sleep(0.1); + sock.close(); + }); +} + TEST(coroutine_socket, writevall_and_readvall) { coroutine::run([&](void *arg) { - int iovcnt = 3; + int write_iovcnt = 4; int pairs[2]; - std::string text = "Hello World"; + + char buf[65536]; + swoole_random_bytes(buf, sizeof(buf)); + + std::string text(buf, sizeof(buf)); size_t length = text.length(); socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); - swoole::Coroutine::create([&](void *) { - std::unique_ptr iov(new iovec[iovcnt]); - for (int i = 0; i < iovcnt; i++) { + Coroutine::create([&](void *) { + std::unique_ptr iov(new iovec[write_iovcnt]); + for (int i = 0; i < write_iovcnt; i++) { iov[i].iov_base = (void *) text.c_str(); iov[i].iov_len = length; } - IOVector io_vector((struct iovec *) iov.get(), iovcnt); Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); - ssize_t result = sock.writev_all(&io_vector); + sock.get_socket()->set_send_buffer_size(sizeof(buf)); + + IOVector io_vector1((struct iovec *) iov.get(), write_iovcnt); + ASSERT_EQ(sock.writev_all(&io_vector1), write_iovcnt * sizeof(buf)); + + System::sleep(0.01); + + IOVector io_vector2((struct iovec *) iov.get(), write_iovcnt); + ASSERT_EQ(sock.writev_all(&io_vector2), write_iovcnt * sizeof(buf)); + sock.close(); - ASSERT_EQ(result, length * 3); }); - std::vector results(iovcnt); - std::unique_ptr iov(new iovec[iovcnt]); - for (int i = 0; i < iovcnt; i++) { - iov[i].iov_base = (void *) results[i].c_str(); + int read_iovcnt = 8; + std::unique_ptr iov(new iovec[read_iovcnt]); + for (int i = 0; i < read_iovcnt; i++) { + iov[i].iov_base = sw_malloc(length); iov[i].iov_len = length; } - IOVector io_vector((struct iovec *) iov.get(), iovcnt); + IOVector io_vector((struct iovec *) iov.get(), read_iovcnt); Socket sock(pairs[1], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_recv_buffer_size(sizeof(buf)); + ssize_t result = sock.readv_all(&io_vector); sock.close(); - ASSERT_EQ(result, length * 3); + ASSERT_EQ(result, length * read_iovcnt); - for (auto iter = results.begin(); iter != results.end(); iter++) { - (*iter)[length] = '\0'; - ASSERT_STREQ(text.c_str(), (*iter).c_str()); + for (int i = 0; i < read_iovcnt; i++) { + ASSERT_MEMEQ(iov[i].iov_base, buf, sizeof(buf)); + sw_free(iov[i].iov_base); } }); } @@ -892,8 +1073,8 @@ TEST(coroutine_socket, sendfile) { coroutine::run([&](void *arg) { int pairs[2]; socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); - swoole::Coroutine::create([&](void *) { - std::string file = get_jpg_file(); + Coroutine::create([&](void *) { + std::string file = swoole::test::get_jpg_file(); Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); bool result = sock.sendfile(file.c_str(), 0, 0); sock.close(); @@ -909,7 +1090,7 @@ TEST(coroutine_socket, sendfile) { }); } -void test_sendto_recvfrom(enum swSocketType sock_type) { +static void test_sendto_recvfrom(enum swSocketType sock_type) { coroutine::run([&](void *arg) { std::string server_text = "hello world!!!"; size_t server_length = server_text.length(); @@ -917,25 +1098,28 @@ void test_sendto_recvfrom(enum swSocketType sock_type) { size_t client_length = client_text.length(); const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; + const char *local = "localhost"; + + int port = swoole::test::get_random_port(); Socket sock_server(sock_type); Socket sock_client(sock_type); - sock_server.bind(ip, 8080); - sock_client.bind(ip, 8081); + sock_server.bind(ip, port); + sock_client.bind(ip, port + 1); ON_SCOPE_EXIT { sock_server.close(); sock_client.close(); }; - sock_server.sendto(ip, 8081, (const void *) server_text.c_str(), server_length); + sock_server.sendto(ip, port + 1, (const void *) server_text.c_str(), server_length); char data_from_server[128] = {}; struct sockaddr_in serveraddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(ip); - serveraddr.sin_port = htons(8080); + serveraddr.sin_port = htons(port); socklen_t addr_length = sizeof(serveraddr); // receive data from server @@ -947,7 +1131,7 @@ void test_sendto_recvfrom(enum swSocketType sock_type) { // receive data from client char data_from_client[128] = {}; - sock_client.sendto(ip, 8080, (const void *) client_text.c_str(), client_length); + sock_client.sendto(local, port, (const void *) client_text.c_str(), client_length); result = sock_server.recvfrom(data_from_client, client_length); data_from_client[client_length] = '\0'; ASSERT_EQ(result, client_length); @@ -960,78 +1144,166 @@ TEST(coroutine_socket, sendto_recvfrom_udp) { test_sendto_recvfrom(SW_SOCK_UDP6); } -void socket_send(Socket &sock, int port) { - bool retval = sock.connect(host, port); +static void socket_test_request_baidu(Socket &sock) { + ASSERT_GT(sock.send(SW_STRL(TEST_REQUEST_BAIDU)), 0); + + String buf(65536); + while (true) { + char rbuf[4096]; + ssize_t nr = sock.recv(rbuf, sizeof(rbuf)); + if (nr <= 0) { + break; + } + buf.append(rbuf, nr); + } + ASSERT_TRUE(buf.contains("www.baidu.com")); +} + +static void proxy_test(Socket &sock, bool https) { + if (https) { + sock.enable_ssl_encrypt(); + } + + bool retval = sock.connect(host, https ? 443 : 80); ON_SCOPE_EXIT { sock.close(); }; ASSERT_EQ(retval, true); - if (443 == port) { + if (https) { ASSERT_NE(sock.ssl_get_peer_cert(), ""); } - sock.send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nUser-Agent: Mozilla/5.0 (Windows NT " - "10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36\r\n\r\n"); + socket_test_request_baidu(sock); +} - char buf[65536]; - ssize_t result = 0; - ssize_t recv_total = 0; - while (true) { - result = sock.recv(buf + recv_total, 65536 - recv_total); - if (0 == result) { - break; - } - recv_total += result; +static void proxy_set_socks5_proxy(Socket &socket, int port, bool auth) { + std::string username, password; + if (auth) { + username = std::string(TEST_SOCKS5_PROXY_USER); + password = std::string(TEST_SOCKS5_PROXY_PASSWORD); } - std::string content(buf); - ASSERT_NE(content.find("baidu"), std::string::npos); + socket.set_socks5_proxy(TEST_SOCKS5_PROXY_HOST, port, username, password); } -TEST(coroutine_socket, socks5_proxy) { +TEST(coroutine_socket, https_get_with_socks5_proxy) { coroutine::run([](void *arg) { - Socket sock(SW_SOCK_TCP); - sock.socks5_proxy = new Socks5Proxy(); - sock.socks5_proxy->host = std::string("127.0.0.1"); - sock.socks5_proxy->port = 1080; - sock.socks5_proxy->dns_tunnel = 1; - sock.socks5_proxy->method = 0x02; - sock.socks5_proxy->username = std::string("user"); - sock.socks5_proxy->password = std::string("password"); - - socket_send(sock, 80); + if (swoole::test::is_github_ci()) { + Socket sock(SW_SOCK_TCP); + proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); + proxy_test(sock, true); + } + // no auth + { + Socket sock(SW_SOCK_TCP); + proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_NO_AUTH_PORT, false); + proxy_test(sock, true); + } }); } -TEST(coroutine_socket, http_proxy) { +TEST(coroutine_socket, http_get_with_socks5_proxy) { + coroutine::run([](void *arg) { + if (swoole::test::is_github_ci()) { + Socket sock(SW_SOCK_TCP); + proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); + proxy_test(sock, false); + } + // no auth + { + Socket sock(SW_SOCK_TCP); + proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_NO_AUTH_PORT, false); + proxy_test(sock, false); + } + }); +} + +static void proxy_set_http_proxy(Socket &socket) { + std::string username, password; + if (swoole::test::is_github_ci()) { + username = std::string(TEST_HTTP_PROXY_USER); + password = std::string(TEST_HTTP_PROXY_PASSWORD); + } + socket.set_http_proxy(TEST_HTTP_PROXY_HOST, TEST_HTTP_PROXY_PORT, username, password); +} + +TEST(coroutine_socket, http_get_with_http_proxy) { coroutine::run([&](void *arg) { Socket sock(SW_SOCK_TCP); - sock.http_proxy = new HttpProxy(); - sock.http_proxy->proxy_host = std::string("127.0.0.1"); - sock.http_proxy->proxy_port = 8888; - sock.http_proxy->username = std::string("user"); - sock.http_proxy->password = std::string("password"); + proxy_set_http_proxy(sock); + proxy_test(sock, false); + }); +} - socket_send(sock, 80); +TEST(coroutine_socket, https_get_with_http_proxy) { + coroutine::run([&](void *arg) { + Socket sock(SW_SOCK_TCP); + proxy_set_http_proxy(sock); + proxy_test(sock, true); }); } -#ifdef SW_USE_OPENSSL TEST(coroutine_socket, ssl) { coroutine::run([&](void *arg) { Socket sock(SW_SOCK_TCP); sock.enable_ssl_encrypt(); - sock.get_ssl_context()->cert_file = swoole::test::get_root_path() + "/tests/include/ssl_certs/client.crt"; - sock.get_ssl_context()->key_file = swoole::test::get_root_path() + "/tests/include/ssl_certs/client.key"; - sock.get_ssl_context()->verify_peer = false; - sock.get_ssl_context()->allow_self_signed = true; - sock.get_ssl_context()->cafile = swoole::test::get_root_path() + "/tests/include/ssl_certs/ca.crt"; + sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/client.crt"); + sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/client.key"); + sock.set_ssl_verify_peer(false); + sock.set_ssl_allow_self_signed(true); + sock.set_ssl_cafile(swoole::test::get_ssl_dir() + "/ca.crt"); - socket_send(sock, 443); + proxy_test(sock, true); }); } -#endif + +TEST(coroutine_socket, ssl_accept) { + const int port = __LINE__ + TEST_PORT; + auto svr = [port](void *arg) { + Socket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + + sock.enable_ssl_encrypt(); + sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/server.crt"); + sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/server.key"); + sock.set_ssl_dhparam(swoole::test::get_ssl_dir() + "/dhparams.pem"); + sock.set_ssl_ecdh_curve("secp256r1"); + + ASSERT_EQ(sock.listen(128), true); + + Socket *conn = sock.accept(); + ASSERT_NE(conn, nullptr); + ASSERT_TRUE(conn->ssl_handshake()); + conn->send(EOF_PACKET); + char rbuf[1024]; + auto n = conn->recv(rbuf, sizeof(rbuf)); + rbuf[n] = 0; + + ASSERT_STREQ(rbuf, EOF_PACKET_2); + conn->close(); + delete conn; + }; + + auto cli = [port](void *arg) { + Socket sock(SW_SOCK_TCP); + sock.enable_ssl_encrypt(); + bool retval = sock.connect("127.0.0.1", port, -1); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + char rbuf[1024]; + auto n = sock.recv(rbuf, sizeof(rbuf)); + rbuf[n] = 0; + ASSERT_STREQ(rbuf, EOF_PACKET); + sock.send(EOF_PACKET_2); + + sock.close(); + }; + + coroutine::run({svr, cli}); +} TEST(coroutine_socket, peek) { coroutine::run([&](void *arg) { @@ -1040,7 +1312,7 @@ TEST(coroutine_socket, peek) { std::string text = "Hello World"; size_t length = text.length(); - swoole::Coroutine::create([&](void *) { + Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); ssize_t result = sock.write(text.c_str(), length); sock.close(); @@ -1063,9 +1335,9 @@ TEST(coroutine_socket, sendmsg_and_recvmsg) { socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); std::string text = "Hello World"; - size_t length = text.length(); + const size_t length = text.length(); - swoole::Coroutine::create([&](void *) { + Coroutine::create([&](void *) { Socket sock(pairs[0], SW_SOCK_UNIX_STREAM); struct msghdr msg; struct iovec ivec; @@ -1108,3 +1380,191 @@ TEST(coroutine_socket, sendmsg_and_recvmsg) { ASSERT_STREQ(buf, text.c_str()); }); } + +std::pair, std::shared_ptr > swoole::test::create_socket_pair() { + int pairs[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + auto sock0 = new Socket(pairs[0], SW_SOCK_UNIX_STREAM); + auto sock1 = new Socket(pairs[1], SW_SOCK_UNIX_STREAM); + + sock0->get_socket()->set_buffer_size(65536); + sock1->get_socket()->set_buffer_size(65536); + + std::pair, std::shared_ptr > result(sock0, sock1); + return result; +} + +TEST(coroutine_socket, close) { + coroutine::run([&](void *arg) { + auto pair = create_socket_pair(); + + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + std::map results; + auto _sock = pair.first; + + // write co + Coroutine::create([&](void *) { + SW_LOOP_N(32) { + ssize_t result = _sock->write(buffer->value(), buffer->get_length()); + if (result < 0 && _sock->errCode == ECANCELED) { + ASSERT_FALSE(_sock->close()); + ASSERT_EQ(_sock->errCode, SW_ERROR_CO_SOCKET_CLOSE_WAIT); + results["write"] = true; + ASSERT_EQ(_sock->write(buffer->value(), buffer->get_length()), -1); + ASSERT_EQ(_sock->errCode, EBADF); + break; + } + } + }); + + // read co + Coroutine::create([&](void *) { + SW_LOOP_N(32) { + char buf[4096]; + ssize_t result = _sock->read(buf, sizeof(buf)); + if (result < 0 && _sock->errCode == ECANCELED) { + ASSERT_TRUE(_sock->close()); + results["read"] = true; + break; + } + } + }); + + System::sleep(0.1); + ASSERT_FALSE(_sock->close()); + ASSERT_EQ(_sock->errCode, SW_ERROR_CO_SOCKET_CLOSE_WAIT); + ASSERT_TRUE(_sock->is_closed()); + ASSERT_TRUE(results["write"]); + ASSERT_TRUE(results["read"]); + ASSERT_FALSE(_sock->close()); + ASSERT_EQ(_sock->errCode, EBADF); + }); +} + +TEST(coroutine_socket, cancel) { + coroutine::run([&](void *arg) { + auto pair = create_socket_pair(); + + auto buffer = sw_tg_buffer(); + buffer->clear(); + buffer->append_random_bytes(256 * 1024, false); + + std::map results; + // read co + Coroutine::create([&](void *) { + SW_LOOP_N(32) { + char buf[4096]; + ssize_t result = pair.first->read(buf, sizeof(buf)); + if (result < 0 && pair.first->errCode == ECANCELED) { + results["read"] = true; + break; + } + } + }); + + System::sleep(0.1); + pair.first->cancel(SW_EVENT_READ); + ASSERT_TRUE(results["read"]); + }); +} + +TEST(coroutine_socket, get_event_str) { + Socket sock; + ASSERT_STREQ(sock.get_event_str(SW_EVENT_READ), "reading"); + ASSERT_STREQ(sock.get_event_str(SW_EVENT_WRITE), "writing"); +} + +TEST(coroutine_socket, option) { + Socket sock(SW_SOCK_TCP); + int optval; + + ASSERT_TRUE(sock.get_option(SOL_SOCKET, SO_RCVBUF, &optval)); + ASSERT_GT(optval, 65536); + + optval *= 2; + ASSERT_TRUE(sock.set_option(SOL_SOCKET, SO_RCVBUF, optval)); +} + +static void test_ssl_verify() { + Socket sock(SW_SOCK_TCP); + sock.enable_ssl_encrypt(); + sock.set_tls_host_name(TEST_HTTP_DOMAIN); + sock.set_ssl_verify_peer(true); + ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 443)); + ASSERT_TRUE(sock.ssl_verify(false)); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + ASSERT_EQ(sock.send(req.c_str(), req.length()), req.length()); + ASSERT_TRUE(sock.check_liveness()); + + String buf(65536); + SW_LOOP { + auto n = sock.recv(buf.str + buf.length, buf.size - buf.length); + if (n <= 0) { + break; + } + buf.grow(n); + } + + ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); + + usleep(50000); + ASSERT_FALSE(sock.check_liveness()); +} + +TEST(coroutine_socket, ssl_verify) { + coroutine::run([](void *arg) { test_ssl_verify(); }); +} + +TEST(coroutine_socket, shutdown) { + coroutine::run([](void *arg) { + Socket sock(SW_SOCK_TCP); + ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 80)); + ASSERT_TRUE(sock.shutdown(SHUT_RD)); + ASSERT_FALSE(sock.shutdown(SHUT_RD)); + ASSERT_ERREQ(ENOTCONN); + + ASSERT_TRUE(sock.shutdown(SHUT_WR)); + ASSERT_FALSE(sock.shutdown(SHUT_WR)); + ASSERT_ERREQ(ENOTCONN); + + ASSERT_FALSE(sock.shutdown()); + ASSERT_ERREQ(ENOTCONN); + }); +} + +TEST(coroutine_socket, recv_packet) { + coroutine::run([](void *arg) { + Socket sock(SW_SOCK_TCP); + ASSERT_TRUE(sock.connect(TEST_HTTP_DOMAIN, 80)); + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + ASSERT_EQ(sock.send(req.c_str(), req.length()), req.length()); + ASSERT_TRUE(sock.check_liveness()); + auto n = sock.recv_packet(); + ASSERT_GT(n, 0); + auto buf = sock.get_read_buffer(); + ASSERT_TRUE(buf->contains(TEST_HTTP_EXPECT)); + }); +} + +TEST(coroutine_socket, set_error) { + Socket sock(SW_SOCK_TCP); + sock.set_err(1000, std::string(TEST_STR)); + + ASSERT_EQ(sock.errCode, 1000); + ASSERT_STREQ(sock.errMsg, TEST_STR); +} + +TEST(coroutine_socket, reinit) { + coroutine::run([](void *arg) { + Socket sock(SW_SOCK_TCP6); + ASSERT_EQ(sock.get_sock_domain(), AF_INET6); + proxy_set_socks5_proxy(sock, TEST_SOCKS5_PROXY_PORT, true); + sock.connect("::1", 80); + ASSERT_EQ(sock.get_sock_domain(), AF_INET); + }); +} diff --git a/core-tests/src/coroutine/system.cpp b/core-tests/src/coroutine/system.cpp index 95dac5a4d8..2400c447df 100644 --- a/core-tests/src/coroutine/system.cpp +++ b/core-tests/src/coroutine/system.cpp @@ -49,23 +49,21 @@ TEST(coroutine_system, flock) { ASSERT_EQ(swoole_random_bytes(buf->str, buf->size - 1), buf->size - 1); buf->str[buf->size - 1] = 0; - swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - - Coroutine::create([&buf](void *) { + test::coroutine::run([&buf](void *) { int fd = swoole_coroutine_open(test_file, File::WRITE | File::CREATE, 0666); ASSERT_TRUE(fd > 0); - swoole_coroutine_flock_ex(test_file, fd, LOCK_EX); + swoole_coroutine_flock(fd, LOCK_EX); for (int i = 0; i < 4; i++) { Coroutine::create([&buf](void *) { int fd = swoole_coroutine_open(test_file, File::READ, 0); ASSERT_TRUE(fd > 0); - swoole_coroutine_flock_ex(test_file, fd, LOCK_SH); + swoole_coroutine_flock(fd, LOCK_SH); String read_buf(DATA_SIZE_2); auto rn = swoole_coroutine_read(fd, read_buf.str, read_buf.size - 1); ASSERT_EQ(rn, read_buf.size - 1); read_buf.str[read_buf.size - 1] = 0; - swoole_coroutine_flock_ex(test_file, fd, LOCK_UN); + swoole_coroutine_flock(fd, LOCK_UN); EXPECT_STREQ(read_buf.str, buf->str); swoole_coroutine_close(fd); }); @@ -73,27 +71,37 @@ TEST(coroutine_system, flock) { auto wn = swoole_coroutine_write(fd, buf->str, buf->size - 1); ASSERT_EQ(wn, buf->size - 1); - swoole_coroutine_flock_ex(test_file, fd, LOCK_UN); + swoole_coroutine_flock(fd, LOCK_UN); swoole_coroutine_close(fd); }); - swoole_event_wait(); unlink(test_file); } TEST(coroutine_system, flock_nb) { - coroutine::run([&](void *arg) { + swoole::coroutine::run([&](void *arg) { + DEBUG() << "[thread-1] open" << std::endl; int fd = swoole_coroutine_open(test_file, File::WRITE | File::CREATE, 0666); - ASSERT_EQ(swoole_coroutine_flock_ex(test_file, fd, LOCK_EX | LOCK_NB), 0); + DEBUG() << "[thread-1] LOCK_EX | LOCK_NB" << std::endl; + ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX | LOCK_NB), 0); + + std::thread t([]() { + int fd = open(test_file, File::WRITE | File::CREATE, 0666); + DEBUG() << "[thread-2] LOCK_EX | LOCK_NB" << std::endl; + ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_EX), 0); - swoole::Coroutine::create([&](void *arg) { - ASSERT_EQ(swoole_coroutine_flock_ex(test_file, fd, LOCK_EX), 0); - ASSERT_EQ(swoole_coroutine_flock_ex(test_file, fd, LOCK_UN), 0); + DEBUG() << "[thread-2] LOCK_UN" << std::endl; + ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); + + DEBUG() << "[thread-2] close" << std::endl; swoole_coroutine_close(fd); unlink(test_file); }); - ASSERT_EQ(swoole_coroutine_flock_ex(test_file, fd, LOCK_UN), 0); + DEBUG() << "[thread-1] LOCK_UN" << std::endl; + ASSERT_EQ(swoole_coroutine_flock(fd, LOCK_UN), 0); + + t.join(); }); } @@ -108,13 +116,40 @@ TEST(coroutine_system, cancel_sleep) { }); } +static void test_getaddrinfo( + const std::string &host, int family, int type, int protocol, const char *service, double timeout) { + std::vector ip_list = System::getaddrinfo(host, family, type, protocol, service, timeout); + ASSERT_GT(ip_list.size(), 0); + for (auto &ip : ip_list) { + ASSERT_TRUE(swoole::network::Address::verify_ip(family, ip)); + network::Client c(family == AF_INET ? SW_SOCK_TCP : SW_SOCK_TCP6, false); + if (!test::is_github_ci()) { + std::cout << ip.c_str() << "\n"; + ASSERT_EQ(c.connect(ip.c_str(), 443), SW_OK); + } + } +} + TEST(coroutine_system, getaddrinfo) { test::coroutine::run([](void *arg) { - std::vector ip_list = System::getaddrinfo("www.baidu.com", AF_INET, SOCK_STREAM, 0, "http", 1); - ASSERT_GT(ip_list.size(), 0); - for (auto &ip : ip_list) { - ASSERT_TRUE(swoole::network::Address::verify_ip(AF_INET, ip)); - } + test_getaddrinfo(TEST_HTTP_DOMAIN, AF_INET, SOCK_STREAM, 0, "http", -1); + test_getaddrinfo(TEST_HTTP_DOMAIN, AF_INET6, SOCK_STREAM, 0, "http", -1); + }); +} + +TEST(coroutine_system, getaddrinfo_fail) { + test::coroutine::run([](void *arg) { + auto ip_list = System::getaddrinfo("w11.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, "http", -1); + ASSERT_EQ(ip_list.size(), 0); + ASSERT_ERREQ(EAI_NONAME); + }); +} + +TEST(coroutine_system, getaddrinfo_timeout) { + test::coroutine::run([](void *arg) { + auto ip_list = System::getaddrinfo("w12.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, "http", 0.005); + ASSERT_EQ(ip_list.size(), 0); + ASSERT_ERREQ(SW_ERROR_CO_TIMEDOUT); }); } @@ -124,8 +159,24 @@ TEST(coroutine_system, wait_signal) { System::sleep(0.002); kill(getpid(), SIGUSR1); }); - ASSERT_TRUE(System::wait_signal(SIGUSR1, 1.0)); - ASSERT_FALSE(System::wait_signal(SIGUSR2, 0.1)); + ASSERT_EQ(System::wait_signal(SIGUSR1, 1.0), SIGUSR1); + ASSERT_EQ(System::wait_signal(SIGUSR2, 0.1), -1); + }); +} + +TEST(coroutine_system, wait_signal_invalid_signo) { + test::coroutine::run([](void *arg) { + ASSERT_EQ(System::wait_signal(SW_SIGNO_MAX), SW_ERR); + ASSERT_ERREQ(EINVAL); + }); +} + +TEST(coroutine_system, wait_signal_fail) { + test::coroutine::run([](void *arg) { + SwooleG.signal_listener_num = 1; + ASSERT_EQ(System::wait_signal(SIGUSR1, 1.0), SW_ERR); + ASSERT_ERREQ(EBUSY); + SwooleG.signal_listener_num = 0; }); } @@ -141,13 +192,30 @@ TEST(coroutine_system, wait_event_readable) { ASSERT_GT(p.write(GREETING, strlen(GREETING)), 0); }); + // bad fd + EXPECT_EQ(System::wait_event(9999, SW_EVENT_READ, 1), -1); + EXPECT_EQ(errno, EBADF); + EXPECT_ERREQ(EBADF); + + // trigger event char buffer[128]; auto pipe_sock = p.get_socket(false); - System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ, 1); + // readable + EXPECT_EQ(System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ, 1), SW_EVENT_READ); + // readable + writable + EXPECT_EQ(System::wait_event(pipe_sock->get_fd(), SW_EVENT_READ | SW_EVENT_WRITE, 1), + SW_EVENT_READ | SW_EVENT_WRITE); + ssize_t n = pipe_sock->read(buffer, sizeof(buffer)); buffer[n] = 0; EXPECT_EQ(strlen(GREETING), n); EXPECT_STREQ(GREETING, buffer); + + // timeout + auto pipe_sock_2 = p.get_socket(true); + EXPECT_EQ(System::wait_event(pipe_sock_2->get_fd(), SW_EVENT_READ, 0.1), -1); + EXPECT_EQ(errno, SW_ERROR_CO_TIMEDOUT); + EXPECT_ERREQ(SW_ERROR_CO_TIMEDOUT); }); } @@ -200,6 +268,25 @@ TEST(coroutine_system, wait_event_writable) { }); } +TEST(coroutine_system, wait_event_fail) { + UnixSocket p(true, SOCK_DGRAM); + test::coroutine::run([&](void *arg) { + ASSERT_EQ(System::wait_event(9999, 0, 1), SW_ERR); + ASSERT_ERREQ(EINVAL); + + ASSERT_EQ(System::wait_event(p.get_socket(true)->get_fd(), SW_EVENT_READ, 0), SW_ERR); + ASSERT_ERREQ(ETIMEDOUT); + + ASSERT_EQ(System::wait_event(p.get_socket(false)->get_fd(), SW_EVENT_WRITE, 0), SW_EVENT_WRITE); + + ASSERT_EQ(System::wait_event(9999, SW_EVENT_WRITE, 0), -1); + ASSERT_ERREQ(EBADF); + + ASSERT_EQ(System::wait_event(9999, SW_EVENT_WRITE, 1.0), -1); + ASSERT_ERREQ(EBADF); + }); +} + TEST(coroutine_system, swoole_stream_select) { UnixSocket p(true, SOCK_STREAM); std::unordered_map fds; @@ -272,3 +359,56 @@ TEST(coroutine_system, timeout_is_zero) { ASSERT_TRUE(result); }); } + +TEST(coroutine_system, exec) { + test::coroutine::run([](void *arg) { + int status; + auto buffer = std::shared_ptr(swoole::make_string(1024)); + ASSERT_TRUE(System::exec("ls /", true, buffer, &status)); + ASSERT_TRUE(buffer->contains(SW_STRL("tmp"))); + }); +} + +TEST(coroutine_system, waitpid) { + auto pid = spawn_exec([]() { sleep(2000); }); + + test::coroutine::run([pid](void *arg) { + int status; + ASSERT_EQ(System::waitpid(pid, &status, WNOHANG, -1), 0); + ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), -1); + ASSERT_ERREQ(ETIMEDOUT); + + kill(pid, SIGKILL); + System::sleep(0.3); + ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), pid); + }); +} + +TEST(coroutine_system, waitpid_any) { + auto pid = spawn_exec([]() { sleep(2000); }); + + test::coroutine::run([pid](void *arg) { + int status; + ASSERT_EQ(System::waitpid(pid, &status, WNOHANG, -1), 0); + ASSERT_EQ(System::waitpid(pid, &status, 0, 0.1), -1); + ASSERT_ERREQ(ETIMEDOUT); + + kill(pid, SIGKILL); + System::sleep(0.3); + ASSERT_EQ(System::waitpid(-1, &status, 0, 0.1), pid); + }); +} + +TEST(coroutine_system, read_file_fail) { + test::coroutine::run([](void *arg) { + ASSERT_EQ(System::read_file("/tmp/not-exists", true), nullptr); + ASSERT_EQ(errno, ENOENT); + }); +} + +TEST(coroutine_system, write_file_fail) { + test::coroutine::run([](void *arg) { + ASSERT_EQ(System::write_file("/tmp/not-exists/file.log", SW_STRL(TEST_STR)), -1); + ASSERT_EQ(errno, ENOENT); + }); +} diff --git a/core-tests/src/coroutine/uring_socket.cpp b/core-tests/src/coroutine/uring_socket.cpp new file mode 100644 index 0000000000..2186b3718f --- /dev/null +++ b/core-tests/src/coroutine/uring_socket.cpp @@ -0,0 +1,562 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "test_coroutine.h" +#include "swoole_uring_socket.h" +#include "swoole_util.h" +#include "swoole_file.h" + +#include +#include + +#ifdef SW_USE_IOURING +using swoole::Coroutine; +using swoole::File; +using swoole::Iouring; +using swoole::Reactor; +using swoole::String; + +using swoole::coroutine::System; +using swoole::coroutine::UringSocket; +using swoole::network::IOVector; +using swoole::test::coroutine; +using swoole::test::create_socket_pair; +using swoole::test::get_jpg_file; + +TEST(uring_socket, connect) { + coroutine::run([](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.connect(TEST_HTTP_DOMAIN, 80); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + ssize_t rv; + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + + rv = sock.send(req.c_str(), req.length()); + ASSERT_EQ(rv, req.length()); + + char buf[4096]; + + rv = sock.recv(buf, sizeof(buf)); + ASSERT_GT(rv, 100); + + std::string s{buf}; + ASSERT_TRUE(s.find(TEST_HTTP_EXPECT) != s.npos); + }); +} + +TEST(uring_socket, ssl_connect) { + coroutine::run([](void *arg) { + UringSocket sock(SW_SOCK_TCP); + sock.enable_ssl_encrypt(); + sock.set_tls_host_name(TEST_HTTP_DOMAIN); + sock.set_ssl_verify_peer(true); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + + bool retval = sock.connect(TEST_HTTP_DOMAIN, 443); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + auto rv = sock.send(req.c_str(), req.length()); + ASSERT_EQ(rv, req.length()); + + ASSERT_TRUE(sock.check_liveness()); + + swoole::String buf(1024 * 1024); + while (true) { + char rbuf[16384]; + ssize_t nr = sock.recv(rbuf, sizeof(rbuf)); + if (nr <= 0) { + break; + } + buf.append(rbuf, nr); + } + ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); + }); +} + +TEST(uring_socket, accept) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[port](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.listen(128), true); + + UringSocket *conn = sock.accept(); + ASSERT_NE(conn, nullptr); + conn->write(TEST_STR, strlen(TEST_STR)); + + char buf[128]; + auto n = conn->recv(buf, sizeof(buf)); + ASSERT_EQ(n, strlen(TEST_STR2)); + buf[n] = '\0'; + ASSERT_STREQ(buf, TEST_STR2); + + delete conn; + }, + + [port](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.connect("127.0.0.1", port, -1); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + char buf[128]; + auto n = sock.read(buf, sizeof(buf)); + ASSERT_EQ(n, strlen(TEST_STR)); + buf[n] = '\0'; + ASSERT_STREQ(buf, TEST_STR); + + ASSERT_EQ(sock.send(TEST_STR2, strlen(TEST_STR2)), strlen(TEST_STR2)); + + sock.close(); + }}); +} + +TEST(uring_socket, ssl_accept) { + const int port = __LINE__ + TEST_PORT; + auto jpg_file = get_jpg_file(); + File file(jpg_file, File::READ); + + auto svr = [port, &file](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + + sock.enable_ssl_encrypt(); + sock.set_ssl_cert_file(swoole::test::get_ssl_dir() + "/server.crt"); + sock.set_ssl_key_file(swoole::test::get_ssl_dir() + "/server.key"); + sock.set_ssl_dhparam(swoole::test::get_ssl_dir() + "/dhparams.pem"); + sock.set_ssl_ecdh_curve("secp256r1"); + + ASSERT_EQ(sock.listen(128), true); + + UringSocket *conn = sock.accept(); + ASSERT_NE(conn, nullptr); + ASSERT_TRUE(conn->ssl_handshake()); + ASSERT_EQ(conn->send(EOF_PACKET, strlen(EOF_PACKET)), strlen(EOF_PACKET)); + char rbuf[1024]; + + auto n = conn->recv(rbuf, sizeof(rbuf)); + ASSERT_GT(n, 0); + rbuf[n] = 0; + ASSERT_STREQ(rbuf, EOF_PACKET_2); + + size_t fsize = file.get_size(); + char *jpg = new char[fsize]; + size_t nr = 0; + + while (nr < fsize) { + auto ret = conn->recv(jpg + nr, fsize - nr); + ASSERT_GT(ret, 0); + nr += ret; + } + auto content = file.read_content(); + ASSERT_MEMEQ(jpg, content.get()->value(), fsize); + + conn->close(); + delete conn; + }; + + auto cli = [port, &file](void *arg) { + UringSocket sock(SW_SOCK_TCP); + sock.enable_ssl_encrypt(); + bool retval = sock.connect("127.0.0.1", port, -1); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + char rbuf[1024]; + auto n = sock.recv(rbuf, sizeof(rbuf)); + ASSERT_GT(n, 0); + rbuf[n] = 0; + ASSERT_STREQ(rbuf, EOF_PACKET); + ASSERT_EQ(sock.send(EOF_PACKET_2, strlen(EOF_PACKET_2)), strlen(EOF_PACKET_2)); + + ASSERT_TRUE(sock.sendfile(file.get_path().c_str(), 0, file.get_size())); + + sock.close(); + }; + + coroutine::run({svr, cli}); +} + +static void socket_set_length_protocol_1(UringSocket &sock) { + sock.protocol = {}; + + sock.protocol.package_length_type = 'n'; + sock.protocol.package_length_size = swoole_type_size(sock.protocol.package_length_type); + sock.protocol.package_body_offset = 2; + sock.protocol.get_package_length = swoole::Protocol::default_length_func; + sock.protocol.package_max_length = 65535; + + sock.open_length_check = true; +} + +TEST(uring_socket, length_3) { + const int port = __LINE__ + TEST_PORT; + coroutine::run({[](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.bind("127.0.0.1", port); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.listen(128), true); + + UringSocket *conn = sock.accept(); + char buf[1024]; + memset(buf, 'A', sizeof(buf)); + *(uint16_t *) buf = htons(65530); + + conn->send(buf, sizeof(buf)); + }, + + [](void *arg) { + UringSocket sock(SW_SOCK_TCP); + bool retval = sock.connect("127.0.0.1", port, -1); + ASSERT_EQ(retval, true); + ASSERT_EQ(sock.errCode, 0); + + socket_set_length_protocol_1(sock); + sock.protocol.package_max_length = 4096; + + ssize_t l = sock.recv_packet(RECV_TIMEOUT); + ASSERT_EQ(l, -1); + ASSERT_EQ(sock.errCode, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); + }}); +} + +TEST(uring_socket, sendmsg_and_recvmsg) { + coroutine::run([&](void *arg) { + int pairs[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + std::string text = "Hello World"; + const size_t length = text.length(); + + Coroutine::create([&](void *) { + UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); + struct msghdr msg; + struct iovec ivec; + + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = 0; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = &ivec; + msg.msg_iovlen = 1; + + ivec.iov_base = (void *) text.c_str(); + ivec.iov_len = length; + + ssize_t ret = sock.sendmsg(&msg, 0); + sock.close(); + ASSERT_EQ(ret, length); + }); + + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + struct msghdr msg; + struct iovec ivec; + char buf[length + 1]; + + msg.msg_control = nullptr; + msg.msg_controllen = 0; + msg.msg_flags = 0; + msg.msg_name = nullptr; + msg.msg_namelen = 0; + msg.msg_iov = &ivec; + msg.msg_iovlen = 1; + + ivec.iov_base = buf; + ivec.iov_len = length; + + ssize_t ret = sock.recvmsg(&msg, 0); + buf[ret] = '\0'; + sock.close(); + ASSERT_STREQ(buf, text.c_str()); + }); +} + +static void test_sendto_recvfrom(enum swSocketType sock_type) { + coroutine::run([&](void *arg) { + std::string server_text = "hello world!!!"; + size_t server_length = server_text.length(); + std::string client_text = "hello swoole!!!"; + size_t client_length = client_text.length(); + + const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; + const char *local = "localhost"; + + int port = swoole::test::get_random_port(); + + UringSocket sock_server(sock_type); + UringSocket sock_client(sock_type); + sock_server.bind(ip, port); + sock_client.bind(ip, port + 1); + + ON_SCOPE_EXIT { + sock_server.close(); + sock_client.close(); + }; + + sock_server.sendto(ip, port + 1, (const void *) server_text.c_str(), server_length); + + char data_from_server[128] = {}; + struct sockaddr_in serveraddr; + bzero(&serveraddr, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_addr.s_addr = inet_addr(ip); + serveraddr.sin_port = htons(port); + socklen_t addr_length = sizeof(serveraddr); + + // receive data from server + ssize_t result = + sock_client.recvfrom(data_from_server, server_length, (struct sockaddr *) &serveraddr, &addr_length); + data_from_server[result] = '\0'; + ASSERT_EQ(result, server_length); + ASSERT_STREQ(data_from_server, server_text.c_str()); + + // receive data from client + char data_from_client[128] = {}; + sock_client.sendto(local, port, (const void *) client_text.c_str(), client_length); + result = sock_server.recvfrom(data_from_client, client_length); + data_from_client[client_length] = '\0'; + ASSERT_EQ(result, client_length); + ASSERT_STREQ(data_from_client, client_text.c_str()); + }); +} + +TEST(uring_socket, sendto_recvfrom_udp) { + test_sendto_recvfrom(SW_SOCK_UDP); + test_sendto_recvfrom(SW_SOCK_UDP6); +} + +TEST(uring_socket, writev_and_readv) { + coroutine::run([&](void *arg) { + int iovcnt = 3; + int pairs[2]; + std::string text = "Hello World"; + size_t length = text.length(); + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + Coroutine::create([&](void *) { + std::unique_ptr iov(new iovec[iovcnt]); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = (void *) text.c_str(); + iov[i].iov_len = length; + } + IOVector io_vector((struct iovec *) iov.get(), iovcnt); + + UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); + ssize_t result = sock.writev(&io_vector); + sock.close(); + ASSERT_EQ(result, length * 3); + }); + + std::vector results(iovcnt); + std::unique_ptr iov(new iovec[iovcnt]); + for (int i = 0; i < iovcnt; i++) { + iov[i].iov_base = (void *) results[i].c_str(); + iov[i].iov_len = length; + } + IOVector io_vector((struct iovec *) iov.get(), iovcnt); + + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + ssize_t result = sock.readv(&io_vector); + sock.close(); + ASSERT_EQ(result, length * 3); + + for (auto iter = results.begin(); iter != results.end(); iter++) { + (*iter)[length] = '\0'; + ASSERT_STREQ(text.c_str(), (*iter).c_str()); + } + }); +} + +TEST(uring_socket, writevall_and_readvall) { + coroutine::run([&](void *arg) { + int write_iovcnt = 4; + int pairs[2]; + + char buf[65536]; + swoole_random_bytes(buf, sizeof(buf)); + + std::string text(buf, sizeof(buf)); + size_t length = text.length(); + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + Coroutine::create([&](void *) { + std::unique_ptr iov(new iovec[write_iovcnt]); + for (int i = 0; i < write_iovcnt; i++) { + iov[i].iov_base = (void *) text.c_str(); + iov[i].iov_len = length; + } + + UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_send_buffer_size(sizeof(buf)); + + IOVector io_vector1((struct iovec *) iov.get(), write_iovcnt); + ASSERT_EQ(sock.writev_all(&io_vector1), write_iovcnt * sizeof(buf)); + + System::sleep(0.01); + + IOVector io_vector2((struct iovec *) iov.get(), write_iovcnt); + ASSERT_EQ(sock.writev_all(&io_vector2), write_iovcnt * sizeof(buf)); + + sock.close(); + }); + + int read_iovcnt = 8; + std::unique_ptr iov(new iovec[read_iovcnt]); + for (int i = 0; i < read_iovcnt; i++) { + iov[i].iov_base = sw_malloc(length); + iov[i].iov_len = length; + } + IOVector io_vector((struct iovec *) iov.get(), read_iovcnt); + + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_recv_buffer_size(sizeof(buf)); + + ssize_t result = sock.readv_all(&io_vector); + sock.close(); + ASSERT_EQ(result, length * read_iovcnt); + + for (int i = 0; i < read_iovcnt; i++) { + ASSERT_MEMEQ(iov[i].iov_base, buf, sizeof(buf)); + sw_free(iov[i].iov_base); + } + }); +} + +TEST(uring_socket, sendfile) { + coroutine::run([&](void *arg) { + int pairs[2]; + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + Coroutine::create([&](void *) { + std::string file = swoole::test::get_jpg_file(); + UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); + bool result = sock.sendfile(file.c_str(), 0, 0); + std::cout << sock.errMsg << "\n"; + sock.close(); + ASSERT_TRUE(result); + }); + + char data[250000]; + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + ssize_t result = sock.read(data, 250000); + data[result] = '\0'; + sock.close(); + ASSERT_GT(result, 0); + }); +} + +TEST(uring_socket, send_and_recv_all) { + coroutine::run([&](void *arg) { + int pairs[2]; + + String wbuf; + wbuf.append_random_bytes(4 * 1024 * 1024, false); + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + Coroutine::create([&](void *) { + UringSocket sock(pairs[0], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_send_buffer_size(65536); + + ASSERT_EQ(sock.send_all(wbuf.str, wbuf.length), wbuf.length); + + System::sleep(0.1); + + sock.close(); + }); + + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_recv_buffer_size(65536); + + String rbuf(wbuf.length); + ssize_t result = sock.recv_all(rbuf.str, wbuf.length); + ASSERT_EQ(result, wbuf.length); + ASSERT_MEMEQ(wbuf.str, rbuf.str, wbuf.length); + System::sleep(0.1); + sock.close(); + }); +} + +TEST(uring_socket, poll) { + coroutine::run([&](void *arg) { + int pairs[2]; + + String wbuf; + wbuf.append_random_bytes(4 * 1024 * 1024, false); + socketpair(AF_UNIX, SOCK_STREAM, 0, pairs); + + UringSocket sock(pairs[1], SW_SOCK_UNIX_STREAM); + sock.get_socket()->set_recv_buffer_size(65536); + + bool rs; + + rs = sock.poll(SW_EVENT_READ, 0.01); + ASSERT_FALSE(rs); + ASSERT_EQ(sock.errCode, ETIMEDOUT); + + TEST_WRITE(pairs[0], TEST_STR); + rs = sock.poll(SW_EVENT_READ, 0.01); + ASSERT_TRUE(rs); + }); +} + +TEST(uring_socket, ssl_readv) { + coroutine::run([&](void *arg) { + UringSocket client(SW_SOCK_TCP); + client.enable_ssl_encrypt(); + client.set_tls_host_name(TEST_HTTP_DOMAIN); + ASSERT_TRUE(client.connect(TEST_HTTP_DOMAIN, 443)); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + + constexpr off_t offset1 = TEST_WRITEV_OFFSET; + iovec wr_iov[2]; + wr_iov[0].iov_base = (void *) req.c_str(); + wr_iov[0].iov_len = offset1; + wr_iov[1].iov_base = (char *) req.c_str() + offset1; + wr_iov[1].iov_len = req.length() - offset1; + + swoole::network::IOVector wr_vec(wr_iov, 2); + ASSERT_EQ(client.writev(&wr_vec), req.length()); + + sw_tg_buffer()->clear(); + if (sw_tg_buffer()->size < 1024 * 1024) { + sw_tg_buffer()->extend(1024 * 1024); + } + + constexpr off_t offset2 = TEST_READV_OFFSET; + iovec rd_iov[2]; + rd_iov[0].iov_base = sw_tg_buffer()->str; + rd_iov[0].iov_len = offset2; + rd_iov[1].iov_base = sw_tg_buffer()->str + offset2; + rd_iov[1].iov_len = sw_tg_buffer()->size - offset2; + + swoole::network::IOVector rd_vec(rd_iov, 2); + auto rv = client.readv(&rd_vec); + ASSERT_GT(rv, 1024); + sw_tg_buffer()->length = rv; + sw_tg_buffer()->set_null_terminated(); + + ASSERT_TRUE(sw_tg_buffer()->contains(TEST_HTTPS_EXPECT)); + }); +} +#endif diff --git a/core-tests/src/lock/lock.cpp b/core-tests/src/lock/lock.cpp index 263c87ab5a..a5b38540eb 100644 --- a/core-tests/src/lock/lock.cpp +++ b/core-tests/src/lock/lock.cpp @@ -17,21 +17,22 @@ +----------------------------------------------------------------------+ */ -#include "test_core.h" +#include "test_coroutine.h" #include "swoole_lock.h" #include "swoole_util.h" #include -using swLock = swoole::Lock; - +using swoole::Lock; using swoole::RWLock; -#ifdef HAVE_SPINLOCK using swoole::SpinLock; -#endif +using swoole::Coroutine; +using swoole::CoroutineLock; using swoole::Mutex; +using swoole::coroutine::System; +using swoole::test::coroutine; -static void test_func(swLock &lock) { +static void test_func(Lock &lock) { int count = 0; const int N = 100000; @@ -52,23 +53,23 @@ static void test_func(swLock &lock) { ASSERT_EQ(count, N * 2); } -static void test_lock_rd_func(swLock &lock) { +static void test_lock_rd_func(Lock &lock) { std::thread t1([&lock]() { - ASSERT_EQ(lock.lock_rd(), 0); + ASSERT_EQ(lock.lock(LOCK_SH), 0); usleep(2000); // wait lock.unlock(); }); std::thread t2([&lock]() { usleep(1000); - ASSERT_GE(lock.trylock_rd(), 0); + ASSERT_GE(lock.lock(LOCK_SH | LOCK_NB), 0); }); t1.join(); t2.join(); } -static void test_share_lock_fun(swLock &lock) { +static void test_share_lock_fun(Lock &lock) { lock.lock(); const int sleep_us = 10000; int magic_num = swoole_rand(100000, 9999999); @@ -96,7 +97,7 @@ static void test_share_lock_fun(swLock &lock) { TEST(lock, mutex) { Mutex lock(0); - test_func(reinterpret_cast(lock)); + test_func(reinterpret_cast(lock)); } TEST(lock, lockwait) { @@ -107,13 +108,13 @@ TEST(lock, lockwait) { std::thread t1([&lock]() { long ms1 = swoole::time(); const int TIMEOUT_1 = 2; - ASSERT_EQ(lock.lock_wait(TIMEOUT_1), ETIMEDOUT); + ASSERT_EQ(lock.lock(LOCK_EX, TIMEOUT_1), ETIMEDOUT); long ms2 = swoole::time(); ASSERT_GE(ms2 - ms1, TIMEOUT_1); const int TIMEOUT_2 = 10; - ASSERT_EQ(lock.lock_wait(TIMEOUT_2), 0); + ASSERT_EQ(lock.lock(LOCK_EX, TIMEOUT_2), 0); long ms3 = swoole::time(); ASSERT_LE(ms3 - ms2, TIMEOUT_2); @@ -126,7 +127,7 @@ TEST(lock, lockwait) { } TEST(lock, shared) { - Mutex lock(Mutex::PROCESS_SHARED); + Mutex lock(true); test_share_lock_fun(lock); } @@ -135,9 +136,77 @@ TEST(lock, try_rd) { test_lock_rd_func(lock); } +TEST(lock, coroutine_lock) { + auto *lock = new CoroutineLock(false); + ASSERT_EQ(lock->lock(), SW_ERROR_CO_OUT_OF_COROUTINE); + ASSERT_EQ(lock->unlock(), SW_ERROR_CO_OUT_OF_COROUTINE); + + coroutine::run([lock](void *arg) { + Coroutine::create([lock](void *) { + ASSERT_EQ(lock->lock(), 0); + ASSERT_EQ(lock->lock(), 0); + System::sleep(1); + ASSERT_EQ(lock->unlock(), 0); + }); + + Coroutine::create([lock](void *) { + ASSERT_EQ(lock->lock(), 0); + System::sleep(1); + ASSERT_EQ(lock->unlock(), 0); + // unlock 2, no effect + ASSERT_EQ(lock->unlock(), 0); + }); + + Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_NB), EBUSY); }); + }); + + delete lock; +} + +#ifndef HAVE_IOURING_FUTEX +TEST(lock, coroutine_lock_cancel) { + CoroutineLock lock(true); + coroutine::run([&](void *arg) { + ASSERT_EQ(lock.lock(), 0); + Coroutine::create([&](void *) { + auto co = Coroutine::get_current(); + swoole_timer_after(20, [co](TIMER_PARAMS) { + DEBUG() << "cancel coroutine " << co->get_cid() << "\n"; + co->cancel(); + }); + ASSERT_EQ(lock.lock(), SW_ERROR_CO_CANCELED); + }); + }); +} +#endif + +TEST(lock, coroutine_lock_rd) { + auto *lock = new CoroutineLock(false); + ASSERT_EQ(lock->lock(LOCK_SH), SW_ERROR_CO_OUT_OF_COROUTINE); + + coroutine::run([lock](void *arg) { + Coroutine::create([lock](void *) { + ASSERT_EQ(lock->lock(LOCK_SH), 0); + ASSERT_EQ(lock->lock(LOCK_SH), 0); + System::sleep(0.3); + ASSERT_EQ(lock->unlock(), 0); + }); + + Coroutine::create([lock](void *) { + ASSERT_EQ(lock->lock(LOCK_SH), 0); + System::sleep(0.3); + ASSERT_EQ(lock->unlock(), 0); + }); + + Coroutine::create([lock](void *) { ASSERT_EQ(lock->lock(LOCK_SH | LOCK_NB), EBUSY); }); + }); + + delete lock; +} + #ifdef HAVE_RWLOCK TEST(lock, rwlock_shared) { - RWLock lock(Mutex::PROCESS_SHARED); + RWLock lock(true); test_share_lock_fun(lock); } @@ -161,7 +230,7 @@ TEST(lock, rw_try_wr) { std::thread t2([&lock]() { usleep(1000); - ASSERT_GT(lock.trylock(), 0); + ASSERT_GT(lock.lock(LOCK_NB), 0); }); t1.join(); t2.join(); @@ -170,7 +239,7 @@ TEST(lock, rw_try_wr) { #ifdef HAVE_SPINLOCK TEST(lock, spinlock_shared) { - SpinLock lock(Mutex::PROCESS_SHARED); + SpinLock lock(true); test_share_lock_fun(lock); } diff --git a/core-tests/src/main.cpp b/core-tests/src/main.cpp index 4cd1757cdc..b8eacdeee9 100644 --- a/core-tests/src/main.cpp +++ b/core-tests/src/main.cpp @@ -1,20 +1,38 @@ #include "test_core.h" +#include "swoole_memory.h" + +#include +#include using namespace swoole; using namespace std; static string root_path; +static int *test_counter; static void init_root_path(const char *); int main(int argc, char **argv) { swoole_init(); + SwooleG.max_sockets = 20000; init_root_path(argv[0]); if (getenv("DISPLAY_BACKTRACE") != nullptr) { sw_logger()->display_backtrace(); } +#ifdef SW_VERBOSE + swoole_set_log_level(SW_LOG_TRACE); + swoole_set_trace_flags(SW_TRACE_ALL); +#endif + + if (getenv("VERBOSE") != nullptr && std::string(getenv("VERBOSE")) == "0") { + swoole_set_log_level(SW_LOG_INFO); + test::debug_output = test::null_stream; + } + + test_counter = static_cast(sw_mem_pool()->alloc(sizeof(int) * TEST_COUNTER_NUM)); + ::testing::InitGoogleTest(&argc, argv); int retval = RUN_ALL_TESTS(); @@ -32,7 +50,7 @@ static void init_root_path(const char *_exec_file) { char *dir = getcwd(buf, sizeof(buf)); file = string(dir) + "/" + _exec_file; } - string relative_root_path = file.substr(0, file.rfind('/')) + "/../../"; + string relative_root_path = file.substr(0, file.rfind('/')) + "/../"; char *_realpath = realpath(relative_root_path.c_str(), buf); if (_realpath == nullptr) { root_path = relative_root_path; @@ -41,20 +59,319 @@ static void init_root_path(const char *_exec_file) { } } -namespace swoole { -namespace test { +namespace swoole::test { +NullStream null_stream; +std::reference_wrapper debug_output(std::cout); + +void counter_init() { + sw_memset_zero(test_counter, sizeof(int) * TEST_COUNTER_NUM); +} + +int *counter_ptr() { + return test_counter; +} + +int counter_incr(int index, int add) { + return sw_atomic_add_fetch(&test_counter[index], add); +} + +int counter_get(int index) { + return test_counter[index]; +} + +void counter_set(int index, int value) { + test_counter[index] = value; +} + +void counter_incr_and_put_log(int index, const char *msg) { + DEBUG() << "PID: " << getpid() << ", VALUE: " << counter_incr(index) << "; " << msg << std::endl; +} +/** + * swoole-src root path + */ const string &get_root_path() { return root_path; } +string get_ssl_dir() { + return get_root_path() + "/tests/include/ssl_certs"; +} + string get_jpg_file() { return root_path + TEST_JPG_FILE; } +string http_get_request(const string &domain, const string &path) { + return "GET " + path + + " HTTP/1.1\r\n" + "Host: " + + domain + + "\r\n" + "Connection: close\r\n" + "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/51.0.2704.106 Safari/537.36" + "\r\n\r\n"; +} + bool is_github_ci() { return getenv("GITHUB_ACTIONS") != nullptr; } -} // namespace test -} // namespace swoole +int exec_js_script(const std::string &file, const std::string &args) { + std::string command = "bash -c 'node " + get_root_path() + "/core-tests/js/" + file + " " + args + "'"; + return std::system(command.c_str()); +} + +int get_random_port() { + return TEST_PORT + swoole_random_int() % 10000; +} + +bool is_valid_fd(int fd) { + return fcntl(fd, F_GETFD) != -1; +} + +int wait_all_child_processes(bool verbose) { + pid_t pid; + int status; + int count = 0; + + // 循环等待所有子进程结束 + while (true) { + // 使用waitpid等待任意子进程,这里会阻塞直到有子进程退出 + pid = waitpid(-1, &status, 0); + + if (pid > 0) { + // 成功回收一个子进程 + count++; + + // 输出子进程退出状态(如果启用详细输出) + if (verbose) { + if (WIFEXITED(status)) { + std::cout << "子进程 " << pid << " 正常退出,退出码: " << WEXITSTATUS(status) << std::endl; + } else if (WIFSIGNALED(status)) { + std::cout << "子进程 " << pid << " 被信号 " << WTERMSIG(status) << " 终止"; + + if (WCOREDUMP(status)) { + std::cout << " (核心已转储)"; + } + + std::cout << std::endl; + } + } + } else if (pid < 0) { + if (errno == ECHILD) { + // 没有子进程了,完成回收 + if (verbose) { + std::cout << "所有子进程已回收,共 " << count << " 个" << std::endl; + } + break; + } else { + // 其他错误 + if (verbose) { + perror("waitpid failed"); + } + return -1; + } + } + } + + return count; +} + +// 检测子进程 +int has_child_processes() { + pid_t current_pid = getpid(); + DIR *proc_dir; + struct dirent *entry; + char stat_path[512]; + FILE *stat_file; + char buffer[1024]; + pid_t pid, ppid; + + // 尝试使用waitpid快速检测 + if (waitpid(-1, NULL, WNOHANG) == -1 && errno == ECHILD) { + return 0; // 没有子进程 + } + + // 如果waitpid没有明确结果,使用/proc检测 + proc_dir = opendir("/proc"); + if (!proc_dir) { + perror("opendir /proc failed"); + return -1; + } + + while ((entry = readdir(proc_dir)) != NULL) { + if (entry->d_type == DT_DIR && entry->d_name[0] >= '0' && entry->d_name[0] <= '9') { + snprintf(stat_path, sizeof(stat_path), "/proc/%s/stat", entry->d_name); + stat_file = fopen(stat_path, "r"); + if (stat_file) { + if (fgets(buffer, sizeof(buffer), stat_file)) { + sscanf(buffer, "%d %*s %*c %d", &pid, &ppid); + if (ppid == current_pid) { + fclose(stat_file); + closedir(proc_dir); + return 1; // 找到子进程 + } + } + fclose(stat_file); + } + } + } + + closedir(proc_dir); + return 0; // 没有子进程 +} + +// 检测线程 +int has_threads() { + FILE *status_file; + char path[256]; + char line[256]; + int thread_count = -1; + + snprintf(path, sizeof(path), "/proc/%d/status", getpid()); + status_file = fopen(path, "r"); + if (!status_file) { + perror("fopen failed"); + return -1; + } + + while (fgets(line, sizeof(line), status_file)) { + if (strncmp(line, "Threads:", 8) == 0) { + sscanf(line, "Threads: %d", &thread_count); + break; + } + } + + fclose(status_file); + return thread_count; +} + +/** + * 检查目录是否为空 + * @param path 目录路径 + * @return 如果目录为空返回1,否则返回0 + */ +int is_directory_empty(const char *path) { + DIR *dir = opendir(path); + if (dir == NULL) { + perror("opendir"); + return 0; + } + + int is_empty = 1; + struct dirent *entry; + + while ((entry = readdir(dir)) != NULL) { + // 跳过 "." 和 ".." 目录 + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) { + is_empty = 0; + break; + } + } + + closedir(dir); + return is_empty; +} + +/** + * 检查路径是否为目录 + * @param path 路径 + * @return 如果是目录返回1,否则返回0 + */ +int is_directory(const char *path) { + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return 0; + } + return S_ISDIR(path_stat.st_mode); +} + +/** + * 获取父目录路径 + * @param path 当前路径 + * @param parent_path 用于存储父目录路径的缓冲区 + * @param size 缓冲区大小 + * @return 成功返回1,失败返回0 + */ +int get_parent_directory(const char *path, char *parent_path, size_t size) { + auto last_slash = strrchr(path, '/'); + if (last_slash == NULL || last_slash == path) { + // 没有斜杠或者斜杠是第一个字符(根目录) + return 0; + } + + size_t parent_length = last_slash - path; + if (parent_length >= size) { + return 0; + } + + strncpy(parent_path, path, parent_length); + parent_path[parent_length] = '\0'; + + // 处理路径只有一个斜杠的情况 + if (parent_length == 0) { + parent_path[0] = '/'; + parent_path[1] = '\0'; + } + + return 1; +} + +/** + * 递归删除空目录 + * @param path 要删除的目录路径 + * @return 成功删除的目录数量 + */ +int recursive_rmdir(const char *path) { + // 检查路径是否存在且是目录 + if (!is_directory(path)) { + return 0; + } + + // 检查目录是否为空 + if (!is_directory_empty(path)) { + return 0; + } + + int deleted_count = 0; + + // 删除当前空目录 + if (rmdir(path) == 0) { + deleted_count++; + + // 获取父目录 + char parent_path[PATH_MAX]; + if (get_parent_directory(path, parent_path, PATH_MAX)) { + // 如果父目录存在且不是当前目录,则尝试删除父目录 + if (strcmp(parent_path, path) != 0) { + deleted_count += recursive_rmdir(parent_path); + } + } + } + + return deleted_count; +} + +pid_t spawn_exec(const std::function &fn) { + pid_t child_pid = fork(); + if (child_pid == -1) { + throw std::system_error{errno, std::generic_category()}; + } else if (child_pid == 0) { + fn(); + exit(0); + } + return child_pid; +} + +int spawn_exec_and_wait(const std::function &fn) { + int status; + pid_t pid = spawn_exec(fn); + if (swoole_waitpid(pid, &status, 0) == pid) { + return status; + } else { + return -1; + } +} +} // namespace swoole::test diff --git a/core-tests/src/memory/buffer.cpp b/core-tests/src/memory/buffer.cpp index 0f768e85d7..c9e1ceddb8 100644 --- a/core-tests/src/memory/buffer.cpp +++ b/core-tests/src/memory/buffer.cpp @@ -26,19 +26,22 @@ using namespace swoole; TEST(buffer, append_iov) { Buffer buf(1024); + Buffer buf_for_offset(1024); - int iovcnt = 4; - iovec v[iovcnt]; + constexpr int N = 4; + int iovcnt = N; + iovec v[N]; + size_t total_len = 0; - v[0].iov_len = swoole_rand(99, 4095); - v[1].iov_len = swoole_rand(99, 4095); - v[2].iov_len = swoole_rand(99, 4095); - v[3].iov_len = swoole_rand(99, 4095); + SW_LOOP_N(iovcnt) { + v[i].iov_len = swoole_rand(99, 4095); + total_len += v[i].iov_len; + } - unique_ptr s1(new char[v[0].iov_len]); - unique_ptr s2(new char[v[1].iov_len]); - unique_ptr s3(new char[v[2].iov_len]); - unique_ptr s4(new char[v[3].iov_len]); + unique_ptr s1(new char[v[0].iov_len]); + unique_ptr s2(new char[v[1].iov_len]); + unique_ptr s3(new char[v[2].iov_len]); + unique_ptr s4(new char[v[3].iov_len]); v[0].iov_base = s1.get(); v[1].iov_base = s2.get(); @@ -51,21 +54,29 @@ TEST(buffer, append_iov) { memset(v[3].iov_base, 'D', v[3].iov_len); buf.append(v, iovcnt, 0); + ASSERT_EQ(buf.length(), total_len); - ASSERT_EQ(buf.length(), v[0].iov_len + v[1].iov_len + v[2].iov_len+ v[3].iov_len); + size_t offset = swoole_rand(v[0].iov_len + 1, total_len - 1); + buf_for_offset.append(v, iovcnt, offset); + ASSERT_EQ(buf_for_offset.length(), total_len - offset); - String str(buf.length()); + String str(buf_for_offset.length()); - while(!buf.empty()) { - auto chunk = buf.front(); - str.append(chunk->value.ptr, chunk->length); - buf.pop(); + while (!buf_for_offset.empty()) { + auto chunk = buf_for_offset.front(); + str.append(chunk->value.str, chunk->length); + buf_for_offset.pop(); } - size_t offset = 0; + size_t indent = 0; + SW_LOOP_N(iovcnt) { + if (offset >= v[i].iov_len) { + offset -= v[i].iov_len; + continue; + } - SW_LOOP_N (iovcnt) { - ASSERT_EQ(memcmp(str.str + offset, v[i].iov_base, v[i].iov_len), 0); - offset += v[i].iov_len; + ASSERT_EQ(memcmp(str.str + indent, (char *) v[i].iov_base + offset, v[i].iov_len - offset), 0); + indent += v[i].iov_len - offset; + offset = 0; } } diff --git a/core-tests/src/memory/fixed_pool.cpp b/core-tests/src/memory/fixed_pool.cpp index 2a4a21cb8e..212dc79680 100644 --- a/core-tests/src/memory/fixed_pool.cpp +++ b/core-tests/src/memory/fixed_pool.cpp @@ -59,5 +59,4 @@ TEST(fixed_pool, realloc) { sw_shm_free(new_memory); }; ASSERT_NE(new_memory, nullptr); - } diff --git a/core-tests/src/memory/global_memory.cpp b/core-tests/src/memory/global_memory.cpp index d573caba3a..72590cccb2 100644 --- a/core-tests/src/memory/global_memory.cpp +++ b/core-tests/src/memory/global_memory.cpp @@ -45,6 +45,5 @@ TEST(global_memory, alloc) { ASSERT_STREQ(ptr2, "hello, world, #2"); ASSERT_STREQ(ptr3, "hello, world, #3"); - pool->destroy(); delete pool; } diff --git a/core-tests/src/memory/lru_cache.cpp b/core-tests/src/memory/lru_cache.cpp index f569e3a097..cbada48cb9 100644 --- a/core-tests/src/memory/lru_cache.cpp +++ b/core-tests/src/memory/lru_cache.cpp @@ -4,8 +4,6 @@ using namespace swoole; using namespace std; -LRUCache cache(2); - int dtor_num = 0; class lru_cache_test_class { public: @@ -17,6 +15,7 @@ class lru_cache_test_class { }; TEST(lru_cache, basic) { + LRUCache cache(2); shared_ptr val = make_shared("hello"); shared_ptr val1 = make_shared("hello1"); @@ -37,6 +36,7 @@ TEST(lru_cache, basic) { } TEST(lru_cache, memory_free) { + LRUCache cache(2); shared_ptr val = make_shared(); cache.set("test", val); ASSERT_EQ(cache.get("test").get(), val.get()); @@ -47,6 +47,7 @@ TEST(lru_cache, memory_free) { } TEST(lru_cache, lru_kick) { + LRUCache cache(2); dtor_num = 0; shared_ptr val = make_shared(); shared_ptr val1 = make_shared(); @@ -69,9 +70,9 @@ TEST(lru_cache, lru_kick) { ASSERT_EQ(dtor_num, 1); ASSERT_EQ(cache.get("test"), nullptr); - shared_ptr val_str = make_shared("hello"); - cache.set("test1", val_str); // update test1 and will del test2 - ASSERT_EQ(cache.get("test1").get(), val_str.get()); + shared_ptr val4 = make_shared(); + cache.set("test1", val4); // update test1 and will del test2 + ASSERT_EQ(cache.get("test1").get(), val4.get()); ASSERT_EQ(dtor_num, 2); cache.set("test3", val3); diff --git a/core-tests/src/memory/ringbuffer.cpp b/core-tests/src/memory/ringbuffer.cpp index 00f7d327f2..1c8ee20eeb 100644 --- a/core-tests/src/memory/ringbuffer.cpp +++ b/core-tests/src/memory/ringbuffer.cpp @@ -26,16 +26,16 @@ typedef struct { } ThreadObject; static void thread_read(int i); -static void thread_write(void); +static void thread_write(); static ThreadObject threads[READ_THREAD_N]; -TEST(ringbuffer, thread) { +static void test_ringbuffer(bool shared) { int i; - pool = new RingBuffer(1024 * 1024 * 4, true); + pool = new RingBuffer(1024 * 1024 * 4, shared); ASSERT_NE(nullptr, pool); for (i = 0; i < READ_THREAD_N; i++) { - threads[i].pipe = new UnixSocket(true, SOCK_DGRAM); + threads[i].pipe = new UnixSocket(true, SOCK_DGRAM); ASSERT_TRUE(threads[i].pipe->ready()); threads[i].thread = new std::thread(thread_read, i); } @@ -49,9 +49,16 @@ TEST(ringbuffer, thread) { delete threads[i].pipe; delete threads[i].thread; } + + delete pool; +} + +TEST(ringbuffer, thread) { + test_ringbuffer(true); + test_ringbuffer(false); } -static void thread_write(void) { +static void thread_write() { uint32_t size, yield_count = 0, yield_total_count = 0; void *ptr; pkg send_pkg; @@ -89,8 +96,7 @@ static void thread_write(void) { //在指针末尾保存一个串号 memcpy((char *) ptr + size - 4, &(send_pkg.serial_num), sizeof(send_pkg.serial_num)); - ASSERT_FALSE( - threads[i % READ_THREAD_N].pipe->write(&send_pkg, sizeof(send_pkg)) < 0); + ASSERT_FALSE(threads[i % READ_THREAD_N].pipe->write(&send_pkg, sizeof(send_pkg)) < 0); } // printf("yield_total_count=%d\n", yield_total_count); diff --git a/core-tests/src/memory/table.cpp b/core-tests/src/memory/table.cpp index d58ca0b0ac..8a02e5a46f 100644 --- a/core-tests/src/memory/table.cpp +++ b/core-tests/src/memory/table.cpp @@ -58,9 +58,9 @@ class table_t { throw exception_t("alloc failed", swoole_get_last_error()); } - table->add_column("id", TableColumn::TYPE_INT, 0); - table->add_column("name", TableColumn::TYPE_STRING, 32); - table->add_column("score", TableColumn::TYPE_FLOAT, 0); + EXPECT_TRUE(table->add_column("id", TableColumn::TYPE_INT, 0)); + EXPECT_TRUE(table->add_column("name", TableColumn::TYPE_STRING, 32)); + EXPECT_TRUE(table->add_column("score", TableColumn::TYPE_FLOAT, 0)); if (!table->create()) { throw exception_t("create failed", swoole_get_last_error()); @@ -133,10 +133,15 @@ class table_t { TEST(table, create) { table_t table(1024); + auto ptr = table.ptr(); - table.set("php", {"php", 1, 1.245}); - table.set("java", {"java", 2, 3.1415926}); - table.set("c++", {"c++", 3, 4.888}); + ASSERT_GT(ptr->get_memory_size(), ptr->get_size() * ptr->get_column_size()); + + ASSERT_FALSE(ptr->create()); // create again should fail + + ASSERT_TRUE(table.set("php", {"php", 1, 1.245})); + ASSERT_TRUE(table.set("java", {"java", 2, 3.1415926})); + ASSERT_TRUE(table.set("c++", {"c++", 3, 4.888})); ASSERT_EQ(table.count(), 3); @@ -145,9 +150,16 @@ TEST(table, create) { ASSERT_EQ(r1.score, 3.1415926); ASSERT_EQ(r1.name, std::string("java")); + ASSERT_FALSE(ptr->get_column("not-exists")); + ASSERT_TRUE(table.exists("php")); ASSERT_TRUE(table.del("php")); ASSERT_FALSE(table.exists("php")); + + ASSERT_FALSE(table.del("not-exists")); + + // Test with a string that is longer than the column size + ASSERT_TRUE(table.set("golang", {"golang " TEST_JPG_MD5SUM TEST_JPG_MD5SUM, 3, 4.888})); } void start_iterator(Table *_ptr) { @@ -330,3 +342,54 @@ TEST(table, lock) { } _rowlock->unlock(); } + +TEST(table, size_limit) { + auto t1 = Table::make(0x90000000, 1.2); + ASSERT_EQ(t1->get_size(), SW_TABLE_MAX_ROW_SIZE); + ASSERT_EQ(t1->get_conflict_proportion(), 1.0); + + EXPECT_FALSE(t1->add_column("bad_field", (TableColumn::Type) 8, 0)); + + auto t2 = Table::make(1024, 0.1); + ASSERT_EQ(t2->get_size(), 1024); + ASSERT_EQ(t2->get_conflict_proportion(), (float) SW_TABLE_CONFLICT_PROPORTION); +} + +TEST(table, lock_crash) { + table_t table(test_table_size); + create_table(table); + auto ptr = table.ptr(); + + auto child = test::spawn_exec([ptr]() { + TableRow *_rowlock = nullptr; + ptr->get("java", 4, &_rowlock); + usleep(5); + exit(200); // Simulate a crash in the child process, no release lock + }); + ASSERT_GT(child, 0); + test::wait_all_child_processes(); + + TableRow *_rowlock = nullptr; + ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); + _rowlock->unlock(); +} + +TEST(table, lock_race) { + table_t table(test_table_size); + create_table(table); + auto ptr = table.ptr(); + + auto child = test::spawn_exec([ptr]() { + TableRow *_rowlock = nullptr; + ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); + usleep(5); + _rowlock->unlock(); + }); + ASSERT_GT(child, 0); + + TableRow *_rowlock = nullptr; + ASSERT_NE(ptr->get("java", 4, &_rowlock), nullptr); + _rowlock->unlock(); + + test::wait_all_child_processes(); +} \ No newline at end of file diff --git a/core-tests/src/network/address.cpp b/core-tests/src/network/address.cpp new file mode 100644 index 0000000000..30049487ae --- /dev/null +++ b/core-tests/src/network/address.cpp @@ -0,0 +1,152 @@ +/* ++----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +using swoole::network::Address; + +TEST(address, basic) { + Address address{}; + ASSERT_TRUE(address.empty()); + ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU, 80, true)); + address.set_port(443); + ASSERT_EQ(address.get_port(), 443); + + ASSERT_TRUE(address.assign(SW_SOCK_TCP6, TEST_HTTP_DOMAIN, 80, true)); + address.set_port(9501); + ASSERT_EQ(address.get_port(), 9501); +} + +TEST(address, dns_fail) { + Address address{}; + ASSERT_FALSE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU "not-exists", 80, true)); + ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); +} + +TEST(address, path_to_long) { + Address address{}; + swoole::String path; + path.repeat("HELLO", 5, 128); + ASSERT_FALSE(address.assign(SW_SOCK_UNIX_DGRAM, path.to_std_string())); + ASSERT_ERREQ(SW_ERROR_NAME_TOO_LONG); +} + +TEST(address, bad_type) { + Address address{}; + ASSERT_FALSE(address.assign((swSocketType) (SW_SOCK_RAW6 + 9), TEST_DOMAIN_BAIDU)); + ASSERT_ERREQ(SW_ERROR_BAD_SOCKET_TYPE); +} + +TEST(address, type_str) { + ASSERT_STREQ(Address::type_str(SW_SOCK_TCP), "IPv4"); + ASSERT_STREQ(Address::type_str(SW_SOCK_UNIX_STREAM), "UnixSocket"); + ASSERT_STREQ(Address::type_str(SW_SOCK_TCP6), "IPv6"); + ASSERT_STREQ(Address::type_str((swSocketType) (SW_SOCK_RAW6 + 9)), "Unknown"); +} + +TEST(address, is_loopback_addr) { + Address address{}; + ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_DOMAIN_BAIDU, 80, true)); + ASSERT_FALSE(address.is_loopback_addr()); + + ASSERT_TRUE(address.assign(SW_SOCK_TCP, TEST_HOST, 80, true)); + ASSERT_TRUE(address.is_loopback_addr()); + + ASSERT_TRUE(address.assign(SW_SOCK_TCP6, "::1", 80, true)); + ASSERT_TRUE(address.is_loopback_addr()); + + ASSERT_TRUE(address.assign(SW_SOCK_TCP6, TEST_HTTP_DOMAIN, 443, true)); + ASSERT_FALSE(address.is_loopback_addr()); + + ASSERT_TRUE(address.assign(SW_SOCK_UNIX_DGRAM, TEST_LOG_FILE)); + ASSERT_FALSE(address.is_loopback_addr()); +} + +TEST(address, ipv4_addr) { + auto sock = swoole::make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + Address addr; + + ASSERT_TRUE(addr.assign("tcp://127.0.0.1:12345")); + ASSERT_EQ(sock->connect(addr), SW_ERR); + ASSERT_EQ(errno, ECONNREFUSED); + + ASSERT_TRUE(addr.assign("tcp://localhost:12345")); + ASSERT_EQ(sock->connect(addr), SW_ERR); + ASSERT_EQ(errno, ECONNREFUSED); + + sock->free(); +} + +TEST(address, ipv6_addr) { + auto sock = swoole::make_socket(SW_SOCK_TCP6, SW_FD_STREAM, 0); + Address addr; + + ASSERT_TRUE(addr.assign("tcp://[::1]:12345")); + ASSERT_EQ(sock->connect(addr), SW_ERR); + ASSERT_EQ(errno, ECONNREFUSED); + + ASSERT_TRUE(addr.assign("tcp://[ip6-localhost]:12345")); + ASSERT_EQ(sock->connect(addr), SW_ERR); + ASSERT_EQ(errno, ECONNREFUSED); + + sock->free(); +} + +TEST(address, unix_addr) { + auto sock = swoole::make_socket(SW_SOCK_UNIX_STREAM, SW_FD_STREAM, 0); + Address addr; + ASSERT_TRUE(addr.assign("unix:///tmp/swoole-not-exists.sock")); + ASSERT_EQ(sock->connect(addr), SW_ERR); + ASSERT_EQ(errno, ENOENT); + sock->free(); +} + +TEST(address, bad_addr) { + Address addr; + ASSERT_FALSE(addr.assign("test://[::1]:12345")); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_BAD_HOST_ADDR); + + uchar buf[16]; + ASSERT_EQ(Address::addr_str(AF_INET6 + 9, buf), nullptr); + ASSERT_EQ(errno, EAFNOSUPPORT); +} + +TEST(address, bad_port) { + Address addr; + ASSERT_FALSE(addr.assign("tcp://[::1]:92345")); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_BAD_PORT); +} + +TEST(address, loopback_addr) { + Address addr1; + addr1.assign(SW_SOCK_TCP, "127.0.0.1", 0); + ASSERT_TRUE(addr1.is_loopback_addr()); + + Address addr2; + addr2.assign(SW_SOCK_TCP6, "::1", 0); + ASSERT_TRUE(addr1.is_loopback_addr()); + + Address addr3; + addr3.assign(SW_SOCK_TCP, "192.168.1.2", 0); + ASSERT_FALSE(addr3.is_loopback_addr()); + + Address addr4; + addr4.assign(SW_SOCK_TCP6, "192::66::88", 0); + ASSERT_FALSE(addr4.is_loopback_addr()); +} diff --git a/core-tests/src/network/client.cpp b/core-tests/src/network/client.cpp index edf38e4e66..3f409d24c1 100644 --- a/core-tests/src/network/client.cpp +++ b/core-tests/src/network/client.cpp @@ -1,15 +1,25 @@ #include "test_core.h" #include "test_server.h" #include "test_process.h" +#include "core-tests/include/test_core.h" + +#include +#include +#include #define GREETER "Hello Swoole" #define GREETER_SIZE sizeof(GREETER) using swoole::HttpProxy; +using swoole::Mutex; using swoole::Pipe; using swoole::Socks5Proxy; +using swoole::String; +using swoole::network::Address; using swoole::network::AsyncClient; using swoole::network::Client; +using swoole::network::Socket; +using swoole::network::SyncClient; using swoole::test::Process; using swoole::test::Server; @@ -18,64 +28,74 @@ TEST(client, tcp) { char buf[128]; pid_t pid; + int port = swoole::test::get_random_port(); - Process proc([](Process *proc) { - on_receive_lambda_type receive_fn = [](ON_RECEIVE_PARAMS) { + Process proc([port](Process *proc) { + Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); + serv.on("Receive", [](ON_RECEIVE_PARAMS) { SERVER_THIS->send(req->info.fd, req->data, req->info.len); - }; - - Server serv(TEST_HOST, TEST_PORT, swoole::Server::MODE_BASE, SW_SOCK_TCP); - serv.on("onReceive", (void *) receive_fn); + return 0; + }); serv.start(); }); pid = proc.start(); - sleep(1); // wait for the test server to start + usleep(300000); // wait for the test server to start Client cli(SW_SOCK_TCP, false); ASSERT_NE(cli.socket, nullptr); - ret = cli.connect(&cli, TEST_HOST, TEST_PORT, -1, 0); + ret = cli.connect(TEST_HOST, port, -1, 0); ASSERT_EQ(ret, 0); - ret = cli.send(&cli, SW_STRS(GREETER), 0); + ret = cli.send(SW_STRS(GREETER), 0); ASSERT_GT(ret, 0); - ret = cli.recv(&cli, buf, 128, 0); + ret = cli.recv(buf, 128, 0); ASSERT_EQ(ret, GREETER_SIZE); ASSERT_STREQ(GREETER, buf); + Address peer_name; + ASSERT_EQ(cli.get_peer_name(&peer_name), 0); + ASSERT_STREQ(peer_name.get_addr(), "127.0.0.1"); + ASSERT_EQ(peer_name.get_port(), port); + + ASSERT_EQ(cli.close(), SW_OK); + ASSERT_EQ(cli.close(), SW_ERR); + kill(pid, SIGTERM); int status; wait(&status); } -TEST(client, udp) { +static void test_sync_client_dgram(const char *host, int port, enum swSocketType type) { int ret; char buf[128]; - pid_t pid; - Process proc([](Process *proc) { - on_packet_lambda_type packet_fn = [](ON_PACKET_PARAMS) { + Mutex *lock = new Mutex(true); + lock->lock(); + + Process proc([&](Process *proc) { + Server serv(host, port, swoole::Server::MODE_BASE, type); + serv.on("Packet", [](ON_PACKET_PARAMS) -> int { swoole::DgramPacket *packet = (swoole::DgramPacket *) req->data; SERVER_THIS->sendto(packet->socket_addr, packet->data, packet->length, req->info.server_fd); - }; - - Server serv(TEST_HOST, TEST_PORT, swoole::Server::MODE_BASE, SW_SOCK_UDP); - serv.on("onPacket", (void *) packet_fn); + return 0; + }); + serv.on("Start", [lock](ON_START_PARAMS) { lock->unlock(); }); serv.start(); }); pid = proc.start(); - sleep(1); // wait for the test server to start + lock->lock(); - Client cli(SW_SOCK_UDP, false); + Client cli(type, false); ASSERT_NE(cli.socket, nullptr); - ret = cli.connect(&cli, TEST_HOST, TEST_PORT, -1, 0); + ret = cli.connect(host, port, -1, 0); ASSERT_EQ(ret, 0); - ret = cli.send(&cli, SW_STRS(GREETER), 0); + ret = cli.send(SW_STRS(GREETER), 0); ASSERT_GT(ret, 0); - ret = cli.recv(&cli, buf, 128, 0); + ret = cli.recv(buf, 128, 0); ASSERT_EQ(ret, GREETER_SIZE); ASSERT_STREQ(GREETER, buf); @@ -84,30 +104,40 @@ TEST(client, udp) { wait(&status); } -TEST(client, async_tcp) { - pid_t pid; +TEST(client, udp) { + int port = swoole::test::get_random_port(); + test_sync_client_dgram("127.0.0.1", port, SW_SOCK_UDP); +} + +TEST(client, udp6) { + int port = swoole::test::get_random_port(); + test_sync_client_dgram("::1", port, SW_SOCK_UDP6); +} + +TEST(client, udg) { + test_sync_client_dgram("/tmp/swoole_core_tests.sock", 0, SW_SOCK_UNIX_DGRAM); +} +static void test_async_client_tcp(const char *host, int port, enum swSocketType type) { + pid_t pid; Pipe p(true); ASSERT_TRUE(p.ready()); - Process proc([&p](Process *proc) { - on_receive_lambda_type receive_fn = [](ON_RECEIVE_PARAMS) { - SERVER_THIS->send(req->info.fd, req->data, req->info.len); - }; - - Server serv(TEST_HOST, TEST_PORT, swoole::Server::MODE_BASE, SW_SOCK_TCP); + Process proc([&](Process *proc) { + Server serv(Socket::is_inet6(type) ? TEST_HOST6 : TEST_HOST, port, swoole::Server::MODE_BASE, type); serv.set_private_data("pipe", &p); - serv.on("onReceive", (void *) receive_fn); + serv.on("Receive", [](ON_RECEIVE_PARAMS) { + SERVER_THIS->send(req->info.fd, req->data, req->info.len); + return 0; + }); - on_workerstart_lambda_type worker_start_fn = [](ON_WORKERSTART_PARAMS) { + serv.on("WorkerStart", [](ON_WORKER_START_PARAMS) { Pipe *p = (Pipe *) SERVER_THIS->get_private_data("pipe"); int64_t value = 1; p->write(&value, sizeof(value)); - }; - - serv.on("onWorkerStart", (void *) worker_start_fn); + }); serv.start(); }); @@ -119,16 +149,12 @@ TEST(client, async_tcp) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - AsyncClient ac(SW_SOCK_TCP); + AsyncClient ac(type); ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); - ac.on_close([](AsyncClient *ac) { - - }); - ac.on_error([](AsyncClient *ac) { - - }); + ac.on_close([](AsyncClient *ac) {}); + ac.on_error([](AsyncClient *ac) {}); ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { ASSERT_EQ(len, GREETER_SIZE); @@ -136,7 +162,7 @@ TEST(client, async_tcp) { ac->close(); }); - bool retval = ac.connect(TEST_HOST, TEST_PORT); + bool retval = ac.connect(host, port, 1.0); EXPECT_TRUE(retval); swoole_event_wait(); @@ -146,18 +172,532 @@ TEST(client, async_tcp) { wait(&status); } +TEST(client, async_tcp) { + test_async_client_tcp(TEST_HOST, swoole::test::get_random_port(), SW_SOCK_TCP); +} + +TEST(client, async_tcp_dns) { + test_async_client_tcp("localhost", swoole::test::get_random_port(), SW_SOCK_TCP); +} + +TEST(client, async_tcp6) { + test_async_client_tcp("::1", swoole::test::get_random_port(), SW_SOCK_TCP6); +} + +TEST(client, async_tcp6_dns) { + test_async_client_tcp("localhost", swoole::test::get_random_port(), SW_SOCK_TCP6); +} + +TEST(client, async_tcp_dns_fail) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + Client ac(SW_SOCK_TCP, true); + + ASSERT_EQ(ac.connect(TEST_HOST, 9999), SW_ERR); + + bool success = true; + + ac.onConnect = [&success](Client *ac) { + ac->send(SW_STRS(GREETER)); + success = true; + }; + + ac.onClose = [](Client *ac) {}; + + ac.onError = [&success](Client *ac) { + DEBUG() << "connect failed, ERROR: " << errno << "\n"; + ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + success = false; + }; + + ac.onReceive = [](Client *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }; + + ASSERT_EQ(ac.connect("www.baidu.com-not-found", 80, 1.0), SW_OK); + + swoole_event_wait(); + + ASSERT_FALSE(success); +} + +TEST(client, async_tcp_ssl_handshake_fail) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + Client ac(SW_SOCK_TCP, true); + + bool success = true; + + ac.onConnect = [&success](Client *ac) { + ac->send(SW_STRS(GREETER)); + success = true; + }; + + ac.onClose = [](Client *ac) {}; + + ac.onError = [&success](Client *ac) { + DEBUG() << "connect failed, ERROR: " << errno << "\n"; + ASSERT_ERREQ(SW_ERROR_SSL_HANDSHAKE_FAILED); + success = false; + }; + + ac.onReceive = [](Client *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }; + + ac.enable_ssl_encrypt(); + + ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); + + swoole_event_wait(); + + ASSERT_FALSE(success); +} + +TEST(client, async_tcp_http_proxy_handshake_fail) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + Client ac(SW_SOCK_TCP, true); + + bool success = true; + + ac.onConnect = [&success](Client *ac) { + ac->send(SW_STRS(GREETER)); + success = true; + }; + + ac.onClose = [](Client *ac) {}; + + ac.onError = [&success](Client *ac) { + DEBUG() << "connect failed, ERROR: " << errno << "\n"; + ASSERT_ERREQ(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); + success = false; + }; + + ac.onReceive = [](Client *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }; + + ac.set_http_proxy("www.baidu.com", 80); + + ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); + + swoole_event_wait(); +} + +TEST(client, async_tcp_socks5_proxy_handshake_fail) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + Client ac(SW_SOCK_TCP, true); + + bool success = true; + + ac.onConnect = [&success](Client *ac) { + ac->send(SW_STRS(GREETER)); + success = true; + }; + + ac.onClose = [](Client *ac) {}; + + ac.onError = [&success](Client *ac) { + DEBUG() << "connect failed, ERROR: " << errno << "\n"; + ASSERT_ERREQ(ETIMEDOUT); + success = false; + }; + + ac.onReceive = [](Client *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }; + + ac.set_socks5_proxy("www.baidu.com", 80); + + ASSERT_EQ(ac.connect("www.baidu.com", 80, 1.0), SW_OK); + + swoole_event_wait(); +} + +TEST(client, sleep) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + String buf(65536); + + auto domain = TEST_HTTP_DOMAIN; + + Client client(SW_SOCK_TCP, true); + client.onConnect = [&domain](Client *cli) { + cli->sleep(); + swoole_timer_after(200, [cli, &domain](auto _1, auto _2) { + auto req = swoole::test::http_get_request(domain, "/"); + cli->send(req.c_str(), req.length(), 0); + cli->wakeup(); + }); + }; + + client.onError = [](Client *cli) {}; + client.onClose = [](Client *cli) {}; + client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; + + ASSERT_EQ(client.connect(domain, 80, -1, 0), 0); + + swoole_event_wait(); + + ASSERT_TRUE(buf.contains(TEST_HTTP_EXPECT)); +} + +TEST(client, sleep_2) { + auto port = __LINE__ + TEST_PORT; + auto server_pid = swoole::test::spawn_exec([port]() { + Server serv(TEST_HOST, port, swoole::Server::MODE_BASE, SW_SOCK_TCP); + serv.on("Receive", [](ON_RECEIVE_PARAMS) { + usleep(10000); + return SW_OK; + }); + serv.on("workerStart", [](ON_WORKER_START_PARAMS) { DEBUG() << "Worker started, PID: " << getpid() << "\n"; }); + serv.start(); + }); + + ASSERT_GT(server_pid, 0); + + swoole::test::counter_init(); + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + String buf(65536); + String wbuf(8 * 1024 * 1024); + wbuf.append_random_bytes(wbuf.size); + + Client client(SW_SOCK_TCP, true); + + client.buffer_high_watermark = 1024 * 1024; + client.buffer_low_watermark = 32 * 1024; + + client.onBufferFull = [](Client *cli) { + DEBUG() << "Buffer is full, waiting for data to be sent...\n"; + swoole::test::counter_incr(0); + }; + + client.onBufferEmpty = [server_pid](Client *cli) { + DEBUG() << "Buffer is empty, ready to send more data...\n"; + swoole::test::counter_incr(1); + swoole_timer_after(200, [cli, server_pid](auto _1, auto _2) { + cli->close(); + DEBUG() << "Client closed, terminating server...\n"; + kill(server_pid, SIGTERM); + }); + }; + + client.onConnect = [&wbuf, server_pid](Client *cli) { + DEBUG() << "Client connected, sending data...\n"; + EXPECT_EQ(cli->send(wbuf.str, wbuf.length), wbuf.length); + EXPECT_EQ(cli->send(wbuf.str, wbuf.length), -1); + ASSERT_ERREQ(SW_ERROR_OUTPUT_BUFFER_OVERFLOW); + swoole_timer_after(10, [cli, server_pid](auto _1, auto _2) { + cli->sleep(); + DEBUG() << "Client is sleeping...\n"; + swoole_timer_after(15, [cli](auto _1, auto _2) { + cli->wakeup(); + DEBUG() << "Client woke up, closing connection...\n"; + }); + }); + }; + + client.onError = [](Client *cli) { + DEBUG() << "Client error occurred, ERROR: " << swoole_get_last_error() << "\n"; + }; + client.onClose = [](Client *cli) { DEBUG() << "Client connection closed.\n"; }; + client.onReceive = [](Client *cli, const char *data, size_t length) { + DEBUG() << "Client received data, length: " << length << "\n"; + }; + + ASSERT_EQ(client.connect(TEST_HOST, port, -1, 0), 0); + + swoole_event_wait(); + + swoole::test::wait_all_child_processes(); + + ASSERT_GE(swoole::test::counter_get(0), 1); + ASSERT_GE(swoole::test::counter_get(1), 1); +} + TEST(client, connect_refuse) { int ret; Client cli(SW_SOCK_TCP, false); - ret = cli.connect(&cli, TEST_HOST, TEST_PORT + 10001, -1, 0); + ret = cli.connect(TEST_HOST, swoole::test::get_random_port(), -1, 0); ASSERT_EQ(ret, -1); ASSERT_EQ(swoole_get_last_error(), ECONNREFUSED); } +TEST(client, bind) { + Client cli(SW_SOCK_TCP, false); + ASSERT_EQ(cli.bind("127.0.0.1", 9999), SW_OK); + ASSERT_EQ(cli.bind("192.0.0.1", 9999), SW_ERR); + ASSERT_ERREQ(EADDRNOTAVAIL); + ASSERT_EQ(cli.bind("127.0.0.1", 80), SW_ERR); + if (swoole::test::is_github_ci()) { + ASSERT_ERREQ(EINVAL); + } else { + ASSERT_ERREQ(EACCES); + } +} + +// DNS 报文头部结构 +struct DNSHeader { + uint16_t id; // 标识符 + uint16_t flags; // 各种标志 + uint16_t qdcount; // 问题数量 + uint16_t ancount; // 回答数量 + uint16_t nscount; // 授权记录数量 + uint16_t arcount; // 附加记录数量 +}; + +// 将域名转换为 DNS 格式 +std::vector encodeDomainName(const std::string &domain) { + std::vector result; + std::string label; + + for (char c : domain) { + if (c == '.') { + result.push_back(static_cast(label.length())); + for (char lc : label) { + result.push_back(static_cast(lc)); + } + label.clear(); + } else { + label += c; + } + } + + // 处理最后一个标签 + if (!label.empty()) { + result.push_back(static_cast(label.length())); + for (char lc : label) { + result.push_back(static_cast(lc)); + } + } + + // 添加结束符 + result.push_back(0); + + return result; +} + +// 构建 DNS 查询报文 +std::vector buildDNSQuery(const std::string &domain, uint16_t recordType = 1) { + std::vector query; + + // 生成随机 ID + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, 65535); + uint16_t transactionId = dist(gen); + + // 构建 DNS 头部 + DNSHeader header; + header.id = htons(transactionId); // 网络字节序 + header.flags = htons(0x0100); // RD=1, 其余为0 + header.qdcount = htons(1); // 1个问题 + header.ancount = htons(0); // 0个回答 + header.nscount = htons(0); // 0个授权记录 + header.arcount = htons(0); // 0个附加记录 + + // 将头部添加到查询报文 + uint8_t *headerPtr = reinterpret_cast(&header); + query.insert(query.end(), headerPtr, headerPtr + sizeof(DNSHeader)); + + // 添加问题部分 - 域名 + std::vector qname = encodeDomainName(domain); + query.insert(query.end(), qname.begin(), qname.end()); + + // 添加问题部分 - 查询类型和查询类 + uint16_t qtype = htons(recordType); // 查询类型(如A记录=1) + uint16_t qclass = htons(1); // 查询类(IN=1) + + uint8_t *qtypePtr = reinterpret_cast(&qtype); + uint8_t *qclassPtr = reinterpret_cast(&qclass); + + query.insert(query.end(), qtypePtr, qtypePtr + sizeof(uint16_t)); + query.insert(query.end(), qclassPtr, qclassPtr + sizeof(uint16_t)); + + return query; +} + +// 将二进制数据转换为十六进制字符串 +std::string bytesToHexString(const std::vector &data) { + std::stringstream ss; + + for (size_t i = 0; i < data.size(); ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(data[i]); + if (i < data.size() - 1) { + ss << " "; + } + } + + return ss.str(); +} + +TEST(client, sendto) { + Client cli(SW_SOCK_TCP, false); + ASSERT_EQ(cli.sendto("127.0.0.1", 9999, SW_STRL(TEST_STR)), SW_ERR); + ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + + auto dns_server = swoole_get_dns_server(); + Client dsock(SW_SOCK_UDP, false); + auto dnsQuery = buildDNSQuery("www.baidu.com"); + ASSERT_EQ(dsock.sendto(dns_server.host, dns_server.port, (const char *) dnsQuery.data(), dnsQuery.size()), SW_OK); + ASSERT_GT(dsock.recv(sw_tg_buffer()->str, sw_tg_buffer()->size), 0); + + Address ra; + ASSERT_EQ(dsock.get_peer_name(&ra), SW_OK); + ASSERT_STREQ(ra.get_addr(), dns_server.host.c_str()); + ASSERT_EQ(ra.get_port(), dns_server.port); + + Client cli2(SW_SOCK_UDP, false); + ASSERT_EQ(cli2.sendto("www.baidu.com-not-exists", 9999, SW_STRL(TEST_STR)), SW_ERR); + ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + + Client cli3(SW_SOCK_UNIX_DGRAM, false); + ASSERT_EQ(cli3.sendto("/tmp/swoole.sock", 0, SW_STRL(TEST_STR)), SW_ERR); + ASSERT_ERREQ(ENOENT); +} + +TEST(client, async_unix_connect_refuse) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + std::unordered_map flags; + + AsyncClient ac(SW_SOCK_UNIX_DGRAM); + + ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); + + ac.on_close([](AsyncClient *ac) {}); + + ac.on_error([&](AsyncClient *ac) { flags["onError"] = true; }); + + ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }); + + bool retval = ac.connect("/tmp/swoole-not-exists.sock", 0); + + ASSERT_EQ(retval, false); + ASSERT_TRUE(flags["onError"]); + ASSERT_EQ(errno, ENOENT); + + swoole_event_wait(); +} + +TEST(client, async_connect_timeout) { + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + std::unordered_map flags; + + AsyncClient ac(SW_SOCK_TCP); + + ac.on_connect([](AsyncClient *ac) { ac->send(SW_STRS(GREETER)); }); + + ac.on_close([](AsyncClient *ac) {}); + + ac.on_error([&](AsyncClient *ac) { + flags["onError"] = true; + ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); + }); + + ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }); + + ASSERT_TRUE(ac.connect("192.168.1.199", 19999, 0.2)); + swoole_event_wait(); + + ASSERT_TRUE(flags["onError"]); +} + +static void test_async_client_dgram(const char *host, int port, enum swSocketType type) { + pid_t pid; + + Mutex *lock = new Mutex(true); + lock->lock(); + + Process proc([&](Process *proc) { + Server serv(host, port, swoole::Server::MODE_BASE, type); + serv.on("Packet", [](ON_PACKET_PARAMS) -> int { + swoole::DgramPacket *packet = (swoole::DgramPacket *) req->data; + SERVER_THIS->sendto(packet->socket_addr, packet->data, packet->length, req->info.server_fd); + return 0; + }); + serv.on("Start", [lock](ON_START_PARAMS) { lock->unlock(); }); + serv.start(); + }); + + pid = proc.start(); + + lock->lock(); + + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + std::unordered_map flags; + + AsyncClient ac(type); + + ac.on_connect([&](AsyncClient *ac) { + flags["onConnect"] = true; + ac->send(SW_STRS(GREETER)); + }); + + ac.on_close([&](AsyncClient *ac) { flags["onClose"] = true; }); + + ac.on_error([&](AsyncClient *ac) { + flags["onError"] = true; + ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); + }); + + ac.on_receive([&](AsyncClient *ac, const char *data, size_t len) { + flags["onReceive"] = true; + ASSERT_EQ(len, GREETER_SIZE); + ASSERT_STREQ(GREETER, data); + ac->close(); + }); + + ASSERT_TRUE(ac.connect(host, port, 0.2)); + swoole_event_wait(); + + kill(pid, SIGTERM); + int status; + wait(&status); + + ASSERT_TRUE(flags["onConnect"]); + ASSERT_TRUE(flags["onReceive"]); + ASSERT_TRUE(flags["onClose"]); + ASSERT_FALSE(flags["onError"]); +} + +TEST(client, async_udp) { + test_async_client_dgram(TEST_HOST, swoole::test::get_random_port(), SW_SOCK_UDP); +} + +TEST(client, async_udp_dns) { + test_async_client_dgram("localhost", swoole::test::get_random_port(), SW_SOCK_UDP); +} + +TEST(client, async_udp6) { + test_async_client_dgram("::1", swoole::test::get_random_port(), SW_SOCK_UDP6); +} + TEST(client, connect_timeout) { int ret; Client cli(SW_SOCK_TCP, false); - ret = cli.connect(&cli, "19.168.0.99", TEST_PORT + 10001, 0.2, 0); + ret = cli.connect("19.168.0.99", swoole::test::get_random_port(), 0.2, 0); ASSERT_EQ(ret, -1); ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); } @@ -166,10 +706,13 @@ TEST(client, shutdown_write) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); - ret = cli.connect(&cli, "www.baidu.com", 80, -1, 0); + ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); - cli.shutdown(SHUT_WR); - ssize_t retval = cli.send(&cli, SW_STRL("hello world"), 0); + + ASSERT_EQ(cli.shutdown(SHUT_WR), 0); + ASSERT_EQ(cli.shutdown(SHUT_WR), SW_ERR); // already shutdown + + ssize_t retval = cli.send(SW_STRL("hello world"), 0); ASSERT_EQ(retval, -1); ASSERT_EQ(swoole_get_last_error(), EPIPE); } @@ -178,15 +721,17 @@ TEST(client, shutdown_read) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); - ret = cli.connect(&cli, "www.baidu.com", 80, -1, 0); + ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); - cli.shutdown(SHUT_RD); - ssize_t retval = cli.send(&cli, SW_STRL("hello world\r\n\r\n"), 0); + ASSERT_EQ(cli.shutdown(SHUT_RD), SW_OK); + ASSERT_EQ(cli.shutdown(SHUT_RD), SW_ERR); // already shutdown + + ssize_t retval = cli.send(SW_STRL("hello world\r\n\r\n"), 0); ASSERT_GT(retval, 0); char buf[1024]; - retval = cli.recv(&cli, buf, sizeof(buf), 0); + retval = cli.recv(buf, sizeof(buf), 0); ASSERT_EQ(retval, 0); } @@ -194,90 +739,79 @@ TEST(client, shutdown_all) { signal(SIGPIPE, SIG_IGN); int ret; Client cli(SW_SOCK_TCP, false); - ret = cli.connect(&cli, "www.baidu.com", 80, -1, 0); + ret = cli.connect("www.baidu.com", 80, -1, 0); ASSERT_EQ(ret, 0); - cli.shutdown(SHUT_RDWR); + ASSERT_EQ(cli.shutdown(SHUT_RDWR), SW_OK); + ASSERT_EQ(cli.shutdown(SHUT_RDWR + 99), SW_ERR); + ASSERT_ERREQ(EINVAL); - ssize_t retval = cli.send(&cli, SW_STRL("hello world\r\n\r\n"), 0); + ssize_t retval = cli.send(SW_STRL("hello world\r\n\r\n"), 0); ASSERT_EQ(retval, -1); ASSERT_EQ(swoole_get_last_error(), EPIPE); char buf[1024]; - retval = cli.recv(&cli, buf, sizeof(buf), 0); + retval = cli.recv(buf, sizeof(buf), 0); ASSERT_EQ(retval, 0); } -#ifdef SW_USE_OPENSSL -TEST(client, ssl_1) { - int ret; - +static void test_ssl_http_get() { bool connected = false; bool closed = false; - swoole::String buf(65536); + String buf(65536); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); Client client(SW_SOCK_TCP, true); - client.enable_ssl_encrypt(); + + ASSERT_EQ(client.enable_ssl_encrypt(), SW_OK); + ASSERT_EQ(client.enable_ssl_encrypt(), SW_ERR); // already enabled + client.onConnect = [&connected](Client *cli) { connected = true; - cli->send(cli, - SW_STRL("GET / HTTP/1.1\r\n" - "Host: www.baidu.com\r\n" - "Connection: close\r\n" - "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/51.0.2704.106 Safari/537.36" - "\r\n\r\n"), - 0); + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + cli->send(req.c_str(), req.length(), 0); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; - ret = client.connect(&client, "www.baidu.com", 443, -1, 0); - ASSERT_EQ(ret, 0); + + ASSERT_EQ(client.connect(TEST_HTTP_DOMAIN, 443, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(connected); ASSERT_TRUE(closed); - ASSERT_TRUE(buf.contains("Baidu")); + ASSERT_TRUE(buf.contains(TEST_HTTPS_EXPECT)); } +TEST(client, ssl_1) { + test_ssl_http_get(); +} -TEST(client, http_proxy) { - int ret; - +TEST(client, ssl_sendfile) { bool connected = false; bool closed = false; - swoole::String buf(65536); + String buf(65536); swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + auto file = swoole::make_tmpfile(); + file.write(SW_STRL(TEST_REQUEST_BAIDU)); + Client client(SW_SOCK_TCP, true); client.enable_ssl_encrypt(); - client.http_proxy = new HttpProxy(); - client.http_proxy->proxy_host = std::string(TEST_HTTP_PROXY_HOST); - client.http_proxy->proxy_port = TEST_HTTP_PROXY_PORT; - - client.onConnect = [&connected](Client *cli) { + client.onConnect = [&connected, &file](Client *cli) { connected = true; - cli->send(cli, - SW_STRL("GET / HTTP/1.1\r\n" - "Host: www.baidu.com\r\n" - "Connection: close\r\n" - "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/51.0.2704.106 Safari/537.36" - "\r\n\r\n"), - 0); + cli->sendfile(file.get_path().c_str(), 0, file.get_size()); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; - ret = client.connect(&client, "www.baidu.com", 443, -1, 0); - ASSERT_EQ(ret, 0); + + ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, 443, -1, 0), 0); swoole_event_wait(); @@ -286,48 +820,250 @@ TEST(client, http_proxy) { ASSERT_TRUE(buf.contains("Baidu")); } -TEST(client, socks5_proxy) { - int ret; - - bool connected = false; - bool closed = false; - swoole::String buf(65536); +TEST(client, sync_ssl_sendfile) { + auto file = swoole::make_tmpfile(); + file.write(SW_STRL(TEST_REQUEST_BAIDU)); + + SyncClient client(SW_SOCK_TCP); + ASSERT_TRUE(client.connect(TEST_DOMAIN_BAIDU, 443, -1)); + ASSERT_TRUE(client.enable_ssl_encrypt()); + ASSERT_TRUE(client.sendfile(file.get_path().c_str())); + + String buf(65536); + while (true) { + ssize_t nr = client.recv(buf.str, buf.size - buf.length); + if (nr <= 0) { + break; + } + buf.grow(nr); + } + client.close(); + ASSERT_TRUE(buf.contains("baidu.com")); + unlink(file.get_path().c_str()); +} +static void proxy_async_test(Client &client, bool https) { swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - Client client(SW_SOCK_TCP, true); - client.enable_ssl_encrypt(); + bool connected = false; + bool closed = false; + String buf(65536); - client.socks5_proxy = new Socks5Proxy(); - client.socks5_proxy->host = std::string("127.0.0.1"); - client.socks5_proxy->port = 1080; - client.socks5_proxy->dns_tunnel = 1; - client.socks5_proxy->method = 0x02; - client.socks5_proxy->username = std::string("user"); - client.socks5_proxy->password = std::string("password"); + if (https) { + client.enable_ssl_encrypt(); + } client.onConnect = [&connected](Client *cli) { connected = true; - cli->send(cli, - SW_STRL("GET / HTTP/1.1\r\n" - "Host: www.baidu.com\r\n" - "Connection: close\r\n" - "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/51.0.2704.106 Safari/537.36" - "\r\n\r\n"), - 0); + cli->send(SW_STRL(TEST_REQUEST_BAIDU), 0); }; client.onError = [](Client *cli) {}; client.onClose = [&closed](Client *cli) { closed = true; }; client.onReceive = [&buf](Client *cli, const char *data, size_t length) { buf.append(data, length); }; - ret = client.connect(&client, "www.baidu.com", 443, -1, 0); - ASSERT_EQ(ret, 0); + + ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, https ? 443 : 80, -1, 0), 0); swoole_event_wait(); ASSERT_TRUE(connected); ASSERT_TRUE(closed); - ASSERT_TRUE(buf.contains("Baidu")); + ASSERT_TRUE(buf.contains("www.baidu.com")); +} + +static void proxy_sync_test(Client &client, bool https) { + String buf(65536); + if (https) { + client.enable_ssl_encrypt(); + } + + std::string host = TEST_DOMAIN_BAIDU; + if (client.socks5_proxy && !client.socks5_proxy->dns_tunnel) { + host = swoole::network::gethostbyname(AF_INET, host); + DEBUG() << "Resolved domain " << TEST_DOMAIN_BAIDU << " to " << host << "\n"; + } + + ASSERT_EQ(client.connect(host.c_str(), https ? 443 : 80, -1, 0), 0); + ASSERT_GT(client.send(SW_STRL(TEST_REQUEST_BAIDU), 0), 0); + + while (true) { + char rbuf[4096]; + auto nr = client.recv(rbuf, sizeof(rbuf), 0); + if (nr <= 0) { + break; + } + buf.append(rbuf, nr); + } + + ASSERT_TRUE(buf.contains("www.baidu.com")); +} + +static void proxy_set_socks5_proxy(Client &client) { + std::string username = std::string(TEST_SOCKS5_PROXY_USER); + std::string password = std::string(TEST_SOCKS5_PROXY_PASSWORD); + client.set_socks5_proxy(TEST_SOCKS5_PROXY_HOST, TEST_SOCKS5_PROXY_PORT, username, password); +} + +static void proxy_set_http_proxy(Client &client) { + std::string username, password; + if (swoole::test::is_github_ci()) { + username = std::string(TEST_HTTP_PROXY_USER); + password = std::string(TEST_HTTP_PROXY_PASSWORD); + } + client.set_http_proxy(TEST_HTTP_PROXY_HOST, TEST_HTTP_PROXY_PORT, username, password); +} + +TEST(client, https_get_async_with_http_proxy) { + Client client(SW_SOCK_TCP, true); + proxy_set_http_proxy(client); + proxy_async_test(client, true); +} + +TEST(client, https_get_async_with_socks5_proxy) { + Client client(SW_SOCK_TCP, true); + proxy_set_socks5_proxy(client); + proxy_async_test(client, true); +} + +TEST(client, https_get_sync_with_http_proxy) { + Client client(SW_SOCK_TCP, false); + proxy_set_http_proxy(client); + proxy_sync_test(client, true); +} + +TEST(client, https_get_sync_with_socks5_proxy) { + Client client(SW_SOCK_TCP, false); + proxy_set_socks5_proxy(client); + proxy_sync_test(client, true); +} + +TEST(client, http_get_sync_with_socks5_proxy_no_dns_tunnel) { + Client client(SW_SOCK_TCP, false); + proxy_set_socks5_proxy(client); + client.socks5_proxy->dns_tunnel = 0; + proxy_sync_test(client, false); +} + +TEST(client, http_get_async_with_http_proxy) { + Client client(SW_SOCK_TCP, true); + proxy_set_http_proxy(client); + proxy_async_test(client, false); +} + +TEST(client, http_get_async_with_socks5_proxy) { + Client client(SW_SOCK_TCP, true); + proxy_set_socks5_proxy(client); + proxy_async_test(client, false); +} + +TEST(client, http_get_sync_with_http_proxy) { + Client client(SW_SOCK_TCP, false); + proxy_set_http_proxy(client); + proxy_sync_test(client, false); +} + +TEST(client, http_get_sync_with_socks5_proxy) { + Client client(SW_SOCK_TCP, false); + proxy_set_socks5_proxy(client); + proxy_sync_test(client, false); +} + +TEST(client, ssl) { + Client client(SW_SOCK_TCP, false); + client.enable_ssl_encrypt(); + client.set_tls_host_name(TEST_HTTP_DOMAIN); + ASSERT_EQ(client.connect(TEST_HTTP_DOMAIN, 443, -1, 0), SW_OK); + + auto sock = client.socket; + ASSERT_TRUE(sock->ssl_get_peer_certificate(sw_tg_buffer())); + auto ls = sock->ssl_get_peer_cert_chain(10); + ASSERT_FALSE(ls.empty()); + swoole::test::dump_cert_info(sw_tg_buffer()->str, sw_tg_buffer()->length); + ASSERT_EQ(client.ssl_verify(false), SW_OK); + + auto req = swoole::test::http_get_request(TEST_HTTP_DOMAIN, "/"); + + constexpr off_t offset1 = TEST_WRITEV_OFFSET; + iovec wr_iov[2]; + wr_iov[0].iov_base = (void *) req.c_str(); + wr_iov[0].iov_len = offset1; + wr_iov[1].iov_base = (char *) req.c_str() + offset1; + wr_iov[1].iov_len = req.length() - offset1; + + swoole::network::IOVector wr_vec(wr_iov, 2); + ASSERT_EQ(sock->ssl_writev(&wr_vec), req.length()); + + sw_tg_buffer()->clear(); + if (sw_tg_buffer()->size < 1024 * 1024) { + sw_tg_buffer()->extend(1024 * 1024); + } + + constexpr off_t offset2 = TEST_READV_OFFSET; + iovec rd_iov[2]; + rd_iov[0].iov_base = sw_tg_buffer()->str; + rd_iov[0].iov_len = offset2; + rd_iov[1].iov_base = sw_tg_buffer()->str + offset2; + rd_iov[1].iov_len = sw_tg_buffer()->size - offset2; + + swoole::network::IOVector rd_vec(rd_iov, 2); + auto rv = sock->ssl_readv(&rd_vec); + ASSERT_GT(rv, 1024); + sw_tg_buffer()->length = rv; + sw_tg_buffer()->set_null_terminated(); + + ASSERT_TRUE(sw_tg_buffer()->contains(TEST_HTTPS_EXPECT)); +} + +TEST(client, fail) { + Client c(static_cast(SW_SOCK_RAW6 + 1), false); + ASSERT_FALSE(c.ready()); + ASSERT_ERREQ(ESOCKTNOSUPPORT); +} + +static void test_recv_timeout(Client &c) { + std::thread t([]() { + SW_LOOP_N(20) { + usleep(50000); + kill(getpid(), SIGIO); + } + }); + + swoole_signal_set( + SIGIO, [](int) { swoole::test::counter_incr(0); }, 0, 1); + + auto buf = sw_tg_buffer(); + while (true) { + auto rv = c.recv(buf->str, buf->size); + DEBUG() << "rv: " << rv << ", error=" << errno << "\n"; + if (c.has_timedout()) { + break; + } + } + + t.join(); +} + +TEST(client, recv_timeout) { + Client c(SW_SOCK_TCP, false); + ASSERT_TRUE(c.ready()); + ASSERT_EQ(c.connect(TEST_HTTP_DOMAIN, 80, 1.0), SW_OK); + test_recv_timeout(c); +} + +TEST(client, ssl_recv_timeout) { + Client c(SW_SOCK_TCP, false); + ASSERT_TRUE(c.ready()); + c.enable_ssl_encrypt(); + + ASSERT_EQ(c.connect(TEST_HTTP_DOMAIN, 443, 1.0), SW_OK); + test_recv_timeout(c); +} + +TEST(client, sync_nonblock) { + Client cli(SW_SOCK_TCP, false); + ASSERT_NE(cli.socket, nullptr); + auto rv = cli.connect(TEST_HTTP_DOMAIN, 80, -1, 1); + ASSERT_EQ(rv, -1); + ASSERT_EQ(errno, EINPROGRESS); + ASSERT_TRUE(cli.async_connect); } -#endif diff --git a/core-tests/src/network/dns.cpp b/core-tests/src/network/dns.cpp index cc7cd23002..4bac26a4a6 100644 --- a/core-tests/src/network/dns.cpp +++ b/core-tests/src/network/dns.cpp @@ -27,7 +27,6 @@ using namespace swoole; using swoole::coroutine::Socket; using swoole::coroutine::System; using namespace swoole::test; -using namespace std; TEST(dns, lookup1) { test::coroutine::run([](void *arg) { @@ -64,7 +63,7 @@ TEST(dns, cancel) { test::coroutine::run([](void *arg) { auto co = Coroutine::get_current_safe(); Coroutine::create([co](void *) { - System::sleep(0.002); + System::sleep(0.001); co->cancel(); }); auto list1 = swoole::coroutine::dns_lookup("www.baidu-not-found-for-cancel.com", AF_INET, 2); @@ -73,57 +72,77 @@ TEST(dns, cancel) { }); } +TEST(dns, gethostbyname) { + GethostbynameRequest req1(TEST_HTTP_DOMAIN, AF_INET); + ASSERT_EQ(network::gethostbyname(&req1), 0); + ASSERT_TRUE(network::Address::verify_ip(AF_INET, req1.addr)); + + GethostbynameRequest req2(TEST_HTTP_DOMAIN, AF_INET6); + ASSERT_EQ(network::gethostbyname(&req2), 0); + ASSERT_TRUE(network::Address::verify_ip(AF_INET6, req2.addr)); +} + TEST(dns, getaddrinfo) { - char buf[1024] = {}; - swoole::network::GetaddrinfoRequest req = {}; - req.hostname = "www.baidu.com"; - req.family = AF_INET; - req.socktype = SOCK_STREAM; - req.protocol = 0; - req.service = nullptr; - req.result = buf; - ASSERT_EQ(swoole::network::getaddrinfo(&req), 0); + GetaddrinfoRequest req("www.baidu.com", AF_INET, SOCK_STREAM, 0, ""); + ASSERT_EQ(network::getaddrinfo(&req), 0); ASSERT_GT(req.count, 0); - vector ip_list; + std::vector ip_list; req.parse_result(ip_list); for (auto &ip : ip_list) { - ASSERT_TRUE(swoole::network::Address::verify_ip(AF_INET, ip)); + ASSERT_TRUE(network::Address::verify_ip(AF_INET, ip)); + } +} + +TEST(dns, getaddrinfo_fail) { + GetaddrinfoRequest req("www.baidu.com-not-exists", AF_INET, SOCK_STREAM, 0, ""); + ASSERT_EQ(network::getaddrinfo(&req), -1); + ASSERT_EQ(req.error, EAI_NONAME); +} + +TEST(dns, getaddrinfo_ipv6) { + GetaddrinfoRequest req(TEST_HTTP_DOMAIN, AF_INET6, SOCK_STREAM, 0, ""); + ASSERT_EQ(network::getaddrinfo(&req), 0); + ASSERT_GT(req.count, 0); + + DEBUG() << "result count: " << req.count << std::endl; + + std::vector ip_list; + req.parse_result(ip_list); + + for (auto &ip : ip_list) { + ASSERT_TRUE(network::Address::verify_ip(AF_INET6, ip)); } } TEST(dns, load_resolv_conf) { - // reset - SwooleG.dns_server_host = ""; - SwooleG.dns_server_port = 0; + int port = get_random_port(); - auto dns_server = swoole_get_dns_server(); - ASSERT_TRUE(dns_server.first.empty()); - ASSERT_EQ(dns_server.second, 0); + auto ori_dns_server = swoole_get_dns_server(); // with port - std::string test_server = "127.0.0.1:8080"; // fake dns server + std::string test_server = "127.0.0.1:" + std::to_string(port); // fake dns server swoole_set_dns_server(test_server); - dns_server = swoole_get_dns_server(); - ASSERT_STREQ(dns_server.first.c_str(), "127.0.0.1"); - ASSERT_EQ(dns_server.second, 8080); + auto dns_server = swoole_get_dns_server(); + ASSERT_STREQ(dns_server.host.c_str(), "127.0.0.1"); + ASSERT_EQ(dns_server.port, port); // invalid port test_server = "127.0.0.1:808088"; swoole_set_dns_server(test_server); dns_server = swoole_get_dns_server(); - ASSERT_EQ(dns_server.second, SW_DNS_SERVER_PORT); + ASSERT_EQ(dns_server.port, SW_DNS_SERVER_PORT); ASSERT_TRUE(swoole_load_resolv_conf()); dns_server = swoole_get_dns_server(); - ASSERT_FALSE(dns_server.first.empty()); - ASSERT_NE(dns_server.second, 0); + ASSERT_EQ(dns_server.host, ori_dns_server.host); + ASSERT_EQ(dns_server.port, ori_dns_server.port); } TEST(dns, gethosts) { char hosts_file[] = "/tmp/swoole_hosts"; - ofstream file(hosts_file); + std::ofstream file(hosts_file); if (!file.is_open()) { std::cout << std::string("file open failed: ") + std::string(strerror(errno)) << std::endl; throw strerror(errno); @@ -207,12 +226,20 @@ void name_resolver_test_fn_2() { ASSERT_TRUE(swoole::network::Address::verify_ip(AF_INET, ip)); } -TEST(dns, name_resolver_1) { +TEST(dns, name_resolve_1) { name_resolver_test_fn_1(); test::coroutine::run([](void *arg) { name_resolver_test_fn_1(); }); } -TEST(dns, name_resolver_2) { +TEST(dns, name_resolve_2) { name_resolver_test_fn_2(); test::coroutine::run([](void *arg) { name_resolver_test_fn_2(); }); } + +TEST(dns, name_resolve_fail) { + NameResolver::Context ctx; + ctx = {AF_INET}; + auto ip = swoole_name_resolver_lookup("www.baidu.com-not-exists", &ctx); + ASSERT_TRUE(ip.empty()); + ASSERT_ERREQ(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); +} diff --git a/core-tests/src/network/socket.cpp b/core-tests/src/network/socket.cpp index d37b47d150..2cef064c2e 100644 --- a/core-tests/src/network/socket.cpp +++ b/core-tests/src/network/socket.cpp @@ -25,6 +25,108 @@ using namespace swoole; const char test_data[] = "hello swoole, hello world, php is best"; +TEST(socket, connect_sync) { + network::Address sa; + network::Socket *sock; + + sock = make_socket(SW_SOCK_UNIX_STREAM, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + sa.assign(SW_SOCK_UNIX_STREAM, "/tmp/swole-not-exists.sock"); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), ENOENT); + sock->free(); + + sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + sa.assign(SW_SOCK_TCP, "192.168.199.199", 80); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), ETIMEDOUT); + sock->free(); + + sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + sa.assign(SW_SOCK_TCP, "127.0.0.1", 59999); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), ECONNREFUSED); + sock->free(); + + sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + sa.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_OK); + sock->free(); + + sock = make_socket(SW_SOCK_UDP, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + sa.assign(SW_SOCK_UDP, "127.0.0.1", 9900); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_OK); + sock->free(); +} + +TEST(socket, fail) { + auto *sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_NE(sock, nullptr); + + network::Address sa; + sa.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); + sock->set_timeout(0.3, SW_TIMEOUT_CONNECT); + ASSERT_EQ(sock->connect_sync(sa), SW_OK); + + close(sock->get_fd()); + + ASSERT_EQ(sock->get_name(), -1); + ASSERT_EQ(errno, EBADF); + + network::Address peer; + ASSERT_EQ(sock->get_peer_name(&peer), -1); + ASSERT_EQ(errno, EBADF); + + ASSERT_EQ(sock->set_tcp_nopush(1), -1); + ASSERT_EQ(sock->listen(1), -1); + + ASSERT_FALSE(sock->set_buffer_size(1)); + ASSERT_FALSE(sock->set_recv_buffer_size(1)); + ASSERT_FALSE(sock->set_send_buffer_size(1)); + + ASSERT_FALSE(sock->set_tcp_nodelay()); + ASSERT_FALSE(sock->cork()); + ASSERT_FALSE(sock->uncork()); + + ASSERT_FALSE(sock->set_kernel_read_timeout(0.1)); + ASSERT_FALSE(sock->set_kernel_write_timeout(0.1)); + + sock->move_fd(); + sock->free(); +} + +TEST(socket, ssl_fail) { + sysv_signal(SIGPIPE, SIG_IGN); + network::Client client(SW_SOCK_TCP, false); + client.enable_ssl_encrypt(); + + ASSERT_EQ(client.connect(TEST_DOMAIN_BAIDU, 443, -1, 0), 0); + ASSERT_EQ(client.shutdown(SHUT_WR), 0); + + ASSERT_EQ(client.get_socket()->ssl_send(SW_STRL(TEST_STR)), SW_ERR); + ASSERT_EQ(errno, SW_ERROR_SSL_RESET); + + ASSERT_EQ(client.shutdown(SHUT_RD), 0); + + char buf[1024]; + errno = 0; + ASSERT_EQ(client.get_socket()->ssl_recv(SW_STRL(buf)), 0); + ASSERT_EQ(errno, 0); + ASSERT_EQ(close(client.get_socket()->get_fd()), 0); + client.get_socket()->move_fd(); + + ASSERT_EQ(client.get_socket()->ssl_recv(SW_STRL(buf)), 0); +} + TEST(socket, sendto) { char sock1_path[] = "/tmp/udp_unix1.sock"; char sock2_path[] = "/tmp/udp_unix2.sock"; @@ -33,10 +135,10 @@ TEST(socket, sendto) { unlink(sock2_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock1->bind(sock1_path, nullptr); + sock1->bind(sock1_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock2->bind(sock2_path, nullptr); + sock2->bind(sock2_path); ASSERT_GT(sock1->sendto(sock2_path, 0, test_data, strlen(test_data)), 0); @@ -45,7 +147,7 @@ TEST(socket, sendto) { sa.type = SW_SOCK_UNIX_DGRAM; ASSERT_GT(sock2->recvfrom(buf, sizeof(buf), 0, &sa), 0); ASSERT_STREQ(test_data, buf); - ASSERT_STREQ(sa.get_ip(), sock1_path); + ASSERT_STREQ(sa.get_addr(), sock1_path); sock1->free(); sock2->free(); @@ -54,16 +156,17 @@ TEST(socket, sendto) { } static void test_sendto(enum swSocketType sock_type) { - int port1 = 0, port2 = 0; const char *ip = sock_type == SW_SOCK_UDP ? "127.0.0.1" : "::1"; auto sock1 = make_socket(sock_type, SW_FD_DGRAM_SERVER, 0); - sock1->bind(ip, &port1); + ASSERT_EQ(sock1->bind(ip, 0), SW_OK); + ASSERT_EQ(sock1->get_name(), SW_OK); auto sock2 = make_socket(sock_type, SW_FD_DGRAM_SERVER, 0); - sock2->bind(ip, &port2); + ASSERT_EQ(sock2->bind(ip, 0), SW_OK); + ASSERT_EQ(sock2->get_name(), SW_OK); - ASSERT_GT(sock1->sendto(ip, port2, test_data, strlen(test_data)), 0); + ASSERT_GT(sock1->sendto(ip, sock2->get_port(), test_data, strlen(test_data)), 0); char buf[1024] = {}; network::Address sa; @@ -71,8 +174,8 @@ static void test_sendto(enum swSocketType sock_type) { ASSERT_GT(sock2->recvfrom(buf, sizeof(buf), 0, &sa), 0); ASSERT_STREQ(test_data, buf); - ASSERT_EQ(sa.get_port(), port1); - ASSERT_STREQ(sa.get_ip(), ip); + ASSERT_EQ(sa.get_port(), sock1->get_port()); + ASSERT_STREQ(sa.get_addr(), ip); sock1->free(); sock2->free(); @@ -89,9 +192,10 @@ TEST(socket, sendto_ipv6) { TEST(socket, recv) { mutex m; m.lock(); + int port = swoole::test::get_random_port(); - thread t1([&m]() { - auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + thread t1([&m, port]() { + auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, port); char buf[1024] = {}; svr->set_block(); m.unlock(); @@ -103,10 +207,10 @@ TEST(socket, recv) { svr->free(); }); - thread t2([&m]() { + thread t2([&m, port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); - ASSERT_EQ(cli->connect(TEST_HOST, TEST_PORT), SW_OK); + ASSERT_EQ(cli->connect(TEST_HOST, port), SW_OK); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cli->send(test_data, sizeof(test_data), 0); cli->free(); @@ -116,26 +220,27 @@ TEST(socket, recv) { t2.join(); } -TEST(socket, recvfrom_blocking) { +TEST(socket, recvfrom_sync) { mutex m; m.lock(); + int port = swoole::test::get_random_port(); - thread t1([&m]() { - auto svr = make_server_socket(SW_SOCK_UDP, TEST_HOST, TEST_PORT); + thread t1([&m, port]() { + auto svr = make_server_socket(SW_SOCK_UDP, TEST_HOST, port); network::Address addr; char buf[1024] = {}; svr->set_nonblock(); m.unlock(); - svr->recvfrom_blocking(buf, sizeof(buf), 0, &addr); + svr->recvfrom_sync(buf, sizeof(buf), 0, &addr); ASSERT_STREQ(test_data, buf); svr->free(); }); - thread t2([&m]() { + thread t2([&m, port]() { m.lock(); auto cli = make_socket(SW_SOCK_UDP, SW_FD_STREAM_CLIENT, 0); network::Address addr; - addr.assign(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + addr.assign(SW_SOCK_TCP, TEST_HOST, port); ASSERT_EQ(cli->connect(addr), SW_OK); std::this_thread::sleep_for(std::chrono::milliseconds(100)); cli->send(test_data, sizeof(test_data), 0); @@ -146,38 +251,82 @@ TEST(socket, recvfrom_blocking) { t2.join(); } -TEST(socket, sendfile_blocking) { +TEST(socket, send_async_1) { + auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); + ASSERT_TRUE(sock->set_block()); + ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), SW_OK); + + auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); + ASSERT_EQ(sock->send_async(req.c_str(), req.length()), req.length()); + + auto buf = sw_tg_buffer(); + auto n = sock->recv_sync(buf->str, buf->size, 0); + ASSERT_GT(n, 0); + buf->length = n; + ASSERT_TRUE(buf->contains(SW_STRL(TEST_HTTP_EXPECT))); + + sock->free(); +} + +TEST(socket, send_async_2) { + auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); + ASSERT_TRUE(sock->set_block()); + ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), SW_OK); + + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); + ASSERT_EQ(sock->send_async(req.c_str(), req.length()), req.length()); + + swoole_event_set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_READ, [](Reactor *reactor, Event *event) { + auto buf = sw_tg_buffer(); + auto n = event->socket->recv_sync(buf->str, buf->size, 0); + EXPECT_GT(n, 0); + buf->length = n; + EXPECT_TRUE(buf->contains(SW_STRL(TEST_HTTP_EXPECT))); + + return 0; + }); + + swoole_event_add(sock, SW_EVENT_READ | SW_EVENT_ONCE); + swoole_event_wait(); + + sock->free(); +} + +TEST(socket, sendfile_sync) { string file = test::get_root_path() + "/examples/test.jpg"; mutex m; + int port = swoole::test::get_random_port(); m.lock(); auto str = file_get_contents(file); - thread t1([&m, &str]() { - auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + thread t1([&m, &str, port]() { + auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, port); m.unlock(); auto cli = svr->accept(); int len; - cli->recv_blocking(&len, sizeof(len), MSG_WAITALL); + cli->recv_sync(&len, sizeof(len), MSG_WAITALL); int _len = ntohl(len); ASSERT_EQ(_len, str->get_length()); ASSERT_LT(_len, 1024 * 1024); std::unique_ptr data(new char[_len]); - cli->recv_blocking(data.get(), _len, MSG_WAITALL); + cli->recv_sync(data.get(), _len, MSG_WAITALL); ASSERT_STREQ(data.get(), str->value()); cli->free(); svr->free(); }); - thread t2([&m, &file, &str]() { + thread t2([&m, &file, &str, port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); network::Address addr; - addr.assign(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + addr.assign(SW_SOCK_TCP, TEST_HOST, port); ASSERT_EQ(cli->connect(addr), SW_OK); int len = htonl(str->get_length()); cli->send(&len, sizeof(len), 0); - ASSERT_EQ(cli->sendfile_blocking(file.c_str(), 0, 0, -1), SW_OK); + ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_OK); cli->free(); }); @@ -185,6 +334,48 @@ TEST(socket, sendfile_blocking) { t2.join(); } +TEST(socket, sendfile) { + string file = "/tmp/swoole-file-not-exists"; + auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); + network::Address addr; + addr.assign(SW_SOCK_TCP, TEST_HTTP_DOMAIN, 80); + ASSERT_EQ(cli->connect(addr), SW_OK); + + ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_ERR); + ASSERT_EQ(errno, ENOENT); + + File fp(file, File::WRITE | File::CREATE); + ASSERT_TRUE(fp.ready()); + + ASSERT_EQ(cli->sendfile_sync(file.c_str(), 0, 0), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_FILE_EMPTY); + + fp.write(SW_STRL(TEST_STR)); + fp.close(); + + ASSERT_EQ(cli->sendfile_sync(file.c_str(), 10, 100), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_INVALID_PARAMS); + + ASSERT_TRUE(fp.open(file, File::WRITE | File::APPEND)); + auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/"); + fp.write(req); + fp.close(); + + ASSERT_EQ(cli->sendfile_sync(file.c_str(), strlen(TEST_STR), 0), SW_OK); + + char rbuf[4096]; + auto n = cli->recv_sync(rbuf, sizeof(rbuf), 0); + ASSERT_GT(n, 0); + + String resp(rbuf, n); + + ASSERT_TRUE(resp.contains(SW_STRL(TEST_HTTP_EXPECT))); + + cli->free(); + + ASSERT_TRUE(File::remove(file)); +} + TEST(socket, peek) { char sock1_path[] = "/tmp/udp_unix1.sock"; char sock2_path[] = "/tmp/udp_unix2.sock"; @@ -193,10 +384,10 @@ TEST(socket, peek) { unlink(sock2_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock1->bind(sock1_path, nullptr); + sock1->bind(sock1_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock2->bind(sock2_path, nullptr); + sock2->bind(sock2_path); ASSERT_GT(sock1->sendto(sock2_path, 0, test_data, strlen(test_data)), 0); @@ -214,18 +405,16 @@ TEST(socket, peek) { unlink(sock2_path); } -TEST(socket, sendto_blocking) { +TEST(socket, sendto_sync) { char sock1_path[] = "/tmp/udp_unix1.sock"; unlink(sock1_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock1->bind(sock1_path, nullptr); - sock1->info.assign(SW_SOCK_UNIX_DGRAM, sock1_path, 0); + sock1->bind(sock1_path); char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock2_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock2->bind(sock2_path, nullptr); - sock2->info.assign(SW_SOCK_UNIX_DGRAM, sock2_path, 0); + sock2->bind(sock2_path); char sendbuf[65536] = {}; swoole_random_string(sendbuf, sizeof(sendbuf) - 1); @@ -245,10 +434,13 @@ TEST(socket, sendto_blocking) { } }); + network::Address sock2_addr; + ASSERT_TRUE(sock2_addr.assign(SW_SOCK_UNIX_DGRAM, sock2_path)); + for (int i = 0; i < 10; i++) { - ASSERT_GT(sock1->sendto_blocking(sock2->info, sendbuf, strlen(sendbuf)), 0); + ASSERT_GT(sock1->sendto_sync(sock2_addr, sendbuf, strlen(sendbuf)), 0); } - ASSERT_GT(sock1->sendto_blocking(sock2->info, "end", 3), 0); + ASSERT_GT(sock1->sendto_sync(sock2_addr, "end", 3), 0); t1.join(); @@ -262,20 +454,21 @@ TEST(socket, clean) { char sock1_path[] = "/tmp/udp_unix1.sock"; unlink(sock1_path); auto sock1 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock1->bind(sock1_path, nullptr); - sock1->info.assign(SW_SOCK_UNIX_DGRAM, sock1_path, 0); + sock1->bind(sock1_path); char sock2_path[] = "/tmp/udp_unix2.sock"; unlink(sock2_path); auto sock2 = make_socket(SW_SOCK_UNIX_DGRAM, SW_FD_DGRAM_SERVER, 0); - sock2->bind(sock2_path, nullptr); - sock2->info.assign(SW_SOCK_UNIX_DGRAM, sock2_path, 0); + sock2->bind(sock2_path); char sendbuf[65536] = {}; swoole_random_string(sendbuf, sizeof(sendbuf) - 1); + network::Address sock2_addr; + ASSERT_TRUE(sock2_addr.assign(SW_SOCK_UNIX_DGRAM, sock2_path)); + for (int i = 0; i < 3; i++) { - ASSERT_GT(sock1->sendto_blocking(sock2->info, sendbuf, strlen(sendbuf)), 0); + ASSERT_GT(sock1->sendto(sock2_addr, sendbuf, strlen(sendbuf)), 0); } sock2->clean(); @@ -291,10 +484,11 @@ TEST(socket, clean) { TEST(socket, check_liveness) { mutex m; + int svr_port = TEST_PORT + __LINE__; m.lock(); - thread t1([&m]() { - auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + thread t1([&m, svr_port]() { + auto svr = make_server_socket(SW_SOCK_TCP, TEST_HOST, svr_port); m.unlock(); auto cli = svr->accept(); @@ -313,11 +507,11 @@ TEST(socket, check_liveness) { svr->free(); }); - thread t2([&m]() { + thread t2([&m, svr_port]() { m.lock(); auto cli = make_socket(SW_SOCK_TCP, SW_FD_STREAM_CLIENT, 0); - ASSERT_EQ(cli->connect(TEST_HOST, TEST_PORT), SW_OK); + ASSERT_EQ(cli->connect(TEST_HOST, svr_port), SW_OK); cli->send(test_data, sizeof(test_data), 0); std::this_thread::sleep_for(std::chrono::milliseconds(1)); @@ -333,3 +527,327 @@ TEST(socket, check_liveness) { t1.join(); t2.join(); } + +#define CRLF "\r\n" + +static void test_socket_sync(network::Socket *sock, bool connect = true) { + if (connect) { + network::Address addr; + ASSERT_TRUE(addr.assign("tcp://" TEST_HTTP_DOMAIN ":80")); + ASSERT_EQ(sock->connect(addr), 0); + } + + auto req = test::http_get_request(TEST_HTTP_DOMAIN, "/get"); + ASSERT_EQ(sock->write_sync(req.c_str(), req.length()), req.length()); + ASSERT_TRUE(sock->check_liveness()); + + string resp; + SW_LOOP { + char buf[1024]; + auto n = sock->read_sync(buf, sizeof(buf)); + if (n == 0) { + break; + } + ASSERT_GT(n, 0); + resp.append(buf, n); + } + + ASSERT_TRUE(resp.find(TEST_HTTP_EXPECT) != resp.npos); + + usleep(50000); + ASSERT_FALSE(sock->check_liveness()); + + sock->free(); +} + +TEST(socket, sync) { + auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + test_socket_sync(sock); +} + +TEST(socket, dup) { + auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + network::Address addr; + ASSERT_TRUE(addr.assign("tcp://" TEST_HTTP_DOMAIN ":80")); + ASSERT_EQ(sock->connect(addr), 0); + + auto sock_2 = sock->dup(); + sock->free(); + + test_socket_sync(sock_2, false); +} + +TEST(socket, convert_to_type) { + ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_STREAM), SW_SOCK_TCP); + ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_STREAM), SW_SOCK_TCP6); + ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_DGRAM), SW_SOCK_UDP); + ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_DGRAM), SW_SOCK_UDP6); + ASSERT_EQ(network::Socket::convert_to_type(AF_LOCAL, SOCK_STREAM), SW_SOCK_UNIX_STREAM); + ASSERT_EQ(network::Socket::convert_to_type(AF_LOCAL, SOCK_DGRAM), SW_SOCK_UNIX_DGRAM); + ASSERT_EQ(network::Socket::convert_to_type(AF_INET, SOCK_RAW), SW_SOCK_RAW); + ASSERT_EQ(network::Socket::convert_to_type(AF_INET6, SOCK_RAW), SW_SOCK_RAW6); + + std::string s1("unix:///tmp/swoole.sock"); + ASSERT_EQ(network::Socket::convert_to_type(s1), SW_SOCK_UNIX_STREAM); + ASSERT_EQ(s1, "/tmp/swoole.sock"); + + std::string s2("127.0.0.1"); + ASSERT_EQ(network::Socket::convert_to_type(s2), SW_SOCK_TCP); + + std::string s3("::1"); + ASSERT_EQ(network::Socket::convert_to_type(s3), SW_SOCK_TCP6); + + std::string s4("unix:/tmp/swoole.sock"); + ASSERT_EQ(network::Socket::convert_to_type(s4), SW_SOCK_UNIX_STREAM); + ASSERT_EQ(s4, "/tmp/swoole.sock"); +} + +static void test_sock_type(SocketType type, int expect_sock_domain, int expect_sock_type) { + int sock_domain, sock_type; + ASSERT_EQ(network::Socket::get_domain_and_type(type, &sock_domain, &sock_type), SW_OK); + ASSERT_EQ(sock_domain, expect_sock_domain); + ASSERT_EQ(sock_type, expect_sock_type); +} + +TEST(socket, get_domain_and_type) { + test_sock_type(SW_SOCK_TCP, AF_INET, SOCK_STREAM); + test_sock_type(SW_SOCK_TCP6, AF_INET6, SOCK_STREAM); + test_sock_type(SW_SOCK_UDP, AF_INET, SOCK_DGRAM); + test_sock_type(SW_SOCK_UDP6, AF_INET6, SOCK_DGRAM); + test_sock_type(SW_SOCK_UNIX_STREAM, AF_LOCAL, SOCK_STREAM); + test_sock_type(SW_SOCK_UNIX_DGRAM, AF_LOCAL, SOCK_DGRAM); + test_sock_type(SW_SOCK_RAW, AF_INET, SOCK_RAW); + test_sock_type(SW_SOCK_RAW6, AF_INET6, SOCK_RAW); + + ASSERT_TRUE(network::Socket::is_dgram(SW_SOCK_UDP6)); + ASSERT_TRUE(network::Socket::is_stream(SW_SOCK_TCP)); + + int sock_domain, sock_type; + ASSERT_EQ( + network::Socket::get_domain_and_type(static_cast(SW_SOCK_RAW6 + 1), &sock_domain, &sock_type), + SW_ERR); +} + +TEST(socket, make_socket) { + network::Socket *sock; + + sock = make_socket(SW_SOCK_RAW, SW_FD_STREAM, 0); + ASSERT_EQ(sock, nullptr); + ASSERT_EQ(errno, EPROTONOSUPPORT); + ASSERT_EQ(swoole_get_last_error(), EPROTONOSUPPORT); + + sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, AF_INET6, SOCK_RDM, 999, 0); + ASSERT_EQ(sock, nullptr); + ASSERT_EQ(errno, EINVAL); + ASSERT_EQ(swoole_get_last_error(), EINVAL); +} + +TEST(socket, make_server_socket) { + network::Socket *sock; + + auto bad_addr = "199.199.0.0"; + + sock = make_server_socket(SW_SOCK_RAW, bad_addr); + ASSERT_EQ(sock, nullptr); + if (geteuid() == 0) { // root + ASSERT_EQ(errno, EPROTONOSUPPORT); + ASSERT_EQ(swoole_get_last_error(), EPROTONOSUPPORT); + } else { + ASSERT_EQ(errno, ESOCKTNOSUPPORT); + ASSERT_EQ(swoole_get_last_error(), ESOCKTNOSUPPORT); + } + + sock = make_server_socket(SW_SOCK_TCP, bad_addr); + ASSERT_EQ(sock, nullptr); + ASSERT_EQ(errno, EADDRNOTAVAIL); + + sock = make_server_socket(SW_SOCK_TCP, TEST_HOST, 0, -1); + ASSERT_NE(sock, nullptr); + sock->free(); +} + +TEST(socket, ssl_get_error_reason) { + swoole_ssl_init(); + { + int reason = -1; + const char *error_str = network::Socket::ssl_get_error_reason(&reason); + + EXPECT_EQ(error_str, nullptr); + EXPECT_EQ(reason, 0); + } + // 测试单个错误的情况 + { + // 生成一个 OpenSSL 错误 + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED, __FILE__, __LINE__); + + int reason = -1; + const char *error_str = network::Socket::ssl_get_error_reason(&reason); + + // 验证错误原因代码 + EXPECT_EQ(reason, SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED); + + // 验证错误字符串 + EXPECT_NE(error_str, nullptr); + EXPECT_TRUE(strstr(error_str, "certificate expired") != nullptr || + strstr(error_str, "CERTIFICATE_EXPIRED") != nullptr); + + // 验证错误队列现在应该为空(因为 ERR_get_error 会移除错误) + EXPECT_EQ(ERR_peek_error(), 0); + } + + // 测试多个错误的情况(只返回第一个) + { + // 生成多个 OpenSSL 错误 + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_SSLV3_ALERT_BAD_CERTIFICATE, __FILE__, __LINE__); + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SHUTDOWN, SSL_R_PROTOCOL_IS_SHUTDOWN, __FILE__, __LINE__); + + int reason = -1; + const char *error_str = network::Socket::ssl_get_error_reason(&reason); + + // 验证返回的是第一个错误的原因代码 + EXPECT_EQ(reason, SSL_R_SSLV3_ALERT_BAD_CERTIFICATE); + + // 验证错误字符串 + EXPECT_NE(error_str, nullptr); + EXPECT_TRUE(strstr(error_str, "bad certificate") != nullptr || strstr(error_str, "BAD_CERTIFICATE") != nullptr); + + // 验证错误队列中还有一个错误 + EXPECT_NE(ERR_peek_error(), 0); + + ERR_get_error(); + } + + // 测试不同库的错误 + { + // 生成一个 BIO 库错误 + ERR_put_error(ERR_LIB_BIO, BIO_F_BIO_WRITE, BIO_R_BROKEN_PIPE, __FILE__, __LINE__); + + int reason = -1; + const char *error_str = network::Socket::ssl_get_error_reason(&reason); + + // 验证错误原因代码 + EXPECT_EQ(reason, BIO_R_BROKEN_PIPE); + + // 验证错误字符串 + EXPECT_NE(error_str, nullptr); + EXPECT_TRUE(strstr(error_str, "broken pipe") != nullptr || strstr(error_str, "BROKEN_PIPE") != nullptr); + } + + // 测试 reason 参数为 nullptr 的情况(如果函数支持) + { + // 生成一个 OpenSSL 错误 + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_READ, SSL_R_SSL_HANDSHAKE_FAILURE, __FILE__, __LINE__); + + // 调用函数,传入 nullptr 作为 reason 参数 + // 注意:如果函数不支持 nullptr 参数,这个测试会导致段错误 + // 在这种情况下,应该跳过这个测试或修改函数以支持 nullptr + const char *error_str = network::Socket::ssl_get_error_reason(nullptr); + + // 验证错误字符串 + EXPECT_NE(error_str, nullptr); + EXPECT_TRUE(strstr(error_str, "handshake failure") != nullptr || + strstr(error_str, "HANDSHAKE_FAILURE") != nullptr); + } + + // 测试错误队列中有错误但 ERR_reason_error_string 返回 nullptr 的情况 + { + // 使用一个不常见的错误代码,可能没有对应的错误字符串 + // 注意:这个测试可能不稳定,因为 OpenSSL 可能为所有错误代码都提供字符串 + ERR_put_error(ERR_LIB_USER, 0, 12345, __FILE__, __LINE__); + + int reason = -1; + const char *error_str = network::Socket::ssl_get_error_reason(&reason); + + // 验证错误原因代码 + EXPECT_EQ(reason, 12345); + + // 错误字符串可能为 nullptr 或包含通用错误信息 + // 这个验证可能需要根据实际情况调整 + if (error_str != nullptr) { + EXPECT_TRUE(true); // 如果有字符串,测试通过 + } else { + EXPECT_EQ(error_str, nullptr); // 如果没有字符串,也测试通过 + } + } + + // 测试函数在多次调用后的行为 + { + // 生成一个 OpenSSL 错误 + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_CTX_NEW, SSL_R_LIBRARY_HAS_NO_CIPHERS, __FILE__, __LINE__); + + // 第一次调用 + int reason1 = -1; + const char *error_str1 = network::Socket::ssl_get_error_reason(&reason1); + + // 验证第一次调用的结果 + EXPECT_EQ(reason1, SSL_R_LIBRARY_HAS_NO_CIPHERS); + EXPECT_NE(error_str1, nullptr); + + // 第二次调用,应该没有错误了 + int reason2 = -1; + const char *error_str2 = network::Socket::ssl_get_error_reason(&reason2); + + // 验证第二次调用的结果 + EXPECT_EQ(reason2, 0); + EXPECT_EQ(error_str2, nullptr); + } +} + +TEST(socket, catch_error) { + network::Socket fake_sock; + ASSERT_EQ(fake_sock.catch_write_pipe_error(ENOBUFS), SW_REDUCE_SIZE); + ASSERT_EQ(fake_sock.catch_write_pipe_error(EMSGSIZE), SW_REDUCE_SIZE); + ASSERT_EQ(fake_sock.catch_write_pipe_error(EAGAIN), SW_WAIT); + + ASSERT_EQ(fake_sock.catch_write_error(ENOBUFS), SW_WAIT); +} + +TEST(socket, misc) { + auto sock = make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_TRUE(sock->set_nonblock()); + ASSERT_EQ(sock->get_out_buffer_length(), 0); + ASSERT_TRUE(sock->set_block()); + ASSERT_GT(sock->get_fd(), 0); + ASSERT_EQ(sock->connect(TEST_HTTP_DOMAIN, 80), 0); + ASSERT_EQ(sock->get_name(), 0); + ASSERT_GT(sock->get_port(), 1024); + + auto fd = sock->get_fd(); + auto sock_fd = sock->move_fd(); + ASSERT_EQ(fd, sock_fd); + ASSERT_EQ(sock->get_fd(), -1); + + ASSERT_EQ(sock->catch_write_pipe_error(EMSGSIZE), SW_REDUCE_SIZE); + ASSERT_FALSE(sock->kernel_nobufs); + + ASSERT_EQ(sock->catch_write_pipe_error(ENOBUFS), SW_REDUCE_SIZE); + ASSERT_TRUE(sock->kernel_nobufs); + + ASSERT_EQ(sock->catch_write_error(EAGAIN), sock->catch_write_pipe_error(EAGAIN)); + + delete sock; + + ASSERT_TRUE(network::Socket::is_dgram(SW_SOCK_UDP)); + ASSERT_FALSE(network::Socket::is_dgram(SW_SOCK_TCP6)); + + ASSERT_TRUE(network::Socket::is_stream(SW_SOCK_TCP)); + ASSERT_FALSE(network::Socket::is_stream(SW_SOCK_UDP)); + + ASSERT_TRUE(network::Socket::is_inet6(SW_SOCK_UDP6)); + ASSERT_FALSE(network::Socket::is_inet6(SW_SOCK_UDP)); + + int fds[2]; + ASSERT_EQ(pipe(fds), 0); + + network::Socket sock1{}; + sock1.fd = fds[1]; + + network::Socket sock2{}; + sock2.fd = fds[0]; + + ASSERT_EQ(sock1.write(test_data, strlen(test_data)), strlen(test_data)); + + char rbuf[128]; + ASSERT_EQ(sock2.read(rbuf, sizeof(rbuf)), strlen(test_data)); + ASSERT_MEMEQ(test_data, rbuf, strlen(test_data)); +} diff --git a/core-tests/src/network/stream.cpp b/core-tests/src/network/stream.cpp index 0389a14dcd..c22e55ec0d 100644 --- a/core-tests/src/network/stream.cpp +++ b/core-tests/src/network/stream.cpp @@ -21,15 +21,17 @@ #include "swoole_server.h" using namespace std; +using namespace swoole; using namespace swoole::network; TEST(stream, send) { - swServer serv(swoole::Server::MODE_BASE); + Server serv(Server::MODE_BASE); serv.worker_num = 1; + int svr_port = swoole::test::get_random_port(); int ori_log_level = sw_logger()->get_level(); sw_logger()->set_level(SW_LOG_ERROR); - swListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, TEST_PORT); + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, svr_port); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); @@ -67,9 +69,10 @@ TEST(stream, send) { ASSERT_EQ(stream1->send(buf, sizeof(buf)), SW_OK); // success requset - auto stream2 = Stream::create(TEST_HOST, TEST_PORT, SW_SOCK_TCP); + auto stream2 = Stream::create(TEST_HOST, svr_port, SW_SOCK_TCP); ASSERT_TRUE(stream2); stream2->private_data = new string(buf, sizeof(buf)); + stream2->set_max_length(8 * 1024 * 1024); stream2->response = [](Stream *stream, const char *data, uint32_t length) { string *buf = (string *) stream->private_data; string pkt = string("Server: ") + *buf; @@ -83,9 +86,9 @@ TEST(stream, send) { kill(getpid(), SIGTERM); }); - serv.onWorkerStart = [&lock](swServer *serv, int worker_id) { lock.unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; - serv.onReceive = [&buf](swServer *serv, swRecvData *req) -> int { + serv.onReceive = [&buf](Server *serv, RecvData *req) -> int { string req_body(req->data + 4, req->info.len - 4); EXPECT_EQ(string(buf, sizeof(buf)), req_body); diff --git a/core-tests/src/os/async.cpp b/core-tests/src/os/async.cpp index b34f5ef005..95cd335946 100644 --- a/core-tests/src/os/async.cpp +++ b/core-tests/src/os/async.cpp @@ -29,24 +29,25 @@ using namespace swoole; static int callback_count; TEST(async, dispatch) { + int count = 1000; callback_count = 0; std::atomic handle_count(0); AsyncEvent event = {}; event.object = &handle_count; event.callback = [](AsyncEvent *event) { callback_count++; }; - event.handler = [](AsyncEvent *event) { (*(std::atomic *) event->object)++; }; + event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - for (int i = 0; i < 1000; ++i) { + for (int i = 0; i < count; ++i) { auto ret = swoole::async::dispatch(&event); EXPECT_EQ(ret->object, event.object); } swoole_event_wait(); - ASSERT_EQ(handle_count, 1000); - ASSERT_EQ(callback_count, 1000); + ASSERT_EQ(handle_count, count); + ASSERT_EQ(callback_count, count); } TEST(async, schedule) { @@ -62,7 +63,7 @@ TEST(async, schedule) { event.callback = [](AsyncEvent *event) { callback_count++; }; event.handler = [](AsyncEvent *event) { usleep(swoole_rand(50000, 100000)); - (*(std::atomic *) event->object)++; + ++(*static_cast *>(event->object)); }; SwooleG.aio_core_worker_num = 4; @@ -78,12 +79,29 @@ TEST(async, schedule) { count--; if (count == 0) { swoole_timer_del(timer); - ASSERT_EQ(SwooleTG.async_threads->get_worker_num(), 128); - ASSERT_GT(SwooleTG.async_threads->get_queue_size(), 100); - ASSERT_GT(SwooleTG.async_threads->get_task_num(), 100); + ASSERT_GT(sw_async_threads()->get_worker_num(), 16); + ASSERT_GT(sw_async_threads()->get_queue_size(), 100); + ASSERT_GT(sw_async_threads()->get_task_num(), 100); break; + } else if (count == N - 1) { + ASSERT_EQ(sw_async_threads()->get_worker_num(), 4); + ASSERT_LE(sw_async_threads()->get_queue_size(), 1); + ASSERT_EQ(sw_async_threads()->get_task_num(), 1); + } else if (count < N / 2) { + ASSERT_GT(sw_async_threads()->get_worker_num(), 4); } } + + if (count % 50 == 0) { + DEBUG() << "async worker thread num=" << sw_async_threads()->get_worker_num() << "\n"; + } + }); + + swoole_timer_tick(2000, [](TIMER_PARAMS) { + DEBUG() << "async worker thread num=" << sw_async_threads()->get_worker_num() << "\n"; + if (sw_async_threads()->get_worker_num() < 16) { + swoole_timer_del(tnode); + } }); swoole_event_wait(); @@ -91,3 +109,34 @@ TEST(async, schedule) { ASSERT_EQ(handle_count, N); ASSERT_EQ(callback_count, N); } + +TEST(async, misc) { + callback_count = 0; + std::atomic handle_count(0); + AsyncEvent event = {}; + AsyncEvent *rv; + event.object = &handle_count; + event.callback = [](AsyncEvent *event) { callback_count++; }; + event.handler = [](AsyncEvent *event) { ++(*static_cast *>(event->object)); }; + + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + auto ret = swoole::async::dispatch(&event); + EXPECT_EQ(ret->object, event.object); + + sw_async_threads()->notify_one(); + + AsyncEvent event2 = {}; + event2.callback = [](AsyncEvent *event) { + ASSERT_EQ(event->retval, -1); + ASSERT_EQ(event->error, SW_ERROR_AIO_BAD_REQUEST); + callback_count++; + }; + rv = swoole::async::dispatch(&event2); + EXPECT_NE(rv, nullptr); + + swoole_event_wait(); + + ASSERT_EQ(handle_count, 1); + ASSERT_EQ(callback_count, 2); +} diff --git a/core-tests/src/os/file.cpp b/core-tests/src/os/file.cpp new file mode 100644 index 0000000000..381d60f185 --- /dev/null +++ b/core-tests/src/os/file.cpp @@ -0,0 +1,174 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "swoole_file.h" +#include "swoole_pipe.h" + +using namespace swoole; + +TEST(file, read_line) { + std::string filename = test::get_root_path() + "/tests/include/bootstrap.php"; + File file(filename, File::READ); + FILE *stdc_file = fopen(filename.c_str(), "r"); + ASSERT_NE(stdc_file, nullptr); + char buf1[1024]; + char buf2[1024]; + + size_t size = file.get_size(); + size_t total = 0; + + while (true) { + auto retval = file.read_line(buf1, sizeof(buf1)); + if (retval == 0) { + break; + } + total += retval; + ASSERT_NE(fgets(buf2, sizeof(buf2), stdc_file), nullptr); + ASSERT_STREQ(buf1, buf2); + } + ASSERT_EQ(total, size); +} + +TEST(file, read_line_no_crlf) { + String buf(1024); + swoole_random_string(buf.str, buf.size - 1); + buf.str[buf.size - 1] = '\0'; + + std::string filename = "/tmp/swoole_file_read_line_no_crlf.txt"; + ASSERT_TRUE(file_put_contents(filename, buf.str, buf.size - 1)); + + File file(filename, File::READ); + char rbuf[1024]; + ASSERT_EQ(file.read_line(rbuf, sizeof(rbuf)), sizeof(rbuf) - 1); + ASSERT_EQ(rbuf[sizeof(rbuf) - 1], '\0'); + + remove(filename.c_str()); +} + +TEST(file, file_put_contents) { + std::string filename = "/tmp/not-exists-dir/test.txt"; + + ASSERT_FALSE(file_put_contents(filename, TEST_STR, 0)); + ASSERT_ERREQ(SW_ERROR_FILE_EMPTY); + + ASSERT_FALSE(file_put_contents(filename, TEST_STR, SwooleG.max_file_content + 1)); + ASSERT_ERREQ(SW_ERROR_FILE_TOO_LARGE); + + ASSERT_FALSE(file_put_contents(filename, SW_STRL(TEST_STR))); + ASSERT_ERREQ(ENOENT); +} + +TEST(file, file_get_contents) { + std::string filename = "/tmp/not-exists-dir/test.txt"; + + ASSERT_EQ(file_get_contents(filename), nullptr); + ASSERT_ERREQ(ENOENT); + + ASSERT_EQ(file_get_contents("/tmp"), nullptr); + ASSERT_ERREQ(EISDIR); + + auto empty_file = "/tmp/empty-file.txt"; + int fd = open(empty_file, O_CREAT | O_RDWR, 0644); + close(fd); + + ASSERT_EQ(file_get_contents(empty_file), nullptr); + ASSERT_ERREQ(SW_ERROR_FILE_EMPTY); + remove(empty_file); + + auto large_file = test::get_root_path() + "/bin/core-tests"; + SwooleG.max_file_content = 1024 * 1024; + ASSERT_EQ(file_get_contents(large_file), nullptr); + ASSERT_ERREQ(SW_ERROR_FILE_TOO_LARGE); + SwooleG.max_file_content = SW_MAX_FILE_CONTENT; +} + +TEST(file, file_get_size) { + ASSERT_EQ(file_get_size("/tmp/not-exists-file.txt"), -1); + ASSERT_ERREQ(ENOENT); + + ASSERT_EQ(file_get_size(9999), -1); + ASSERT_ERREQ(EBADF); + + int fd = open("/tmp", O_RDONLY); + ASSERT_EQ(file_get_size(fd), -1); + ASSERT_ERREQ(EISDIR); +} + +TEST(file, open_twice) { + auto fname = "/tmp/swoole_file_open_twice.txt"; + File file1(fname, File::WRITE | File::CREATE); + ASSERT_TRUE(file1.ready()); + + file1.open(fname, File::READ); + ASSERT_TRUE(file1.ready()); + ASSERT_TRUE(file1.close()); + ASSERT_FALSE(file1.close()); + + remove(fname); +} + +TEST(file, error) { + Pipe p(true); + auto buf = sw_tg_buffer(); + File fp(p.get_socket(true)->get_fd()); + ASSERT_EQ(fp.read_all(buf->str, buf->size), 0); + ASSERT_ERREQ(ESPIPE); + + ASSERT_EQ(fp.write_all(SW_STRL(TEST_STR)), 0); + ASSERT_ERREQ(ESPIPE); + + p.close(); + + FileStatus stat; + ASSERT_FALSE(fp.stat(&stat)); + ASSERT_ERREQ(EBADF); + + fp.release(); +} + +TEST(file, tmp_file) { + char buf[128] = "/tmp/not-exists-dir/test.XXXXXX"; + ASSERT_EQ(swoole_tmpfile(buf), -1); + ASSERT_ERREQ(ENOENT); + + auto ori_tmp_dir = swoole_get_task_tmpdir(); + // 这里不能使用 swoole_set_task_tmpdir() ,它会递归创建目录 + SwooleG.task_tmpfile = buf; + auto fp = make_tmpfile(); + ASSERT_FALSE(fp.ready()); + SwooleG.task_tmpfile = ori_tmp_dir; +} + +TEST(file, empty_file) { + auto fname = "/tmp/swoole_empty_file.txt"; + File fp(fname, File::WRITE | File::CREATE); + + fp.open(fname, File::READ); + char buf[128]; + ASSERT_EQ(fp.read_all(buf, sizeof(buf)), 0); + swoole_clear_last_error(); + errno = 0; + ASSERT_ERREQ(0); + ASSERT_EQ(errno, 0); + fp.close(); + + remove(fname); +} \ No newline at end of file diff --git a/core-tests/src/os/msg_queue.cpp b/core-tests/src/os/msg_queue.cpp index 31a7a72f75..7c7f7f2595 100644 --- a/core-tests/src/os/msg_queue.cpp +++ b/core-tests/src/os/msg_queue.cpp @@ -26,13 +26,12 @@ using swoole::QueueNode; TEST(msg_queue, rbac) { MsgQueue q(0x950001); ASSERT_TRUE(q.ready()); + ASSERT_GE(q.get_id(), 0); QueueNode in; in.mtype = 999; strcpy(in.mdata, "hello world"); - if (!swoole::test::is_github_ci()) { - ASSERT_TRUE(q.set_capacity(8192)); - } + ASSERT_TRUE(q.set_capacity(8192)); // input data ASSERT_TRUE(q.push(&in, strlen(in.mdata))); @@ -54,4 +53,20 @@ TEST(msg_queue, rbac) { ASSERT_STREQ(out.mdata, in.mdata); ASSERT_TRUE(q.destroy()); + ASSERT_FALSE(q.destroy()); + ASSERT_ERREQ(EINVAL); + + q.set_blocking(false); + + ASSERT_EQ(q.pop(&out, sizeof(out.mdata)), -1); + ASSERT_ERREQ(EINVAL); + + ASSERT_FALSE(q.push(&in, strlen(in.mdata))); + ASSERT_ERREQ(EINVAL); + + ASSERT_FALSE(q.stat(&queue_num, &queue_bytes)); + ASSERT_ERREQ(EINVAL); + + ASSERT_FALSE(q.set_capacity(8192)); + ASSERT_ERREQ(EINVAL); } diff --git a/core-tests/src/os/os.cpp b/core-tests/src/os/os.cpp new file mode 100644 index 0000000000..92cb412e19 --- /dev/null +++ b/core-tests/src/os/os.cpp @@ -0,0 +1,89 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "swoole_file.h" +#include "swoole_thread.h" + +using namespace swoole; + +TEST(os, daemon) { + auto sid = getsid(getpid()); + int status; + swoole_waitpid(test::spawn_exec([sid]() { + ASSERT_EQ(sid, getsid(getpid())); + ASSERT_TRUE(isatty(STDIN_FILENO)); + + ASSERT_EQ(swoole_daemon(0, 0), 0); + ASSERT_NE(sid, getsid(getpid())); + + ASSERT_FALSE(isatty(STDIN_FILENO)); + }), + &status, + 0); +} + +TEST(os, cpu_affinity) { + cpu_set_t ori_affinity, affinity; + ASSERT_EQ(swoole_get_cpu_affinity(&affinity), 0); + ori_affinity = affinity; + + CPU_ZERO(&affinity); + CPU_SET(1, &affinity); + + ASSERT_EQ(swoole_set_cpu_affinity(&affinity), 0); + ASSERT_EQ(swoole_get_cpu_affinity(&affinity), 0); + + auto cpu_n = SW_CPU_NUM; + SW_LOOP_N(cpu_n) { + if (i == 1) { + ASSERT_TRUE(CPU_ISSET(i, &affinity)); + } else { + ASSERT_FALSE(CPU_ISSET(i, &affinity)); + } + } + + ASSERT_EQ(swoole_set_cpu_affinity(&ori_affinity), 0); +} + +TEST(os, thread_name) { + std::thread t([]() { + char new_name[512]; + auto thread_name = "sw-core-tests"; + ASSERT_TRUE(swoole_thread_set_name(thread_name)); + ASSERT_TRUE(swoole_thread_get_name(new_name, sizeof(new_name))); + + ASSERT_STREQ(thread_name, new_name); + + ASSERT_FALSE(swoole_thread_set_name("swoole-core-tests-max-size-is-16")); + ASSERT_EQ(swoole_get_last_error(), ERANGE); + }); + t.join(); +} + +TEST(os, thread_id) { + auto tid = swoole_thread_id_to_str(std::this_thread::get_id()); + DEBUG() << "current thread id: " << tid << "\n"; + ASSERT_FALSE(tid.empty()); +} + +TEST(os, set_isolation) { + swoole_set_isolation("not-exists-group", "not-exists-user", "/tmp/not-exists-dir"); +} diff --git a/core-tests/src/os/pipe.cpp b/core-tests/src/os/pipe.cpp index f966e0cb2e..8dbc6af636 100644 --- a/core-tests/src/os/pipe.cpp +++ b/core-tests/src/os/pipe.cpp @@ -40,7 +40,6 @@ TEST(pipe, base) { Pipe p(true); ASSERT_TRUE(p.ready()); - ret = p.write((void *) SW_STRL("hello world\n")); ASSERT_GT(ret, 0); ret = p.write((void *) SW_STRL("你好中国。\n")); diff --git a/core-tests/src/os/process_pool.cpp b/core-tests/src/os/process_pool.cpp index 87413f3c8a..905d145d02 100644 --- a/core-tests/src/os/process_pool.cpp +++ b/core-tests/src/os/process_pool.cpp @@ -1,107 +1,361 @@ #include "test_core.h" #include "swoole_process_pool.h" -#include +#include #ifdef __MACH__ #define sysv_signal signal #endif +#include "swoole_signal.h" +#include +#include using namespace swoole; +constexpr int magic_number = 99900011; +static ProcessPool *current_pool = nullptr; +static Worker *current_worker = nullptr; + static void test_func(ProcessPool &pool) { EventData data{}; - data.info.len = strlen(TEST_JPG_MD5SUM); - strcpy(data.data, TEST_JPG_MD5SUM); + size_t size = swoole_system_random(1024, 4096); + String rmem(size); + rmem.append_random_bytes(size - 1); + rmem.append("\0"); + + data.info.len = size; + memcpy(data.data, rmem.value(), size); + + DEBUG() << "dispatch: " << size << " bytes\n"; int worker_id = -1; - ASSERT_EQ(pool.dispatch_blocking(&data, &worker_id), SW_OK); + ASSERT_EQ(pool.dispatch_sync(&data, &worker_id), SW_OK); pool.running = true; - pool.onTask = [](ProcessPool *pool, EventData *task) -> int { + pool.ptr = &rmem; + if (pool.onWorkerStart) { + pool.onWorkerStart(&pool, pool.get_worker(0)); + } + pool.main_loop(&pool, pool.get_worker(0)); + pool.destroy(); +} + +static void test_func_task_protocol(ProcessPool &pool) { + pool.set_protocol(SW_PROTOCOL_TASK); + pool.onTask = [](ProcessPool *pool, Worker *worker, EventData *task) -> int { pool->running = false; - EXPECT_MEMEQ(task->data, TEST_JPG_MD5SUM, task->info.len); + auto *_data = (String *) pool->ptr; + usleep(10000); + EXPECT_MEMEQ(_data->str, task->data, task->len()); return 0; }; - pool.main_loop(&pool, pool.get_worker(0)); - pool.destroy(); + test_func(pool); } -TEST(process_pool, tcp) { +static void test_func_message_protocol(ProcessPool &pool) { + pool.set_protocol(SW_PROTOCOL_MESSAGE); + pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { + pool->running = false; + String *_data = static_cast(pool->ptr); + usleep(10000); + + DEBUG() << "received: " << rdata->info.len << " bytes\n"; + EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); + }; + test_func(pool); +} + +static void test_func_stream_protocol(ProcessPool &pool) { + pool.set_protocol(SW_PROTOCOL_STREAM); + pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { + pool->running = false; + String *_data = (String *) pool->ptr; + EventData *msg = (EventData *) rdata->data; + usleep(10000); + + DEBUG() << "received: " << rdata->info.len << " bytes\n"; + EXPECT_MEMEQ(_data->str, msg->data, msg->len()); + }; + test_func(pool); +} + +TEST(process_pool, tcp) { ProcessPool pool{}; + int svr_port = TEST_PORT + __LINE__; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); - ASSERT_EQ(pool.listen(TEST_HOST, TEST_PORT, 128), SW_OK); - - test_func(pool); + ASSERT_EQ(pool.listen(TEST_HOST, svr_port, 128), SW_OK); + + test_func_task_protocol(pool); } -TEST(process_pool, unix_sock) { +TEST(process_pool, unix_sock) { ProcessPool pool{}; signal(SIGPIPE, SIG_IGN); ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + ASSERT_EQ(pool.listen(TEST_HOST, TEST_PORT, 128), SW_ERR); + ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + ASSERT_EQ(pool.listen(TEST_SOCK_FILE, 128), SW_ERR); + ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); - test_func(pool); + test_func_task_protocol(pool); } -TEST(process_pool, tcp_raw) { +TEST(process_pool, tcp_raw) { ProcessPool pool{}; - constexpr int size = 2*1024*1024; + constexpr int size = 2 * 1024 * 1024; + int svr_port = TEST_PORT + __LINE__; ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); - ASSERT_EQ(pool.listen(TEST_HOST, TEST_PORT, 128), SW_OK); - pool.set_protocol(0, size); + ASSERT_EQ(pool.listen(TEST_HOST, svr_port, 128), SW_OK); + pool.set_max_packet_size(size); + pool.set_protocol(SW_PROTOCOL_STREAM); String data(size); - data.append_random_bytes(size-1); + data.append_random_bytes(size - 1); data.append("\0"); - - ASSERT_EQ(pool.dispatch_blocking(data.str, data.length), SW_OK); + + ASSERT_EQ(pool.dispatch_sync(data.str, data.length), SW_OK); pool.running = true; pool.ptr = &data; - pool.onMessage = [](ProcessPool *pool, const char *recv_data, uint32_t len) -> void { + pool.onMessage = [](ProcessPool *pool, RecvData *rdata) -> void { pool->running = false; String *_data = (String *) pool->ptr; - EXPECT_MEMEQ(_data->str, recv_data, len); + EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); }; pool.main_loop(&pool, pool.get_worker(0)); pool.destroy(); } -TEST(process_pool, msgqueue) { +#ifdef HAVE_MSGQUEUE +TEST(process_pool, msgqueue) { ProcessPool pool{}; ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); + test_func_task_protocol(pool); +} + +TEST(process_pool, msgqueue_2) { + auto key = 0x9501 + __LINE__; + auto msg_id_ = msgget(key, IPC_CREAT); + ASSERT_GE(msg_id_, 0); + + test::spawn_exec_and_wait([key]() { + ProcessPool pool{}; + swoole_set_isolation("", "nobody", ""); + ASSERT_EQ(pool.create(1, key, SW_IPC_MSGQUEUE), SW_ERR); + ASSERT_ERREQ(EACCES); + }); +} +#endif + +TEST(process_pool, message_protocol) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + + test_func_message_protocol(pool); +} + +TEST(process_pool, message_protocol_with_timer) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + + pool.set_protocol(SW_PROTOCOL_MESSAGE); + + swoole_signal_set(SIGTERM, [](int) { + DEBUG() << "received SIGTERM signal\n"; + current_pool->running = false; + }); + + pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { + DEBUG() << "onStart\n"; + current_pool = pool; + swoole_timer_after(50, [pool](TIMER_PARAMS) { + DEBUG() << "kill master\n"; + kill(getpid(), SIGTERM); + }); + }; + + pool.onMessage = [](ProcessPool *pool, RecvData *rdata) { + auto *_data = static_cast(pool->ptr); + usleep(10000); + + DEBUG() << "received: " << rdata->info.len << " bytes\n"; + EXPECT_MEMEQ(_data->str, rdata->data, rdata->info.len); + }; + test_func(pool); } -constexpr int magic_number = 99900011; -static ProcessPool *current_pool = nullptr; +TEST(process_pool, stream_protocol) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); -TEST(process_pool, shutdown) { + test_func_stream_protocol(pool); +} + +TEST(process_pool, stream_protocol_with_msgq) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); + + test_func_stream_protocol(pool); +} + +TEST(process_pool, shutdown) { ProcessPool pool{}; - int *shm_value = (int *) sw_mem_pool()->alloc(sizeof(int)); + int *shm_value = (int *) sw_mem_pool()->alloc(sizeof(int)); ASSERT_EQ(pool.create(1, 0x9501, SW_IPC_MSGQUEUE), SW_OK); - // init - pool.set_protocol(1, 8192); + // init + pool.set_max_packet_size(8192); + pool.set_protocol(SW_PROTOCOL_TASK); pool.ptr = shm_value; - pool.onWorkerStart = [](ProcessPool *pool, int worker_id) { + pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { int *shm_value = (int *) pool->ptr; *shm_value = magic_number; usleep(1); }; - pool.onTask = [](ProcessPool *pool, EventData *task) -> int { + pool.onTask = [](ProcessPool *pool, Worker *worker, EventData *task) -> int { + usleep(1000); kill(pool->master_pid, SIGTERM); - return 0; }; + pool.onStart = [](ProcessPool *pool) { + EventData msg{}; + msg.info.len = 128; + swoole_random_string(msg.data, msg.info.len); + int worker_id = -1; + pool->dispatch_sync(&msg, &worker_id); + }; + current_pool = &pool; - sysv_signal(SIGTERM, [](int sig) { - current_pool->running = false; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + + // start + ASSERT_EQ(pool.start(), SW_OK); + // wait + ASSERT_EQ(pool.wait(), SW_OK); + + pool.destroy(); + + ASSERT_EQ(*shm_value, magic_number); + + sysv_signal(SIGTERM, SIG_DFL); +} + +TEST(process_pool, reload) { + ProcessPool pool{}; + test::counter_init(); + ASSERT_EQ(pool.create(2), SW_OK); + + // init + pool.set_max_packet_size(8192); + pool.max_wait_time = 1; + + pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { + test::counter_incr(0); + current_pool = pool; + + DEBUG() << "onWorkerStart " << worker->id << "\n"; + + sysv_signal(SIGTERM, SIG_IGN); + sysv_signal(SIGRTMIN, [](int) { current_pool->reopen_logger(); }); + sysv_signal(SIGWINCH, [](int) { current_pool->reopen_logger(); }); + + while (true) { + sleep(10000); + } + }; + + pool.onStart = [](ProcessPool *pool) { + pool->reopen_logger(); + swoole_timer_after(50, [pool](TIMER_PARAMS) { kill(pool->get_worker(0)->pid, SIGRTMIN); }); + swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->reload(); }); + }; + + pool.onBeforeReload = [](ProcessPool *pool) { DEBUG() << "onBeforeReload\n"; }; + + pool.onAfterReload = [](ProcessPool *pool) { + DEBUG() << "onAfterReload\n"; + swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->shutdown(); }); + }; + + pid_t other_child_pid = test::spawn_exec([]() { + usleep(10000); + exit(123); }); - + test::counter_set(20, other_child_pid); + + pool.onWorkerError = [](ProcessPool *pool, Worker *worker, const ExitStatus &exit_status) { + DEBUG() << "onWorkerError " << exit_status.get_pid() << "\n"; + ASSERT_EQ(exit_status.get_signal(), SIGKILL); + }; + + pool.onWorkerMessage = [](ProcessPool *pool, EventData *msg) { + DEBUG() << "onWorkerMessage: type " << msg->info.type << ", content=" << std::string(msg->data, msg->info.len); + EXPECT_EQ(msg->info.type, SW_WORKER_MESSAGE_STOP + 1); + EXPECT_MEMEQ(msg->data, TEST_STR, msg->info.len); + }; + + pool.onWorkerNotFound = [](ProcessPool *pool, const ExitStatus &exit_status) -> int { + DEBUG() << "onWorkerNotFound " << exit_status.get_pid() << "\n"; + EXPECT_EQ(exit_status.get_pid(), test::counter_get(20)); + EXPECT_EQ(exit_status.get_code(), 123); + EXPECT_EQ(pool->push_message(SW_WORKER_MESSAGE_STOP + 1, SW_STRL(TEST_STR)), SW_OK); + return SW_OK; + }; + + current_pool = &pool; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + sysv_signal(SIGIO, [](int sig) { current_pool->read_message = true; }); + + ASSERT_EQ(pool.start(), SW_OK); + ASSERT_EQ(pool.wait(), SW_OK); + + pool.destroy(); + + ASSERT_EQ(test::counter_get(0), 4); + + sysv_signal(SIGTERM, SIG_DFL); +} + +static void test_async_pool() { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + + // init + pool.set_max_packet_size(8192); + pool.set_protocol(SW_PROTOCOL_TASK); + pool.async = true; + test::counter_init(); + + pool.onStart = [](ProcessPool *pool) { + current_pool = pool; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + }; + + pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { + test::counter_set(0, magic_number); + current_worker = worker; + current_pool = pool; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + + swoole_signal_set(SIGTERM, [](int sig) { + DEBUG() << "value: " << test::counter_incr(0) << "; " + << "SIGTERM, stop worker\n"; + current_pool->stop(current_worker); + }); + + usleep(10); + }; + + pool.onMessage = [](ProcessPool *pool, RecvData *msg) { + DEBUG() << "value: " << test::counter_incr(0) << "; " + << "onMessage, kill\n"; + kill(pool->master_pid, SIGTERM); + }; + // start ASSERT_EQ(pool.start(), SW_OK); @@ -109,14 +363,317 @@ TEST(process_pool, shutdown) { msg.info.len = 128; swoole_random_string(msg.data, msg.info.len); int worker_id = -1; - pool.dispatch_blocking(&msg, &worker_id); + pool.dispatch_sync(&msg, &worker_id); // wait ASSERT_EQ(pool.wait(), SW_OK); - // shutdown - pool.shutdown(); pool.destroy(); - - ASSERT_EQ(*shm_value, magic_number); + + ASSERT_EQ(test::counter_get(0), magic_number + 2); + + swoole_signal_clear(); + sysv_signal(SIGTERM, SIG_DFL); +} + +TEST(process_pool, async) { + test_async_pool(); + // ASSERT_EQ(test::spawn_exec_and_wait([]() { test_async_pool(); }), 0); +} + +static void test_async_pool_with_mb() { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + ASSERT_EQ(pool.create_message_bus(), SW_OK); + + if (swoole_timer_is_available()) { + swoole_timer_free(); + } + swoole_signal_clear(); + + // init + pool.set_max_packet_size(8192); + pool.set_protocol(SW_PROTOCOL_TASK); + test::counter_init(); + pool.async = true; + + pool.onWorkerStart = [](ProcessPool *pool, Worker *worker) { + current_worker = worker; + current_pool = pool; + + test::counter_incr_and_put_log(0, "onWorkerStart"); + + swoole_signal_set(SIGTERM, [](int sig) { + test::counter_incr_and_put_log(0, "SIGTERM, stop worker"); + current_pool->stop(sw_worker()); + }); + + usleep(10); + }; + + pool.onWorkerStop = [](ProcessPool *pool, Worker *worker) { + current_worker = worker; + current_pool = pool; + + test::counter_incr_and_put_log(0, "onWorkerStop"); + }; + + pool.onWorkerExit = [](ProcessPool *pool, Worker *worker) { test::counter_incr_and_put_log(0, "onWorkerExit"); }; + + pool.onStart = [](ProcessPool *pool) { + current_pool = pool; + swoole_signal_set(SIGTERM, [](int sig) { current_pool->running = false; }); + swoole_signal_set(SIGIO, [](int sig) { current_pool->read_message = true; }); + + test::counter_incr_and_put_log(0, "onStart"); + + swoole_timer_after(100, [pool](TIMER_PARAMS) { + pool->send_message(0, SW_STRL("detach")); + + swoole_timer_after(100, [pool](TIMER_PARAMS) { pool->send_message(0, SW_STRL("shutdown")); }); + }); + }; + + pool.onShutdown = [](ProcessPool *pool) { test::counter_incr_and_put_log(0, "onShutdown"); }; + + pool.onMessage = [](ProcessPool *pool, RecvData *msg) { + auto req = std::string(msg->data, msg->info.len); + + if (req == "detach") { + test::counter_incr_and_put_log(0, "onMessage, detach"); + ASSERT_TRUE(pool->detach()); + } else if ((req == "shutdown")) { + test::counter_incr_and_put_log(0, "onMessage, shutdown"); + pool->shutdown(); + } + }; + + // start + ASSERT_EQ(pool.start(), SW_OK); + // wait + ASSERT_EQ(pool.wait(), SW_OK); + + pool.destroy(); + + ASSERT_GE(test::counter_get(0), 8); + + swoole_signal_clear(); + sysv_signal(SIGTERM, SIG_DFL); + sysv_signal(SIGIO, SIG_DFL); +} + +TEST(process_pool, async_mb) { + test_async_pool_with_mb(); +} + +TEST(process_pool, mb1) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_NONE), SW_OK); + ASSERT_EQ(pool.create_message_bus(), SW_ERR); + ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + + pool.destroy(); +} + +TEST(process_pool, mb2) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + ASSERT_EQ(pool.create_message_bus(), SW_OK); + ASSERT_EQ(pool.create_message_bus(), SW_ERR); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + + pool.destroy(); +} + +TEST(process_pool, socket) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); + ASSERT_EQ(pool.start(), SW_ERR); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + + pool.destroy(); +} + +TEST(process_pool, listen) { + ProcessPool pool{}; + auto port = TEST_PORT + __LINE__; + ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); + ASSERT_EQ(pool.listen("127.0.0.1", port, 128), SW_OK); + + pool.set_protocol(SW_PROTOCOL_STREAM); + + size_t size = 2048; + String rmem(size); + rmem.append_random_bytes(size - 1); + rmem.append('\0'); + + String wmem(size); + wmem.append_random_bytes(size - 1); + wmem.append('\0'); + + pool.ptr = &wmem; + + pool.onMessage = [](ProcessPool *pool, RecvData *msg) { + String *wmem = (String *) pool->ptr; + ASSERT_EQ(pool->response(wmem->str, wmem->length), SW_OK); + ASSERT_EQ(pool->response(nullptr, 999), SW_ERR); + ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); + ASSERT_EQ(pool->response(wmem->str, 0), SW_ERR); + ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); + }; + + current_pool = &pool; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + + ASSERT_EQ(pool.start(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect("127.0.0.1", port); + + uint32_t pkt_len = htonl(rmem.length); + + c.send((char *) &pkt_len, sizeof(pkt_len)); + c.send(rmem.str, rmem.length); + char buf[4096]; + + EXPECT_EQ(c.recv((char *) &pkt_len, sizeof(pkt_len)), 4); + c.recv(buf, ntohl(pkt_len)); + + EXPECT_MEMEQ(buf, wmem.str, wmem.length); + + ASSERT_EQ(pool.response(wmem.str, wmem.length), SW_ERR); + ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); + + c.close(); + + kill(getpid(), SIGTERM); + }); + + ASSERT_EQ(pool.wait(), SW_OK); + pool.destroy(); + + sysv_signal(SIGTERM, SIG_DFL); + + t1.join(); +} + +const char *test_sock = "/tmp/swoole_process_pool.sock"; + +TEST(process_pool, listen_unixsock) { + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_SOCKET), SW_OK); + ASSERT_EQ(pool.listen(test_sock, 128), SW_OK); + + pool.set_protocol(SW_PROTOCOL_STREAM); + + size_t size = 2048; + String rmem(size); + rmem.append_random_bytes(size - 1); + rmem.append('\0'); + + String wmem(size); + wmem.append_random_bytes(size - 1); + wmem.append('\0'); + + pool.ptr = &wmem; + + pool.onMessage = [](ProcessPool *pool, RecvData *msg) { + String *wmem = (String *) pool->ptr; + pool->response(wmem->str, wmem->length); + }; + + current_pool = &pool; + sysv_signal(SIGTERM, [](int sig) { current_pool->running = false; }); + + ASSERT_EQ(pool.start(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + + network::SyncClient c(SW_SOCK_UNIX_STREAM); + c.connect(test_sock, 0); + + uint32_t pkt_len = htonl(rmem.length); + + c.send((char *) &pkt_len, sizeof(pkt_len)); + c.send(rmem.str, rmem.length); + char buf[4096]; + + EXPECT_EQ(c.recv((char *) &pkt_len, sizeof(pkt_len)), 4); + c.recv(buf, ntohl(pkt_len)); + + EXPECT_MEMEQ(buf, wmem.str, wmem.length); + + c.close(); + + kill(getpid(), SIGTERM); + }); + + ASSERT_EQ(pool.wait(), SW_OK); + + pool.destroy(); + + sysv_signal(SIGTERM, SIG_DFL); + + t1.join(); +} + +TEST(process_pool, worker) { + Worker worker{}; + worker.init(); + + ASSERT_TRUE(worker.is_running()); + ASSERT_GT(worker.start_time, 0); + worker.set_max_request(1000, 200); + + ASSERT_GT(SwooleWG.max_request, 1000); + ASSERT_LE(SwooleWG.max_request, 1200); + + worker.shutdown(); + ASSERT_TRUE(worker.is_shutdown()); + + swoole_set_worker_type(SW_USER_WORKER); + ASSERT_EQ(swoole_get_worker_symbol(), '@'); + + swoole_set_worker_type(SW_TASK_WORKER); + ASSERT_EQ(swoole_get_worker_symbol(), '^'); + + swoole_set_worker_type(SW_WORKER); + ASSERT_EQ(swoole_get_worker_symbol(), '*'); + + swoole_set_worker_type(SW_MASTER); + ASSERT_EQ(swoole_get_worker_symbol(), '#'); + + swoole_set_worker_type(SW_MANAGER); + ASSERT_EQ(swoole_get_worker_symbol(), '$'); + + worker.set_status_to_idle(); + ASSERT_TRUE(worker.is_idle()); + ASSERT_FALSE(worker.is_busy()); + + worker.set_status_to_busy(); + ASSERT_FALSE(worker.is_idle()); + ASSERT_TRUE(worker.is_busy()); + + worker.set_status(SW_WORKER_EXIT); + ASSERT_FALSE(worker.is_idle()); + ASSERT_FALSE(worker.is_busy()); +} + +TEST(process_pool, add_worker) { + Worker worker{}; + worker.pid = getpid(); + + ProcessPool pool{}; + ASSERT_EQ(pool.create(1, 0, SW_IPC_UNIXSOCK), SW_OK); + + pool.add_worker(&worker); + + auto *worker2 = pool.get_worker_by_pid(getpid()); + ASSERT_EQ(&worker, worker2); + + ASSERT_TRUE(pool.del_worker(worker2)); } diff --git a/core-tests/src/os/signal.cpp b/core-tests/src/os/signal.cpp index a2f2527a14..9b79215e9a 100644 --- a/core-tests/src/os/signal.cpp +++ b/core-tests/src/os/signal.cpp @@ -1,4 +1,5 @@ #include "test_core.h" +#include "swoole_process_pool.h" #include "swoole_signal.h" #ifdef HAVE_SIGNALFD @@ -34,3 +35,89 @@ TEST(os_signal, signalfd) { swoole_event_wait(); } #endif + +TEST(os_signal, block) { + ASSERT_EQ(swoole::test::spawn_exec_and_wait([]() { + sysv_signal(SIGIO, [](int signo) { exit(255); }); + + std::thread t([] { + swoole_signal_block_all(); + pthread_kill(pthread_self(), SIGIO); + }); + t.join(); + }), + 0); +} + +TEST(os_signal, unblock) { + auto status = swoole::test::spawn_exec_and_wait([]() { + sysv_signal(SIGIO, [](int signo) { exit(255); }); + + std::thread t([] { + swoole_signal_block_all(); + pthread_kill(pthread_self(), SIGIO); + swoole_signal_unblock_all(); + }); + t.join(); + }); + + auto exit_status = swoole::ExitStatus(getpid(), status); + + ASSERT_EQ(exit_status.get_code(), 255); +} + +TEST(os_signal, signal_to_str) { + ASSERT_STREQ(swoole_signal_to_str(SIGTERM), "Terminated: 15"); + ASSERT_STREQ(swoole_signal_to_str(SIGIO), "I/O possible: 29"); + ASSERT_STREQ(swoole_signal_to_str(SIGRTMIN), "Real-time signal 0: 34"); + ASSERT_STREQ(swoole_signal_to_str(99999), "Unknown signal 99999: 99999"); +} + +TEST(os_signal, set) { + swoole_signal_set(SIGIO, [](int signo) { exit(255); }); + ASSERT_TRUE(swoole_signal_isset(SIGIO)); + ASSERT_FALSE(swoole_signal_isset(SIGTERM)); + swoole_signal_set(SIGIO, nullptr); + ASSERT_FALSE(swoole_signal_isset(SIGIO)); +} + +static int trigger_signal = 0; + +TEST(os_signal, dispatch) { + trigger_signal = 0; + swoole_signal_set( + SIGIO, [](int signo) { trigger_signal = signo; }, true); + swoole_kill(getpid(), SIGIO); + ASSERT_EQ(trigger_signal, 0); + + ASSERT_EQ(swoole_signal_get_handler(SIGTERM), nullptr); + ASSERT_NE(swoole_signal_get_handler(SIGIO), nullptr); + + swoole_signal_dispatch(); + ASSERT_EQ(trigger_signal, SIGIO); + + trigger_signal = 0; + + swoole_signal_dispatch(); + ASSERT_EQ(trigger_signal, 0); + + ASSERT_EQ(swoole_signal_get_listener_num(), 0); + + swoole_signal_clear(); +} + +TEST(os_signal, error) { + swoole_signal_set(SIGIO, nullptr, 0, 0); + swoole_signal_block_all(); + swoole_signal_block_all(); // no effect + swoole_signal_unblock_all(); + swoole_signal_unblock_all(); // no effect + + swoole_signal_callback(SW_SIGNO_MAX); // no effect + + swoole_clear_last_error(); + swoole_signal_callback(SIGIO); + ASSERT_ERREQ(SW_ERROR_UNREGISTERED_SIGNAL); + + ASSERT_EQ(swoole_signal_get_handler(SW_SIGNO_MAX), nullptr); +} \ No newline at end of file diff --git a/core-tests/src/os/timer.cpp b/core-tests/src/os/timer.cpp index 852ddd71d9..a03fb9ed89 100644 --- a/core-tests/src/os/timer.cpp +++ b/core-tests/src/os/timer.cpp @@ -18,6 +18,7 @@ */ #include "test_core.h" +#include "swoole_signal.h" #include "swoole_util.h" #include "swoole_timer.h" @@ -25,34 +26,43 @@ using swoole::Timer; using swoole::TimerNode; TEST(timer, sys) { - int timer1_count = 0; - int timer2_count = 0; - int timer_running = true; + swoole::test::counter_init(); + auto counter = swoole::test::counter_ptr(); uint64_t ms1 = swoole::time(); - swoole_timer_add( - 20, false, [&](Timer *, TimerNode *) { timer1_count++; }, nullptr); + ASSERT_TRUE(swoole_timer_add( + 20L, false, [&](TIMER_PARAMS) { counter[0]++; }, nullptr)); + + swoole_clear_last_error(); + ASSERT_EQ(swoole_timer_add(-1L, false, [&](TIMER_PARAMS) {}), nullptr); + ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); + + swoole_clear_last_error(); + ASSERT_EQ(swoole_timer_add(0L, false, [&](TIMER_PARAMS) {}), nullptr); + ASSERT_ERREQ(SW_ERROR_INVALID_PARAMS); + + // dtor test + auto timer = swoole_timer_add(20L, false, [&](TIMER_PARAMS) { counter[2]++; }); + ASSERT_TRUE(timer); + timer->destructor = [&](TimerNode *tnode) { counter[3] = 9999; }; swoole_timer_add( - 100, + 100L, true, [&](Timer *, TimerNode *tnode) { - timer2_count++; - if (timer2_count == 5) { + counter[1]++; + if (counter[1] == 5) { swoole_timer_del(tnode); - timer_running = false; } }, nullptr); - while (1) { + while (sw_timer()->count() > 0) { sleep(10); + swoole_signal_dispatch(); if (SwooleG.signal_alarm) { swoole_timer_select(); - if (!timer_running) { - break; - } } } @@ -61,8 +71,10 @@ TEST(timer, sys) { swoole_timer_free(); ASSERT_LE(ms2 - ms1, 510); - ASSERT_EQ(timer1_count, 1); - ASSERT_EQ(timer2_count, 5); + ASSERT_EQ(counter[0], 1); + ASSERT_EQ(counter[1], 5); + ASSERT_EQ(counter[2], 1); + ASSERT_EQ(counter[3], 9999); } TEST(timer, async) { @@ -113,6 +125,7 @@ TEST(timer, get) { TimerNode *timerNode = swoole_timer_get(timer_id); ASSERT_EQ(timerNode->id, timer_id); + swoole_timer_free(); } TEST(timer, delay) { @@ -126,6 +139,8 @@ TEST(timer, delay) { swoole_timer_delay(timerNode, 100); swoole_event_wait(); ASSERT_GE(ms2 - ms1, 100); + swoole_timer_del(timerNode); + swoole_timer_free(); } TEST(timer, error) { @@ -133,7 +148,7 @@ TEST(timer, error) { SwooleTG.timer = nullptr; swoole_timer_free(); - ASSERT_EQ(swoole_timer_select(), SW_ERR); + swoole_timer_select(); // no effect ASSERT_EQ(swoole_timer_get(1), nullptr); ASSERT_FALSE(swoole_timer_clear(1)); ASSERT_FALSE(swoole_timer_exists(1)); @@ -149,4 +164,52 @@ TEST(timer, error) { swoole_timer_delay(nullptr, 100); ASSERT_FALSE(swoole_timer_del(nullptr)); SwooleTG.timer = tmp; + + swoole_timer_free(); +} + +TEST(timer, reinit) { + int timer1_count = 0; + int timer2_count = 0; + + swoole_timer_after( + 20, + [&](Timer *, TimerNode *) { + timer1_count++; + DEBUG() << "timer2_count" << timer2_count << "\n"; + }, + nullptr); + + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + sw_timer()->reinit(); + + uint64_t ms1 = swoole::time(); + + swoole_timer_tick( + 100, + [&](Timer *, TimerNode *tnode) { + timer2_count++; + DEBUG() << "timer2_count" << timer2_count << "\n"; + if (timer2_count == 5) { + swoole_timer_del(tnode); + } + }, + nullptr); + + swoole_event_wait(); + uint64_t ms2 = swoole::time(); + ASSERT_LE(ms2 - ms1, 510); + ASSERT_EQ(timer1_count, 1); + ASSERT_EQ(timer2_count, 5); +} + +TEST(timer, realtime_add) { + timespec ts; + ts.tv_sec = 1; + ts.tv_nsec = 900L * SW_NUM_MILLION; + + swoole::realtime_add(&ts, 1905); + ASSERT_EQ(ts.tv_sec, 3); + ASSERT_EQ(ts.tv_nsec, 805L * SW_NUM_MILLION); } diff --git a/core-tests/src/os/wait.cpp b/core-tests/src/os/wait.cpp index 431c30c568..7a034ef899 100644 --- a/core-tests/src/os/wait.cpp +++ b/core-tests/src/os/wait.cpp @@ -2,17 +2,34 @@ using namespace swoole; using namespace swoole::test; +using swoole::coroutine::System; -TEST(os_wait, waitpid_before_child_exit) { - test::coroutine::run([](void *arg) { - pid_t pid = fork(); - ASSERT_NE(pid, -1); +static pid_t fork_child() { + pid_t pid = fork(); + EXPECT_NE(pid, -1); - if (pid == 0) { - usleep(100000); - exit(0); - } + if (pid == 0) { + usleep(100000); + exit(0); + } + return pid; +} + +static pid_t fork_child2() { + pid_t pid = fork(); + EXPECT_NE(pid, -1); + if (pid == 0) { + exit(0); + } + + usleep(100000); + return pid; +} + +TEST(os_wait, waitpid_before_child_exit) { + test::coroutine::run([](void *arg) { + auto pid = fork_child(); int status = -1; pid_t pid2 = swoole_coroutine_waitpid(pid, &status, 0); ASSERT_EQ(status, 0); @@ -22,14 +39,7 @@ TEST(os_wait, waitpid_before_child_exit) { TEST(os_wait, waitpid_after_child_exit) { test::coroutine::run([](void *arg) { - pid_t pid = fork(); - ASSERT_NE(pid, -1); - - if (pid == 0) { - exit(0); - } - - usleep(100000); + pid_t pid = fork_child2(); int status = -1; pid_t pid2 = swoole_coroutine_waitpid(pid, &status, 0); ASSERT_EQ(status, 0); @@ -39,14 +49,7 @@ TEST(os_wait, waitpid_after_child_exit) { TEST(os_wait, wait_before_child_exit) { test::coroutine::run([](void *arg) { - pid_t pid = fork(); - ASSERT_NE(pid, -1); - - if (pid == 0) { - usleep(100000); - exit(0); - } - + pid_t pid = fork_child(); int status = -1; pid_t pid2 = -1; @@ -63,14 +66,7 @@ TEST(os_wait, wait_before_child_exit) { TEST(os_wait, wait_after_child_exit) { test::coroutine::run([](void *arg) { - pid_t pid = fork(); - ASSERT_NE(pid, -1); - - if (pid == 0) { - exit(0); - } - - usleep(100000); + pid_t pid = fork_child2(); int status = -1; pid_t pid2 = -1; @@ -84,3 +80,14 @@ TEST(os_wait, wait_after_child_exit) { ASSERT_EQ(WEXITSTATUS(status), 0); }); } + +TEST(os_wait, waitpid_safe) { + test::coroutine::run([](void *arg) { + pid_t pid = fork_child(); + int status = -1; + + pid_t pid2 = System::waitpid_safe(pid, &status, 0); + ASSERT_EQ(pid2, pid); + ASSERT_EQ(WEXITSTATUS(status), 0); + }); +} diff --git a/core-tests/src/protocol/base.cpp b/core-tests/src/protocol/base.cpp index 6bd8420c67..cf4efd2462 100644 --- a/core-tests/src/protocol/base.cpp +++ b/core-tests/src/protocol/base.cpp @@ -25,63 +25,614 @@ using namespace swoole; using namespace std; -constexpr int PKG_N = 32; -constexpr int MAX_SIZE = 128000; +constexpr int PKG_N = 128; +constexpr int MAX_SIZE = 1 * 1024 * 1024 + 65536; constexpr int MIN_SIZE = 512; -TEST(protocol, eof) { +static void test_protocol(Server &serv, ListenPort *port, String *pkgs) { + mutex lock; + lock.lock(); + serv.create(); + + String wbuf; + + for (int i = 0; i < PKG_N; i++) { + wbuf.append(pkgs[i]); + } + + DEBUG() << "data total length: " << wbuf.length << "\n"; + + thread t1([&]() { + swoole_signal_block_all(); + lock.lock(); + + network::Client cli(SW_SOCK_TCP, false); + EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); + + off_t offset = 0; + while (offset < (off_t) wbuf.length) { + auto n = wbuf.length - offset > 65536 ? swoole_random_int() % 65536 + 1 : wbuf.length - offset; + ASSERT_EQ(cli.send(wbuf.str + offset, n), n); + offset += n; + } + + usleep(100000); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + int recv_count = 0; + + serv.onReceive = [&](Server *serv, RecvData *req) -> int { + EXPECT_EQ(memcmp(req->data, pkgs[recv_count].str, req->info.len), 0); + recv_count++; + if (recv_count == PKG_N) { + usleep(100000); + serv->shutdown(); + } + return SW_OK; + }; + + serv.start(); + + t1.join(); +} + +TEST(protocol, length) { Server serv(Server::MODE_BASE); serv.worker_num = 1; String pkgs[PKG_N]; for (int i = 0; i < PKG_N; i++) { - pkgs[i].append_random_bytes(swoole_rand(MIN_SIZE, MAX_SIZE), true); - pkgs[i].append("\r\n"); + auto pkt_len = swoole_rand(MIN_SIZE, MAX_SIZE); + auto pkt_len_net = htonl(pkt_len); + pkgs[i].append((char *) &pkt_len_net, sizeof(pkt_len_net)); + pkgs[i].append_random_bytes(pkt_len, false); } sw_logger()->set_level(SW_LOG_WARNING); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); - port->set_eof_protocol("\r\n", false); + port->set_stream_protocol(); + + test_protocol(serv, port, pkgs); +} + +TEST(protocol, length_2) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + port->set_stream_protocol(); mutex lock; lock.lock(); serv.create(); thread t1([&]() { + swoole_signal_block_all(); lock.lock(); + char rbuf[32]; + usleep(50000); - network::Client cli(SW_SOCK_TCP, false); - EXPECT_EQ(cli.connect(&cli, TEST_HOST, port->port, 1, 0), 0); + // 测试分多次发送长度 + { + network::Client cli(SW_SOCK_TCP, false); + EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); - for (int i = 0; i < PKG_N; i++) { - EXPECT_EQ(cli.send(&cli, pkgs[i].str, pkgs[i].length, 0), pkgs[i].length); + String wbuf; + + auto pkt_len = swoole_rand(MIN_SIZE, MAX_SIZE); + auto pkt_len_net = htonl(pkt_len); + wbuf.append((char *) &pkt_len_net, sizeof(pkt_len_net)); + wbuf.append_random_bytes(pkt_len, false); + + ASSERT_EQ(cli.send(wbuf.str, 2), 2); + usleep(10); + ASSERT_EQ(cli.send(wbuf.str + 2, 4), 4); + usleep(10); + ASSERT_EQ(cli.send(wbuf.str + 2, wbuf.length - 6), wbuf.length - 6); + + ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 3); + ASSERT_STREQ(rbuf, "OK"); } - }); - serv.onWorkerStart = [&lock](Server *serv, WorkerId worker_id) { lock.unlock(); }; + // 发送 0 长度的包 + { + network::Client cli(SW_SOCK_TCP, false); + EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); - int recv_count = 0; + auto pkt_len = 0; + auto pkt_len_net = htonl(pkt_len); - serv.onReceive = [&](Server *serv, RecvData *req) -> int { - // printf("[1]LEN=%d, count=%d\n%s\n---------------------------------\n", req->info.len, recv_count, - // req->data); printf("[2]LEN=%d\n%s\n---------------------------------\n", pkgs[recv_count].length, - // pkgs[recv_count].str); + ASSERT_EQ(cli.send((char *) &pkt_len_net, sizeof(pkt_len)), sizeof(pkt_len)); + ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 3); + ASSERT_STREQ(rbuf, "OK"); + } - EXPECT_EQ(memcmp(req->data, pkgs[recv_count].str, req->info.len), 0); + // 发送 INT_MAX 长度的包 + { + network::Client cli(SW_SOCK_TCP, false); + EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); - recv_count++; + auto pkt_len = INT_MAX; + auto pkt_len_net = htonl(pkt_len); - if (recv_count == PKG_N) { - kill(serv->get_master_pid(), SIGTERM); + ASSERT_EQ(cli.send((char *) &pkt_len_net, sizeof(pkt_len)), sizeof(pkt_len)); + ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 0); } + usleep(50000); + serv.shutdown(); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + serv.onReceive = [&](Server *serv, RecvData *req) -> int { + serv->send(req->session_id(), SW_STRL("OK\0")); return SW_OK; }; serv.start(); + t1.join(); +} +TEST(protocol, length_3) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + port->set_length_protocol(0, 'l', 4); + + mutex lock; + lock.lock(); + serv.create(); + + thread t1([&]() { + swoole_signal_block_all(); + lock.lock(); + char rbuf[32]; + usleep(50000); + + network::Client cli(SW_SOCK_TCP, false); + EXPECT_EQ(cli.connect(TEST_HOST, port->port, 1, 0), 0); + + auto pkt_len = -1; + + ASSERT_EQ(cli.send((char *) &pkt_len, sizeof(pkt_len)), sizeof(pkt_len)); + ASSERT_EQ(cli.recv(rbuf, sizeof(rbuf), 0), 0); + + usleep(50000); + serv.shutdown(); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onReceive = [&](Server *serv, RecvData *req) -> int { + serv->send(req->session_id(), SW_STRL("OK\0")); + return SW_OK; + }; + + serv.start(); t1.join(); } + +TEST(protocol, eof) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + String pkgs[PKG_N]; + + for (int i = 0; i < PKG_N; i++) { + pkgs[i].append_random_bytes(swoole_rand(MIN_SIZE, MAX_SIZE), true); + pkgs[i].append("\r\n"); + } + + sw_logger()->set_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + port->set_eof_protocol("\r\n", false); + + test_protocol(serv, port, pkgs); +} + +TEST(protocol, socks5_strerror) { + SW_LOOP_N(16) { + auto error = Socks5Proxy::strerror(i); + if (i > 0x08) { + ASSERT_STREQ("Unknown error", error); + } else { + ASSERT_GT(strlen(error), 5); + } + } +} + +TEST(protocol, swap_byte_order) { + { + EXPECT_EQ(swoole_swap_endian16(0x1234), 0x3412); + EXPECT_EQ(swoole_swap_endian16(0x0001), 0x0100); + EXPECT_EQ(swoole_swap_endian16(0x00FF), 0xFF00); + EXPECT_EQ(swoole_swap_endian16(0xFF00), 0x00FF); + EXPECT_EQ(swoole_swap_endian16(0xFFFF), 0xFFFF); + } + + { + EXPECT_EQ(swoole_swap_endian32(0x12345678), 0x78563412); + EXPECT_EQ(swoole_swap_endian32(0x00000001), 0x01000000); + EXPECT_EQ(swoole_swap_endian32(0x0000FF00), 0x00FF0000); + EXPECT_EQ(swoole_swap_endian32(0xFF000000), 0x000000FF); + EXPECT_EQ(swoole_swap_endian32(0xFFFFFFFF), 0xFFFFFFFF); + } + + { + uint16_t v = 0xABCD; + EXPECT_EQ(swoole_swap_endian16(swoole_swap_endian16(v)), v); + } + + { + uint32_t v = 0xABCDEF01; + EXPECT_EQ(swoole_swap_endian32(swoole_swap_endian32(v)), v); + } + + { + uint64_t val = 0x1122334455667788ULL; + auto converted = swoole_swap_endian64(val); + + auto str = (uchar *) &converted; + EXPECT_EQ(str[0], 0x11); + EXPECT_EQ(str[1], 0x22); + EXPECT_EQ(str[2], 0x33); + EXPECT_EQ(str[3], 0x44); + EXPECT_EQ(str[4], 0x55); + EXPECT_EQ(str[5], 0x66); + EXPECT_EQ(str[6], 0x77); + EXPECT_EQ(str[7], 0x88); + } +} + +// Helper function to create binary data for testing +template +void createBinaryData(T value, char *buffer) { + memcpy(buffer, &value, sizeof(T)); +} + +TEST(protocol, unpack) { + // Tests for 8-bit integer formats + { + char buffer[8]; + + // Test signed char ('c') + int8_t c_val = -42; + createBinaryData(c_val, buffer); + EXPECT_EQ(swoole_unpack('c', buffer), -42); + + // Test unsigned char ('C') + uint8_t C_val = 200; + createBinaryData(C_val, buffer); + EXPECT_EQ(swoole_unpack('C', buffer), 200); + + // Test extreme values + createBinaryData(INT8_MIN, buffer); + EXPECT_EQ(swoole_unpack('c', buffer), INT8_MIN); + + createBinaryData(INT8_MAX, buffer); + EXPECT_EQ(swoole_unpack('c', buffer), INT8_MAX); + + createBinaryData(UINT8_MAX, buffer); + EXPECT_EQ(swoole_unpack('C', buffer), UINT8_MAX); + } + + // Tests for 16-bit integer formats + { + char buffer[8]; + + // Test signed short ('s') + int16_t s_val = -12345; + createBinaryData(s_val, buffer); + EXPECT_EQ(swoole_unpack('s', buffer), -12345); + + // Test unsigned short ('S') + uint16_t S_val = 54321; + createBinaryData(S_val, buffer); + EXPECT_EQ(swoole_unpack('S', buffer), 54321); + + // Test big-endian unsigned short ('n') + uint16_t n_val = 0x1234; + uint16_t n_be = (n_val >> 8) | (n_val << 8); // Convert to big-endian + createBinaryData(n_be, buffer); + EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); + + // Test little-endian unsigned short ('v') + uint16_t v_val = 0x1234; + createBinaryData(v_val, buffer); + EXPECT_EQ(swoole_unpack('v', buffer), 0x1234); + + // Test extreme values + createBinaryData(INT16_MIN, buffer); + EXPECT_EQ(swoole_unpack('s', buffer), INT16_MIN); + + createBinaryData(INT16_MAX, buffer); + EXPECT_EQ(swoole_unpack('s', buffer), INT16_MAX); + + createBinaryData(UINT16_MAX, buffer); + EXPECT_EQ(swoole_unpack('S', buffer), UINT16_MAX); + } + + // Tests for 32-bit integer formats + { + char buffer[8]; + + // Test signed long ('l') + int32_t l_val = -123456789; + createBinaryData(l_val, buffer); + EXPECT_EQ(swoole_unpack('l', buffer), -123456789); + + // Test unsigned long ('L') + uint32_t L_val = 3000000000; + createBinaryData(L_val, buffer); + EXPECT_EQ(swoole_unpack('L', buffer), 3000000000); + + // Test big-endian unsigned long ('N') + uint32_t N_val = 0x12345678; + uint32_t N_be = + ((N_val & 0xFF) << 24) | ((N_val & 0xFF00) << 8) | ((N_val & 0xFF0000) >> 8) | ((N_val & 0xFF000000) >> 24); + createBinaryData(N_be, buffer); + EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); + + // Test little-endian unsigned long ('V') + uint32_t V_val = 0x12345678; + createBinaryData(V_val, buffer); + EXPECT_EQ(swoole_unpack('V', buffer), 0x12345678); + + // Test extreme values + createBinaryData(INT32_MIN, buffer); + EXPECT_EQ(swoole_unpack('l', buffer), INT32_MIN); + + createBinaryData(INT32_MAX, buffer); + EXPECT_EQ(swoole_unpack('l', buffer), INT32_MAX); + + createBinaryData(UINT32_MAX, buffer); + EXPECT_EQ(swoole_unpack('L', buffer), UINT32_MAX); + } + + // Tests for 64-bit integer formats + { + char buffer[8]; + + // Test signed long long ('q') + int64_t q_val = -1234567890123456789LL; + createBinaryData(q_val, buffer); + EXPECT_EQ(swoole_unpack('q', buffer), -1234567890123456789LL); + + // Test unsigned long long ('Q') + uint64_t Q_val = 10234567890123456789ULL; + createBinaryData(Q_val, buffer); + EXPECT_EQ(swoole_unpack('Q', buffer), 10234567890123456789ULL); + + // Test big-endian unsigned long long ('J') + uint64_t J_val = 0x123456789ABCDEF0ULL; + uint64_t J_be = swoole_swap_endian64(J_val); // Use our swap function for test + createBinaryData(J_be, buffer); + EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); + + // Test little-endian unsigned long long ('P') + uint64_t P_val = 0x123456789ABCDEF0ULL; + createBinaryData(P_val, buffer); + EXPECT_EQ(swoole_unpack('P', buffer), 0x123456789ABCDEF0ULL); + + // Test extreme values (be careful with signed min/max due to two's complement) + createBinaryData(INT64_MIN, buffer); + EXPECT_EQ(swoole_unpack('q', buffer), INT64_MIN); + + createBinaryData(INT64_MAX, buffer); + EXPECT_EQ(swoole_unpack('q', buffer), INT64_MAX); + + // For UINT64_MAX, be aware that the return type is int64_t, so this might not work as expected + // This test might fail due to the limitation of the return type + createBinaryData(UINT64_MAX, buffer); + EXPECT_EQ(swoole_unpack('Q', buffer), (int64_t) UINT64_MAX); + } + + // Tests for machine-dependent integer formats + { + char buffer[8]; + + // Test signed integer ('i') + int i_val = -987654321; + createBinaryData(i_val, buffer); + EXPECT_EQ(swoole_unpack('i', buffer), -987654321); + + // Test unsigned integer ('I') + unsigned int I_val = 3000000000; + createBinaryData(I_val, buffer); + EXPECT_EQ(swoole_unpack('I', buffer), 3000000000); + + // Test extreme values + createBinaryData(INT_MIN, buffer); + EXPECT_EQ(swoole_unpack('i', buffer), INT_MIN); + + createBinaryData(INT_MAX, buffer); + EXPECT_EQ(swoole_unpack('i', buffer), INT_MAX); + + createBinaryData(UINT_MAX, buffer); + EXPECT_EQ(swoole_unpack('I', buffer), (int64_t) UINT_MAX); + } + + // Test for invalid format specifier + { + char buffer[8] = {0}; + + // Test invalid format specifier + EXPECT_EQ(swoole_unpack('x', buffer), -1); + EXPECT_EQ(swoole_unpack('?', buffer), -1); + EXPECT_EQ(swoole_unpack('Z', buffer), -1); + } + + // Test for endianness-specific behavior + { + char buffer[8]; + + // Test that 'n' and 'v' formats handle endianness correctly + buffer[0] = 0x12; + buffer[1] = 0x34; + EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); + + buffer[0] = 0x34; + buffer[1] = 0x12; + EXPECT_EQ(swoole_unpack('v', buffer), 0x1234); + + // Test that 'N' and 'V' formats handle endianness correctly + buffer[0] = 0x12; + buffer[1] = 0x34; + buffer[2] = 0x56; + buffer[3] = 0x78; + EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); + + buffer[0] = 0x78; + buffer[1] = 0x56; + buffer[2] = 0x34; + buffer[3] = 0x12; + EXPECT_EQ(swoole_unpack('V', buffer), 0x12345678); + + // Test that 'J' and 'P' formats handle endianness correctly + buffer[0] = 0x12; + buffer[1] = 0x34; + buffer[2] = 0x56; + buffer[3] = 0x78; + buffer[4] = 0x9A; + buffer[5] = 0xBC; + buffer[6] = 0xDE; + buffer[7] = 0xF0; + EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); + + buffer[0] = 0xF0; + buffer[1] = 0xDE; + buffer[2] = 0xBC; + buffer[3] = 0x9A; + buffer[4] = 0x78; + buffer[5] = 0x56; + buffer[6] = 0x34; + buffer[7] = 0x12; + EXPECT_EQ(swoole_unpack('P', buffer), 0x123456789ABCDEF0ULL); + } + + { + char buffer[8]; + + // Test that 'n' format uses ntohs() correctly + uint16_t test16 = 0x1234; + uint16_t be16 = htons(test16); // Convert to network byte order + createBinaryData(be16, buffer); + EXPECT_EQ(swoole_unpack('n', buffer), 0x1234); + + // Test that 'N' format uses ntohl() correctly + uint32_t test32 = 0x12345678; + uint32_t be32 = htonl(test32); // Convert to network byte order + createBinaryData(be32, buffer); + EXPECT_EQ(swoole_unpack('N', buffer), 0x12345678); + + // Test that 'J' format uses swoole_ntoh64() correctly + uint64_t test64 = 0x123456789ABCDEF0ULL; + uint64_t be64 = swoole_hton64(test64); // Convert to network byte order + createBinaryData(be64, buffer); + EXPECT_EQ(swoole_unpack('J', buffer), 0x123456789ABCDEF0ULL); + } +} + +TEST(protocol, hton64) { + { + uint64_t val = 0x1122334455667788ULL; + uint64_t converted = swoole_hton64(val); + + auto str = (uchar *) &converted; + EXPECT_EQ(str[0], 0x11); + EXPECT_EQ(str[1], 0x22); + EXPECT_EQ(str[2], 0x33); + EXPECT_EQ(str[3], 0x44); + EXPECT_EQ(str[4], 0x55); + EXPECT_EQ(str[5], 0x66); + EXPECT_EQ(str[6], 0x77); + EXPECT_EQ(str[7], 0x88); + + uint64_t reversed = swoole_ntoh64(converted); + EXPECT_EQ(reversed, val); + } + + { + uint64_t min_val = 0ULL; + uint64_t min_converted = swoole_hton64(min_val); + + auto min_str = (unsigned char *) &min_converted; + for (int i = 0; i < 8; i++) { + EXPECT_EQ(min_str[i], 0x00) << "Byte " << i << " should be 0x00"; + } + + EXPECT_EQ(swoole_ntoh64(min_converted), min_val); + + // 测试最大值 + uint64_t max_val = UINT64_MAX; + uint64_t max_converted = swoole_hton64(max_val); + + auto max_str = (unsigned char *) &max_converted; + for (int i = 0; i < 8; i++) { + EXPECT_EQ(max_str[i], 0xFF) << "Byte " << i << " should be 0xFF"; + } + + EXPECT_EQ(swoole_ntoh64(max_converted), max_val); + } + + { + uint64_t alt_pattern = 0xAAAAAAAAAAAAAAAAULL; + uint64_t alt_converted = swoole_hton64(alt_pattern); + EXPECT_EQ(swoole_ntoh64(alt_converted), alt_pattern); + + uint64_t alt_pattern2 = 0x5555555555555555ULL; + uint64_t alt_converted2 = swoole_hton64(alt_pattern2); + EXPECT_EQ(swoole_ntoh64(alt_converted2), alt_pattern2); + + // 测试单字节模式 + for (int i = 0; i < 8; i++) { + uint64_t single_byte = 0xFFULL << (i * 8); + uint64_t converted = swoole_hton64(single_byte); + EXPECT_EQ(swoole_ntoh64(converted), single_byte) << "Failed for byte position " << i; + } + } + + { + for (int i = 0; i < 100; i++) { + uint64_t random_val = swoole_random_int(); + uint64_t converted = swoole_hton64(random_val); + uint64_t reversed = swoole_ntoh64(converted); + + EXPECT_EQ(reversed, random_val) << "Failed for random value: 0x" << std::hex << random_val; + } + } + + { + uint64_t test_val = 0x0102030405060708ULL; + uint64_t converted = swoole_hton64(test_val); + + auto bytes = (unsigned char *) &converted; + + EXPECT_EQ(bytes[0], 0x01); + EXPECT_EQ(bytes[1], 0x02); + EXPECT_EQ(bytes[2], 0x03); + EXPECT_EQ(bytes[3], 0x04); + EXPECT_EQ(bytes[4], 0x05); + EXPECT_EQ(bytes[5], 0x06); + EXPECT_EQ(bytes[6], 0x07); + EXPECT_EQ(bytes[7], 0x08); + } + + { + for (int i = 0; i < 100; i++) { + uint64_t val = swoole_random_int(); + EXPECT_EQ(swoole_ntoh64(swoole_hton64(val)), val) << "hton64->ntoh64 failed for 0x" << std::hex << val; + EXPECT_EQ(swoole_hton64(swoole_ntoh64(val)), val) << "ntoh64->hton64 failed for 0x" << std::hex << val; + } + } +} diff --git a/core-tests/src/protocol/http2.cpp b/core-tests/src/protocol/http2.cpp new file mode 100644 index 0000000000..06f5929fab --- /dev/null +++ b/core-tests/src/protocol/http2.cpp @@ -0,0 +1,545 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" +#include "test_coroutine.h" +#include "redis_client.h" +#include "swoole_server.h" +#include "swoole_http.h" +#include "swoole_http2.h" + +#include +#include + +using namespace swoole; +using namespace std; +using http_server::Context; +using network::Client; +using network::SyncClient; +using swoole::network::AsyncClient; + +const std::string REDIS_TEST_KEY = "key-swoole"; +const std::string REDIS_TEST_VALUE = "value-swoole"; + +TEST(http2, default_settings) { + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE), SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH), SW_HTTP2_DEFAULT_ENABLE_PUSH); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), + SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE), SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE), SW_HTTP2_DEFAULT_MAX_FRAME_SIZE); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE), + SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); + + http2::Settings _settings = { + (uint32_t) swoole_rand(1, 100000), + (uint32_t) swoole_rand(1, 100000), + (uint32_t) swoole_rand(1, 100000), + (uint32_t) swoole_rand(1, 100000), + (uint32_t) swoole_rand(1, 100000), + (uint32_t) swoole_rand(1, 100000), + }; + + http2::put_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE, _settings.header_table_size); + http2::put_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH, _settings.enable_push); + http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, _settings.max_concurrent_streams); + http2::put_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, _settings.init_window_size); + http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, _settings.max_frame_size); + http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, _settings.max_header_list_size); + + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE), _settings.header_table_size); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH), _settings.enable_push); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), _settings.max_concurrent_streams); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE), _settings.init_window_size); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE), _settings.max_frame_size); + ASSERT_EQ(http2::get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE), _settings.max_header_list_size); +} + +TEST(http2, pack_setting_frame) { + char frame[SW_HTTP2_SETTING_FRAME_SIZE + SW_HTTP2_FRAME_HEADER_SIZE]; + http2::Settings settings_1{}; + http2::init_settings(&settings_1); + size_t n = http2::pack_setting_frame(frame, settings_1, false); + + ASSERT_GT(n, 16); + + http2::Settings settings_2{}; + http2::unpack_setting_data( + frame + SW_HTTP2_FRAME_HEADER_SIZE, n, [&settings_2](uint16_t id, uint32_t value) -> ReturnCode { + switch (id) { + case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: + settings_2.header_table_size = value; + break; + case SW_HTTP2_SETTINGS_ENABLE_PUSH: + settings_2.enable_push = value; + break; + case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + settings_2.max_concurrent_streams = value; + break; + case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: + settings_2.init_window_size = value; + break; + case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: + settings_2.max_frame_size = value; + break; + case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + settings_2.max_header_list_size = value; + break; + default: + return SW_ERROR; + } + return SW_SUCCESS; + }); + + ASSERT_MEMEQ(&settings_1, &settings_2, sizeof(settings_2)); +} + +#define HTTP2_GET_TYPE_TEST(t) ASSERT_STREQ(http2::get_type(SW_HTTP2_TYPE_##t), #t) + +TEST(http2, get_type) { + HTTP2_GET_TYPE_TEST(DATA); + HTTP2_GET_TYPE_TEST(HEADERS); + HTTP2_GET_TYPE_TEST(PRIORITY); + HTTP2_GET_TYPE_TEST(RST_STREAM); + HTTP2_GET_TYPE_TEST(SETTINGS); + HTTP2_GET_TYPE_TEST(PUSH_PROMISE); + HTTP2_GET_TYPE_TEST(PING); + HTTP2_GET_TYPE_TEST(GOAWAY); + HTTP2_GET_TYPE_TEST(WINDOW_UPDATE); + HTTP2_GET_TYPE_TEST(CONTINUATION); +} + +TEST(http2, get_type_color) { + SW_LOOP_N(SW_HTTP2_TYPE_GOAWAY + 2) { + ASSERT_GE(http2::get_type_color(i), 0); + } +} + +struct Http2Session { + SessionId fd; + nghttp2_session *session; + Server *server; + std::unordered_map stream_paths; + std::unordered_map stream_data; + + Http2Session(SessionId _fd, Server *_serv) : fd(_fd), session(nullptr), server(_serv) {} + ~Http2Session() { + if (session) { + nghttp2_session_del(session); + session = nullptr; + } + } +}; + +#define CHECK_NGHTTP2(expr, error_msg) \ + do { \ + int rv = (expr); \ + if (rv != 0) { \ + swoole_error_log(SW_LOG_ERROR, "%s: %s", error_msg, nghttp2_strerror(rv)); \ + return -1; \ + } \ + } while (0) + +std::unordered_map> sessions; + +static nghttp2_settings_entry default_settings[] = { + { + NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, + SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE, + }, + { + NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, + }, + { + NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, + SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE, + }, + { + NGHTTP2_SETTINGS_MAX_FRAME_SIZE, + SW_HTTP2_DEFAULT_MAX_FRAME_SIZE, + }, + { + NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, + SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE, + }, +}; + +static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *user_data) { + auto http2_session = static_cast(user_data); + Server *server = static_cast(http2_session->server); + + bool ret = server->send(http2_session->fd, reinterpret_cast(data), length); + if (!ret) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + + return length; +} + +static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, uint32_t error_code, void *user_data) { + return 0; +} + +// 处理头部回调 +static int on_header_callback(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, + size_t namelen, + const uint8_t *value, + size_t valuelen, + uint8_t flags, + void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + DEBUG() << "Header: " << std::string(reinterpret_cast(name), namelen) << ": " + << std::string(reinterpret_cast(value), valuelen) << std::endl; + + return 0; +} + +// 处理请求开始回调 +static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + DEBUG() << "New request started on stream ID: " << frame->hd.stream_id << std::endl; + + return 0; +} + +static void handle_request(nghttp2_session *session, int32_t stream_id, Http2Session *http2_session); + +static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { + auto http2_session = static_cast(user_data); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + swoole_trace_log(SW_TRACE_HTTP2, "Received HEADERS frame for stream %d", frame->hd.stream_id); + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + handle_request(session, frame->hd.stream_id, http2_session); + } + } + break; + case NGHTTP2_DATA: + swoole_trace_log(SW_TRACE_HTTP2, "Received DATA frame for stream %d", frame->hd.stream_id); + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + handle_request(session, frame->hd.stream_id, http2_session); + } + break; + } + + return 0; +} + +static int on_data_chunk_recv_callback( + nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { + auto http2_session = static_cast(user_data); + + // 将数据块添加到对应流的数据中 + http2_session->stream_data[stream_id].append(reinterpret_cast(data), len); + + swoole_trace_log(SW_TRACE_HTTP2, "Received %zu bytes of DATA for stream %d", len, stream_id); + + return 0; +} + +static int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data) { + // 处理帧发送失败 + std::cerr << "Failed to send frame type: " << frame->hd.type << std::endl; + return 0; +} + +static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { + if (frame->hd.type == NGHTTP2_WINDOW_UPDATE) { + DEBUG() << "Window update sent: stream=" << frame->hd.stream_id + << ", increment=" << frame->window_update.window_size_increment << std::endl; + } + return 0; +} + +static ssize_t string_read_callback(nghttp2_session *session, + int32_t stream_id, + uint8_t *buf, + size_t length, + uint32_t *data_flags, + nghttp2_data_source *source, + void *user_data) { + const char *data = static_cast(source->ptr); + size_t datalen = strlen(data); + + if (datalen <= length) { + memcpy(buf, data, datalen); + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return datalen; + } else { + memcpy(buf, data, length); + return length; + } +} + +static void handle_request(nghttp2_session *session, int32_t stream_id, Http2Session *http2_session) { + // 获取路径 + std::string path = "/"; + auto path_it = http2_session->stream_paths.find(stream_id); + if (path_it != http2_session->stream_paths.end()) { + path = path_it->second; + } + + // 获取请求体 + std::string request_body; + auto body_it = http2_session->stream_data.find(stream_id); + if (body_it != http2_session->stream_data.end()) { + request_body = body_it->second; + } + + swoole_trace_log(SW_TRACE_HTTP2, + "Request fully received on stream %d, path: %s, body length: %zu", + stream_id, + path.c_str(), + request_body.length()); + + auto header_server = "nghttp2-server/" NGHTTP2_VERSION; + // 准备响应头 + nghttp2_nv hdrs[] = { + {(uint8_t *) ":status", (uint8_t *) "200", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *) "content-type", (uint8_t *) "text/html", 12, 9, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *) "server", (uint8_t *) header_server, 6, strlen(header_server), NGHTTP2_NV_FLAG_NONE}}; + + if (path == "/" || path == "/index.html") { + const char *body = "

Welcome to HTTP/2 Server

" + "

This is a simple HTTP/2 server implementation.

" + ""; + + nghttp2_data_provider data_prd; + data_prd.source.ptr = (void *) body; + data_prd.read_callback = string_read_callback; + + // 提交响应 + int rv = nghttp2_submit_response(session, stream_id, hdrs, sizeof(hdrs) / sizeof(hdrs[0]), &data_prd); + if (rv != 0) { + swoole_error_log( + SW_LOG_ERROR, SW_ERROR_HTTP2_INTERNAL_ERROR, "Failed to submit response: %s", nghttp2_strerror(rv)); + return; + } + } else { + // 404 Not Found + nghttp2_nv error_hdrs[] = {{(uint8_t *) ":status", (uint8_t *) "404", 7, 3, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *) "content-type", (uint8_t *) "text/html", 12, 9, NGHTTP2_NV_FLAG_NONE}, + {(uint8_t *) "server", (uint8_t *) header_server, 6, 17, NGHTTP2_NV_FLAG_NONE}}; + + const char *body = "

404 Not Found

" + "

The requested resource was not found on this server.

" + ""; + + nghttp2_data_provider data_prd; + data_prd.source.ptr = (void *) body; + data_prd.read_callback = string_read_callback; + + nghttp2_submit_response(session, stream_id, error_hdrs, sizeof(error_hdrs) / sizeof(error_hdrs[0]), &data_prd); + } + + nghttp2_session_send(session); +} + +static void http2_send_settings(Http2Session *session_data, const nghttp2_settings_entry *settings, size_t num) { + auto rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, settings, num); + if (rv != 0) { + swoole_error_log( + SW_LOG_ERROR, SW_ERROR_HTTP2_INTERNAL_ERROR, "Failed to submit settings: %s", nghttp2_strerror(rv)); + return; + } + nghttp2_session_send(session_data->session); +} + +static std::shared_ptr create_http2_session(Server *serv, SessionId fd) { + auto session_data = std::make_shared(fd, serv); + + nghttp2_session_callbacks *callbacks; + int rv = nghttp2_session_callbacks_new(&callbacks); + if (rv != 0) { + swoole_warning("Failed to create nghttp2 callbacks: %s", nghttp2_strerror(rv)); + return nullptr; + } + + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks, on_frame_not_send_callback); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, on_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks, on_frame_not_send_callback); + + rv = nghttp2_session_server_new(&session_data->session, callbacks, session_data.get()); + nghttp2_session_callbacks_del(callbacks); + + if (rv != 0) { + swoole_error_log( + SW_LOG_ERROR, SW_ERROR_HTTP2_INTERNAL_ERROR, "Failed to create nghttp2 session: %s", nghttp2_strerror(rv)); + return nullptr; + } + + nghttp2_session_set_user_data(session_data->session, session_data.get()); + + return session_data; +} + +static void test_ssl_http2(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_INFO); + + Mutex *lock = new Mutex(true); + lock->lock(); + + const int server_port = __LINE__ + TEST_PORT; + ListenPort *port = serv.add_port((enum swSocketType)(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, server_port); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + port->open_http2_protocol = 1; + port->open_http_protocol = 1; + port->open_websocket_protocol = 1; + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->ssl_context->http = 1; + port->ssl_context->http_v2 = 1; + port->ssl_init(); + + ASSERT_EQ(serv.create(), SW_OK); + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + lock->lock(); + + auto cmd = "nghttp -v -y https://127.0.0.1:" + std::to_string(server_port) + "/"; + pid_t pid; + auto _pipe = swoole_shell_exec(cmd.c_str(), &pid, 1); + String buf(1024); + while (1) { + auto n = read(_pipe, buf.str + buf.length, buf.size - buf.length); + if (n > 0) { + buf.grow(n); + continue; + } + break; + } + + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); + close(_pipe); + + usleep(10000); + + DEBUG() << "NGHTTP2 VERSION: " << NGHTTP2_VERSION << std::endl; + DEBUG() << buf.to_std_string(); + + EXPECT_TRUE(buf.contains("user-agent: nghttp2/")); + // FIXME There is a bug in nghttp's processing of settings frames, + // so it can only give up detecting response content. + // EXPECT_TRUE(buf.contains("Welcome to HTTP/2 Server")); + + serv->shutdown(); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onConnect = [](Server *serv, DataHead *ev) { + SessionId fd = ev->fd; + DEBUG() << "New connection: " << fd << std::endl; + + auto session = create_http2_session(serv, fd); + if (!session) { + serv->close(fd); + return; + } + + sessions[fd] = session; + ssize_t consumed = nghttp2_session_mem_recv( + session->session, (uint8_t *) SW_HTTP2_PRI_STRING, sizeof(SW_HTTP2_PRI_STRING) - 1); + if (consumed < 0) { + swoole_error_log(SW_LOG_ERROR, + SW_ERROR_HTTP2_INTERNAL_ERROR, + "nghttp2_session_mem_recv() error: %s", + nghttp2_strerror((int) consumed)); + serv->close(fd); + return; + } + http2_send_settings(session.get(), default_settings, sizeof(default_settings) / sizeof(default_settings[0])); + }; + + serv.onClose = [](Server *serv, DataHead *ev) { + SessionId fd = ev->fd; + DEBUG() << "Close connection: " << fd << std::endl; + sessions.erase(fd); + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + SessionId fd = req->info.fd; + std::shared_ptr session; + if (sessions.find(fd) == sessions.end()) { + serv->close(fd); + return SW_ERR; + } + + session = sessions[fd]; + const uint8_t *data_ptr = reinterpret_cast(req->data); + size_t data_len = req->info.len; + + ssize_t consumed = nghttp2_session_mem_recv(session->session, data_ptr, data_len); + if (consumed < 0) { + swoole_error_log(SW_LOG_ERROR, + SW_ERROR_HTTP2_INTERNAL_ERROR, + "nghttp2_session_mem_recv() error: %s", + nghttp2_strerror((int) consumed)); + serv->close(fd); + return SW_ERR; + } + + if (nghttp2_session_want_write(session->session)) { + nghttp2_session_send(session->session); + } + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; +} + +TEST(http2, ssl) { + test_ssl_http2(Server::MODE_BASE); +} diff --git a/core-tests/src/protocol/redis.cpp b/core-tests/src/protocol/redis.cpp index 0c3a1e412f..7e5194515a 100644 --- a/core-tests/src/protocol/redis.cpp +++ b/core-tests/src/protocol/redis.cpp @@ -49,9 +49,10 @@ TEST(redis, server) { port->open_redis_protocol = true; serv.create(); + std::unordered_map redis_data; - serv.onWorkerStart = [&](swServer *serv, int worker_id) { - if (worker_id != 0) { + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id != 0) { return; } swoole::Coroutine::create( @@ -61,12 +62,25 @@ TEST(redis, server) { ASSERT_TRUE(redis.Connect("127.0.0.1", serv->get_primary_port()->port)); ASSERT_TRUE(redis.Set(REDIS_TEST_KEY, REDIS_TEST_VALUE)); ASSERT_EQ(redis.Get(REDIS_TEST_KEY), REDIS_TEST_VALUE); + + ASSERT_EQ(redis.Get(REDIS_TEST_KEY + "-not-exists"), ""); + + String rdata; + rdata.append_random_bytes(128 * 1024, true); + auto data = rdata.to_std_string(); + + ASSERT_TRUE(redis.Set(REDIS_TEST_KEY + "-big-key", data)); + ASSERT_EQ(redis.Get(REDIS_TEST_KEY + "-big-key"), data); + ASSERT_EQ(redis.Ttl(REDIS_TEST_KEY), -1); + ASSERT_FALSE(redis.Select(1)); + ASSERT_EQ(redis.Role(), "master"); + kill(serv->gs->master_pid, SIGTERM); }, serv); }; - serv.onReceive = [](swServer *serv, swRecvData *req) -> int { + serv.onReceive = [&redis_data](Server *serv, RecvData *req) -> int { int session_id = req->info.fd; auto list = redis::parse(req->data, req->info.len); @@ -74,15 +88,51 @@ TEST(redis, server) { buffer->clear(); if (strcasecmp(list[0].c_str(), "GET") == 0) { - redis::format(buffer, redis::REPLY_STRING, REDIS_TEST_VALUE); - serv->send(session_id, buffer->str, buffer->length); + auto result = redis_data.find(list[1]); + if (result == redis_data.end()) { + redis::format_nil(buffer); + } else { + char buf[64]; + auto n = snprintf(buf, sizeof(buf), "$%zu\r\n", result->second.length()); + serv->send(session_id, buf, n); + serv->send(session_id, result->second.c_str(), result->second.length()); + serv->send(session_id, SW_CRLF, SW_CRLF_LEN); + return SW_OK; + } } else if (strcasecmp(list[0].c_str(), "SET") == 0) { redis::format(buffer, redis::REPLY_STATUS, "OK"); - serv->send(session_id, buffer->str, buffer->length); + redis_data[list[1]] = list[2]; + } else if (strcasecmp(list[0].c_str(), "TTL") == 0) { + redis::format(buffer, redis::REPLY_INT, -1); + } else if (strcasecmp(list[0].c_str(), "ROLE") == 0) { + redis::format(buffer, redis::REPLY_STRING, "master"); + } else { + redis::format(buffer, redis::REPLY_ERROR, "Not Suppport"); } + serv->send(session_id, buffer->str, buffer->length); return SW_OK; }; serv.start(); } + +TEST(redis, format) { + auto buf = sw_tg_buffer(); + + buf->clear(); + redis::format(buf, redis::REPLY_STATUS, ""); + ASSERT_MEMEQ(buf->str, "+OK\r\n", buf->length); + + buf->clear(); + redis::format(buf, redis::REPLY_ERROR, ""); + ASSERT_MEMEQ(buf->str, "-ERR\r\n", buf->length); +} + +TEST(redis, parse) { + auto buf = sw_tg_buffer(); + + buf->clear(); + auto rs = redis::parse(SW_STRL(":3\r\n")); + ASSERT_EQ(rs[0], "3"); +} diff --git a/core-tests/src/protocol/ssl.cpp b/core-tests/src/protocol/ssl.cpp new file mode 100644 index 0000000000..6f50918073 --- /dev/null +++ b/core-tests/src/protocol/ssl.cpp @@ -0,0 +1,75 @@ +/* ++----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include +#include +#include +#include +#include + +using swoole::SSLContext; +using swoole::String; + +TEST(ssl, destroy) { + swoole_ssl_init(); + swoole_ssl_destroy(); + ASSERT_EQ(ERR_peek_error(), 0); +} + +TEST(ssl, get_error) { + swoole_ssl_init(); + { + ERR_clear_error(); + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_CERTIFICATE_VERIFY_FAILED, __FILE__, __LINE__); + const char *error_str = swoole_ssl_get_error(); + EXPECT_NE(error_str, nullptr); + String str(error_str); + DEBUG() << str.to_std_string() << std::endl; + ASSERT_TRUE(str.contains("certificate verify failed")); + } + { + ERR_clear_error(); + + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SET_SESSION, SSL_R_CERTIFICATE_VERIFY_FAILED, __FILE__, __LINE__); + ERR_put_error(ERR_LIB_SSL, SSL_F_SSL_SHUTDOWN, SSL_R_PROTOCOL_IS_SHUTDOWN, __FILE__, __LINE__); + + const char *error_str = swoole_ssl_get_error(); + EXPECT_NE(error_str, nullptr); + + const char *error_str2 = swoole_ssl_get_error(); + EXPECT_NE(error_str2, nullptr); + + String str(error_str2); + DEBUG() << str.to_std_string() << std::endl; + ASSERT_TRUE(str.contains("protocol is shutdown")); + + const char *error_st3 = swoole_ssl_get_error(); + ASSERT_STREQ(error_st3, ""); + } +} + +TEST(ssl, password) { + SSLContext ctx; + ctx.key_file = swoole::test::get_ssl_dir() + "/passwd_key.pem"; + ctx.passphrase = "swoole"; + ctx.cert_file = swoole::test::get_ssl_dir() + "/passwd.crt"; + ASSERT_TRUE(ctx.create()); +} diff --git a/core-tests/src/reactor/base.cpp b/core-tests/src/reactor/base.cpp index 046885e176..9f00b3939f 100644 --- a/core-tests/src/reactor/base.cpp +++ b/core-tests/src/reactor/base.cpp @@ -18,8 +18,13 @@ */ #include "test_core.h" +#include "swoole_reactor.h" #include "swoole_pipe.h" +#include "swoole_signal.h" +#include "swoole_util.h" +#include "swoole_api.h" +using namespace std; using namespace swoole; TEST(reactor, create) { @@ -29,7 +34,7 @@ TEST(reactor, create) { ASSERT_EQ(reactor->max_event_num, SW_REACTOR_MAXEVENTS); - ASSERT_TRUE(reactor->running); + ASSERT_TRUE(reactor->ready()); ASSERT_NE(reactor->write, nullptr); ASSERT_NE(reactor->close, nullptr); ASSERT_EQ(reactor->defer_tasks, nullptr); @@ -38,22 +43,22 @@ TEST(reactor, create) { /** * coroutine socket reactor */ - ASSERT_NE(reactor->read_handler[Reactor::get_fd_type(SW_FD_CO_SOCKET | SW_EVENT_READ)], nullptr); - ASSERT_NE(reactor->write_handler[Reactor::get_fd_type(SW_FD_CO_SOCKET | SW_EVENT_WRITE)], nullptr); - ASSERT_NE(reactor->error_handler[Reactor::get_fd_type(SW_FD_CO_SOCKET | SW_EVENT_ERROR)], nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_SOCKET, SW_EVENT_READ), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_SOCKET, SW_EVENT_WRITE), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_SOCKET, SW_EVENT_ERROR), nullptr); /** * system reactor */ - ASSERT_NE(reactor->read_handler[Reactor::get_fd_type(SW_FD_CO_POLL | SW_EVENT_READ)], nullptr); - ASSERT_NE(reactor->write_handler[Reactor::get_fd_type(SW_FD_CO_POLL | SW_EVENT_WRITE)], nullptr); - ASSERT_NE(reactor->error_handler[Reactor::get_fd_type(SW_FD_CO_POLL | SW_EVENT_ERROR)], nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_POLL, SW_EVENT_READ), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_POLL, SW_EVENT_WRITE), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_POLL, SW_EVENT_ERROR), nullptr); - ASSERT_NE(reactor->read_handler[Reactor::get_fd_type(SW_FD_CO_EVENT | SW_EVENT_READ)], nullptr); - ASSERT_NE(reactor->write_handler[Reactor::get_fd_type(SW_FD_CO_EVENT | SW_EVENT_WRITE)], nullptr); - ASSERT_NE(reactor->error_handler[Reactor::get_fd_type(SW_FD_CO_EVENT | SW_EVENT_ERROR)], nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_EVENT, SW_EVENT_READ), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_EVENT, SW_EVENT_WRITE), nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_CO_EVENT, SW_EVENT_ERROR), nullptr); - ASSERT_NE(reactor->read_handler[Reactor::get_fd_type(SW_FD_AIO | SW_EVENT_READ)], nullptr); + ASSERT_NE(reactor->get_handler(SW_FD_AIO, SW_EVENT_READ), nullptr); swoole_event_free(); } @@ -61,14 +66,17 @@ TEST(reactor, create) { TEST(reactor, set_handler) { Reactor reactor; - reactor.set_handler(SW_EVENT_READ, (ReactorHandler) 0x1); - ASSERT_EQ(reactor.read_handler[Reactor::get_fd_type(SW_EVENT_READ)], (ReactorHandler) 0x1); + reactor.set_handler(SW_FD_SESSION, SW_EVENT_READ, (ReactorHandler) 0x1); + ASSERT_TRUE(reactor.isset_handler(SW_FD_SESSION, SW_EVENT_READ)); + ASSERT_EQ(reactor.get_handler(SW_FD_SESSION, SW_EVENT_READ), (ReactorHandler) 0x1); - reactor.set_handler(SW_EVENT_WRITE, (ReactorHandler) 0x2); - ASSERT_EQ(reactor.write_handler[Reactor::get_fd_type(SW_EVENT_WRITE)], (ReactorHandler) 0x2); + reactor.set_handler(SW_FD_SESSION, SW_EVENT_WRITE, (ReactorHandler) 0x2); + ASSERT_TRUE(reactor.isset_handler(SW_FD_SESSION, SW_EVENT_WRITE)); + ASSERT_EQ(reactor.get_handler(SW_FD_SESSION, SW_EVENT_WRITE), (ReactorHandler) 0x2); - reactor.set_handler(SW_EVENT_ERROR, (ReactorHandler) 0x3); - ASSERT_EQ(reactor.error_handler[Reactor::get_fd_type(SW_EVENT_ERROR)], (ReactorHandler) 0x3); + reactor.set_handler(SW_FD_SESSION, SW_EVENT_ERROR, (ReactorHandler) 0x3); + ASSERT_TRUE(reactor.isset_handler(SW_FD_SESSION, SW_EVENT_ERROR)); + ASSERT_EQ(reactor.get_handler(SW_FD_SESSION, SW_EVENT_ERROR), (ReactorHandler) 0x3); } TEST(reactor, wait) { @@ -79,11 +87,12 @@ TEST(reactor, wait) { ret = swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); ASSERT_EQ(ret, SW_OK); ASSERT_NE(SwooleTG.reactor, nullptr); + ASSERT_TRUE(SwooleTG.reactor->ready()); - swoole_event_set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { char buffer[16]; - ssize_t n = read(ev->fd, buffer, sizeof(buffer)); + ssize_t n = ev->socket->read(buffer, sizeof(buffer)); EXPECT_EQ(sizeof("hello world"), n); EXPECT_STREQ("hello world", buffer); reactor->del(ev->socket); @@ -114,10 +123,10 @@ TEST(reactor, write) { ASSERT_EQ(ret, SW_OK); ASSERT_NE(SwooleTG.reactor, nullptr); - swoole_event_set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { char buffer[16]; - ssize_t n = read(ev->fd, buffer, sizeof(buffer)); + ssize_t n = ev->socket->read(buffer, sizeof(buffer)); EXPECT_EQ(sizeof("hello world"), n); EXPECT_STREQ("hello world", buffer); reactor->del(ev->socket); @@ -138,6 +147,64 @@ TEST(reactor, write) { ASSERT_EQ(SwooleTG.reactor, nullptr); } +TEST(reactor, wait_timeout) { + ASSERT_EQ(swoole_event_init(SW_EVENTLOOP_WAIT_EXIT), SW_OK); + ASSERT_NE(SwooleTG.reactor, nullptr); + + sw_reactor()->set_timeout_msec(30); + auto started_at = swoole::microtime(); + ASSERT_EQ(sw_reactor()->wait(), SW_OK); + + auto dr = swoole::microtime() - started_at; + ASSERT_GE(dr, 0.03); + ASSERT_LT(dr, 0.05); + + swoole_event_free(); +} + +TEST(reactor, wait_error) { + ASSERT_EQ(swoole_event_init(SW_EVENTLOOP_WAIT_EXIT), SW_OK); + ASSERT_NE(SwooleTG.reactor, nullptr); + + // ERROR: EINVAL epfd is not an epoll file descriptor, or maxevents is less than or equal to zero. + sw_reactor()->max_event_num = 0; + ASSERT_EQ(sw_reactor()->wait(), SW_ERR); + ASSERT_EQ(errno, EINVAL); + + swoole_event_free(); +} + +TEST(reactor, writev) { + network::Socket fake_sock{}; + + UnixSocket p(true, SOCK_DGRAM); + ASSERT_TRUE(p.ready()); + + fake_sock.fd = p.get_socket(true)->get_fd(); + + swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); + + struct iovec iov[2]; + + iov[0].iov_base = (void *) "hello "; + iov[0].iov_len = 6; + + iov[1].iov_base = (void *) "world\n"; + iov[1].iov_len = 6; + + ASSERT_EQ(swoole_event_writev(&fake_sock, iov, 2), 12); + + char buf[32]; + ASSERT_EQ(p.get_socket(false)->read(buf, sizeof(buf)), 12); + ASSERT_MEMEQ("hello world\n", buf, 12); + + fake_sock.ssl = (SSL *) -1; + ASSERT_EQ(swoole_event_writev(&fake_sock, iov, 2), -1); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_OPERATION_NOT_SUPPORT); + + swoole_event_wait(); +} + constexpr int DATA_SIZE = 2 * SW_NUM_MILLION; TEST(reactor, write_2m) { @@ -149,9 +216,9 @@ TEST(reactor, write_2m) { ASSERT_EQ(ret, SW_OK); ASSERT_NE(SwooleTG.reactor, nullptr); - swoole_event_set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { auto tg_buf = sw_tg_buffer(); - ssize_t n = read(ev->fd, tg_buf->str + tg_buf->length, tg_buf->size - tg_buf->length); + ssize_t n = ev->socket->read(tg_buf->str + tg_buf->length, tg_buf->size - tg_buf->length); if (n <= 0) { return SW_ERR; } @@ -175,9 +242,20 @@ TEST(reactor, write_2m) { sw_tg_buffer()->clear(); - auto n = swoole_event_write(p.get_socket(true), str.value(), str.get_length()); + auto sock = p.get_socket(true); + sock->buffer_size = 2 * 1024 * 1024; + + auto n = swoole_event_write(sock, str.value(), str.get_length()); ASSERT_EQ(n, str.get_length()); - ASSERT_GT(p.get_socket(true)->out_buffer->length(), 1024); + ASSERT_GT(sock->get_out_buffer_length(), 1024); + + std::cout << sock->get_out_buffer_length() << "\n"; + + ASSERT_EQ(swoole_event_write(sock, str.value(), 256 * 1024), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_OUTPUT_BUFFER_OVERFLOW); + + ASSERT_EQ(swoole_event_write(sock, str.value(), sock->buffer_size + 8192), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); ret = swoole_event_wait(); ASSERT_EQ(ret, SW_OK); @@ -193,20 +271,20 @@ TEST(reactor, bad_fd) { ASSERT_EQ(n, SW_ERR); ASSERT_EQ(swoole_get_last_error(), EBADF); swoole_event_free(); - sock->fd = -1; + sock->move_fd(); sock->free(); } static const char *pkt = "hello world\r\n"; static void reactor_test_func(Reactor *reactor) { - Pipe p(true); + Pipe p(false); ASSERT_TRUE(p.ready()); - reactor->set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *event) -> int { + reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *event) -> int { char buf[1024]; size_t l = strlen(pkt); - size_t n = read(event->fd, buf, sizeof(buf)); + size_t n = event->socket->read(buf, sizeof(buf)); EXPECT_EQ(n, l); buf[n] = 0; EXPECT_EQ(std::string(buf, n), std::string(pkt)); @@ -214,30 +292,130 @@ static void reactor_test_func(Reactor *reactor) { return SW_OK; }); - reactor->set_handler(SW_FD_PIPE | SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { + + reactor->set_handler(SW_FD_PIPE, SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { size_t l = strlen(pkt); - EXPECT_EQ(write(event->fd, pkt, l), l); + EXPECT_EQ(event->socket->write(pkt, l), l); reactor->del(event->socket); return SW_OK; }); - reactor->add(p.get_socket(false), SW_EVENT_READ); - reactor->add(p.get_socket(true), SW_EVENT_WRITE); - reactor->wait(nullptr); + + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor->add(p.get_socket(true), SW_EVENT_WRITE), SW_OK); + + UnixSocket unsock(false, SOCK_STREAM); + ASSERT_TRUE(unsock.ready()); + + int write_count = 0; + auto sock2 = unsock.get_socket(false); + sock2->object = &write_count; + sock2->fd_type = SW_FD_STREAM; + + reactor->set_handler(SW_FD_STREAM, SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { + int *count = (int *) event->socket->object; + (*count)++; + return SW_OK; + }); + ASSERT_EQ(reactor->add(sock2, SW_FD_STREAM | SW_EVENT_WRITE | SW_EVENT_ONCE), SW_OK); + + ASSERT_EQ(write_count, 0); + + ASSERT_EQ(reactor->wait(), SW_OK); + + ASSERT_EQ(write_count, 1); } -TEST(reactor, poll) { - Reactor reactor(1024, Reactor::TYPE_POLL); +TEST(reactor, epoll) { + Reactor reactor(1024, Reactor::TYPE_EPOLL); reactor.wait_exit = true; reactor_test_func(&reactor); } -TEST(reactor, select) { - Reactor reactor(1024, Reactor::TYPE_SELECT); +TEST(reactor, poll) { + Reactor reactor(1024, Reactor::TYPE_POLL); reactor.wait_exit = true; reactor_test_func(&reactor); } +TEST(reactor, poll_extra) { + Reactor reactor(32, Reactor::TYPE_POLL); + + network::Socket fake_sock1{}; + fake_sock1.fd = 12345; + + network::Socket fake_sock2{}; + fake_sock2.fd = 99999; + + ASSERT_EQ(reactor.add(&fake_sock1, SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor.add(&fake_sock2, SW_EVENT_READ), SW_OK); + + ASSERT_EQ(reactor.add(&fake_sock1, SW_EVENT_READ), SW_ERR); + + ASSERT_EQ(reactor.get_event_num(), 2); + + ASSERT_EQ(reactor.set(&fake_sock2, SW_EVENT_READ | SW_EVENT_WRITE | SW_EVENT_ERROR), SW_OK); + + ASSERT_EQ(reactor.del(&fake_sock2), SW_OK); + ASSERT_EQ(reactor.get_event_num(), 1); + + network::Socket fake_sock3{}; + fake_sock3.fd = 88888; + + ASSERT_EQ(reactor.set(&fake_sock3, SW_EVENT_READ | SW_EVENT_WRITE), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_SOCKET_NOT_EXISTS); + + ASSERT_EQ(reactor.del(&fake_sock3), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_SOCKET_NOT_EXISTS); + + network::Socket fake_socks[32]; + SW_LOOP_N(32) { + fake_socks[i].fd = i + 1024; + if (i <= 30) { + ASSERT_EQ(reactor.add(&fake_socks[i], SW_EVENT_READ), SW_OK); + } else { + ASSERT_EQ(reactor.add(&fake_socks[i], SW_EVENT_READ), SW_ERR); + } + } + + for (auto i = 31; i <= 0; i--) { + fake_socks[i].fd = i + 1024; + if (i <= 30) { + ASSERT_EQ(reactor.del(&fake_socks[i]), SW_OK); + } else { + ASSERT_EQ(reactor.del(&fake_socks[i]), SW_ERR); + } + } +} + +TEST(reactor, poll_extra2) { + Reactor reactor(32, Reactor::TYPE_POLL); + reactor.once = true; + reactor.set_timeout_msec(10); + reactor.set_end_callback(Reactor::PRIORITY_DEFER_TASK, [](Reactor *reactor) { + DEBUG() << "end callback\n"; + ASSERT_TRUE(reactor->timed_out); + }); + ASSERT_EQ(reactor.wait(), SW_OK); + + swoole_signal_set( + SIGIO, [](int sig) { DEBUG() << "SIGIO received\n"; }, 0, 0); + + reactor.erase_end_callback(Reactor::PRIORITY_DEFER_TASK); + reactor.set_timeout_msec(1000); + + std::thread t([]() { + swoole_signal_block_all(); + usleep(10000); + kill(getpid(), SIGIO); + }); + errno = 0; + ASSERT_EQ(reactor.wait(), SW_OK); + ASSERT_EQ(errno, EINTR); + + t.join(); +} + TEST(reactor, add_or_update) { int ret; UnixSocket p(true, SOCK_DGRAM); @@ -270,3 +448,230 @@ TEST(reactor, defer_task) { ASSERT_EQ(count, 1); swoole_event_free(); } + +TEST(reactor, cycle) { + Reactor reactor(1024, Reactor::TYPE_POLL); + reactor.wait_exit = true; + + int event_loop_count = 0; + const char *test = "hello world"; + + reactor.future_task.callback = [&event_loop_count](void *data) { + ASSERT_STREQ((char *) data, "hello world"); + event_loop_count++; + }; + reactor.future_task.data = (void *) test; + + reactor_test_func(&reactor); + + ASSERT_GT(event_loop_count, 0); +} + +static void event_idle_callback(void *data) { + ASSERT_STREQ((char *) data, "hello world"); +} + +TEST(reactor, priority_idle_task) { + Reactor reactor(1024, Reactor::TYPE_POLL); + reactor.wait_exit = true; + + const char *test = "hello world"; + reactor.idle_task.callback = event_idle_callback; + reactor.idle_task.data = (void *) test; + reactor_test_func(&reactor); +} + +TEST(reactor, hook) { + Reactor *reactor = new Reactor(1024, Reactor::TYPE_POLL); + reactor->wait_exit = true; + + swoole_add_hook( + SW_GLOBAL_HOOK_ON_REACTOR_CREATE, + [](void *data) -> void { + Reactor *reactor = (Reactor *) data; + ASSERT_EQ(Reactor::TYPE_POLL, reactor->type_); + }, + 1); + + swoole_add_hook( + SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, + [](void *data) -> void { + Reactor *reactor = (Reactor *) data; + ASSERT_EQ(Reactor::TYPE_POLL, reactor->type_); + }, + 1); + + ON_SCOPE_EXIT { + SwooleG.hooks[SW_GLOBAL_HOOK_ON_REACTOR_CREATE] = nullptr; + SwooleG.hooks[SW_GLOBAL_HOOK_ON_REACTOR_DESTROY] = nullptr; + }; + + reactor_test_func(reactor); + delete reactor; +} + +TEST(reactor, set_fd) { + UnixSocket p(true, SOCK_DGRAM); + Reactor *reactor = new Reactor(1024, Reactor::TYPE_EPOLL); + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor->set(p.get_socket(false), SW_EVENT_WRITE), SW_OK); + delete reactor; + + reactor = new Reactor(1024, Reactor::TYPE_POLL); + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor->set(p.get_socket(false), SW_EVENT_WRITE), SW_OK); + delete reactor; +} + +static void test_error_event(Reactor::Type type, int retval) { + Pipe p(true); + ASSERT_TRUE(p.ready()); + + Reactor *reactor = new Reactor(1024, type); + SwooleTG.reactor = reactor; + + reactor->ptr = &retval; + + reactor->set_handler(SW_FD_PIPE, SW_EVENT_ERROR, [](Reactor *reactor, Event *event) -> int { + EXPECT_EQ(reactor->del(event->socket), SW_OK); + reactor->running = false; + return *(int *) reactor->ptr; + }); + + reactor->add(p.get_socket(true), SW_EVENT_ERROR); + reactor->add(p.get_socket(false), SW_EVENT_ERROR); + + p.close(SW_PIPE_CLOSE_WORKER); + ASSERT_EQ(reactor->wait(), SW_OK); + delete reactor; + SwooleTG.reactor = nullptr; +} + +TEST(reactor, error_event) { + test_error_event(Reactor::TYPE_EPOLL, SW_OK); + test_error_event(Reactor::TYPE_POLL, SW_OK); + + test_error_event(Reactor::TYPE_EPOLL, SW_ERR); + test_error_event(Reactor::TYPE_POLL, SW_ERR); +} + +TEST(reactor, error) { + UnixSocket p(true, SOCK_DGRAM); + + swoole_set_print_backtrace_on_error(true); + + Reactor *reactor = new Reactor(1024, Reactor::TYPE_EPOLL); + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_WRITE), SW_ERR); + ASSERT_EQ(errno, EEXIST); + + network::Socket bad_sock; + bad_sock.removed = 1; + bad_sock.fd_type = SW_FD_PIPE; + bad_sock.fd = dup(p.get_socket(false)->get_fd()); + ASSERT_EQ(reactor->del(&bad_sock), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_EVENT_REMOVE_FAILED); + + ASSERT_EQ(reactor->add(&bad_sock, SW_EVENT_READ), SW_OK); + close(bad_sock.fd); + + ASSERT_EQ(reactor->set(&bad_sock, SW_EVENT_READ | SW_EVENT_READ), SW_ERR); + ASSERT_EQ(errno, EBADF); + + ASSERT_EQ(reactor->del(&bad_sock), SW_OK); + + delete reactor; + + reactor = new Reactor(1024, Reactor::TYPE_POLL); + ASSERT_EQ(reactor->add(p.get_socket(false), SW_EVENT_READ), SW_OK); + ASSERT_EQ(reactor->del(p.get_socket(false)), SW_OK); + ASSERT_EQ(reactor->del(p.get_socket(false)), SW_ERR); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_SOCKET_NOT_EXISTS); + delete reactor; +} + +TEST(reactor, drain_write_buffer) { + int ret; + UnixSocket p(true, SOCK_STREAM); + ASSERT_TRUE(p.ready()); + + ASSERT_EQ(swoole_event_init(SW_EVENTLOOP_WAIT_EXIT), SW_OK); + + p.set_blocking(false); + p.set_buffer_size(65536); + + String str(DATA_SIZE); + str.append_random_bytes(str.size - 1, false); + str.append('\0'); + + auto wsock = p.get_socket(true); + + auto n = swoole_event_write(wsock, str.value(), str.get_length()); + ASSERT_EQ(n, str.get_length()); + ASSERT_GT(wsock->get_out_buffer_length(), 1024); + + std::thread t([&]() { + usleep(10000); + auto rsock = p.get_socket(false); + + String rbuf(DATA_SIZE); + while (true) { + rsock->wait_event(1000, SW_EVENT_READ); + auto n = rsock->read(rbuf.str + rbuf.length, rbuf.size - rbuf.length); + if (n > 0) { + rbuf.length += n; + if (rbuf.length == rbuf.size) { + break; + } + } + } + + ASSERT_MEMEQ(rbuf.str, str.str, DATA_SIZE); + }); + + sw_reactor()->drain_write_buffer(wsock); + + ret = swoole_event_wait(); + ASSERT_EQ(ret, SW_OK); + ASSERT_FALSE(swoole_event_is_available()); + t.join(); +} + +TEST(reactor, handle_fail) { + int ret; + UnixSocket p(true, SOCK_DGRAM); + ASSERT_TRUE(p.ready()); + + ASSERT_EQ(swoole_event_init(SW_EVENTLOOP_WAIT_EXIT), SW_OK); + ASSERT_NE(SwooleTG.reactor, nullptr); + + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + char buffer[16]; + + ssize_t n = ev->socket->read(buffer, sizeof(buffer)); + EXPECT_EQ(strlen(pkt), n); + EXPECT_MEMEQ(pkt, buffer, n); + EXPECT_EQ(reactor->del(ev->socket), 0); + + EXPECT_EQ(reactor->get_event_num(), 0); + + return SW_ERR; + }); + + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_WRITE, [](Reactor *reactor, Event *ev) -> int { + EXPECT_EQ(reactor->set(ev->socket, SW_EVENT_READ), 0); + UnixSocket *p = (UnixSocket *) ev->socket->object; + swoole_timer_after(10, [p](auto r1, auto r2) { p->get_socket(true)->write(pkt, strlen(pkt)); }); + return SW_ERR; + }); + + auto sock = p.get_socket(false); + sock->object = &p; + + ret = swoole_event_add(sock, SW_EVENT_READ | SW_EVENT_WRITE); + ASSERT_EQ(ret, SW_OK); + + ret = swoole_event_wait(); + ASSERT_EQ(ret, SW_OK); + ASSERT_EQ(SwooleTG.reactor, nullptr); +} diff --git a/core-tests/src/server/buffer.cpp b/core-tests/src/server/buffer.cpp index 9de1cf85c1..848bf0e378 100644 --- a/core-tests/src/server/buffer.cpp +++ b/core-tests/src/server/buffer.cpp @@ -26,12 +26,12 @@ using namespace swoole; static const char *packet = "hello world\n"; TEST(server, send_buffer) { - swServer serv(swoole::Server::MODE_BASE); + Server serv(Server::MODE_BASE); serv.worker_num = 1; sw_logger()->set_level(SW_LOG_WARNING); - swListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); @@ -65,12 +65,12 @@ TEST(server, send_buffer) { kill(getpid(), SIGTERM); }); - serv.onWorkerStart = [&lock](swServer *serv, int worker_id) { lock.unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; - serv.onReceive = [](swServer *serv, swRecvData *req) -> int { + serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); - swString resp(1024 * 1024 * 16); + String resp(1024 * 1024 * 16); resp.repeat("A", 1, resp.capacity()); EXPECT_TRUE(serv->send(req->info.fd, resp.value(), resp.get_length())); EXPECT_TRUE(serv->close(req->info.fd, 0)); diff --git a/core-tests/src/server/http.cpp b/core-tests/src/server/http.cpp deleted file mode 100644 index e2bf8bbf92..0000000000 --- a/core-tests/src/server/http.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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. | - +----------------------------------------------------------------------+ - | @link https://www.swoole.com/ | - | @contact team@swoole.com | - | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | - | @Author Tianfeng Han | - +----------------------------------------------------------------------+ -*/ - -#include "test_core.h" - -#include "httplib_client.h" -#include "llhttp.h" -#include "swoole_server.h" -#include "swoole_file.h" -#include "swoole_http.h" - -using namespace swoole; -using namespace std; -using swoole::network::SyncClient; -using swoole::http_server::Context; - -struct http_context { - unordered_map headers; - unordered_map response_headers; - string url; - string current_key; - Server *server; - int fd; - bool completed; - - void setHeader(string key, string value) { - response_headers[key] = value; - } - - void response(enum swHttpStatusCode code, string body) { - response_headers["Content-Length"] = to_string(body.length()); - response(code); - server->send(fd, body.c_str(), body.length()); - } - - void response(int code) { - swString *buf = swoole::make_string(1024); - buf->length = sw_snprintf(buf->str, buf->size, "HTTP/1.1 %s\r\n", http_server::get_status_message(code)); - for (auto &kv : response_headers) { - buf->append(kv.first.c_str(), kv.first.length()); - buf->append(SW_STRL(": ")); - buf->append(kv.second.c_str(), kv.second.length()); - buf->append(SW_STRL("\r\n")); - } - buf->append(SW_STRL("\r\n")); - server->send(fd, buf->str, buf->length); - delete buf; - } -}; - -static int handle_on_message_complete(llhttp_t *parser) { - http_context *ctx = reinterpret_cast(parser->data); - ctx->completed = true; - return 0; -} - -static int handle_on_header_field(llhttp_t *parser, const char *at, size_t length) { - http_context *ctx = reinterpret_cast(parser->data); - ctx->current_key = string(at, length); - return 0; -} - -static int handle_on_header_value(llhttp_t *parser, const char *at, size_t length) { - http_context *ctx = reinterpret_cast(parser->data); - ctx->headers[ctx->current_key] = string(at, length); - return 0; -} - -static int handle_on_url(llhttp_t *parser, const char *at, size_t length) { - http_context *ctx = reinterpret_cast(parser->data); - ctx->url = std::string(at, length); - return 0; -} - -static void test_run_server(function fn) { - thread child_thread; - Server serv(swoole::Server::MODE_BASE); - serv.worker_num = 1; - serv.private_data_2 = (void *) &fn; - - serv.enable_static_handler = true; - serv.set_document_root(test::get_root_path()); - serv.add_static_handler_location("/examples"); - - sw_logger()->set_level(SW_LOG_WARNING); - - ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); - if (!port) { - swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); - exit(2); - } - port->open_http_protocol = 1; - port->open_websocket_protocol = 1; - - serv.create(); - - serv.onWorkerStart = [&child_thread](Server *serv, int worker_id) { - function fn = *(function *) serv->private_data_2; - child_thread = thread(fn, serv); - }; - - serv.onReceive = [](Server *serv, swRecvData *req) -> int { - SessionId session_id = req->info.fd; - auto conn = serv->get_connection_by_session_id(session_id); - - if (conn->websocket_status == swoole::websocket::STATUS_ACTIVE) { - sw_tg_buffer()->clear(); - std::string resp = "Swoole: " + string(req->data, req->info.len); - swoole::websocket::encode(sw_tg_buffer(), - resp.c_str(), - resp.length(), - swoole::websocket::OPCODE_TEXT, - swoole::websocket::FLAG_FIN); - serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length); - return SW_OK; - } - - llhttp_t parser = {}; - llhttp_settings_t settings = {}; - llhttp_init(&parser, HTTP_REQUEST, &settings); - - http_context ctx = {}; - parser.data = &ctx; - ctx.server = serv; - ctx.fd = session_id; - - settings.on_url = handle_on_url; - settings.on_header_field = handle_on_header_field; - settings.on_header_value = handle_on_header_value; - settings.on_message_complete = handle_on_message_complete; - - enum llhttp_errno err = llhttp_execute(&parser, req->data, req->info.len); - - if (err == HPE_PAUSED_UPGRADE) { - ctx.setHeader("Connection", "Upgrade"); - ctx.setHeader("Sec-WebSocket-Accept", "IIRiohCjop4iJrmvySrFcwcXpHo="); - ctx.setHeader("Sec-WebSocket-Version", "13"); - ctx.setHeader("Upgrade", "websocket"); - ctx.setHeader("Content-Length", "0"); - - ctx.response(SW_HTTP_SWITCHING_PROTOCOLS); - - conn->websocket_status = swoole::websocket::STATUS_ACTIVE; - - return SW_OK; - } - - if (err != HPE_OK) { - fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); - return SW_ERR; - } - EXPECT_EQ(err, HPE_OK); - - ctx.response(SW_HTTP_OK, "hello world"); - - EXPECT_EQ(ctx.headers["User-Agent"], httplib::USER_AGENT); - - return SW_OK; - }; - - serv.start(); - child_thread.join(); -} - -TEST(http_server, get) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - - auto port = serv->get_primary_port(); - - httplib::Client cli(TEST_HOST, port->port); - auto resp = cli.Get("/index.html"); - EXPECT_EQ(resp->status, 200); - EXPECT_EQ(resp->body, string("hello world")); - - kill(getpid(), SIGTERM); - }); -} - -TEST(http_server, post) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - - auto port = serv->get_primary_port(); - - httplib::Client cli(TEST_HOST, port->port); - httplib::Params params; - params.emplace("name", "john"); - params.emplace("note", "coder"); - auto resp = cli.Post("/index.html", params); - EXPECT_EQ(resp->status, 200); - EXPECT_EQ(resp->body, string("hello world")); - - kill(getpid(), SIGTERM); - }); -} - -TEST(http_server, static_get) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - - auto port = serv->get_primary_port(); - - httplib::Client cli(TEST_HOST, port->port); - auto resp = cli.Get("/examples/test.jpg"); - EXPECT_EQ(resp->status, 200); - - string file = test::get_root_path() + "/examples/test.jpg"; - File fp(file, O_RDONLY); - EXPECT_TRUE(fp.ready()); - - auto str = fp.read_content(); - - EXPECT_EQ(resp->body, str->to_std_string()); - - kill(getpid(), SIGTERM); - }); -} - -static void websocket_test(int server_port, const char *data, size_t length) { - httplib::Client cli(TEST_HOST, server_port); - - httplib::Headers headers; - EXPECT_TRUE(cli.Upgrade("/websocket", headers)); - EXPECT_TRUE(cli.Push(data, length)); - - auto msg = cli.Recv(); - EXPECT_EQ(string(msg->payload, msg->payload_length), string("Swoole: ") + string(data, length)); -} - -TEST(http_server, websocket_small) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - websocket_test(serv->get_primary_port()->get_port(), SW_STRL("hello world, swoole is best!")); - kill(getpid(), SIGTERM); - }); -} - -TEST(http_server, websocket_medium) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - - swString str(8192); - str.repeat("A", 1, 8192); - websocket_test(serv->get_primary_port()->get_port(), str.value(), str.get_length()); - - kill(getpid(), SIGTERM); - }); -} - -TEST(http_server, websocket_big) { - test_run_server([](Server *serv) { - swoole_signal_block_all(); - - swString str(128 * 1024); - str.repeat("A", 1, str.capacity() - 1); - websocket_test(serv->get_primary_port()->get_port(), str.value(), str.get_length()); - - kill(getpid(), SIGTERM); - }); -} - -TEST(http_server, parser1) { - std::thread t; - auto server = swoole::http_server::listen(":0", [](Context &ctx) { - EXPECT_EQ(ctx.form_data.size(), 3); - ctx.end("DONE"); - }); - server->worker_num = 1; - server->onWorkerStart = [&t](Server *server, uint32_t worker_id) { - t = std::thread([server]() { - swoole_signal_block_all(); - string file = test::get_root_path() + "/core-tests/fuzz/cases/req1.bin"; - File fp(file, O_RDONLY); - EXPECT_TRUE(fp.ready()); - auto str = fp.read_content(); - SyncClient c(SW_SOCK_TCP); - c.connect(TEST_HOST, server->get_primary_port()->port); - c.send(str->value(), str->get_length()); - char buf[1024]; - auto n = c.recv(buf, sizeof(buf)); - c.close(); - std::string resp(buf, n); - - EXPECT_TRUE(resp.find("200 OK") != resp.npos); - - kill(server->get_master_pid(), SIGTERM); - }); - }; - server->start(); - t.join(); -} - -TEST(http_server, parser2) { - std::thread t; - auto server = swoole::http_server::listen(":0", [](Context &ctx) { - EXPECT_EQ(ctx.form_data.size(), 3); - ctx.end("DONE"); - }); - server->worker_num = 1; - server->get_primary_port()->set_package_max_length(64 * 1024); - server->upload_max_filesize = 1024 * 1024; - server->onWorkerStart = [&t](Server *server, uint32_t worker_id) { - t = std::thread([server]() { - swoole_signal_block_all(); - string file = test::get_root_path() + "/core-tests/fuzz/cases/req2.bin"; - File fp(file, O_RDONLY); - EXPECT_TRUE(fp.ready()); - auto str = fp.read_content(); - SyncClient c(SW_SOCK_TCP); - c.connect(TEST_HOST, server->get_primary_port()->port); - c.send(str->value(), str->get_length()); - char buf[1024]; - auto n = c.recv(buf, sizeof(buf)); - c.close(); - std::string resp(buf, n); - - EXPECT_TRUE(resp.find("200 OK") != resp.npos); - - kill(server->get_master_pid(), SIGTERM); - }); - }; - server->start(); - t.join(); -} diff --git a/core-tests/src/server/http_parser.cpp b/core-tests/src/server/http_parser.cpp new file mode 100644 index 0000000000..422464e4d1 --- /dev/null +++ b/core-tests/src/server/http_parser.cpp @@ -0,0 +1,770 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | Author NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "test_core.h" +#include "swoole_util.h" +#include "swoole_llhttp.h" + +using namespace std; + +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_headers_complete(llhttp_t *parser); +static int http_request_message_complete(llhttp_t *parser); +static int http_llhttp_data_cb(llhttp_t *parser, const char *at, size_t length); +static int http_llhttp_cb(llhttp_t *parser); + +// clang-format off +static const llhttp_settings_t http_parser_settings = +{ + http_llhttp_cb, // on_message_begin + http_llhttp_data_cb, // on_protocol + http_request_on_url, // on_url + http_llhttp_data_cb, // on_status + http_llhttp_data_cb, // on_method + http_llhttp_data_cb, // on_version + http_request_on_header_field, // on_header_field + http_request_on_header_value, // on_header_value + http_llhttp_data_cb, // on_chunk_extension_name + http_llhttp_data_cb, // on_chunk_extension_value + http_request_on_headers_complete, // on_headers_complete + http_request_on_body, // on_body + http_request_message_complete, // on_message_complete + http_llhttp_cb, // on_protocol_complete + http_llhttp_cb, // on_url_complete + http_llhttp_cb, // on_status_complete + http_llhttp_cb, // on_method_complete + http_llhttp_cb, // on_version_complete + http_llhttp_cb, // on_header_field_complete + http_llhttp_cb, // on_header_value_complete + http_llhttp_cb, // on_chunk_extension_name_complete + http_llhttp_cb, // on_chunk_extension_value_complete + http_llhttp_cb, // on_chunk_header + http_llhttp_cb, // on_chunk_complete + http_llhttp_cb, // on_reset +}; +// clang-format on + +struct HttpContext { + long fd; + uchar completed : 1; + uchar end_ : 1; + uchar send_header_ : 1; + + uchar send_chunked : 1; + uchar recv_chunked : 1; + uchar send_trailer_ : 1; + uchar keepalive : 1; + uchar websocket : 1; + + uchar upgrade : 1; + uchar detached : 1; + uchar parse_cookie : 1; + uchar parse_body : 1; + uchar parse_files : 1; + uchar co_socket : 1; + uchar http2 : 1; + + llhttp_t parser; + + uint16_t input_var_num; + char *current_header_name; + size_t current_header_name_len; + char *current_input_name; + size_t current_input_name_len; + char *current_form_data_name; + size_t current_form_data_name_len; + + vector header_fields; + vector header_values; + string query_string; +}; + +static llhttp_t *swoole_http_parser_create(llhttp_type type = HTTP_REQUEST) { + auto *ctx = new HttpContext(); + llhttp_t *parser = &ctx->parser; + swoole_llhttp_parser_init(parser, type, static_cast(ctx)); + return parser; +} + +static void swoole_http_destroy_context(llhttp_t *parser) { + delete static_cast(parser->data); +} + +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); + ctx->query_string = string(at, length); + return 0; +} + +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); + ctx->header_fields.emplace_back(at, length); + return 0; +} + +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) { + auto ctx = static_cast(parser->data); + ctx->header_values.emplace_back(at, length); + return 0; +} + +static int http_request_on_headers_complete(llhttp_t *parser) { + return 0; +} + +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length) { + return 0; +} + +static int http_request_message_complete(llhttp_t *parser) { + auto ctx = static_cast(parser->data); + ctx->completed = 1; + return 0; +} + +static int http_llhttp_data_cb(llhttp_t *parser, const char *at, size_t length) { + return 0; +} + +static int http_llhttp_cb(llhttp_t *parser) { + return 0; +} + +TEST(http_parser, get_request) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string request = "GET /get HTTP/1.1\r\n" + "Host: www.maria.com\r\n" + "User-Agent: curl/7.64.1\r\n" + "Accept: */*\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, request.c_str(), request.length()); + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(length == request.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + ASSERT_TRUE(ctx->completed == 1); + ASSERT_TRUE(llhttp_should_keep_alive(parser) == 1); +} + +TEST(http_parser, version) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string http11 = "GET /get HTTP/1.1\r\n\r\n"; + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, http11.c_str(), http11.length()); + ASSERT_TRUE(length == http11.length()); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + ASSERT_TRUE(ctx->completed == 1); + ASSERT_TRUE(llhttp_get_http_major(parser) == 1); + ASSERT_TRUE(llhttp_get_http_minor(parser) == 1); +} + +TEST(http_parser, incomplete) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string incomplete = "GET /get HTTP/1.1\r\n"; + size_t length = + swoole_llhttp_parser_execute(parser, &http_parser_settings, incomplete.c_str(), incomplete.length()); + ASSERT_TRUE(length == incomplete.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(ctx->completed == 0); +} + +TEST(http_parser, method) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string incomplete = "GET /get HTTP/1.1\r\n\r\n"; + size_t length = + swoole_llhttp_parser_execute(parser, &http_parser_settings, incomplete.c_str(), incomplete.length()); + ASSERT_TRUE(length == incomplete.length()); + ASSERT_TRUE(llhttp_get_method(parser) == HTTP_GET); + ASSERT_STREQ(llhttp_method_name(HTTP_GET), "GET"); +} + +TEST(http_parser, websocket) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string websocket = "GET /chat HTTP/1.1\r\n" + "Host: example.com\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Origin: http://example.com\r\n\r\n"; + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, websocket.c_str(), websocket.length()); + ASSERT_TRUE(length == websocket.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + ASSERT_TRUE(llhttp_get_upgrade(parser) == 1); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(ctx->completed == 1); +} + +TEST(http_parser, http2) { + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + string http2 = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, http2.c_str(), http2.length()); + ASSERT_TRUE(length == http2.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_PAUSED_H2_UPGRADE); + ASSERT_TRUE(llhttp_get_method(parser) == HTTP_PRI); +} + +TEST(http_parser, header_field_and_value) { + string request = "GET /get HTTP/1.1\r\n" + "Host: www.maria.com\r\n" + "User-Agent: curl/7.64.1\r\n" + "Accept: */*\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, request.c_str(), request.length()); + ASSERT_TRUE(length == request.length()); + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(ctx->completed == 1); + + ASSERT_STREQ(ctx->header_fields[0].c_str(), "Host"); + ASSERT_STREQ(ctx->header_fields[1].c_str(), "User-Agent"); + ASSERT_STREQ(ctx->header_fields[2].c_str(), "Accept"); + ASSERT_STREQ(ctx->header_fields[3].c_str(), "Connection"); + + ASSERT_STREQ(ctx->header_values[0].c_str(), "www.maria.com"); + ASSERT_STREQ(ctx->header_values[1].c_str(), "curl/7.64.1"); + ASSERT_STREQ(ctx->header_values[2].c_str(), "*/*"); + ASSERT_STREQ(ctx->header_values[3].c_str(), "keep-alive"); +} + +TEST(http_parser, query_string) { + string request = "GET /get/swoole?a=1&b=2 HTTP/1.1\r\n\r\n"; + llhttp_t *parser = swoole_http_parser_create(); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, request.c_str(), request.length()); + ASSERT_TRUE(length == request.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_STREQ(ctx->query_string.c_str(), "/get/swoole?a=1&b=2"); +} + +TEST(http_parser, chunk) { + string chunk = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "5\r\n" + "Hello\r\n" + "6\r\n" + " World\r\n" + "3\r\n" + "!!!\r\n" + "0\r\n\r\n"; + + llhttp_t *parser = swoole_http_parser_create(HTTP_RESPONSE); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, chunk.c_str(), chunk.length()); + ASSERT_EQ(length, chunk.length()); + ASSERT_EQ(llhttp_get_errno(parser), HPE_OK); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(ctx->completed == 1); +} + +TEST(http_parser, response) { + string response = "HTTP/1.1 200 OK\r\n" + "Server: CLOUD ELB 1.0.0\r\n" + "Date: Sat, 04 Feb 2023 08:47:14 GMT\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 18\r\n" + "Connection: close\r\n" + "\r\n" + "{\"name\" : \"laala\"}"; + + llhttp_t *parser = swoole_http_parser_create(HTTP_RESPONSE); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + size_t length = swoole_llhttp_parser_execute(parser, &http_parser_settings, response.c_str(), response.length()); + ASSERT_TRUE(length == response.length()); + ASSERT_TRUE(llhttp_get_errno(parser) == HPE_OK); + ASSERT_TRUE(llhttp_get_status_code(parser) == HTTP_STATUS_OK); + ASSERT_TRUE(llhttp_get_http_major(parser) == 1); + ASSERT_TRUE(llhttp_get_http_minor(parser) == 1); + + HttpContext *ctx = static_cast(parser->data); + ASSERT_TRUE(ctx->completed == 1); + ASSERT_STREQ(ctx->header_fields[0].c_str(), "Server"); + ASSERT_STREQ(ctx->header_fields[1].c_str(), "Date"); + ASSERT_STREQ(ctx->header_fields[2].c_str(), "Content-Type"); + ASSERT_STREQ(ctx->header_fields[3].c_str(), "Content-Length"); + ASSERT_STREQ(ctx->header_fields[4].c_str(), "Connection"); + + ASSERT_STREQ(ctx->header_values[0].c_str(), "CLOUD ELB 1.0.0"); + ASSERT_STREQ(ctx->header_values[1].c_str(), "Sat, 04 Feb 2023 08:47:14 GMT"); + ASSERT_STREQ(ctx->header_values[2].c_str(), "application/json"); + ASSERT_STREQ(ctx->header_values[3].c_str(), "18"); + ASSERT_STREQ(ctx->header_values[4].c_str(), "close"); +} + +// clang-format off +const vector request_error_protocols = { + // request/connection + "PUT /url HTTP/1.0\r\n\r\nPUT /url HTTP/1.1\r\n\r\n", + "POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 4\r\nConnection: close\r\n\r\nq=42\r\n\r\nGET / HTTP/1.1\r\n", + "PUT /url HTTP/1.1\r\nConnection : upgrade\r\nContent-Length: 4\r\nUpgrade: ws\r\n\r\nabcdefgh", + + // request/content-length + "PUT /url HTTP/1.1\r\nContent-Length: 1000000000000000000000\r\n\r\n", + "PUT /url HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 2\r\n\r\n", + "PUT /url HTTP/1.1\r\nContent-Length: 1\r\nTransfer-Encoding: identity\r\n\r\n", + "PUT /url HTTP/1.1\r\nConnection: upgrade\r\nContent-Length : 4\r\nUpgrade: ws\r\n\r\nabcdefgh", + "POST / HTTP/1.1\r\nContent-Length: 4 2\r\n\r\n", + "POST / HTTP/1.1\r\nContent-Length: 13 37\r\n\r\n", + "POST / HTTP/1.1\r\nContent-Length:\r\n\r\n", + "PUT /url HTTP/1.1\r\nContent\rLength: 003\r\n\r\nabc", + "PUT /url HTTP/1.1\r\nContent-Length: 3\r\n\rabc", + + // request/method + "PRI * HTTP/1.1\r\n\r\nSM\r\n\r\n", + + // request/sample + "GET / HTTP/1.1\rLine: 1\r\n\r\n", + "GET / HTTP/1.1\r\nLine1: abc\n\tdef\n ghi\n\t\tjkl\n mno \n\t \tqrs\nLine2: \t line2\t\nLine3:\n line3\nLine4: \n \nConnection:\n close\n\n", + + // request/transfer-encoding + "POST /chunked_w_unicorns_after_length HTTP/1.1\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n2 erfrferferf\r\naa\r\n0 rrrr\r\n\r\n", + "POST /chunked_w_unicorns_after_length HTTP/1.1\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n2;\r\naa\r\n0\r\n\r\n", + "POST /chunked_w_unicorns_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5;ilovew3=\"abc\";somuchlove=\"def; ghi\r\nhello\r\n6;blahblah;blah\r\n world\r\n0\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: pigeons\r\n\r\n", + "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: identity\r\nContent-Length: 5\r\n\r\nWorld", + "POST / HTTP/1.1\r\nHost: foo\r\nContent-Length: 10\r\nTransfer-Encoding:\r\nTransfer-Encoding:\r\nTransfer-Encoding:\r\n\r\n2\r\nAA\r\n0\r\n", + "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: chunked, deflate\r\n\r\nWorld", + "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: deflate\r\n\r\nWorld", + "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\nAccept: */*\r\nTransfer-Encoding: chunkedchunked\r\n\r\n5\r\nWorld\r\n0\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nfoo\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n3 \n \r\n\\\r\nfoo\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked abc\r\n\r\n5\r\nWorld\r\n0\r\n\r\n", + "GET / HTTP/1.1\r\nHost: a\r\nConnection: close \r\nTransfer-Encoding: chunked \r\n\r\n5\r\r;ABCD\r\n34\r\nE\r\n0\r\n\r\nGET / HTTP/1.1 \r\nHost: a\r\nContent-Length: 5\r\n\r\n0\r\n\r\n", + "GET / HTTP/1.1\r\nHost: a\r\nConnection: close \r\nTransfer-Encoding: chunked \r\n\r\n5\r\nABCDE0\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na \r\n0123456789\r\n0\r\n\r\n", + + // request/invalid + "GET /music/sweet/music ICE/1.0\r\nHost: example.com\r\n\r\n", + "GET /music/sweet/music IHTTP/1.0\r\nHost: example.com\r\n\r\n", + "PUT /music/sweet/music RTSP/1.0\r\nHost: example.com\r\n\r\n", + "ANNOUNCE /music/sweet/music HTTP/1.0\r\nHost: example.com\r\n\r\n", + "GET / HTTP/1.1\r\nFoo: 1\rBar: 2\r\n\r\n", + "POST / HTTP/1.1\r\nHost: localhost:5000\r\nx:x\nTransfer-Encoding: chunked\r\n\r\n1\r\nA\r\n0\r\n\r\n", + "GET / HTTP/1.1\r\nConnection: close\r\nHost: a\r\n\rZGET /evil: HTTP/1.1\r\nHost: a\r\n\r\n", + "GET / HTTP/1.1\r\nConnection: close\r\nHost: a\r\n\r\nZGET /evil: HTTP/1.1\r\nHost: a\r\n\r\n", + "POST / HTTP/1.1\r\nConnection: Close\r\nHost: localhost:5000\r\nx:\rTransfer-Encoding: chunked\r\n\r\n1\r\nA\r\n0\r\n\r\n", + "POST / HTTP/1.1\r\nHost: localhost:5000\r\nx:\nTransfer-Encoding: chunked\r\n\r\n1\r\nA\r\n0\r\n\r\n", + "GET / HTTP/1.1\r\nFo@: Failure\r\n\r\n", + "GET / HTTP/1.1\r\nFoo\01\test: Bar\r\n\r\n", + "GET / HTTP/1.1\r\n: Bar\r\n\r\n", + "MKCOLA / HTTP/1.1\r\n\r\n", + "GET / HTTP/1.1\r\nname\r\n : value\r\n\r\n", + "GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection\r\033\065\325eep-Alive\r\nAccept-Encoding: gzip\r\n\r\n", + "GET / HTTP/1.1\r\nHost: www.example.com\r\nX-Some-Header\r\033\065\325eep-Alive\r\nAccept-Encoding: gzip\r\n\r\n", + "GET / HTTP/1.1\r\nHost: localhost\r\nDummy: x\nContent-Length: 23\r\n\r\nGET / HTTP/1.1\r\nDummy: GET /admin HTTP/1.1\r\nHost: localhost\r\n\r\n", + "GET / HTTP/5.6", + "GET / HTTP/1.1\r\n Host: foo\r\n", + "POST / HTTP/1.1\nTransfer-Encoding: chunked\nTrailer: Baz\nFoo: abc\nBar: def\n\n1\nA\n1;abc\nB\n1;def=ghi\nC\n1;jkl=\"mno\"\nD\n0\n\nBaz: ghi\n\n", + "POST /hello HTTP/1.1\r\nHost: localhost\r\nFoo: bar\r\n Content-Length: 38\r\n\r\nGET /bye HTTP/1.1\r\nHost: localhost\r\n\r\n", + + // request/uri + "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\nHost: github.com\r\n\r\n", + "GET /foo bar/ HTTP/1.1\r\n\r\n", +}; + +const vector request_error_messages = { + // request/connection + "Data after `Connection: close`", + "Data after `Connection: close`", + "Invalid header field char", + + // request/content-length + "Content-Length overflow", + "Duplicate Content-Length", + "Transfer-Encoding can't be present with Content-Length", + "Invalid header field char", + "Invalid character in Content-Length", + "Invalid character in Content-Length", + "Empty Content-Length", + "Invalid header token", + "Expected LF after headers", + + // request/method + "Pause on PRI/Upgrade", + + // request/sample + "Expected CRLF after version", + "Missing expected CR after header value", + + // request/transfer-encoding + "Invalid character in chunk size", + "Invalid character in chunk extensions", + "Invalid character in chunk extensions quoted value", + "Request has invalid `Transfer-Encoding`", + "Content-Length can't be present with Transfer-Encoding", + "Transfer-Encoding can't be present with Content-Length", + "Invalid `Transfer-Encoding` header value", + "Invalid `Transfer-Encoding` header value", + "Request has invalid `Transfer-Encoding`", + "Invalid character in chunk size", + "Invalid character in chunk size", + "Request has invalid `Transfer-Encoding`", + "Expected LF after chunk size", + "Expected LF after chunk data", + "Invalid character in chunk size", + + // request/invalid + "Expected SOURCE method for ICE/x.x request", + "Expected HTTP/, RTSP/ or ICE/", + "Invalid method for RTSP/x.x request", + "Invalid method for HTTP/x.x request", + "Missing expected LF after header value", + "Missing expected CR after header value", + "Expected LF after headers", + "Data after `Connection: close`", + "Expected LF after CR", + "Invalid header value char", + "Invalid header token", + "Invalid header token", + "Invalid header token", + "Expected space after method", + "Invalid header token", + "Invalid header token", + "Invalid header token", + "Missing expected CR after header value", + "Invalid HTTP version", + "Unexpected space after start line", + "Expected CRLF after version", + "Unexpected whitespace after header value", + + // request/uri + "Invalid char in url path", + "Expected HTTP/, RTSP/ or ICE/", +}; + +const vector response_error_protocols = { + // response/connection + "HTTP/1.1 204 No content\r\nConnection: close\r\n\r\nHTTP/1.1 200 OK", + "HTTP/1.1 200 No content\r\nContent-Length: 5\r\nConnection: close\r\n\r\n2ad731e3-4dcd-4f70-b871-0ad284b29ffc", + + // response/invalid + "HTP/1.1 200 OK\r\n\r\n", + "HTTP/01.1 200 OK\r\n\r\n", + "HTTP/11.1 200 OK\r\n\r\n", + "HTTP/1.01 200 OK\r\n\r\n", + "HTTP/1.1\t200 OK\r\n\r\n", + "\rHTTP/1.1\t200 OK\r\n\r\n", + "HTTP/1.1 200 OK\r\nFoo: 1\rBar: 2\r\n\r\n", + "HTTP/5.6 200 OK\r\n\r\n", + "HTTP/1.1 200 OK\r\n Host: foo\r\n", + "HTTP/1.1 200 OK\r\n\r\n", + "HTTP/1.1 2 OK\r\n\r\n", + "HTTP/1.1 200 OK\nContent-Length: 0\n\n", + "HTTP/1.1 200 OK\nFoo: abc\nBar: def\n\nBODY\n", + + // response/sample + "HTTPER/1.1 200 OK\r\n\r\n", + "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/", + "HTTP/1.1 200 OK\r\nServer: Microsoft-IIS/6.0\r\nX-Powered-By: ASP.NET\r\nen-US Content-Type: text/xml\r\nContent-Type: text/xml\r\nContent-Length: 16\r\nDate: Fri, 23 Jul 2010 18:45:38 GMT\r\nConnection: keep-alive\r\n\r\nhello", + + // response/transfer-encoding + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n1C\r\nand this is the second one\r\n0 \r\n\r\n", + "HTTP/1.1 200 OK\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n2 erfrferferf\r\naa\r\n0 rrrr\r\n\r\n", + "HTTP/1.1 200 OK\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n2;\r\naa\r\n0\r\n\r\n", + "HTTP/1.1 200 OK\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n5;ilovew3=\"abc\";somuchlove=\"def; ghi\r\nhello\r\n6;blahblah;blah\r\n world\r\n0\r\n", +}; + +const vector response_error_messages = { + // response/connection + "Data after `Connection: close`", + "Data after `Connection: close`", + + // response/invalid + "Expected HTTP/, RTSP/ or ICE/", + "Expected dot", + "Expected dot", + "Expected space after version", + "Expected space after version", + "Expected space after version", + "Missing expected LF after header value", + "Invalid HTTP version", + "Unexpected space after start line", + "Invalid status code", + "Invalid status code", + "Missing expected CR after response line", + "Missing expected CR after response line", + + // response/sample + "Expected HTTP/, RTSP/ or ICE/", + "Missing expected CR after response line", + "Invalid header token", + + // response/transfer-encoding + "Invalid character in chunk size", + "Invalid character in chunk size", + "Invalid character in chunk extensions", + "Invalid character in chunk extensions quoted value", +}; +// clang-format on + +TEST(http_parser, request_error_case) { + ASSERT_TRUE(request_error_protocols.size() == request_error_messages.size()); + llhttp_t *parser = swoole_http_parser_create(HTTP_REQUEST); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + for (size_t i = 0; i < request_error_protocols.size(); ++i) { + string error_protocol = request_error_protocols[i]; + swoole_llhttp_parser_execute(parser, &http_parser_settings, error_protocol.c_str(), error_protocol.length()); + ASSERT_STREQ(llhttp_get_error_reason(parser), request_error_messages[i].c_str()); + ASSERT_NE(llhttp_get_errno(parser), HPE_OK); + llhttp_reset(parser); + } +} + +TEST(http_parser, response_error_case) { + ASSERT_TRUE(response_error_protocols.size() == response_error_messages.size()); + llhttp_t *parser = swoole_http_parser_create(HTTP_RESPONSE); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + for (size_t i = 0; i < response_error_protocols.size(); ++i) { + string error_protocol = response_error_protocols[i]; + swoole_llhttp_parser_execute(parser, &http_parser_settings, error_protocol.c_str(), error_protocol.length()); + ASSERT_STREQ(llhttp_get_error_reason(parser), response_error_messages[i].c_str()); + ASSERT_NE(llhttp_get_errno(parser), HPE_OK); + llhttp_reset(parser); + } +} + +// clang-format off +const vector request_success_case = { + "PUT /url HTTP/1.1\r\nConnection: keep-alive\r\n\r\n", + "PUT /url HTTP/1.1\r\nConnection: keep-alive\r\n\r\nPUT /url HTTP/1.1\r\nConnection: keep-alive\r\n\r\n", + "PUT /url HTTP/1.1\r\nConnection: close\r\n\r\n", + "PUT /url HTTP/1.1\r\nConnection: close, token, upgrade, token, keep-alive\r\n\r\n", + "GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive, upgrade\r\nUpgrade: WebSocket\r\n\r\nHot diggity dogg", + "PUT /url HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: ws\r\n\r\n", + "PUT /url HTTP/1.1\r\nConnection: upgrade\r\nContent-Length: 4\r\nUpgrade: ws\r\n\r\nabcdefgh", + "GET /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nSec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\nSec-WebSocket-Protocol: sample\r\nUpgrade: WebSocket\r\nSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\nOrigin: http://example.com\r\n\r\nHot diggity dogg", + "POST /demo HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nUpgrade: HTTP/2.0\r\nContent-Length: 15\r\n\r\nsweet post body\\Hot diggity dogg", + + "PUT /url HTTP/1.1\r\nContent-Length: 003\r\n\r\nabc", + "PUT /url HTTP/1.1\r\nContent-Length: 003\r\nOhai: world\r\n\r\nabc", + "GET /get_funky_content_length_body_hello HTTP/1.0\r\nconTENT-Length: 5\r\n\r\nHELLO", + "POST / HTTP/1.1\r\nContent-Length: 42 \r\n\r\n", + "REPORT /test HTTP/1.1\r\n\r\n", + "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\nsome data\nand yet even more data", + "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\n\r\n", + "CONNECT foo.bar.com:443 HTTP/1.0\r\nUser-agent: Mozilla/1.1N\r\nProxy-authorization: basic aGVsbG86d29ybGQ=\r\nContent-Length: 10\r\n\r\nblarfcicle\"", + "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nST: \"ssdp:all\"\r\n\r\n", + "PATCH /file.txt HTTP/1.1\r\nHost: www.example.com\r\nContent-Type: application/example\r\nIf-Match: \"e0023aa4e\"\r\nContent-Length: 10\r\n\r\ncccccccccc", + "PURGE /file.txt HTTP/1.1\r\nHost: www.example.com\r\n\r\n", + "SEARCH / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", + "LINK /images/my_dog.jpg HTTP/1.1\r\nHost: example.com\r\nLink: ; rel=\"tag\"\r\nLink: ; rel=\"tag\"\r\n\r\n", + "UNLINK /images/my_dog.jpg HTTP/1.1\r\nHost: example.com\r\nLink: ; rel=\"tag\"\r\n\r\n", + "SOURCE /music/sweet/music HTTP/1.1\r\nHost: example.com\r\n\r\n", + "SOURCE /music/sweet/music ICE/1.0\r\nHost: example.com\r\n\r\n", + "OPTIONS /music/sweet/music RTSP/1.0\r\nHost: example.com\r\n\r\n", + "ANNOUNCE /music/sweet/music RTSP/1.0\r\nHost: example.com\r\n\r\n", + "QUERY /contacts HTTP/1.1\r\nHost: example.org\r\nContent-Type: example/query\r\nAccept: text/csv\r\nContent-Length: 41\r\n\r\nselect surname, givenname, email limit 10", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc", + "PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na\r\n0123456789\r\n0\r\n\r\n", + "PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na;foo=bar\r\n0123456789\r\n0\r\n\r\n", + "PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na;foo=bar\r\n0123456789\r\n0\r\n\r\n", + "PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na\r\n0123456789\r\n0\r\n\r\n", + + "POST /aaa HTTP/1.1\r\nContent-Length: 3\r\n\r\nAAA\r\nPUT /bbb HTTP/1.1\r\nContent-Length: 4\r\n\r\nBBBB\r\nPATCH /ccc HTTP/1.1\r\nContent-Length: 5\r\n\r\nCCCC", + "OPTIONS /url HTTP/1.1\r\nHeader1: Value1\r\nHeader2:\t Value2\r\n\r\n", + "HEAD /url HTTP/1.1\r\n\r\n", + "GET /test HTTP/1.1\r\nUser-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\nHost: 0.0.0.0=5000\r\nAccept: */*\r\n\r\n", + "GET /favicon.ico HTTP/1.1\r\nHost: 0.0.0.0=5000\r\nUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\n\r\n", + "GET /dumbpack HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n", + "GET /get_no_headers_no_body/world HTTP/1.1\r\n\r\n", + "GET /get_one_header_no_body HTTP/1.1\r\nAccept: */*\r\n\r\n", + "GET /test HTTP/1.0\r\nHost: 0.0.0.0:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n", + "\r\nGET /test HTTP/1.1\r\n\r\n", + "GET /\r\n\r\n", + "\r\nGET /url HTTP/1.1\r\nHeader1: Value1\r\n\r\n", + "GET / HTTP/1.1\r\nTest: Düsseldorf\r\n\r\n", + "OPTIONS /url HTTP/1.1\r\nHeader1: Value1\r\nHeader2: \xffValue2\r\n\r\n", + "GET / HTTP/1.1\r\nX-SSL-Nonsense: -----BEGIN CERTIFICATE-----\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\tRA==\t-----END CERTIFICATE-----\r\n\r\n", + + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\na\r\n0123456789\r\n0\r\n\r\n", + "PUT /url HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nA\r\n0123456789\r\n0\r\n\r\n", + "POST /post_chunked_all_your_base HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1e\r\nall your base are belong to us\r\n0\r\n\r\n", + "POST /two_chunks_mult_zero_end HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n000\r\n\r\n", + "POST /chunked_w_trailing_headers HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n6\r\n world\r\n0\r\nVary: *\r\nContent-Type: text/plain\r\n\r\n", + "POST /chunked_w_unicorns_after_length HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5;ilovew3;somuchlove=aretheseparametersfor;another=withvalue\r\nhello\r\n6;blahblah;blah\r\n world\r\n0\r\n\r\n", + + "GET /with_\"lovely\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n", + "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n", + "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\r\n", + "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\r\n", + "GET /test.cgi?query=| HTTP/1.1\r\n\r\n", + "GET http://hypnotoad.org:1234 HTTP/1.1\r\n\r\n", + "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n", + "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n\r\n" +}; +// clang-format on + +TEST(http_parser, request_success_case) { + llhttp_t *parser = swoole_http_parser_create(HTTP_REQUEST); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + HttpContext *ctx = nullptr; + for (size_t i = 0; i < request_success_case.size(); ++i) { + string success_protocol = request_success_case[i]; + swoole_llhttp_parser_execute( + parser, &http_parser_settings, success_protocol.c_str(), success_protocol.length()); + ASSERT_EQ(llhttp_get_errno(parser), HPE_OK); + + ctx = static_cast(parser->data); + ASSERT_EQ(ctx->completed, 1); + llhttp_reset(parser); + } +} + +// clang-format off +const vector response_success_case = { + "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world", + "HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\nHTTP/1.0 200 OK", + "HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\nHTTP/1.0 200 OK", + "HTTP/1.1 200 OK\r\n\r\nHTTP/1.1 200 OK", + "HTTP/1.1 204 No content\r\n\r\nHTTP/1.1 200 OK", + "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: h2c\r\nContent-Length: 4\r\n\r\nbody\\\r\nproto", + "HTTP/1.1 101 Switching Protocols\r\nConnection: upgrade\r\nUpgrade: h2c\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nbo\r\n2\r\ndy\r\n0\r\n\r\nproto", + "HTTP/1.1 200 OK\r\nConnection: upgrade\r\nUpgrade: h2c\r\n\r\nbody", + "HTTP/1.1 200 OK\r\nConnection: upgrade\r\nUpgrade: h2c\r\nContent-Length: 4\r\n\r\nbody", + "HTTP/1.1 200 OK\r\nConnection: upgrade\r\nUpgrade: h2c\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nbo\r\n2\r\ndy\r\n0\r\n\r\n", + "HTTP/1.1 304 Not Modified\r\nContent-Length: 10\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello", + "HTTP/1.1 304 Not Modified\r\nTransfer-Encoding: chunked\r\n\r\nHTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", + "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 404 Not Found\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 14\r\nDate: Fri, 15 Sep 2023 19:47:23 GMT\r\nServer: Python/3.10 aiohttp/4.0.0a2.dev0\r\n\r\n404: Not Found", + "HTTP/1.1 103 Early Hints\r\nLink: ; rel=preload; as=style\r\n\r\nHTTP/1.1 200 OK\r\nDate: Wed, 13 Sep 2023 11:09:41 GMT\r\nConnection: keep-alive\r\nKeep-Alive: timeout=5\r\nContent-Length: 17\r\n\r\nresponse content", + + "HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n\n\n \n \n SOAP-ENV:Client\n Client Error\n \n \n", + "HTTP/1.1 200 OK\r\nContent-Length-X: 0\r\nTransfer-Encoding: chunked\r\n\r\n2\r\nOK\r\n0\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 123\r\n\r\nHTTP/1.1 200 OK\r\nContent-Length: 456\r\n\r\n", + + "HTTP/1.1 200 OK\r\n\r\n", + + "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nabc", + "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\na\r\n0123456789\r\n0\r\n\r\n", + "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\na;foo=bar\r\n0123456789\r\n0\r\n\r\n", + + "HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nAAA", + "HTTP/1.1 201 Created\r\nContent-Length: 4\r\n\r\nBBBB", + "HTTP/1.1 202 Accepted\r\nContent-Length: 5\r\n\r\nCCCC", + + "HTTP/1.1 200 OK\r\nHeader1: Value1\r\nHeader2: Value2\r\nContent-Length: 0\r\n\r\n", + "RTSP/1.1 200 OK\r\n\r\n", + "ICE/1.1 200 OK\r\n\r\n", + "HTTP/1.1 200 OK\r\n\r\n", + "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219\r\n\r\n\n301 Moved\n

301 Moved

\nThe document has moved\nhere.\r\n", + "HTTP/1.1 301 MovedPermanently\r\nDate: Wed, 15 May 2013 17:06:33 GMT\r\nServer: Server\r\nx-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\np3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\nx-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\nLocation: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\nVary: Accept-Encoding,User-Agent\r\nContent-Type: text/html; charset=ISO-8859-1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\n\n\r\n0\r\n\r\n", + "HTTP/1.1 404 Not Found\r\n\r\n", + "HTTP/1.1 301\r\n\r\n", + "HTTP/1.1 200 \r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\nthese headers are from http://news.ycombinator.com/", + "HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n", + "HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n", + "HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n", + "HTTP/0.9 200 OK\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world", + "HTTP/1.1 200 OK\r\nHeader1: Value1\r\nHeader2: Value2\r\nContent-Length: 0\r\n\r\n", + + "HTTP/1.1 200 OK\r\nAccept: */*\r\nTransfer-Encoding: chunked, deflate\r\n\r\nWorld", + "HTTP/1.1 200 OK\r\nAccept: */*\r\nTransfer-Encoding: chunked\r\nTransfer-Encoding: identity\r\n\r\nWorld", + "HTTP/1.1 200 OK\r\nAccept: */*\r\nTransfer-Encoding: chunkedchunked\r\n\r\n2\r\nOK\r\n0\r\n\r\n", + "HTTP/1.1 200 OK\r\nHost: localhost\r\nTransfer-encoding: chunked\r\n\r\n5;ilovew3;somuchlove=aretheseparametersfor\r\nhello\r\n6;blahblah;blah\r\n world\r\n0\r\n\r\n", + "HTTP/1.1 200 OK\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n5;ilovew3=\"I love; extensions\";somuchlove=\"aretheseparametersfor\";blah;foo=bar\r\nhello\r\n6;blahblah;blah\r\n world\r\n0\r\n", +}; + +TEST(http_parser, response_success_case) { + llhttp_t *parser = swoole_http_parser_create(HTTP_RESPONSE); + ON_SCOPE_EXIT { + swoole_http_destroy_context(parser); + }; + + HttpContext *ctx = nullptr; + for (size_t i = 0; i < response_success_case.size(); ++i) { + string success_protocol = response_success_case[i]; + swoole_llhttp_parser_execute(parser, &http_parser_settings, success_protocol.c_str(), success_protocol.length()); + ASSERT_EQ(llhttp_get_errno(parser), HPE_OK); + + ctx = static_cast(parser->data); + ASSERT_EQ(ctx->completed, 1); + llhttp_reset(parser); + } +} +// clang-format on diff --git a/core-tests/src/server/http_server.cpp b/core-tests/src/server/http_server.cpp new file mode 100644 index 0000000000..1dde4b50ef --- /dev/null +++ b/core-tests/src/server/http_server.cpp @@ -0,0 +1,1967 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "httplib_client.h" +#include "llhttp.h" +#include "swoole_server.h" +#include "swoole_file.h" +#include "swoole_http.h" +#include "swoole_http2.h" +#include "swoole_util.h" + +#include +#include +#include +#include + +using namespace swoole; +using namespace std; +using http_server::Context; +using network::Client; +using network::SyncClient; +using swoole::http_server::parse_multipart_boundary; + +struct http_context { + unordered_map headers; + unordered_map response_headers; + string url; + string current_key; + Server *server; + int fd; + bool completed; + + void setHeader(string key, string value) { + response_headers[key] = value; + } + + void response(enum swHttpStatusCode code, string body) { + response_headers["Content-Length"] = to_string(body.length()); + response(code); + server->send(fd, body.c_str(), body.length()); + } + + void response(int code) { + String *buf = make_string(1024); + buf->length = sw_snprintf(buf->str, buf->size, "HTTP/1.1 %s\r\n", http_server::get_status_message(code)); + for (auto &kv : response_headers) { + buf->append(kv.first.c_str(), kv.first.length()); + buf->append(SW_STRL(": ")); + buf->append(kv.second.c_str(), kv.second.length()); + buf->append(SW_STRL("\r\n")); + } + buf->append(SW_STRL("\r\n")); + server->send(fd, buf->str, buf->length); + delete buf; + } + + void dump_headers() { + for (auto kv : headers) { + std::cout << kv.first << ": " << kv.second << "\n"; + } + } + + static std::string base64Encode(const unsigned char *input, int length) { + BIO *bmem = nullptr; + BIO *b64 = nullptr; + BUF_MEM *bptr = nullptr; + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + + BIO_write(b64, input, length); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + + std::string encoded(bptr->data, bptr->length); + BIO_free_all(b64); + + return encoded; + } + + std::string createWebSocketAccept() { + std::string keyWithMagic = headers["Sec-WebSocket-Key"] + SW_WEBSOCKET_GUID; + + unsigned char sha1Result[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(keyWithMagic.c_str()), keyWithMagic.length(), sha1Result); + + return base64Encode(sha1Result, SHA_DIGEST_LENGTH); + } +}; + +static int handle_on_message_complete(llhttp_t *parser) { + http_context *ctx = reinterpret_cast(parser->data); + ctx->completed = true; + return 0; +} + +static int handle_on_header_field(llhttp_t *parser, const char *at, size_t length) { + http_context *ctx = reinterpret_cast(parser->data); + ctx->current_key = string(at, length); + return 0; +} + +static int handle_on_header_value(llhttp_t *parser, const char *at, size_t length) { + http_context *ctx = reinterpret_cast(parser->data); + ctx->headers[ctx->current_key] = string(at, length); + return 0; +} + +static int handle_on_url(llhttp_t *parser, const char *at, size_t length) { + http_context *ctx = reinterpret_cast(parser->data); + ctx->url = std::string(at, length); + return 0; +} + +static void test_base_server(function fn) { + thread child_thread; + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + serv.enable_reuse_port = true; + serv.heartbeat_check_interval = 1; + serv.private_data_2 = (void *) &fn; + + test::counter_init(); + + serv.enable_static_handler = true; + ASSERT_TRUE(serv.set_document_root(test::get_root_path())); + + serv.add_static_handler_location("/examples"); + serv.add_http_compression_type("text/html"); + + sw_logger()->set_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + port->open_http_protocol = true; + port->open_websocket_protocol = true; + + serv.create(); + + serv.onWorkerStart = [&child_thread](Server *serv, Worker *worker) { + function fn = *(function *) serv->private_data_2; + child_thread = thread(fn, serv); + }; + + serv.onClose = [](Server *serv, DataHead *info) -> void { + auto session_id = info->fd; + auto conn = serv->get_connection_by_session_id(session_id); + if (conn->close_actively) { + EXPECT_EQ(info->reactor_id, -1); + } else { + EXPECT_GE(info->reactor_id, 0); + } + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + auto session_id = req->info.fd; + auto conn = serv->get_connection_by_session_id(session_id); + + test::counter_incr(0); + + if (conn->websocket_status == websocket::STATUS_ACTIVE) { + sw_tg_buffer()->clear(); + uchar flags = 0; + uchar opcode = 0; + websocket::parse_ext_flags(req->info.ext_flags, &opcode, &flags); + + if (opcode == websocket::OPCODE_PING) { + websocket::encode( + sw_tg_buffer(), req->data, req->info.len, websocket::OPCODE_PONG, websocket::FLAG_FIN); + serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length); + } else if (opcode == websocket::OPCODE_CLOSE) { + // pass + } else { + std::string resp = "Swoole: " + string(req->data, req->info.len); + websocket::encode( + sw_tg_buffer(), resp.c_str(), resp.length(), websocket::OPCODE_TEXT, websocket::FLAG_FIN); + serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length); + } + + return SW_OK; + } + + llhttp_t parser = {}; + llhttp_settings_t settings = {}; + llhttp_init(&parser, HTTP_REQUEST, &settings); + + http_context ctx = {}; + parser.data = &ctx; + ctx.server = serv; + ctx.fd = session_id; + + settings.on_url = handle_on_url; + settings.on_header_field = handle_on_header_field; + settings.on_header_value = handle_on_header_value; + settings.on_message_complete = handle_on_message_complete; + + enum llhttp_errno err = llhttp_execute(&parser, req->data, req->info.len); + + if (err == HPE_PAUSED_UPGRADE) { + ctx.setHeader("Connection", "Upgrade"); + ctx.setHeader("Sec-WebSocket-Accept", ctx.createWebSocketAccept()); + ctx.setHeader("Sec-WebSocket-Version", "13"); + ctx.setHeader("Upgrade", "websocket"); + ctx.setHeader("Content-Length", "0"); + + ctx.response(SW_HTTP_SWITCHING_PROTOCOLS); + + conn->websocket_status = websocket::STATUS_ACTIVE; + + if (ctx.url == "/ws/close") { + swoole_timer_after(200, [serv, session_id](Timer *, TimerNode *) { + sw_tg_buffer()->clear(); + websocket::pack_close_frame( + sw_tg_buffer(), websocket::CLOSE_POLICY_ERROR, SW_STRL("swoole close"), 0); + serv->send(session_id, sw_tg_buffer()->str, sw_tg_buffer()->length); + }); + } + + return SW_OK; + } + + if (err != HPE_OK) { + fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); + return SW_ERR; + } + EXPECT_EQ(err, HPE_OK); + + if (ctx.url == "/just/get/file") { + std::string filename = test::get_root_path() + "/examples/test.jpg"; + serv->sendfile(session_id, filename.c_str(), filename.length(), 0, 0); + } else { + ctx.response(SW_HTTP_OK, "hello world"); + } + + EXPECT_EQ(ctx.headers["User-Agent"], httplib::USER_AGENT); + + return SW_OK; + }; + + serv.start(); + child_thread.join(); +} + +static Server *test_http_server(Server::DispatchMode dispatch_mode = Server::DISPATCH_FDMOD, + bool ssl = false, + int worker_num = 2, + Server::Mode mode = Server::MODE_PROCESS) { + auto server = new Server(mode); + server->user_ = std::string("root"); + server->group_ = std::string("root"); + server->chroot_ = std::string("/"); + server->worker_num = worker_num; + server->dispatch_mode = dispatch_mode; + server->open_cpu_affinity = true; + sw_logger()->set_level(SW_LOG_WARNING); + + ListenPort *port = ssl ? server->add_port((enum swSocketType)(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0) + : server->add_port(SW_SOCK_TCP, TEST_HOST, 0); + + port->open_http_protocol = true; + port->open_websocket_protocol = true; + port->open_tcp_keepalive = true; + port->tcp_fastopen = true; + port->tcp_defer_accept = true; + + server->enable_static_handler = true; + server->set_document_root(test::get_root_path()); + server->add_static_handler_location("/examples"); + + server->create(); + + server->onStart = [](Server *serv) { + // printf("onStart\n"); + }; + + server->onClose = [](Server *serv, DataHead *info) -> void { + auto session_id = info->fd; + auto conn = serv->get_connection_by_session_id(session_id); + if (conn->close_actively) { + ASSERT_EQ(info->reactor_id, -1); + } else { + ASSERT_GE(info->reactor_id, 0); + } + // printf("onClose\n"); + }; + + server->onConnect = [](Server *serv, DataHead *info) -> void { + // printf("onConnect\n"); + }; + + server->onReceive = [&](Server *serv, RecvData *req) -> int { + auto session_id = req->info.fd; + auto conn = serv->get_connection_by_session_id(session_id); + + EXPECT_LE(serv->get_idle_worker_num(), serv->worker_num); + EXPECT_TRUE(serv->is_healthy_connection(microtime(), conn)); + + llhttp_t parser = {}; + llhttp_settings_t settings = {}; + llhttp_init(&parser, HTTP_REQUEST, &settings); + + http_context ctx = {}; + parser.data = &ctx; + ctx.server = serv; + ctx.fd = session_id; + + settings.on_url = handle_on_url; + settings.on_header_field = handle_on_header_field; + settings.on_header_value = handle_on_header_value; + settings.on_message_complete = handle_on_message_complete; + + llhttp_errno err = llhttp_execute(&parser, req->data, req->info.len); + if (err != HPE_OK) { + fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); + return SW_ERR; + } + + if (ctx.url == "/overflow") { + conn->overflow = 1; + } + + if (ctx.url == "/pause") { + serv->feedback(conn, SW_SERVER_EVENT_PAUSE_RECV); + } + + EXPECT_EQ(err, HPE_OK); + ctx.response(SW_HTTP_OK, "hello world"); + + return SW_OK; + }; + + return server; +} + +static Server *test_proxy_server() { + Server *server = new Server(Server::MODE_BASE); + server->worker_num = 1; + + ListenPort *port = server->add_port(SW_SOCK_TCP, TEST_HOST, 0); + port->kernel_socket_send_buffer_size = INT_MAX; + port->kernel_socket_recv_buffer_size = INT_MAX; + port->open_tcp_nodelay = true; + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + server->enable_static_handler = true; + server->set_document_root(test::get_root_path()); + server->add_static_handler_location("/examples"); + + server->get_primary_port()->set_package_max_length(64 * 1024); + port->open_http_protocol = 1; + port->open_websocket_protocol = 1; + + server->create(); + + server->onReceive = [&](Server *server, RecvData *req) -> int { + auto session_id = req->info.fd; + + swoole_set_worker_id(server->worker_num); + + llhttp_t parser = {}; + llhttp_settings_t settings = {}; + llhttp_init(&parser, HTTP_REQUEST, &settings); + + http_context ctx = {}; + parser.data = &ctx; + ctx.server = server; + ctx.fd = session_id; + + settings.on_url = handle_on_url; + settings.on_header_field = handle_on_header_field; + settings.on_header_value = handle_on_header_value; + settings.on_message_complete = handle_on_message_complete; + + enum llhttp_errno err = llhttp_execute(&parser, req->data, req->info.len); + + if (err != HPE_OK) { + fprintf(stderr, "Parse error: %s %s\n", llhttp_errno_name(err), parser.reason); + return SW_ERR; + } + + if (ctx.url == "/just/get/file") { + std::string filename = test::get_root_path() + "/examples/test.jpg"; + server->sendfile(session_id, filename.c_str(), filename.length(), 0, 0); + } else { + ctx.response(SW_HTTP_OK, "hello world"); + } + + EXPECT_EQ(err, HPE_OK); + EXPECT_EQ(ctx.headers["User-Agent"], httplib::USER_AGENT); + return SW_OK; + }; + + return server; +} + +TEST(http_server, get) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + auto port = serv->get_primary_port(); + + httplib::Client cli(TEST_HOST, port->port); + auto resp = cli.Get("/index.html"); + EXPECT_EQ(resp->status, 200); + EXPECT_EQ(resp->body, string("hello world")); + + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, reset_connection) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + const auto port = serv->get_primary_port(); + Client c(SW_SOCK_TCP, false); + EXPECT_EQ(c.connect(TEST_HOST, port->port), 0); + c.send(SW_STRL("GET /index.html HTTP/1.1")); + usleep(10000); + c.close(); + + kill(getpid(), SIGTERM); + }); + + ASSERT_EQ(test::counter_get(0), 0); +} + +TEST(http_server, heartbeat_check_interval) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + auto port = serv->get_primary_port(); + + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/index.html"); + EXPECT_EQ(resp->status, 200); + EXPECT_EQ(resp->body, string("hello world")); + sleep(3); + + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, idle_time) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + auto port = serv->get_primary_port(); + port->max_idle_time = 1; + + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/index.html"); + EXPECT_EQ(resp->status, 200); + + sleep(2); + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, post) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + auto port = serv->get_primary_port(); + + httplib::Client cli(TEST_HOST, port->port); + httplib::Params params; + params.emplace("name", "john"); + params.emplace("note", "coder"); + auto resp = cli.Post("/index.html", params); + EXPECT_EQ(resp->status, 200); + EXPECT_EQ(resp->body, string("hello world")); + + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, static_get) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + auto port = serv->get_primary_port(); + + httplib::Client cli(TEST_HOST, port->port); + auto resp = cli.Get("/examples/test.jpg"); + EXPECT_EQ(resp->status, 200); + + string file = test::get_root_path() + "/examples/test.jpg"; + File fp(file, O_RDONLY); + EXPECT_TRUE(fp.ready()); + + auto str = fp.read_content(); + + EXPECT_EQ(resp->body, str->to_std_string()); + + resp = cli.Get("/just/get/file"); + EXPECT_EQ(resp, nullptr); + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, static_files) { + test_base_server([](Server *serv) { + serv->http_autoindex = true; + serv->add_static_handler_location(""); + + swoole_signal_block_all(); + auto port = serv->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + + auto resp = cli.Get("/"); + EXPECT_EQ(resp->status, 200); + std::string::size_type postion = resp->body.find("Index of"); + EXPECT_TRUE(postion != std::string::npos); + + // directory not exists + resp = cli.Get("/test/../"); + EXPECT_EQ(resp->status, 404); + + // must be document_root + resp = cli.Get("//tests/../"); + EXPECT_EQ(resp->status, 404); + + resp = cli.Get("/tests/../README.md"); + EXPECT_EQ(resp->status, 200); + + // file not exists + resp = cli.Get("/not-exists.jpg"); + EXPECT_EQ(resp->status, 404); + + // try again + serv->add_static_handler_index_files("README.md"); + resp = cli.Get("/"); + postion = resp->body.find("

"); + EXPECT_TRUE(postion != std::string::npos); + + kill(getpid(), SIGTERM); + }); +} + +static void request_with_header(const char *date_format, httplib::Client *cli) { + char temp[128] = {0}; + time_t raw_time = time(NULL) + 7 * 24 * 60 * 60; + tm *time_info = gmtime(&raw_time); + + strftime(temp, sizeof(temp), date_format, time_info); + httplib::Headers headers = {{"If-Modified-Since", temp}}; + auto resp = cli->Get("/", headers); + EXPECT_EQ(resp, nullptr); +} + +TEST(http_server, not_modify) { + test_base_server([](Server *serv) { + serv->http_autoindex = true; + serv->add_static_handler_location(""); + + swoole_signal_block_all(); + auto port = serv->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + + serv->add_static_handler_index_files("swoole-logo.svg"); + auto resp = cli.Get("/"); + EXPECT_EQ(resp->status, 200); + + // 304 not modified + cli.set_read_timeout(0, 100); + request_with_header(SW_HTTP_RFC1123_DATE_GMT, &cli); + request_with_header(SW_HTTP_RFC1123_DATE_UTC, &cli); + request_with_header(SW_HTTP_RFC850_DATE, &cli); + request_with_header(SW_HTTP_ASCTIME_DATE, &cli); + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, proxy_file) { + Server *server = test_proxy_server(); + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + + auto resp = cli.Get("/just/get/file"); + ASSERT_EQ(resp, nullptr); + } +} + +// need fix +TEST(http_server, proxy_response) { + Server *server = test_proxy_server(); + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + auto resp = cli.Get("/"); + ASSERT_EQ(resp, nullptr); + // ASSERT_EQ(resp->body, string("hello world")); + } +} + +static void websocket_test(int server_port, const char *data, size_t length, bool mask = false) { + httplib::Client cli(TEST_HOST, server_port); + + if (mask) { + cli.set_websocket_mask(true); + } + + httplib::Headers headers; + EXPECT_TRUE(cli.Upgrade("/websocket", headers)); + EXPECT_TRUE(cli.Push(data, length)); + + auto msg = cli.Recv(); + ASSERT_NE(msg.get(), nullptr); + EXPECT_EQ(string(msg->payload, msg->payload_length), string("Swoole: ") + string(data, length)); +} + +TEST(http_server, websocket_small) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + websocket_test(serv->get_primary_port()->get_port(), SW_STRL("hello world, swoole is best!")); + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, websocket_medium) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + String str(8192); + str.repeat("A", 1, 8192); + websocket_test(serv->get_primary_port()->get_port(), str.value(), str.get_length()); + + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, websocket_big) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + String str(128 * 1024); + str.repeat("A", 1, str.capacity() - 1); + websocket_test(serv->get_primary_port()->get_port(), str.value(), str.get_length()); + + kill(getpid(), SIGTERM); + }); +} + +TEST(http_server, websocket_mask) { + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + String str(64 * 128); + str.append_random_bytes(str.capacity(), true); + + websocket_test(serv->get_primary_port()->get_port(), str.value(), str.get_length(), true); + + kill(getpid(), SIGTERM); + }); +} + +static auto packet = "hello world\n"; + +TEST(http_server, websocket_encode) { + auto buffer = sw_tg_buffer(); + buffer->clear(); + + auto log_file = TEST_LOG_FILE; + + ASSERT_TRUE(websocket::encode( + buffer, packet, strlen(packet), websocket::OPCODE_TEXT, websocket::FLAG_FIN | websocket::FLAG_MASK)); + websocket::Frame ws; + + ASSERT_TRUE(websocket::decode(&ws, buffer->str, buffer->length)); + + FILE *fp = fopen(log_file, "a+"); + ASSERT_NE(fp, nullptr); + auto ori_fp = swoole_get_stdout_stream(); + swoole_set_stdout_stream(fp); + websocket::print_frame(&ws); + fclose(fp); + swoole_set_stdout_stream(ori_fp); + + File f(log_file, File::READ); + auto rs = f.read_content(); + + ASSERT_TRUE(rs->contains("FIN: 1,")); + ASSERT_TRUE(rs->contains("RSV1: 0,")); + ASSERT_TRUE(rs->contains("opcode: 1,")); + ASSERT_TRUE(rs->contains("payload: hello world\n")); + + f.close(); + + unlink(log_file); +} + +TEST(http_server, node_websocket_client_1) { + unlink(TEST_LOG_FILE); + + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + EXPECT_EQ(test::exec_js_script("ws_1.js", std::to_string(serv->get_primary_port()->get_port())), 0); + + kill(serv->get_master_pid(), SIGTERM); + }); + + File fp(TEST_LOG_FILE, O_RDONLY); + EXPECT_TRUE(fp.ready()); + auto str = fp.read_content(); + ASSERT_TRUE(str->contains("received: Swoole: hello world")); + ASSERT_TRUE(str->contains("the node websocket client is closed")); + + fp.close(); + unlink(TEST_LOG_FILE); +} + +TEST(http_server, node_websocket_client_2) { + unlink(TEST_LOG_FILE); + + test_base_server([](Server *serv) { + swoole_signal_block_all(); + + EXPECT_EQ(test::exec_js_script("ws_2.js", std::to_string(serv->get_primary_port()->get_port())), 0); + + kill(serv->get_master_pid(), SIGTERM); + }); + + File fp(TEST_LOG_FILE, O_RDONLY); + EXPECT_TRUE(fp.ready()); + auto str = fp.read_content(); + ASSERT_TRUE(str->contains("the node websocket client is closed, code: 1008, reason: swoole close")); + + fp.close(); + unlink(TEST_LOG_FILE); +} + +TEST(http_server, parser1) { + std::thread t; + string file = test::get_root_path() + "/core-tests/fuzz/cases/req1.bin"; + auto server = http_server::listen(":0", [](Context &ctx) { + EXPECT_EQ(ctx.form_data.size(), 3); + ctx.end("DONE"); + }); + server->worker_num = 1; + server->onWorkerStart = [&t, &file](Server *server, Worker *worker) { + t = std::thread([server, &file]() { + swoole_signal_block_all(); + File fp(file, O_RDONLY); + EXPECT_TRUE(fp.ready()); + auto str = fp.read_content(); + SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, server->get_primary_port()->port); + c.send(str->value(), str->get_length()); + char buf[1024]; + auto n = c.recv(buf, sizeof(buf)); + c.close(); + std::string resp(buf, n); + + EXPECT_TRUE(resp.find("200 OK") != resp.npos); + + kill(server->get_master_pid(), SIGTERM); + }); + }; + server->start(); + t.join(); +} + +TEST(http_server, parser2) { + std::thread t; + auto server = http_server::listen(":0", [](Context &ctx) { + EXPECT_EQ(ctx.form_data.size(), 3); + ctx.end("DONE"); + }); + server->worker_num = 1; + server->get_primary_port()->set_package_max_length(64 * 1024); + server->upload_max_filesize = 1024 * 1024; + server->onWorkerStart = [&t](Server *server, Worker *worker) { + t = std::thread([server]() { + swoole_signal_block_all(); + string file = test::get_root_path() + "/core-tests/fuzz/cases/req2.bin"; + File fp(file, O_RDONLY); + EXPECT_TRUE(fp.ready()); + auto str = fp.read_content(); + SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, server->get_primary_port()->port); + c.send(str->value(), str->get_length()); + char buf[1024]; + auto n = c.recv(buf, sizeof(buf)); + c.close(); + std::string resp(buf, n); + + EXPECT_TRUE(resp.find("200 OK") != resp.npos); + + kill(server->get_master_pid(), SIGTERM); + }); + }; + server->start(); + t.join(); +} + +TEST(http_server, upload) { + std::thread t; + auto server = http_server::listen(":0", [](Context &ctx) { + EXPECT_EQ(ctx.files.size(), 1); + ctx.setStatusCode(200); + ctx.setHeader("Connection", "close"); + ASSERT_EQ(ctx.request_path, "/upload"); + ASSERT_EQ(ctx.query_string, "test=curl"); + ctx.end(TEST_STR); + }); + server->worker_num = 1; + server->get_primary_port()->set_package_max_length(2 * 1024 * 1024); + server->onWorkerStart = [&t](Server *server, Worker *worker) { + t = std::thread([server]() { + swoole_signal_block_all(); + string jpg_path = test::get_jpg_file(); + string str_1 = "curl -s -S -H 'Transfer-Encoding: chunked' -F \"file=@" + jpg_path + "\" http://"; + string command = + str_1 + TEST_HOST + ":" + to_string(server->get_primary_port()->port) + "/upload?test=curl"; + + pid_t pid2; + int pipe = swoole_shell_exec(command.c_str(), &pid2, 0); + usleep(200000); + char buf[1024] = {}; + read(pipe, buf, sizeof(buf) - 1); + ASSERT_STREQ(buf, TEST_STR); + + kill(server->get_master_pid(), SIGTERM); + }); + }; + server->start(); + t.join(); +} + +TEST(http_server, max_request_size) { + std::thread t; + auto server = http_server::listen(":0", [](Context &ctx) { + EXPECT_EQ(ctx.files.size(), 1); + ctx.setStatusCode(200); + ctx.setHeader("Connection", "close"); + ASSERT_EQ(ctx.request_path, "/upload"); + ASSERT_EQ(ctx.query_string, "test=curl"); + ctx.end(TEST_STR); + }); + server->worker_num = 1; + server->get_primary_port()->set_package_max_length(128 * 1024); + server->onWorkerStart = [&t](Server *server, Worker *worker) { + t = std::thread([server]() { + swoole_signal_block_all(); + string jpg_path = test::get_jpg_file(); + string str_1 = "curl -i -s -S -F \"file=@" + jpg_path + "\" http://"; + string command = + str_1 + TEST_HOST + ":" + to_string(server->get_primary_port()->port) + "/upload?test=curl"; + + pid_t pid2; + int pipe = swoole_shell_exec(command.c_str(), &pid2, 0); + usleep(200000); + char buf[1024] = {}; + read(pipe, buf, sizeof(buf) - 1); + ASSERT_TRUE(strstr(buf, "413 Request Entity Too Large") != nullptr); + + kill(server->get_master_pid(), SIGTERM); + }); + }; + server->start(); + t.join(); +} + +TEST(http_server, heartbeat) { + Server *server = test_http_server(); + server->heartbeat_check_interval = 0; + auto port = server->get_primary_port(); + port->set_package_max_length(1024); + port->heartbeat_idle_time = 2; + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/"); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + sleep(10); + resp = cli.Get("/"); + ASSERT_EQ(resp, nullptr); + } +} + +TEST(http_server, overflow) { + Server *server = test_http_server(); + auto port = server->get_primary_port(); + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/"); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + resp = cli.Get("/overflow"); + ASSERT_EQ(resp, nullptr); + } +} + +TEST(http_server, process) { + Server *server = test_http_server(); + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/"); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + + resp = cli.Get("/"); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + } +} + +TEST(http_server, process1) { + Server *server = test_http_server(); + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/index.html"); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + + sleep(1); + resp = cli.Get("/examples/test.jpg"); + ASSERT_EQ(resp->status, 200); + } +} + +TEST(http_server, redundant_callback) { + Server *server = test_http_server(Server::DISPATCH_IDLE_WORKER); + server->onConnect = [](Server *serv, DataHead *info) -> int { return 0; }; + server->onClose = [](Server *serv, DataHead *info) -> int { return 0; }; + server->onBufferFull = [](Server *serv, DataHead *info) -> int { return 0; }; + server->onBufferEmpty = [](Server *serv, DataHead *info) -> int { return 0; }; + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + ASSERT_EQ(server->onConnect, nullptr); + ASSERT_EQ(server->onClose, nullptr); + ASSERT_EQ(server->onBufferFull, nullptr); + ASSERT_EQ(server->onBufferEmpty, nullptr); + exit(0); + } + + if (pid > 0) { + sleep(2); + kill(server->get_master_pid(), SIGTERM); + } +} + +TEST(http_server, pause) { + Server *server = test_http_server(); + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_keep_alive(true); + auto resp = cli.Get("/pause"); + ASSERT_NE(resp, nullptr); + ASSERT_EQ(resp->status, 200); + ASSERT_EQ(resp->body, string("hello world")); + + resp = cli.Get("/"); + ASSERT_EQ(resp, nullptr); + } +} + +TEST(http_server, sni) { + Server *server = test_http_server(Server::DISPATCH_FDMOD, true); + ListenPort *port = server->get_primary_port(); + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + auto sni_ssl_ctx = port->dup_ssl_context(); + sni_ssl_ctx->set_cert_file(test::get_ssl_dir() + "/sni_server_cs_cert.pem"); + sni_ssl_ctx->set_key_file(test::get_ssl_dir() + "/sni_server_cs_key.pem"); + port->ssl_add_sni_cert("localhost", sni_ssl_ctx); + port->set_ssl_protocols(0); + port->ssl_init(); + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + string port_num = to_string(server->get_primary_port()->get_port()); + + sleep(1); + pid_t pid2; + string command = "curl https://localhost:" + port_num + " -k -vvv --stderr /tmp/wwwsnitestcom.txt"; + swoole_shell_exec(command.c_str(), &pid2, 0); + sleep(1); + + stringstream buffer; + ifstream wwwsnitestcom; + wwwsnitestcom.open("/tmp/wwwsnitestcom.txt"); + ASSERT_TRUE(wwwsnitestcom.is_open()); + buffer << wwwsnitestcom.rdbuf(); + wwwsnitestcom.close(); + string response(buffer.str()); + ASSERT_TRUE(response.find("CN=cs.php.net") != string::npos); + + string command2 = "curl https://127.0.0.1:" + port_num + " -k -vvv --stderr /tmp/wwwsnitest2com.txt"; + swoole_shell_exec(command2.c_str(), &pid2, 0); + sleep(1); + + stringstream buffer2; + ifstream wwwsnitest2com; + wwwsnitest2com.open("/tmp/wwwsnitest2com.txt"); + ASSERT_TRUE(wwwsnitest2com.is_open()); + buffer2 << wwwsnitest2com.rdbuf(); + string response2(buffer2.str()); + wwwsnitest2com.close(); + ASSERT_TRUE(response2.find("CN=127.0.0.1") != string::npos); + } +} + +TEST(http_server, bad_request) { + Server *server = test_http_server(); + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + sleep(1); + + string str_1 = "curl -X UNKNOWN http://"; + string str_2 = ":"; + string str_3 = " -k -vvv --stderr /tmp/bad_request.txt"; + string host = TEST_HOST; + string port = to_string(server->get_primary_port()->port); + string command = str_1 + host + str_2 + port + str_3; + + pid_t pid2; + swoole_shell_exec(command.c_str(), &pid2, 0); + sleep(1); + + stringstream buffer; + ifstream bad_request; + bad_request.open("/tmp/bad_request.txt"); + ASSERT_TRUE(bad_request.is_open()); + buffer << bad_request.rdbuf(); + string response(buffer.str()); + bad_request.close(); + ASSERT_TRUE(response.find("400 Bad Request") != string::npos); + } +} + +TEST(http_server, chunked) { + Server *server = test_http_server(Server::DISPATCH_FDMOD, false, 1, Server::MODE_BASE); + + std::thread t; + server->onStart = [&](Server *_server) { + t = std::thread([server]() { + swoole_signal_block_all(); + usleep(300000); + + string jpg_path = test::get_jpg_file(); + string str_1 = "curl -s -S -H 'Transfer-Encoding: chunked' -F \"file=@" + jpg_path + "\" http://"; + string str_2 = ":"; + string host = TEST_HOST; + string port = to_string(server->get_primary_port()->port); + string command = str_1 + host + str_2 + port; + + pid_t pid2; + int pipe = swoole_shell_exec(command.c_str(), &pid2, 0); + sleep(1); + + char buf[1024] = {}; + read(pipe, buf, sizeof(buf) - 1); + ASSERT_STREQ(buf, "hello world"); + kill(server->get_master_pid(), SIGTERM); + }); + }; + + server->start(); + t.join(); + delete server; +} + +TEST(http_server, max_queued_bytes) { + Server *server = test_http_server(); + server->max_queued_bytes = 100; + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + sleep(1); + + string jpg_path = test::get_jpg_file(); + string str_1 = "curl -s -S -H 'Transfer-Encoding: chunked' -F \"file=@" + jpg_path + "\" http://"; + string command = str_1 + TEST_HOST + ":" + to_string(server->get_primary_port()->port); + + pid_t pid2; + int pipe = swoole_shell_exec(command.c_str(), &pid2, 0); + sleep(1); + + char buf[1024] = {}; + read(pipe, buf, sizeof(buf) - 1); + ASSERT_STREQ(buf, "hello world"); + } + + test::wait_all_child_processes(); +} + +TEST(http_server, dispatch_func_return_error_worker_id) { + Server *server = test_http_server(); + server->dispatch_func = [](Server *serv, Connection *conn, SendData *data) -> int { + return data->info.fd % 2 == 0 ? Server::DISPATCH_RESULT_DISCARD_PACKET + : Server::DISPATCH_RESULT_CLOSE_CONNECTION; + }; + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + }; + + if (pid > 0) { + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + sleep(1); + auto port = server->get_primary_port(); + httplib::Client cli(TEST_HOST, port->port); + cli.set_read_timeout(1, 0); + auto resp = cli.Get("/"); + ASSERT_EQ(resp, nullptr); + resp = cli.Get("/"); + ASSERT_EQ(resp, nullptr); + } +} + +TEST(http_server, client_ca) { + Server *server = test_http_server(Server::DISPATCH_FDMOD, true); + ListenPort *port = server->get_primary_port(); + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->set_ssl_verify_peer(true); + port->set_ssl_allow_self_signed(true); + port->set_ssl_cafile(test::get_ssl_dir() + "/ca.crt"); + port->ssl_init(); + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + string port_num = to_string(server->get_primary_port()->port); + + sleep(1); + pid_t pid2; + string client_cert = " --cert " + test::get_ssl_dir() + "/client.crt"; + string client_key = " --key " + test::get_ssl_dir() + "/client.key"; + string command = "curl https://127.0.0.1:" + port_num + " " + client_cert + client_key + + " -k -vvv --stderr /tmp/client_ca.txt"; + swoole_shell_exec(command.c_str(), &pid2, 0); + sleep(1); + + stringstream buffer; + ifstream client_ca; + client_ca.open("/tmp/client_ca.txt"); + ASSERT_TRUE(client_ca.is_open()); + buffer << client_ca.rdbuf(); + client_ca.close(); + string response(buffer.str()); + ASSERT_TRUE(response.find("200 OK") != response.npos); + + kill(server->get_master_pid(), SIGTERM); + } +} + +static bool request_with_if_range_header(const char *date_format, std::string port) { + struct stat file_stat; + std::string file_path = test::get_root_path() + "/docs/swoole-logo.svg"; + stat(file_path.c_str(), &file_stat); + time_t file_mtime = file_stat.st_mtim.tv_sec; + struct tm *time_info = gmtime(&file_mtime); + + char temp[128] = {0}; + strftime(temp, sizeof(temp), date_format, time_info); + + string str_1 = "curl http://"; + string host = TEST_HOST; + string str_2 = ":"; + string str_3 = "/docs/swoole-logo.svg -k -vvv --stderr /tmp/http_range.txt "; + string headers = "-H 'Range: bytes=0-500' -H 'If-Range: "; + string command = str_1 + host + str_2 + port + str_3 + headers + string(temp) + "'"; + + pid_t pid; + close(swoole_shell_exec(command.c_str(), &pid, 0)); + sleep(2); + + stringstream buffer; + ifstream http_range; + http_range.open("/tmp/http_range.txt"); + if (!http_range.is_open()) { + return false; + } + + buffer << http_range.rdbuf(); + string response(buffer.str()); + http_range.close(); + return response.find("206 Partial Content") != string::npos && response.find("Content-Length: 501") != string::npos; +} + +TEST(http_server, http_range) { + Server *server = test_http_server(); + server->http_autoindex = true; + server->add_static_handler_location("/docs"); + + pid_t pid = fork(); + + if (pid == 0) { + server->start(); + exit(0); + } + + if (pid > 0) { + sleep(1); + ON_SCOPE_EXIT { + kill(server->get_master_pid(), SIGTERM); + }; + + string port = to_string(server->get_primary_port()->port); + ASSERT_TRUE(request_with_if_range_header(SW_HTTP_RFC1123_DATE_GMT, port)); + ASSERT_TRUE(request_with_if_range_header(SW_HTTP_RFC1123_DATE_UTC, port)); + ASSERT_TRUE(request_with_if_range_header(SW_HTTP_RFC850_DATE, port)); + ASSERT_TRUE(request_with_if_range_header(SW_HTTP_ASCTIME_DATE, port)); + } +} + +static bool request_with_diff_range(std::string port, std::string range) { + string str_1 = "curl -X GET http://"; + string host = TEST_HOST; + string str_2 = ":"; + string str_3 = "/docs/swoole-logo.svg -k -vvv --stderr /tmp/http_range.txt "; + string headers = "-H 'Range: bytes=" + range; + string command = str_1 + host + str_2 + port + str_3 + headers + "'"; + + pid_t pid; + close(swoole_shell_exec(command.c_str(), &pid, 0)); + + sleep(2); + stringstream buffer; + ifstream http_range; + http_range.open("/tmp/http_range.txt"); + if (!http_range.is_open()) { + return false; + } + + buffer << http_range.rdbuf(); + string response(buffer.str()); + http_range.close(); + return response.find("206 Partial Content") != string::npos; +} + +TEST(http_server, http_range2) { + Server *server = test_http_server(); + server->add_static_handler_location("/docs"); + server->add_static_handler_index_files("swoole-logo.svg"); + + pid_t pid = test::spawn_exec([&]() { server->start(); }); + + ASSERT_TRUE(request_with_diff_range(to_string(server->get_primary_port()->port), "0-15")); + ASSERT_TRUE(request_with_diff_range(to_string(server->get_primary_port()->port), "16-31")); + ASSERT_TRUE(request_with_diff_range(to_string(server->get_primary_port()->port), "-16")); + ASSERT_TRUE(request_with_diff_range(to_string(server->get_primary_port()->port), "128-")); + ASSERT_TRUE(request_with_diff_range(to_string(server->get_primary_port()->port), "0-0,-1")); + + usleep(100000); + kill(pid, SIGTERM); + + int status; + ASSERT_EQ(waitpid(pid, &status, 0), pid); +} + +// it is always last test +TEST(http_server, abort_connection) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + auto max_sockets = SwooleG.max_sockets; + SwooleG.max_sockets = 2; + serv.set_max_connection(1); + sw_logger()->set_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + port->open_http_protocol = true; + + auto port_dgram = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port_dgram, nullptr); + + serv.create(); + + Mutex lock(true); + lock.lock(); + + thread th([&serv, port, &lock]() { + swoole_signal_block_all(); + lock.lock(); + + int n = 16; + SyncClient *clients[n]; + + SW_LOOP_N(n) { + clients[i] = new SyncClient(SW_SOCK_TCP); + clients[i]->connect(TEST_HOST, port->port, -1); + } + + httplib::Client cli(TEST_HOST, port->port); + auto resp = cli.Get("/"); + EXPECT_EQ(resp, nullptr); + + SW_LOOP_N(n) { + delete clients[i]; + } + serv.shutdown(); + }); + + serv.onStart = [&lock](Server *serv) { lock.unlock(); }; + + serv.onReceive = [&](Server *server, RecvData *req) -> int { return SW_OK; }; + serv.start(); + + SwooleG.max_sockets = max_sockets; + th.join(); +} + +TEST(http_server, EncodeDecodeBasic) { + const char *input = "Hello World!"; + size_t len = strlen(input); + + char *encoded = swoole::http_server::url_encode(input, len); + EXPECT_STREQ(encoded, "Hello%20World%21"); + + size_t decoded_len = swoole::http_server::url_decode(encoded, strlen(encoded)); + + EXPECT_EQ(decoded_len, strlen(input)); + EXPECT_STREQ(encoded, input); + + sw_free(encoded); +} + +TEST(http_server, EncodeDecodeWithSpecialChars) { + const char *input = "C++ Programming & C#"; + size_t len = strlen(input); + + char *encoded = swoole::http_server::url_encode(input, len); + EXPECT_STREQ(encoded, "C%2B%2B%20Programming%20%26%20C%23"); + + size_t decoded_len = swoole::http_server::url_decode(encoded, strlen(encoded)); + + EXPECT_EQ(decoded_len, strlen(input)); + EXPECT_STREQ(encoded, "C++ Programming & C#"); + + sw_free(encoded); +} + +TEST(http_server, get_method) { + ASSERT_EQ(swoole::http_server::get_method(SW_STRL("POST")), SW_HTTP_POST); + ASSERT_EQ(swoole::http_server::get_method(SW_STRL("post")), SW_HTTP_POST); + ASSERT_EQ(swoole::http_server::get_method(SW_STRL("OPTIONS")), SW_HTTP_OPTIONS); +} + +TEST(http_server, get_method_str) { + ASSERT_STREQ(swoole::http_server::get_method_string(SW_HTTP_POST), "POST"); + ASSERT_STREQ(swoole::http_server::get_method_string(SW_HTTP_GET), "GET"); + ASSERT_STREQ(swoole::http_server::get_method_string(SW_HTTP_OPTIONS), "OPTIONS"); +} + +TEST(http_server, has_expect_header) { + swoole::http_server::Request req{}; + req.buffer_ = sw_tg_buffer(); + + req.buffer_->append("HTTP/1.1 200 OK\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html\r\n" + "Pragma: no-cache\r\n\r\n"); + ASSERT_FALSE(req.has_expect_header()); + + req.buffer_->clear(); + req.buffer_->append("POST /submit HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 1000000\r\n" + "Expect: 100-continue\r\n\r\n"); + ASSERT_TRUE(req.has_expect_header()); +} + +TEST(http_server, get_status_message) { + size_t n = sizeof(http_server::list_of_status_code) / sizeof(int); + SW_LOOP_N(n) { + auto code = http_server::list_of_status_code[i]; + if (code == -1) { + break; + } + auto error = http_server::get_status_message(code); + auto str = String(error); + ASSERT_TRUE(str.starts_with(std::to_string(code) + " ")); + } + + auto error = swoole::http_server::get_status_message(999); + auto str = String(error); + ASSERT_TRUE(str.equals(std::to_string(999) + " Unknown Status")); +} + +static swoole::http_server::Request req; + +static void SetRequestContent(const std::string &str) { + delete req.buffer_; + req = {}; + req.buffer_ = new String(str); +}; + +static void SetRequestContent(String *str) { + delete req.buffer_; + req.buffer_ = str; +}; + +TEST(http_server, get_protocol) { + static auto request = &req; + // 测试有效的 GET 请求 + { + SetRequestContent("GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n"); + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_GET); + EXPECT_EQ(request->version, SW_HTTP_VERSION_11); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url, "/index.html"); + } + + // 超长 URL + { + auto s = new String(); + s->append("GET /home/explore/?a="); + s->append_random_bytes(swoole_rand(1024, 2048), 1); + s->append(" HTTP/1.1\r\n"); + s->append("Host: 127.0.0.1\r\n"); + s->append("Connection: keep-alive\r\n"); + s->append("Cache-Control: max-age=0\r\n\r\n"); + SetRequestContent(s); + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_GET); + EXPECT_EQ(request->version, SW_HTTP_VERSION_11); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url.substr(0, url.find('?')), "/home/explore/"); + } + + // 测试有效的 POST 请求 + { + SetRequestContent("POST /submit HTTP/1.0\r\nContent-Type: application/json\r\n\r\n{\"key\":\"value\"}"); + + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_POST); + EXPECT_EQ(request->version, SW_HTTP_VERSION_10); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url, "/submit"); + } + // 测试 PUT 方法 + { + SetRequestContent("PUT /resource HTTP/1.1\r\nContent-Type: text/plain\r\n\r\nNew content"); + + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_PUT); + EXPECT_EQ(request->version, SW_HTTP_VERSION_11); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url, "/resource"); + } + + // 测试 HTTP2 前言 + { + SetRequestContent(SW_HTTP2_PRI_STRING); + + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_PRI); + } + + // 测试无效的请求 - 太短 + { + SetRequestContent("GET /"); + + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, 0); // wait more data + } + + // 测试无效的请求 - 未知方法 + { + SetRequestContent("UNKNOWN /path HTTP/1.1\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, 1); + } + + // 测试无效的请求 - 缺少 URL + { + SetRequestContent("UPDATE HTTP/1.1\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, 1); + } + + // 测试无效的请求 - 无效的 HTTP 版本 + { + SetRequestContent("GET /index.html HTTP/2.0\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, 1); + } + + // 测试无效的请求 - 缺少 HTTP 版本 + { + SetRequestContent("UNSUBSCRIBE /index.html\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, 1); + } + + // 测试复杂 URL + { + SetRequestContent("GET /search?q=test&page=1#results HTTP/1.1\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_GET); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url, "/search?q=test&page=1#results"); + } + + // 测试多余空格 + { + SetRequestContent("GET /index.html HTTP/1.1\r\n"); + + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, SW_HTTP_GET); + EXPECT_EQ(request->version, SW_HTTP_VERSION_11); + + std::string url(request->buffer_->str + request->url_offset_, request->url_length_); + EXPECT_EQ(url, "/index.html"); + } +} + +TEST(http_server, all_method) { + static auto request = &req; + SW_LOOP_N(SW_HTTP_PRI + 4) { + auto str = http_server::get_method_string(i); + if (i == 0 || i > SW_HTTP_PRI) { + ASSERT_EQ(str, nullptr); + } else { + SetRequestContent(str + std::string(" / HTTP/1.1\r\nHost: example.com\r\n\r\n")); + if (i == SW_HTTP_PRI) { + ASSERT_EQ(request->get_protocol(), SW_ERR); + EXPECT_EQ(request->excepted, true); + } else { + ASSERT_EQ(request->get_protocol(), SW_OK); + EXPECT_EQ(request->method, i); + } + } + } +} + +TEST(http_server, parse_multipart_boundary) { + char *boundary_str; + int boundary_len; + { + const char *input = "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = 30; // 从 "boundary=" 之前开始 + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试带引号的 boundary + { + const char *input = "Content-Type: multipart/form-data; boundary=\"----WebKitFormBoundaryX\""; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试带空格的格式 + { + const char *input = "Content-Type: multipart/form-data; boundary = ----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_FALSE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试 boundary 后有其他参数 + { + const char *input = "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX; charset=UTF-8"; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试 boundary 前有其他参数 + { + const char *input = "Content-Type: multipart/form-data; charset=UTF-8; boundary=----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试空 boundary + { + const char *input = "Content-Type: multipart/form-data; boundary="; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_FALSE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + } + + // 测试空引号 boundary + { + const char *input = "Content-Type: multipart/form-data; boundary=\"\""; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_FALSE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + } + + // 测试没有 boundary 参数 + { + const char *input = "Content-Type: multipart/form-data; charset=UTF-8"; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_FALSE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + } + + // 测试大小写不敏感 + { + const char *input = "Content-Type: multipart/form-data; BOUNDARY=----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = 30; + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试 offset 为 0 的情况 + { + const char *input = "boundary=----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = 0; + + ASSERT_TRUE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + EXPECT_EQ(std::string(boundary_str, boundary_len), "----WebKitFormBoundaryX"); + } + + // 测试超出范围的 offset + { + const char *input = "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX"; + size_t length = strlen(input); + size_t offset = length + 1; + + ASSERT_FALSE(parse_multipart_boundary(input, length, offset, &boundary_str, &boundary_len)); + } +} + +TEST(http_server, get_package_length) { + network::Socket fake_sock; + PacketLength pl; + Connection conn; + + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + port->open_http_protocol = true; + port->open_http2_protocol = true; + port->init_protocol(); + ASSERT_EQ(port->protocol.package_length_size, SW_HTTP2_FRAME_HEADER_SIZE); + + port->open_websocket_protocol = true; + port->init_protocol(); + ASSERT_EQ(port->protocol.get_package_length_size, http_server::get_package_length_size); + + fake_sock.fd = port->get_fd(); + fake_sock.object = &conn; + fake_sock.socket_type = port->type; + fake_sock.get_name(); + + conn.info = fake_sock.info; + conn.session_id = 2025; + + ASSERT_EQ(serv.create(), SW_OK); + + // websocket + sw_tg_buffer()->clear(); + conn.websocket_status = websocket::STATUS_HANDSHAKE; + ASSERT_EQ(http_server::get_package_length_size(&fake_sock), SW_WEBSOCKET_FRAME_HEADER_SIZE); + ASSERT_TRUE(websocket::encode(sw_tg_buffer(), SW_STRL(TEST_STR), websocket::OPCODE_TEXT, websocket::FLAG_FIN)); + pl.buf = sw_tg_buffer()->str; + pl.buf_size = sw_tg_buffer()->length; + ASSERT_EQ(http_server::get_package_length(&port->protocol, &fake_sock, &pl), + SW_WEBSOCKET_HEADER_LEN + strlen(TEST_STR)); + + // http2 + sw_tg_buffer()->clear(); + conn.http2_stream = 1; + conn.websocket_status = 0; + ASSERT_EQ(http_server::get_package_length_size(&fake_sock), SW_HTTP2_FRAME_HEADER_SIZE); + http2::Settings settings_1{}; + http2::init_settings(&settings_1); + sw_tg_buffer()->length = http2::pack_setting_frame(sw_tg_buffer()->str, settings_1, false); + pl.buf = sw_tg_buffer()->str; + pl.buf_size = sw_tg_buffer()->length; + + ASSERT_GT(sw_tg_buffer()->length, 16); + ASSERT_EQ(http_server::get_package_length(&port->protocol, &fake_sock, &pl), sw_tg_buffer()->length); + + // http1.1 + conn.websocket_status = 0; + conn.http2_stream = 0; + ASSERT_EQ(http_server::get_package_length_size(&fake_sock), 0); + ASSERT_EQ(http_server::get_package_length(&port->protocol, &fake_sock, &pl), SW_ERR); + + String str(swoole_get_last_error_msg()); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_PROTOCOL_ERROR); + ASSERT_TRUE(str.contains("unexpected protocol status of session")); +} + +static void test_ssl_http(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_INFO); + + Mutex *lock = new Mutex(true); + lock->lock(); + + const int server_port = __LINE__ + TEST_PORT; + ListenPort *port = serv.add_port((enum swSocketType)(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, server_port); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + port->open_http_protocol = 1; + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->ssl_context->http = 1; + port->ssl_init(); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + lock->lock(); + + auto cmd = "curl -k https://127.0.0.1:" + std::to_string(server_port) + "/"; + pid_t pid; + auto _pipe = swoole_shell_exec(cmd.c_str(), &pid, 1); + String buf(1024); + while (1) { + auto n = read(_pipe, buf.str + buf.length, buf.size - buf.length); + if (n > 0) { + buf.grow(n); + continue; + } + break; + } + + close(_pipe); + usleep(10000); + + ASSERT_TRUE(buf.contains(TEST_STR)); + + serv->shutdown(); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + SessionId fd = req->info.fd; + auto resp = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: " + std::to_string(strlen(TEST_STR)) + + "\r\n\r\n" TEST_STR; + serv->send(fd, resp.c_str(), resp.length()); + serv->close(fd); + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; +} + +TEST(http_server, ssl) { + test_ssl_http(Server::MODE_BASE); +} + +TEST(http_server, fail) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + std::string bad_path; + bad_path.resize(PATH_MAX + 4); + ASSERT_FALSE(serv.set_document_root(bad_path)); + ASSERT_ERREQ(SW_ERROR_NAME_TOO_LONG); + + std::string not_exists_path("/tmp/swoole-core-tests-not-exists"); + ASSERT_FALSE(serv.set_document_root(not_exists_path)); + ASSERT_ERREQ(SW_ERROR_DIR_NOT_EXIST); +} diff --git a/core-tests/src/server/message_bus.cpp b/core-tests/src/server/message_bus.cpp index 52f96e78a4..45d7b2c212 100644 --- a/core-tests/src/server/message_bus.cpp +++ b/core-tests/src/server/message_bus.cpp @@ -114,15 +114,13 @@ TEST(message_bus, read) { tmb.mb.set_id_generator([&msg_id]() { return msg_id++; }); tmb.mb.alloc_buffer(); - tmb.read_func = [&tmb](network::Socket *sock) { - return tmb.mb.read(sock); - }; + tmb.read_func = [&tmb](network::Socket *sock) { return tmb.mb.read(sock); }; sw_reactor()->ptr = &tmb; ASSERT_EQ(swoole_event_add(p.get_socket(false), SW_EVENT_READ), SW_OK); - swoole_event_set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { TestMB *tmb = (TestMB *) reactor->ptr; return tmb->read(ev); }); @@ -163,15 +161,13 @@ TEST(message_bus, read_with_buffer) { tmb.mb.set_id_generator([&msg_id]() { return msg_id++; }); tmb.mb.alloc_buffer(); - tmb.read_func = [&tmb](network::Socket *sock) { - return tmb.mb.read_with_buffer(sock); - }; + tmb.read_func = [&tmb](network::Socket *sock) { return tmb.mb.read_with_buffer(sock); }; sw_reactor()->ptr = &tmb; ASSERT_EQ(swoole_event_add(p.get_socket(false), SW_EVENT_READ), SW_OK); - swoole_event_set_handler(SW_FD_PIPE | SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, [](Reactor *reactor, Event *ev) -> int { TestMB *tmb = (TestMB *) reactor->ptr; return tmb->read(ev); }); @@ -188,6 +184,8 @@ TEST(message_bus, read_with_buffer) { MB_ASSERT(2); MB_ASSERT(3); + ASSERT_GT(tmb.mb.get_memory_size(), 1024 * 1024 * 2); + auto r4 = tmb.q.at(3); ASSERT_EQ(r4.fd, 4); ASSERT_STREQ(r4.data.c_str(), ""); diff --git a/core-tests/src/server/mqtt.cpp b/core-tests/src/server/mqtt.cpp new file mode 100644 index 0000000000..18b33d58ec --- /dev/null +++ b/core-tests/src/server/mqtt.cpp @@ -0,0 +1,286 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "swoole_server.h" +#include "swoole_memory.h" +#include "swoole_signal.h" +#include "swoole_lock.h" +#include "swoole_util.h" + +using namespace std; +using namespace swoole; + +enum MqttPacketType { + CONNECT = 1, + CONNACK = 2, + PUBLISH = 3, + PUBACK = 4, + SUBSCRIBE = 8, + SUBACK = 9, + DISCONNECT = 14, +}; + +std::string current_timestamp() { + using namespace std::chrono; + auto now = system_clock::now(); + time_t t = system_clock::to_time_t(now); + char buf[64]; + struct tm tm_now; + localtime_r(&t, &tm_now); + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm_now); + return std::string(buf); +} + +struct MqttSession { + SessionId fd; + bool subscribed = false; + uint16_t count = 0; + uint16_t packet_id_subscribe = 0; + std::string subscribed_topic; + Server *server; + + MqttSession(Server *_server, SessionId fd_) : fd(fd_), server(_server) {} + + // 发送 CONNACK 报文,简单实现:Session Present=0, Connect Return code=0 (Success) + bool send_connack() { + uint8_t connack[] = { + 0x20, + 0x02, // 固定报头:CONNACK, 剩余长度2 + 0x00, // Session Present = 0 + 0x00 // Connect return code = 0 (成功) + }; + return server->send(fd, connack, sizeof(connack)); + } + + // 发送 SUBACK 报文,确认订阅成功 + bool send_suback(uint16_t packet_id) { + uint8_t suback[] = { + 0x90, + 0x03, // 固定报头:SUBACK, 剩余长度3 + uint8_t(packet_id >> 8), + uint8_t(packet_id & 0xFF), // 报文标识符 + 0x00 // 返回码:0x00 QoS 0 + }; + return server->send(fd, suback, sizeof(suback)); + } + + // 发送 PUBLISH 报文,QoS 0 简化,无标识符 + bool send_publish(const std::string &topic, const std::string &message) { + // PUBLISH fixed header: 0x30 (QoS0), 剩余长度计算 + // variable header: topic (2字节长度 + 字符串) + uint16_t topic_len = topic.size(); + size_t var_header_len = 2 + topic_len; + size_t payload_len = message.size(); + size_t remaining_length = var_header_len + payload_len; + + std::vector packet; + packet.push_back(0x30); // PUBLISH, QoS0 + + // MQTT剩余长度使用可变长度编码,这里实现简单编码(长度<128假定) + if (remaining_length < 128) { + packet.push_back(uint8_t(remaining_length)); + } else { + // 简单处理大于127的长度,实际可以完善 + do { + uint8_t byte = remaining_length % 128; + remaining_length /= 128; + if (remaining_length > 0) byte |= 0x80; + packet.push_back(byte); + } while (remaining_length > 0); + } + + // variable header topic + packet.push_back(uint8_t(topic_len >> 8)); + packet.push_back(uint8_t(topic_len & 0xFF)); + packet.insert(packet.end(), topic.begin(), topic.end()); + + // payload + packet.insert(packet.end(), message.begin(), message.end()); + + return server->send(fd, packet.data(), packet.size()) == (ssize_t) packet.size(); + } + + bool send_puback(uint16_t packet_id) { + uint8_t puback[] = {0x40, 0x02, uint8_t(packet_id >> 8), uint8_t(packet_id & 0xFF)}; + return server->send(fd, puback, sizeof(puback)); + } + + bool send_disconnect() { + uint8_t disconnect[] = {0xE0, 0x00}; + return server->send(fd, disconnect, sizeof(disconnect)); + } + + bool process_packet(const uint8_t *data, size_t len) { + uint8_t packet_type = (data[0] >> 4); + switch (packet_type) { + case CONNECT: { + std::cout << "收到 CONNECT 报文\n"; + // 简化:收到CONNECT直接回复CONNACK成功 + return send_connack(); + } + case SUBSCRIBE: { + std::cout << "收到 SUBSCRIBE 报文\n"; + // SUBSCRIBE 报文结构:固定头 + 剩余长度 + 报文标识符 (2bytes) + Payload + // 简化解析报文标识符和第一个订阅主题 + if (len < 5) return false; + uint16_t packet_id = (data[2] << 8) | data[3]; + packet_id_subscribe = packet_id; + + size_t pos = 4; + if (pos + 2 > len) return false; + uint16_t topic_len = (data[pos] << 8) | data[pos + 1]; + pos += 2; + if (pos + topic_len > len) return false; + subscribed_topic.assign((const char *) (data + pos), topic_len); + std::cout << "订阅主题: " << subscribed_topic << std::endl; + + subscribed = true; + return send_suback(packet_id); + } + case PUBLISH: { + std::cout << "收到 PUBLISH 报文\n"; + + uint8_t flags = data[0] & 0x0F; + uint8_t qos = (flags & 0x06) >> 1; + + // TODO 需可变长度解析 + size_t remaining_length = data[1]; + EXPECT_GT(remaining_length, 2); + + size_t pos = 2; + if (pos + 2 > len) return false; + + uint16_t topic_len = (data[pos] << 8) | data[pos + 1]; + pos += 2; + if (pos + topic_len > len) return false; + + std::string topic((const char *) (data + pos), topic_len); + pos += topic_len; + + uint16_t packet_id = 0; + if (qos > 0) { + if (pos + 2 > len) return false; + packet_id = (data[pos] << 8) | data[pos + 1]; + pos += 2; + } + + if (pos > len) return false; + + std::string payload((const char *) (data + pos), len - pos); + + std::cout << "主题: " << topic << ", 消息体: " << payload << ", QoS: " << (int) qos << std::endl; + + // 根据需要处理 payload 内容 + // 例如转发给其他客户端、存储等 + + // QoS1需要发送PUBACK确认 + if (qos == 1) { + return send_puback(packet_id); + } + + // QoS0直接返回成功 + return true; + } + // 你可以增加 PINGREQ、DISCONNECT 等消息处理 + default: { + std::cout << "收到未处理的包类型: " << (int) packet_type << std::endl; + return true; + } + } + } +}; + +static void test_mqtt_server(function fn) { + thread child_thread; + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + serv.enable_reuse_port = true; + serv.private_data_2 = (void *) &fn; + + sw_logger()->set_level(SW_LOG_WARNING); + + std::unordered_map sessions; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 9501); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + port->open_mqtt_protocol = 1; + + serv.create(); + + serv.onWorkerStart = [&child_thread](Server *serv, Worker *worker) { + function fn = *(function *) serv->private_data_2; + child_thread = thread(fn, serv); + }; + + serv.onClose = [&sessions](Server *serv, DataHead *info) -> void { + delete sessions[info->fd]; + sessions.erase(info->fd); + }; + + serv.onConnect = [&sessions](Server *serv, DataHead *info) -> void { + auto session = new MqttSession(serv, info->fd); + sessions[info->fd] = session; + swoole_timer_tick(100, [session, serv](auto r1, TimerNode *tnode) { + if (session->subscribed) { + std::string ts = current_timestamp(); + session->send_publish(session->subscribed_topic, + "Index: " + std::to_string(session->count) + ", Time: " + ts); + session->count++; + if (session->count > 10) { + session->send_disconnect(); + serv->close(session->fd, false); + swoole_timer_del(tnode); + } + } + }); + }; + + serv.onReceive = [&sessions](Server *serv, RecvData *req) -> int { + auto session = sessions[req->info.fd]; + if (!session->process_packet((uint8_t *) req->data, req->info.len)) { + std::cerr << "处理数据包失败,关闭连接\n"; + } + return SW_OK; + }; + + serv.start(); + child_thread.join(); +} + +TEST(mqtt, echo) { + test_mqtt_server([](Server *serv) { + swoole_signal_block_all(); + EXPECT_EQ(test::exec_js_script("mqtt.js", std::to_string(serv->get_primary_port()->get_port())), 0); + kill(serv->get_master_pid(), SIGTERM); + }); + + File fp(TEST_LOG_FILE, O_RDONLY); + EXPECT_TRUE(fp.ready()); + auto str = fp.read_content(); + SW_LOOP_N(10) { + ASSERT_TRUE( + str->contains("received message, topic: test/topic, content: Index: " + std::to_string(i) + ", Time: ")); + } + unlink(TEST_LOG_FILE); +} diff --git a/core-tests/src/server/multipart_parser.cpp b/core-tests/src/server/multipart_parser.cpp new file mode 100644 index 0000000000..aac4e30ef2 --- /dev/null +++ b/core-tests/src/server/multipart_parser.cpp @@ -0,0 +1,255 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | Author NathanFreeman | + +----------------------------------------------------------------------+ + */ +#include "test_core.h" +#include "multipart_parser.h" + +struct MppResult { + std::string data; + std::string header_field; + std::string header_value; + bool header_complete; + bool body_end; +}; + +static int multipart_on_header_field(multipart_parser *p, const char *at, size_t length) { + swoole_trace("on_header_field: at=%.*s, length=%lu", (int) length, at, length); + auto res = (MppResult *) p->data; + res->header_field = std::string(at, length); + return 0; +} + +static int multipart_on_header_value(multipart_parser *p, const char *at, size_t length) { + swoole_trace("on_header_value: at=%.*s, length=%lu", (int) length, at, length); + auto res = (MppResult *) p->data; + res->header_value = std::string(at, length); + return 0; +} + +static int multipart_on_data(multipart_parser *p, const char *at, size_t length) { + swoole_trace("on_data: length=%lu", length); + auto res = (MppResult *) p->data; + res->data.append(at, length); + return 0; +} + +static int multipart_on_header_complete(multipart_parser *p) { + swoole_trace("on_header_complete"); + auto res = (MppResult *) p->data; + res->header_complete = true; + return 0; +} + +static int multipart_on_data_end(multipart_parser *p) { + swoole_trace("on_data_end"); + return 0; +} + +static int multipart_on_part_begin(multipart_parser *p) { + swoole_trace("on_part_begin"); + return 0; +} + +static int multipart_on_body_end(multipart_parser *p) { + swoole_trace("on_body_end"); + auto res = (MppResult *) p->data; + res->body_end = true; + return 0; +} + +static multipart_parser_settings _settings{ + multipart_on_header_field, + multipart_on_header_value, + multipart_on_data, + multipart_on_part_begin, + multipart_on_header_complete, + multipart_on_data_end, + multipart_on_body_end, +}; + +static const std::string boundary = "--WebKitFormBoundaryeGOz80A8JnaO6kuw"; + +static multipart_parser *create_parser() { + return multipart_parser_init(boundary.c_str(), boundary.length(), &_settings); +} + +static void create_error(multipart_parser *parser, multipart_error error_reason, const char *error) { + size_t length = 1024; + char buf[length]; + + parser->error_reason = error_reason; + int result_len = multipart_parser_error_msg(parser, buf, length); + ASSERT_GT(result_len, 0); + buf[result_len] = '\0'; + + std::string response(buf, result_len); + ASSERT_TRUE(response.find(error) != response.npos); +} + +TEST(multipart_parser, error_message) { + size_t length = 1024; + char buf[length]; + auto parser = create_parser(); + + parser->error_reason = MPPE_OK; + ASSERT_EQ(multipart_parser_error_msg(parser, buf, length), 0); + + parser->error_expected = '\0'; + create_error(parser, MPPE_PAUSED, "parser paused"); + create_error(parser, MPPE_BOUNDARY_END_NO_CRLF, "no CRLF at first boundary end: "); + create_error(parser, MPPE_BAD_START_BOUNDARY, "first boundary mismatching: "); + create_error(parser, MPPE_INVALID_HEADER_FIELD_CHAR, "invalid char in header field: "); + create_error(parser, MPPE_INVALID_HEADER_VALUE_CHAR, "invalid char in header value: "); + create_error(parser, MPPE_BAD_PART_END, "no next part or final hyphen: expecting CR or '-' "); + create_error(parser, MPPE_END_BOUNDARY_NO_DASH, "bad final hyphen: "); + + parser->error_expected = '\r'; + create_error(parser, MPPE_PAUSED, "parser paused"); + create_error(parser, MPPE_BOUNDARY_END_NO_CRLF, "no CRLF at first boundary end: "); + create_error(parser, MPPE_BAD_START_BOUNDARY, "first boundary mismatching: "); + create_error(parser, MPPE_INVALID_HEADER_FIELD_CHAR, "invalid char in header field: "); + create_error(parser, MPPE_INVALID_HEADER_VALUE_CHAR, "invalid char in header value: "); + create_error(parser, MPPE_BAD_PART_END, "no next part or final hyphen: expecting CR or '-' "); + create_error(parser, MPPE_END_BOUNDARY_NO_DASH, "bad final hyphen: "); + + parser->error_expected = '\n'; + create_error(parser, MPPE_PAUSED, "parser paused"); + create_error(parser, MPPE_BOUNDARY_END_NO_CRLF, "no CRLF at first boundary end: "); + create_error(parser, MPPE_BAD_START_BOUNDARY, "first boundary mismatching: "); + create_error(parser, MPPE_INVALID_HEADER_FIELD_CHAR, "invalid char in header field: "); + create_error(parser, MPPE_INVALID_HEADER_VALUE_CHAR, "invalid char in header value: "); + create_error(parser, MPPE_BAD_PART_END, "no next part or final hyphen: expecting CR or '-' "); + create_error(parser, MPPE_END_BOUNDARY_NO_DASH, "bad final hyphen: "); + + parser->error_expected = 'a'; + create_error(parser, MPPE_PAUSED, "parser paused"); + create_error(parser, MPPE_BOUNDARY_END_NO_CRLF, "no CRLF at first boundary end: "); + create_error(parser, MPPE_BAD_START_BOUNDARY, "first boundary mismatching: "); + create_error(parser, MPPE_INVALID_HEADER_FIELD_CHAR, "invalid char in header field: "); + create_error(parser, MPPE_INVALID_HEADER_VALUE_CHAR, "invalid char in header value: "); + create_error(parser, MPPE_BAD_PART_END, "no next part or final hyphen: expecting CR or '-' "); + create_error(parser, MPPE_END_BOUNDARY_NO_DASH, "bad final hyphen: "); +} + +TEST(multipart_parser, header_field) { + auto parser = create_parser(); + ssize_t ret; + + // header party + swoole::String header(1024); + header.append("--"); + header.append(boundary); + header.append("\r\n"); + header.append("Content-Disposition: form-data; name=\"test\"\r\n\r\n"); + MppResult result; + parser->data = &result; + + ret = multipart_parser_execute(parser, header.value(), header.get_length()); + ASSERT_EQ(ret, header.length); + + ASSERT_STREQ(result.header_field.c_str(), "Content-Disposition"); + ASSERT_TRUE(result.header_value.find("test") != result.header_value.npos); + + std::string boundary_str(parser->boundary, parser->boundary_length); + ASSERT_EQ(multipart_parser_execute(parser, SW_STRL("\r\n")), 2); + ASSERT_EQ(multipart_parser_execute(parser, boundary_str.c_str(), boundary_str.length()), boundary_str.length()); + ASSERT_EQ(multipart_parser_execute(parser, "--\r\n\r\n", 6), 6); +} + +TEST(multipart_parser, header_error) { + auto parser = create_parser(); + ssize_t ret; + + // header party + swoole::String header(1024); + header.append("--"); + header.append(boundary); + header.append("\r\n"); + header.append("Content-Disposition: form-data; name=\"test\""); + MppResult result; + parser->data = &result; + + ret = multipart_parser_execute(parser, header.value(), header.get_length()); + ASSERT_EQ(ret, -1); + ASSERT_EQ(parser->error_reason, MPPE_HEADER_VALUE_INCOMPLETE); + ASSERT_EQ(parser->error_expected, '\r'); +} + +TEST(multipart_parser, data) { + auto parser = create_parser(); + ssize_t ret; + + // header party + swoole::String header(1024); + header.append("--"); + header.append(boundary); + header.append("\r\n"); + header.append("Content-Disposition: form-data; name=\"test\"\r\n\r\n"); + MppResult result; + parser->data = &result; + ret = multipart_parser_execute(parser, header.value(), header.get_length()); + ASSERT_EQ(ret, header.length); + + std::string boundary_str(parser->boundary, parser->boundary_length); + + // data part + swoole::String data(128); + data.append_random_bytes(swoole_rand(60, 120), true); + data.append("\r"); + data.append_random_bytes(swoole_rand(60, 120), true); + data.append("\r\n"); + data.append_random_bytes(swoole_rand(60, 120), true); + data.append("\r\n"); + data.append(boundary_str.substr(0, swoole_rand(1, parser->boundary_length - 2))); + data.append_random_bytes(swoole_rand(60, 120), true); + ASSERT_EQ(multipart_parser_execute(parser, data.value(), data.get_length()), data.get_length()); + + auto append_data = [&]() { + size_t offset = data.length; + data.append_random_bytes(swoole_rand(60, 120), true); + size_t len = data.length - offset; + ASSERT_EQ(multipart_parser_execute(parser, data.value() + offset, len), len); + }; + + append_data(); + data.append("\r"); + ASSERT_EQ(multipart_parser_execute(parser, SW_STRL("\r")), 1); + + append_data(); + + data.append("\r\n"); + ASSERT_EQ(multipart_parser_execute(parser, SW_STRL("\r\n")), 2); + + append_data(); + + { + size_t offset = data.length; + data.append(boundary_str.substr(0, swoole_rand(1, parser->boundary_length - 2))); + size_t len = data.length - offset; + ASSERT_EQ(multipart_parser_execute(parser, data.value() + offset, len), len); + } + + ASSERT_EQ(multipart_parser_execute(parser, SW_STRL("\r\n")), 2); + ASSERT_EQ(multipart_parser_execute(parser, boundary_str.c_str(), boundary_str.length()), boundary_str.length()); + ASSERT_EQ(multipart_parser_execute(parser, "--\r\n\r\n", 6), 6); + + ASSERT_MEMEQ(data.value(), result.data.c_str(), result.data.length()); + + ASSERT_TRUE(result.header_complete); + ASSERT_TRUE(result.body_end); +} diff --git a/core-tests/src/server/port.cpp b/core-tests/src/server/port.cpp new file mode 100644 index 0000000000..59100a75d4 --- /dev/null +++ b/core-tests/src/server/port.cpp @@ -0,0 +1,63 @@ +/* ++----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "test_core.h" + +#include "swoole_server.h" +#include "test_server.h" + +using swoole::ListenPort; +using swoole::Server; + +TEST(server_port, import) { + ListenPort port(nullptr); + ASSERT_FALSE(port.import(fileno(stdin))); + ASSERT_ERREQ(ENOTSOCK); + + auto sock = swoole::make_socket(SW_SOCK_TCP, SW_FD_STREAM, 0); + ASSERT_FALSE(port.import(sock->fd)); + ASSERT_ERREQ(EINVAL); + sock->free(); +} + +TEST(server_port, create) { + Server server(Server::MODE_BASE); + server.enable_reuse_port = true; + auto port = server.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(nullptr, port); + ASSERT_EQ(SW_OK, port->create_socket()); + + port->open_eof_check = true; + port->protocol.package_eof_len = SW_DATA_EOF_MAXLEN + 10; + port->init_protocol(); + ASSERT_STREQ("eof", port->get_protocols()); + ASSERT_EQ(port->protocol.package_eof_len, SW_DATA_EOF_MAXLEN); + + ASSERT_TRUE(port->ssl_context_init()); + ASSERT_FALSE(port->ssl_context_create(port->ssl_context.get())); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); +} + +TEST(server_port, dgram) { + Server server(Server::MODE_BASE); + server.enable_reuse_port = true; + auto port = server.add_port(SW_SOCK_UDP, TEST_HOST, 0); + ASSERT_NE(nullptr, port); + ASSERT_STREQ("dgram", port->get_protocols()); +} diff --git a/core-tests/src/server/server.cpp b/core-tests/src/server/server.cpp index aa5563e788..26eaf3b6d1 100644 --- a/core-tests/src/server/server.cpp +++ b/core-tests/src/server/server.cpp @@ -23,20 +23,28 @@ #include "swoole_memory.h" #include "swoole_signal.h" #include "swoole_lock.h" +#include "swoole_util.h" + +#include using namespace std; using namespace swoole; +using swoole::network::AsyncClient; + +int beforeReloadPid = 0; TEST(server, schedule) { int ret; Server serv(Server::MODE_PROCESS); serv.worker_num = 6; serv.dispatch_mode = Server::DISPATCH_IDLE_WORKER; + serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ret = serv.create(); ASSERT_EQ(SW_OK, ret); for (uint32_t i = 0; i < serv.worker_num; i++) { - serv.workers[i].status = SW_WORKER_BUSY; + serv.workers[i].set_status_to_busy(); } std::set _worker_id_set; @@ -48,7 +56,7 @@ TEST(server, schedule) { ASSERT_EQ(_worker_id_set.size(), serv.worker_num); for (uint32_t i = 1; i < serv.worker_num - 1; i++) { - serv.workers[i].status = SW_WORKER_IDLE; + serv.workers[i].set_status_to_idle(); } _worker_id_set.clear(); @@ -57,30 +65,290 @@ TEST(server, schedule) { _worker_id_set.insert(worker_id); } ASSERT_EQ(_worker_id_set.size(), serv.worker_num - 2); + + serv.destroy(); +} + +TEST(server, schedule_1) { + int ret; + Server serv(Server::MODE_PROCESS); + serv.worker_num = 8; + serv.dispatch_mode = Server::DISPATCH_ROUND; + serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + + ret = serv.create(); + ASSERT_EQ(SW_OK, ret); + + std::vector counter; + size_t schedule_count = 1024; + + counter.resize(serv.worker_num); + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(i * 13, nullptr); + counter[worker_id]++; + } + + SW_LOOP_N(serv.worker_num) { + ASSERT_EQ(counter[i], schedule_count / serv.worker_num); + } +} + +double average_combined(const std::vector &v1, const std::vector &v2) { + size_t total_size = v1.size() + v2.size(); + if (total_size == 0) return 0.0; + size_t sum1 = std::accumulate(v1.begin(), v1.end(), size_t{0}); + size_t sum2 = std::accumulate(v2.begin(), v2.end(), size_t{0}); + return static_cast(sum1 + sum2) / total_size; +} + +template +static void test_worker_schedule(int dispatch_mode) { + int ret; + Server serv(Server::MODE_PROCESS); + serv.worker_num = 8; + serv.dispatch_mode = dispatch_mode; + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + + ret = serv.create(); + ASSERT_EQ(SW_OK, ret); + + std::vector counter; + counter.resize(serv.worker_num); + + size_t schedule_count = 256 * serv.worker_num; + + std::vector init_counter; + init_counter.resize(serv.worker_num); + + SW_LOOP_N(serv.worker_num) { + T &worker = serv.workers[i]; + init_counter[i] = worker.*member = swoole_rand(32, 512); + } + + network::Socket fake_sock{}; + fake_sock.fd = 199; + serv.add_connection(port, &fake_sock, port->get_fd()); + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock.fd, nullptr); + counter[worker_id]++; + T &worker = serv.workers[worker_id]; + (worker.*member)++; + } + + auto avg_elem = average_combined(init_counter, counter); + SW_LOOP_N(serv.worker_num) { + ASSERT_GE(counter[i] + init_counter[i], (int) avg_elem * 2 - 10); + } +} + +TEST(server, schedule_4) { + int ret; + Server serv(Server::MODE_PROCESS); + serv.worker_num = 4; + serv.dispatch_mode = Server::DISPATCH_IPMOD; + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + auto port6 = serv.add_port(SW_SOCK_TCP6, "::", 0); + ASSERT_NE(port6, nullptr); + + ret = serv.create(); + ASSERT_EQ(SW_OK, ret); + + std::vector counter; + counter.resize(serv.worker_num); + + size_t schedule_count = 256 * serv.worker_num; + + std::vector init_counter; + init_counter.resize(serv.worker_num); + + network::Socket fake_sock{}; + fake_sock.fd = 100; + fake_sock.info.assign(SW_SOCK_TCP, "127.0.0.1", 9501, false); + serv.add_connection(port, &fake_sock, port->get_fd()); + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock.fd, nullptr); + counter[worker_id]++; + } + + network::Socket fake_sock6{}; + fake_sock6.fd = 101; + fake_sock6.info.assign(SW_SOCK_TCP6, "::1", 9502, false); + serv.add_connection(port6, &fake_sock6, port6->get_fd()); + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock6.fd, nullptr); + counter[worker_id]++; + } + + SendData sdata; + auto pkt = reinterpret_cast(sw_tg_buffer()->str); + pkt->socket_addr.assign(SW_SOCK_UDP, "192.168.1.103", 29321, false); + sdata.data = (char *) pkt; + auto worker_id = serv.schedule_worker(9999, &sdata); + counter[worker_id]++; + + ASSERT_EQ(counter[0], 0); + ASSERT_EQ(counter[1], schedule_count + 1); + ASSERT_EQ(counter[2], 0); + ASSERT_EQ(counter[3], schedule_count); +} + +TEST(server, schedule_5) { + int ret; + Server serv(Server::MODE_PROCESS); + serv.worker_num = 4; + serv.dispatch_mode = Server::DISPATCH_UIDMOD; + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + auto port6 = serv.add_port(SW_SOCK_TCP6, "::", 0); + ASSERT_NE(port6, nullptr); + + ret = serv.create(); + ASSERT_EQ(SW_OK, ret); + + std::vector counter; + counter.resize(serv.worker_num); + + size_t schedule_count = 256 * serv.worker_num; + + std::vector init_counter; + init_counter.resize(serv.worker_num); + + network::Socket fake_sock{}; + fake_sock.fd = 100; + fake_sock.info.assign(SW_SOCK_TCP, "127.0.0.1", 9501, false); + auto conn = serv.add_connection(port, &fake_sock, port->get_fd()); + conn->uid = 0; + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock.fd, nullptr); + counter[worker_id]++; + } + + network::Socket fake_sock6{}; + fake_sock6.fd = 101; + + fake_sock6.info.assign(SW_SOCK_TCP6, "::1", 9502, false); + auto conn6 = serv.add_connection(port6, &fake_sock6, port6->get_fd()); + conn6->uid = 839922; + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock6.fd, nullptr); + counter[worker_id]++; + } + + ASSERT_EQ(counter[0], schedule_count); + ASSERT_EQ(counter[1], 0); + ASSERT_EQ(counter[2], schedule_count); + ASSERT_EQ(counter[3], 0); +} + +TEST(server, schedule_8) { + int ret; + Server serv(Server::MODE_PROCESS); + serv.worker_num = 4; + serv.dispatch_mode = Server::DISPATCH_CO_CONN_LB; + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + auto port6 = serv.add_port(SW_SOCK_TCP6, "::", 0); + ASSERT_NE(port6, nullptr); + + ret = serv.create(); + ASSERT_EQ(SW_OK, ret); + + std::vector counter; + counter.resize(serv.worker_num); + + size_t schedule_count = 256 * serv.worker_num; + + std::vector init_counter; + init_counter.resize(serv.worker_num); + + network::Socket fake_sock{}; + fake_sock.fd = 100; + fake_sock.info.assign(SW_SOCK_TCP, "127.0.0.1", 9501, false); + auto conn = serv.add_connection(port, &fake_sock, port->get_fd()); + conn->worker_id = 1; + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock.fd, nullptr); + counter[worker_id]++; + } + + network::Socket fake_sock6{}; + fake_sock6.fd = 101; + + fake_sock6.info.assign(SW_SOCK_TCP6, "::1", 9502, false); + serv.add_connection(port6, &fake_sock6, port6->get_fd()); + + SW_LOOP_N(schedule_count) { + auto worker_id = serv.schedule_worker(fake_sock6.fd, nullptr); + counter[worker_id]++; + } + + auto worker_id = serv.schedule_worker(9999, nullptr); + counter[worker_id]++; + + ASSERT_EQ(counter[0], schedule_count + 1); + ASSERT_EQ(counter[1], schedule_count); + ASSERT_EQ(counter[2], 0); + ASSERT_EQ(counter[3], 0); +} + +TEST(server, schedule_9) { + test_worker_schedule(Server::DISPATCH_CO_REQ_LB); +} + +TEST(server, schedule_10) { + test_worker_schedule(Server::DISPATCH_CONCURRENT_LB); } static const char *packet = "hello world\n"; -TEST(server, base) { +static void test_base() { Server serv(Server::MODE_BASE); serv.worker_num = 1; + serv.pid_file = "/tmp/swoole-core-tests.pid"; - sw_logger()->set_level(SW_LOG_WARNING); + test::counter_init(); + swoole_set_log_level(SW_LOG_WARNING); - swListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); ASSERT_TRUE(port); + serv.add_hook( + Server::HOOK_WORKER_START, + [](void *ptr) { + void **args = (void **) ptr; + Server *serv = (Server *) args[0]; + ASSERT_TRUE(serv->is_started()); + }, + false); + mutex lock; lock.lock(); ASSERT_EQ(serv.create(), SW_OK); + swoole_clear_last_error(); + ASSERT_FALSE(serv.shutdown()); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + std::thread t1([&]() { swoole_signal_block_all(); lock.lock(); - swoole::network::SyncClient c(SW_SOCK_TCP); + network::SyncClient c(SW_SOCK_TCP); c.connect(TEST_HOST, port->port); c.send(packet, strlen(packet)); char buf[1024]; @@ -90,30 +358,73 @@ TEST(server, base) { kill(getpid(), SIGTERM); }); - serv.onWorkerStart = [&lock](swServer *serv, int worker_id) { lock.unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; - serv.onReceive = [](swServer *serv, swRecvData *req) -> int { + serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); + auto conn = serv->get_connection_by_session_id(req->session_id()); + EXPECT_NE(strcmp(serv->get_local_addr(conn), "unknown"), 0); string resp = string("Server: ") + string(packet); serv->send(req->info.fd, resp.c_str(), resp.length()); + EXPECT_FALSE(serv->finish(resp.c_str(), resp.length())); + + EXPECT_EQ(serv->get_connection_num(), 1); + EXPECT_EQ(serv->get_primary_port()->get_connection_num(), 1); + + EXPECT_EQ(serv->get_worker_message_bus()->move_packet(), nullptr); + + // session not exists + SessionId client_fd = 9999; + swoole_clear_last_error(); + EXPECT_FALSE(serv->send(client_fd, resp.c_str(), resp.length())); + EXPECT_ERREQ(SW_ERROR_SESSION_NOT_EXIST); + + swoole_clear_last_error(); + EXPECT_FALSE(serv->close(client_fd)); + EXPECT_ERREQ(SW_ERROR_SESSION_NOT_EXIST); + + swoole_clear_last_error(); + SendData sd{}; + sd.info.fd = client_fd; + sd.info.type = SW_SERVER_EVENT_CLOSE; + EXPECT_EQ(serv->send_to_connection(&sd), SW_ERR); + EXPECT_ERREQ(SW_ERROR_SESSION_NOT_EXIST); + return SW_OK; }; + serv.onStart = [](Server *serv) { ASSERT_EQ(access(serv->pid_file.c_str(), R_OK), 0); }; + + serv.onBeforeShutdown = [](Server *serv) { + beforeReloadPid = serv->gs->master_pid; + test::counter_incr(10); + DEBUG() << "onBeforeShutdown: master_pid=" << beforeReloadPid << "\n"; + }; + serv.start(); t1.join(); + + ASSERT_EQ(access(serv.pid_file.c_str(), R_OK), -1); + ASSERT_EQ(test::counter_get(10), 1); // onBeforeShutdown called } -TEST(server, process) { +TEST(server, base) { + test_base(); +} + +static void test_process(bool single_thread = false) { Server serv(Server::MODE_PROCESS); serv.worker_num = 1; + serv.single_thread = single_thread; + serv.task_worker_num = 3; + swoole_set_log_level(SW_LOG_WARNING); - SwooleG.running = 1; + test::counter_init(); + auto counter = test::counter_ptr(); - sw_logger()->set_level(SW_LOG_WARNING); - - Mutex *lock = new Mutex(Mutex::PROCESS_SHARED); + Mutex *lock = new Mutex(true); lock->lock(); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); @@ -124,91 +435,154 @@ TEST(server, process) { ASSERT_EQ(serv.create(), SW_OK); - serv.onStart = [&lock](swServer *serv) { - thread t1([=]() { + swoole_clear_last_error(); + ASSERT_EQ(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + + swoole_clear_last_error(); + Worker fake_worker{}; + ASSERT_EQ(serv.add_worker(&fake_worker), SW_ERR); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { swoole_signal_block_all(); lock->lock(); - swListenPort *port = serv->get_primary_port(); + kill(serv->get_worker(0)->pid, SIGRTMIN); + + ListenPort *port = serv->get_primary_port(); - swoole::network::SyncClient c(SW_SOCK_TCP); + network::SyncClient c(SW_SOCK_TCP); c.connect(TEST_HOST, port->port); c.send(packet, strlen(packet)); char buf[1024]; c.recv(buf, sizeof(buf)); c.close(); + sleep(2); + kill(serv->gs->master_pid, SIGTERM); }); - t1.detach(); + + // command tests + swoole_clear_last_error(); + serv->call_command_callback(9999, TEST_STR); + ASSERT_ERREQ(SW_ERROR_SERVER_INVALID_COMMAND); + + swoole_clear_last_error(); + serv->call_command_handler_in_master(9999, TEST_STR); + ASSERT_ERREQ(SW_ERROR_SERVER_INVALID_COMMAND); }; - serv.onWorkerStart = [&lock](swServer *serv, int worker_id) { lock->unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + if (worker->id == 0) { + lock->unlock(); + } + test::counter_incr(3); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; - serv.onReceive = [](swServer *serv, swRecvData *req) -> int { + serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); string resp = string("Server: ") + string(packet); serv->send(req->info.fd, resp.c_str(), resp.length()); + EXPECT_EQ(serv->get_connection_num(), 1); + EXPECT_EQ(serv->get_primary_port()->get_connection_num(), 1); + + swoole_timer_after(100, [serv](TIMER_PARAMS) { serv->kill_worker(1 + swoole_random_int() % 3); }); + return SW_OK; }; - ASSERT_EQ(serv.start(), 0); + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; - delete lock; -} + serv.manager_alarm = 1; -#ifdef SW_USE_OPENSSL -TEST(server, ssl) { - Server serv(Server::MODE_PROCESS); - serv.worker_num = 1; + serv.add_hook( + Server::HOOK_MANAGER_TIMER, + [](void *args) { + test::counter_incr(2); + DEBUG() << "manager timer callback\n"; + }, + true); - SwooleG.running = 1; + serv.onManagerStart = [](Server *serv) { + DEBUG() << "onManagerStart\n"; + test::counter_incr(1); + }; - sw_logger()->set_level(SW_LOG_WARNING); + serv.onManagerStop = [](Server *serv) { + DEBUG() << "onManagerStop\n"; + test::counter_incr(1); + }; - Mutex *lock = new Mutex(Mutex::PROCESS_SHARED); - lock->lock(); + serv.onBeforeShutdown = [](Server *serv) { + beforeReloadPid = serv->gs->master_pid; + test::counter_incr(10); + DEBUG() << "onBeforeShutdown: master_pid=" << beforeReloadPid << "\n"; + }; - ListenPort *port = serv.add_port((enum swSocketType)(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0); - if (!port) { - swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); - exit(2); - } + serv.onShutdown = [](Server *serv) { + DEBUG() << "onShutdown\n"; + test::counter_incr(9); + }; - port->ssl_set_cert_file(test::get_root_path() + "/tests/include/ssl_certs/server.crt"); - port->ssl_set_key_file(test::get_root_path() + "/tests/include/ssl_certs/server.key"); - port->ssl_init(); + ASSERT_EQ(serv.start(), 0); - ASSERT_EQ(serv.create(), SW_OK); + t1.join(); + delete lock; + ASSERT_EQ(counter[1], 2); // manager callback + ASSERT_GE(counter[2], 2); // manager timer + ASSERT_GE(counter[3], 4); // worker start + ASSERT_EQ(test::counter_get(9), 1); // onShutdown called + ASSERT_EQ(test::counter_get(10), 1); // onBeforeShutdown called +} - serv.onStart = [&lock](Server *serv) { - thread t1([=]() { - swoole_signal_block_all(); +TEST(server, process) { + test_process(); + test::wait_all_child_processes(); +} - lock->lock(); +TEST(server, process_single_thread) { + test_process(true); + test::wait_all_child_processes(); +} - ListenPort *port = serv->get_primary_port(); +static void test_process_send_in_user_worker() { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + swoole_set_log_level(SW_LOG_WARNING); - EXPECT_EQ(port->ssl, 1); - EXPECT_EQ(swoole_ssl_is_thread_safety(), true); + test::counter_init(); + auto counter = test::counter_ptr(); - swoole::network::SyncClient c(SW_SOCK_TCP); - c.connect(TEST_HOST, port->port); - c.enable_ssl_encrypt(); - c.send(packet, strlen(packet)); - char buf[1024]; - c.recv(buf, sizeof(buf)); - c.close(); + Mutex lock(true); + lock.lock(); - kill(serv->gs->master_pid, SIGTERM); - }); - t1.detach(); + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onUserWorkerStart = [&lock, port](Server *serv, Worker *worker) { + lock.lock(); + DEBUG() << "onUserWorkerStart: id=" << worker->id << "\n"; + sleep(1); + serv->shutdown(); }; - serv.onWorkerStart = [&lock](Server *serv, int worker_id) { lock->unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + if (worker->id == 0) { + lock.unlock(); + } + test::counter_incr(3); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); @@ -216,61 +590,87 @@ TEST(server, ssl) { string resp = string("Server: ") + string(packet); serv->send(req->info.fd, resp.c_str(), resp.length()); + EXPECT_EQ(serv->get_connection_num(), 1); + EXPECT_EQ(serv->get_primary_port()->get_connection_num(), 1); + + swoole_timer_after(100, [serv](TIMER_PARAMS) { serv->kill_worker(1 + swoole_random_int() % 3); }); + return SW_OK; }; + serv.onShutdown = [](Server *serv) { + DEBUG() << "onShutdown\n"; + test::counter_incr(9); + }; + ASSERT_EQ(serv.start(), 0); - delete lock; + ASSERT_EQ(counter[1], 2); // manager callback + ASSERT_GE(counter[2], 2); // manager timer + ASSERT_GE(counter[3], 4); // worker start + ASSERT_EQ(test::counter_get(9), 1); // onShutdown called + ASSERT_EQ(test::counter_get(10), 1); // onBeforeShutdown called } -TEST(server, dtls) { - Server serv(Server::MODE_BASE); - serv.worker_num = 1; - - SwooleG.running = 1; +// TEST(server, process_send_in_user_worker) { +// test_process_send_in_user_worker(); +// test::wait_all_child_processes(); +// } - sw_logger()->set_level(SW_LOG_WARNING); +#ifdef SW_THREAD +TEST(server, thread) { + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; - Mutex *lock = new Mutex(Mutex::PROCESS_SHARED); - lock->lock(); + swoole_set_trace_flags(SW_TRACE_THREAD); + swoole_set_log_level(SW_LOG_TRACE); + test::counter_init(); - ListenPort *port = serv.add_port((enum swSocketType)(SW_SOCK_UDP | SW_SOCK_SSL), TEST_HOST, 0); - if (!port) { - swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); - exit(2); - } + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); - port->ssl_set_cert_file(test::get_root_path() + "/tests/include/ssl_certs/server.crt"); - port->ssl_set_key_file(test::get_root_path() + "/tests/include/ssl_certs/server.key"); - port->ssl_init(); + mutex lock; + lock.lock(); ASSERT_EQ(serv.create(), SW_OK); - serv.onStart = [&lock](Server *serv) { - thread t1([=]() { - swoole_signal_block_all(); + std::thread t1([&]() { + swoole_signal_block_all(); - lock->lock(); + lock.lock(); - ListenPort *port = serv->get_primary_port(); + usleep(1000); - EXPECT_EQ(port->ssl, 1); + network::SyncClient c(SW_SOCK_TCP); + ASSERT_TRUE(c.connect(TEST_HOST, port->port)); + ASSERT_EQ(c.send(packet, strlen(packet)), strlen(packet)); + char buf[1024]; + ASSERT_EQ(c.recv(buf, sizeof(buf)), strlen(packet) + 8); + string resp = string("Server: ") + string(packet); + ASSERT_MEMEQ(buf, resp.c_str(), resp.length()); + c.close(); - swoole::network::SyncClient c(SW_SOCK_UDP); - c.connect(TEST_HOST, port->port); - c.enable_ssl_encrypt(); - c.send(packet, strlen(packet)); - char buf[1024]; - c.recv(buf, sizeof(buf)); - c.close(); + usleep(1000); - kill(serv->gs->master_pid, SIGTERM); - }); - t1.detach(); + ASSERT_FALSE(serv.get_event_worker_pool()->read_message); + kill(serv.get_master_pid(), SIGIO); + usleep(1000); + ASSERT_TRUE(serv.get_event_worker_pool()->read_message); + + DEBUG() << "shutdown\n"; + + serv.shutdown(); + }); + + serv.onStart = [&lock](Server *serv) { + DEBUG() << "onStart\n"; + lock.unlock(); }; - serv.onWorkerStart = [&lock](Server *serv, int worker_id) { lock->unlock(); }; + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + serv->send_pipe_message(1 - worker->id, SW_STRL(TEST_STR)); + }; serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); @@ -278,143 +678,3289 @@ TEST(server, dtls) { string resp = string("Server: ") + string(packet); serv->send(req->info.fd, resp.c_str(), resp.length()); + DEBUG() << "send\n"; + + EXPECT_EQ(serv->get_connection_num(), 1); + EXPECT_EQ(serv->get_primary_port()->get_connection_num(), 1); + return SW_OK; }; - ASSERT_EQ(serv.start(), 0); + serv.onPipeMessage = [](Server *serv, EventData *req) { + DEBUG() << "onPipeMessage: " << string(req->data, req->info.len) << "\n"; + test::counter_incr(4); + }; - delete lock; + ASSERT_EQ(serv.start(), SW_OK); + t1.join(); + + test::wait_all_child_processes(); + ASSERT_EQ(test::counter_get(4), 2); // onPipeMessage called } -#endif -TEST(server, task_worker) { - Server serv; - serv.worker_num = 1; - serv.task_worker_num = 1; +#ifndef SW_USE_ASAN +TEST(server, task_thread) { + DEBUG() << "new server\n"; + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; + serv.task_worker_num = 2; + swoole_set_log_level(SW_LOG_INFO); + + DEBUG() << "add port\n"; ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); - if (!port) { - swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); - exit(2); - } + ASSERT_TRUE(port); - serv.onTask = [](swServer *serv, swEventData *task) -> int { - EXPECT_EQ(string(task->data, task->info.len), string(packet)); - serv->gs->task_workers.running = 0; - return 0; - }; + mutex lock{}; + lock.lock(); + DEBUG() << "create server\n"; ASSERT_EQ(serv.create(), SW_OK); - ASSERT_EQ(serv.create_task_workers(), SW_OK); - thread t1([&serv]() { - serv.gs->task_workers.running = 1; - serv.gs->task_workers.main_loop(&serv.gs->task_workers, &serv.gs->task_workers.workers[0]); - }); + std::thread t1([&]() { + swoole_signal_block_all(); - usleep(10000); + lock.lock(); - EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); + network::SyncClient c(SW_SOCK_TCP); + ASSERT_TRUE(c.connect(TEST_HOST, port->port)); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + usleep(100000); + serv.shutdown(); + }); + + std::atomic count(0); + + serv.onStart = [&lock](Server *serv) { + DEBUG() << "onStart\n"; + lock.unlock(); + }; + + serv.onWorkerStart = [&lock, &count](Server *serv, Worker *worker) { + ++count; + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onFinish = [](Server *serv, EventData *task) -> int { + SessionId client_fd; + memcpy(&client_fd, task->data, sizeof(client_fd)); + string resp = string("Server: ") + string(packet); + EXPECT_TRUE(serv->send(client_fd, resp.c_str(), resp.length())); + return 0; + }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + return 0; + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + EventData msg; + SessionId client_fd = req->info.fd; + Server::task_pack(&msg, &client_fd, sizeof(client_fd)); + msg.info.ext_flags |= SW_TASK_NONBLOCK; + + int dst_worker_id = -1; + EXPECT_TRUE(serv->task(&msg, &dst_worker_id)); + + return SW_OK; + }; + + DEBUG() << "start server\n"; + ASSERT_EQ(serv.start(), SW_OK); + t1.join(); + + ASSERT_EQ(count.load(), serv.get_core_worker_num()); + test::wait_all_child_processes(); +} + +TEST(server, reload_thread) { + DEBUG() << "new server\n"; + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; + serv.task_worker_num = 2; + + swoole_set_trace_flags(SW_TRACE_ALL); + swoole_set_log_level(SW_LOG_TRACE); + + DEBUG() << "add port\n"; + ASSERT_NE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + + Worker user_worker{}; + ASSERT_NE(serv.add_worker(&user_worker), SW_ERR); + + mutex lock{}; + lock.lock(); + + DEBUG() << "create server\n"; + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_thread_init(); + lock.lock(); + usleep(10000); + EXPECT_TRUE(serv.reload(true)); + EXPECT_FALSE(serv.reload(true)); // reload again should fail + EXPECT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + + DEBUG() << "before shutdown, sleep 1s\n"; + sleep(1); + DEBUG() << "shutdown\n"; + EXPECT_TRUE(serv.shutdown()); + swoole_thread_clean(); + }); + + std::atomic count(0); + + serv.onUserWorkerStart = [&lock, &count](Server *serv, Worker *worker) { + DEBUG() << "onUserWorkerStart: id=" << worker->id << "\n"; + while (serv->running) { + usleep(100000); + } + }; + + serv.onStart = [&lock](Server *serv) { DEBUG() << "onStart\n"; }; + + serv.onManagerStart = [&lock](Server *serv) { + DEBUG() << "onManagerStart\n"; + lock.unlock(); + }; + + serv.onBeforeReload = [](Server *serv) { + DEBUG() << "onBeforeReload: master_pid=" << serv->get_manager_pid() << "\n"; + }; + + serv.onAfterReload = [](Server *serv) { + DEBUG() << "onAfterReload: master_pid=" << serv->get_manager_pid() << "\n"; + }; + + serv.onWorkerStart = [&count](Server *serv, Worker *worker) { + ++count; + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onWorkerStop = [](Server *serv, Worker *worker) { DEBUG() << "onWorkerStop: id=" << worker->id << "\n"; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; + + DEBUG() << "start server\n"; + ASSERT_EQ(serv.start(), SW_OK); + t1.join(); + ASSERT_EQ(count.load(), serv.get_core_worker_num() * 2); + test::wait_all_child_processes(); +} + +TEST(server, reload_thread_2) { + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; + serv.task_worker_num = 2; + + test::counter_init(); + + std::unordered_map flags; + swoole_set_log_level(SW_LOG_INFO); + + ASSERT_NE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + + Worker user_worker{}; + + ASSERT_EQ(serv.add_worker(&user_worker), SW_OK); + + mutex lock; + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::atomic count(0); + + serv.onUserWorkerStart = [](Server *serv, Worker *worker) { + usleep(500000); + test::counter_incr(4, 1); + DEBUG() << "onUserWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onWorkerStart = [&lock, &count](Server *serv, Worker *worker) { + if (++count == serv->get_core_worker_num()) { + lock.unlock(); + } + }; + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; + + serv.onBeforeReload = [&flags](Server *serv) { flags["onBeforeReload"] = true; }; + + serv.onAfterReload = [&flags](Server *serv) { + flags["onAfterReload"] = true; + swoole_timer_after(500, [serv, &flags](auto r1, auto r2) { + flags["shutdown"] = true; + serv->shutdown(); + }); + }; + + serv.onManagerStart = [&flags](Server *serv) { + swoole_timer_after(500, [serv, &flags](auto r1, auto r2) { + flags["reload"] = true; + EXPECT_TRUE(serv->reload(true)); + }); + }; + + serv.onManagerStop = [&flags](Server *serv) { flags["onManagerStop"] = true; }; + + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_TRUE(flags["onBeforeReload"]); + ASSERT_TRUE(flags["onAfterReload"]); + ASSERT_TRUE(flags["onManagerStop"]); + ASSERT_TRUE(flags["reload"]); + ASSERT_TRUE(flags["shutdown"]); + ASSERT_GE(test::counter_get(4), 2); + + test::wait_all_child_processes(); +} + +TEST(server, reload_thread_3) { + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; + + std::unordered_map flags; + swoole_set_log_level(SW_LOG_INFO); + + ASSERT_NE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + + ASSERT_EQ(serv.create(), SW_OK); + + std::atomic count(0); + + serv.onWorkerStart = [&count](Server *serv, Worker *worker) { ++count; }; + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; + serv.onAfterReload = [&flags](Server *serv) { + flags["onAfterReload"] = true; + EXPECT_FALSE(serv->reload(false)); + EXPECT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + swoole_timer_after(500, [serv, &flags](auto r1, auto r2) { + flags["shutdown"] = true; + serv->shutdown(); + }); + }; + + serv.onManagerStart = [&flags](Server *serv) { + swoole_timer_after(500, [serv, &flags](auto r1, auto r2) { + flags["reload"] = true; + EXPECT_TRUE(serv->reload(true)); + }); + }; + + serv.onManagerStop = [&flags](Server *serv) { flags["onManagerStop"] = true; }; + + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_TRUE(flags["onAfterReload"]); + ASSERT_TRUE(flags["onManagerStop"]); + ASSERT_TRUE(flags["reload"]); + ASSERT_TRUE(flags["shutdown"]); + + ASSERT_GE(count, serv.worker_num * 2); + + test::wait_all_child_processes(); +} +#endif +#endif + +TEST(server, reload_all_workers) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.max_wait_time = 1; + serv.task_enable_coroutine = true; + + test::counter_init(); + + swoole_set_log_level(SW_LOG_WARNING); + + serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + serv.onReceive = [](Server *serv, RecvData *data) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onBeforeReload = [](Server *serv) { + test::counter_incr(10); + DEBUG() << "onBeforeReload: master_pid=" << beforeReloadPid << "\n"; + }; + + serv.onAfterReload = [](Server *serv) { + DEBUG() << "onAfterReload: master_pid=" << beforeReloadPid << "\n"; + test::counter_incr(11); + }; + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + std::string filename = "/tmp/worker_1.pid"; + if (worker->id == 1) { + if (access(filename.c_str(), R_OK) == -1) { + ofstream file(filename); + file << getpid(); + file.close(); + kill(serv->gs->manager_pid, SIGUSR2); + sleep(1); + kill(serv->gs->manager_pid, SIGUSR1); + } else { + char buf[10] = {0}; + ifstream file(filename.c_str()); + file >> buf; + file.close(); + + int oldPid = 0; + stringstream stringPid(buf); + stringPid >> oldPid; + + EXPECT_TRUE(oldPid != getpid()); + + sleep(1); + remove(filename.c_str()); + kill(serv->gs->master_pid, SIGTERM); + } + } + + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + ASSERT_EQ(serv.start(), 0); + ASSERT_EQ(test::counter_get(10), 2); // onBeforeReload called + ASSERT_EQ(test::counter_get(11), 2); // onAfterReload called +} + +TEST(server, reload_all_workers2) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.max_wait_time = 1; + swoole_set_log_level(SW_LOG_WARNING); + + test::counter_init(); + serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + serv.onReceive = [](Server *serv, RecvData *data) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + std::string filename = "/tmp/worker_2.pid"; + if (worker->id == 1) { + if (access(filename.c_str(), R_OK) == -1) { + ofstream file(filename); + file << getpid(); + file.close(); + kill(serv->gs->master_pid, SIGUSR2); + sleep(1); + kill(serv->gs->master_pid, SIGUSR1); + } else { + char buf[10] = {0}; + ifstream file(filename.c_str()); + file >> buf; + file.close(); + + int oldPid = 0; + stringstream stringPid(buf); + stringPid >> oldPid; + + EXPECT_TRUE(oldPid != getpid()); + + sleep(1); + remove(filename.c_str()); + kill(serv->gs->master_pid, SIGTERM); + } + } + + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onBeforeReload = [](Server *serv) { + test::counter_incr(10); + DEBUG() << "onBeforeReload: master_pid=" << beforeReloadPid << "\n"; + }; + + serv.onAfterReload = [](Server *serv) { + DEBUG() << "onAfterReload: master_pid=" << beforeReloadPid << "\n"; + test::counter_incr(11); + }; + + ASSERT_EQ(serv.start(), 0); + ASSERT_EQ(test::counter_get(10), 2); // onBeforeReload called + ASSERT_EQ(test::counter_get(11), 2); // onAfterReload called +} + +TEST(server, kill_user_workers) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + serv.task_worker_num = 2; + serv.max_wait_time = 1; + swoole_set_log_level(SW_LOG_WARNING); + + auto *worker1 = new Worker(); + auto *worker2 = new Worker(); + ASSERT_EQ(serv.add_worker(worker1), worker1->id); + ASSERT_EQ(serv.add_worker(worker2), worker2->id); + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onUserWorkerStart = [&](Server *serv, Worker *worker) { + EXPECT_GT(worker->id, 0); + while (true) { + sleep(1); + } + }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + while (true) { + sleep(1); + } + return 0; + }; + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id == 1) { + sleep(1); + kill(serv->get_manager_pid(), SIGTERM); + } + }; + + serv.onReceive = [](Server *serv, RecvData *data) -> int { return 0; }; + + ASSERT_EQ(serv.start(), 0); + delete worker1; + delete worker2; +} + +TEST(server, force_kill_all_workers) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 3; + serv.max_wait_time = 1; + swoole_set_log_level(SW_LOG_WARNING); + + auto *worker1 = new Worker(); + auto *worker2 = new Worker(); + ASSERT_EQ(serv.add_worker(worker1), worker1->id); + ASSERT_EQ(serv.add_worker(worker2), worker2->id); + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onUserWorkerStart = [&](Server *serv, Worker *worker) { + test::counter_incr(1); + DEBUG() << "onUserWorkerStart: id=" << worker->id << "\n"; + while (true) { + sleep(1); + } + }; + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + test::counter_incr(1); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + if (serv->is_task_worker()) { + while (true) { + sleep(1); + } + } else { + swoole_timer_tick(10000, [serv](TIMER_PARAMS) {}); + } + }; + + serv.onReceive = [](Server *serv, RecvData *data) -> int { return 0; }; + + serv.onManagerStart = [](Server *serv) { swoole_timer_after(200, [serv](TIMER_PARAMS) { serv->shutdown(); }); }; + + ASSERT_EQ(serv.start(), 0); + ASSERT_EQ(test::counter_get(1), 7); + + delete worker1; + delete worker2; +} + +TEST(server, kill_user_workers1) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + serv.task_worker_num = 2; + serv.max_wait_time = 1; + swoole_set_log_level(SW_LOG_WARNING); + + Worker *worker1 = new Worker(); + Worker *worker2 = new Worker(); + ASSERT_EQ(serv.add_worker(worker1), worker1->id); + ASSERT_EQ(serv.add_worker(worker2), worker2->id); + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onUserWorkerStart = [&](Server *serv, Worker *worker) { EXPECT_GT(worker->id, 0); }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + while (1) { + } + }; + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id == 1) { + sleep(1); + kill(serv->gs->master_pid, SIGTERM); + } + }; + + serv.onReceive = [](Server *serv, RecvData *data) -> int { return 0; }; + + ASSERT_EQ(serv.start(), 0); +} + +TEST(server, create_task_worker_fail) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + serv.task_worker_num = 2; + serv.task_enable_coroutine = true; + serv.task_ipc_mode = Server::TASK_IPC_MSGQUEUE; + swoole_set_log_level(SW_LOG_WARNING); + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + ASSERT_EQ(serv.create(), SW_ERR); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); +} + +TEST(server, ssl) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port(static_cast(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->ssl_init(); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + EXPECT_EQ(port->ssl, 1); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.enable_ssl_encrypt(); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + // bad SSL connection, send plain text packet to SSL server + network::SyncClient c2(SW_SOCK_TCP); + c2.connect(TEST_HOST, port->port); + c2.send(packet, strlen(packet)); + ASSERT_EQ(c2.recv(buf, sizeof(buf)), 0); + c2.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->send(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; +} + +TEST(server, ssl_error) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + Mutex lock(true); + lock.lock(); + + ListenPort *port = serv.add_port(static_cast(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server-not-exists.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server-not-exists.key"); + ASSERT_FALSE(port->ssl_init()); + ASSERT_ERREQ(SW_ERROR_WRONG_OPERATION); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([&lock, serv]() { + swoole_signal_block_all(); + + lock.lock(); + + ListenPort *port = serv->get_primary_port(); + EXPECT_EQ(port->ssl, 1); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.enable_ssl_encrypt(); + c.send(packet, strlen(packet)); + char buf[1024]; + ASSERT_EQ(c.recv(buf, sizeof(buf)), 0); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; + + serv.onConnect = [](Server *serv, DataHead *req) { test::counter_incr(0); }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + ASSERT_EQ(test::counter_get(0), 0); +} + +TEST(server, ssl_write) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + Mutex lock(true); + lock.lock(); + + ListenPort *port = serv.add_port(static_cast(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + ASSERT_TRUE(port->ssl_init()); + + ASSERT_EQ(serv.create(), SW_OK); + + String wbuf(4 * 1024 * 1024); + wbuf.append_random_bytes(wbuf.size); + + thread t1; + + serv.onStart = [&lock, &t1, &wbuf](Server *serv) { + t1 = thread([&lock, serv, &wbuf]() { + swoole_signal_block_all(); + + lock.lock(); + + ListenPort *port = serv->get_primary_port(); + EXPECT_EQ(port->ssl, 1); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.enable_ssl_encrypt(); + c.send(packet, strlen(packet)); + + String rbuf(2 * 1024 * 1024); + + while (true) { + size_t recv_n = rbuf.size - rbuf.length; + if (recv_n > 65536) { + recv_n = 65536; + } + auto n = c.recv(rbuf.str + rbuf.length, rbuf.size - rbuf.length); + if (n <= 0) { + break; + } + rbuf.length += n; + usleep(5000); + } + + ASSERT_MEMEQ(rbuf.str, wbuf.str, rbuf.length); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onReceive = [&wbuf](Server *serv, RecvData *req) -> int { + EXPECT_TRUE(serv->send(req->session_id(), wbuf.str, wbuf.length)); + test::counter_incr(0); + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + ASSERT_EQ(test::counter_get(0), 1); +} + +TEST(server, dtls) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + auto *lock = new Mutex(true); + lock->lock(); + + auto port = serv.add_port((enum swSocketType)(SW_SOCK_UDP | SW_SOCK_SSL), TEST_HOST, 0); + ASSERT_NE(port, nullptr); + ASSERT_TRUE(port->is_dgram()); + ASSERT_EQ(port->object_id, 1); + ASSERT_TRUE(test::is_valid_fd(port->get_fd())); + + auto port6 = serv.add_port((enum swSocketType)(SW_SOCK_UDP6 | SW_SOCK_SSL), TEST_HOST6, 0); + ASSERT_NE(port6, nullptr); + ASSERT_TRUE(port->is_dgram()); + ASSERT_EQ(port6->object_id, 2); + ASSERT_TRUE(test::is_valid_fd(port6->get_fd())); + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->ssl_init(); + + port6->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port6->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port6->ssl_init(); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + auto port = serv->ports.at(0); + EXPECT_EQ(port->ssl, 1); + + auto cli_fn = [](network::SyncClient &c) { + c.enable_ssl_encrypt(); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + }; + + network::SyncClient c(SW_SOCK_UDP); + c.connect(TEST_HOST, port->port); + cli_fn(c); + + auto port6 = serv->ports.at(1); + EXPECT_EQ(port6->ssl, 1); + + network::SyncClient c2(SW_SOCK_UDP6); + c2.connect(TEST_HOST6, port6->port); + cli_fn(c2); + + usleep(10000); + serv->shutdown(); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->send(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; +} + +TEST(server, dtls2) { + Server *server = new Server(Server::MODE_PROCESS); + server->worker_num = 2; + server->single_thread = false; + ListenPort *port = server->add_port((enum swSocketType)(SW_SOCK_UDP | SW_SOCK_SSL), TEST_HOST, 0); + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->ssl_init(); + + server->create(); + server->onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->send(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + pid_t pid = swoole_fork(0); + + if (pid > 0) { + server->start(); + delete server; + } + + if (pid == 0) { + sleep(1); + auto port = server->get_primary_port(); + + network::SyncClient c(SW_SOCK_UDP); + c.connect(TEST_HOST, port->port); + c.enable_ssl_encrypt(); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + kill(server->get_master_pid(), SIGTERM); + exit(0); + } +} + +static void test_ssl_client_cert(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_INFO); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port((enum swSocketType)(SW_SOCK_TCP | SW_SOCK_SSL), TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + port->set_ssl_cert_file(test::get_ssl_dir() + "/server.crt"); + port->set_ssl_key_file(test::get_ssl_dir() + "/server.key"); + port->set_ssl_verify_peer(true); + port->set_ssl_allow_self_signed(true); + port->set_ssl_client_cert_file(test::get_ssl_dir() + "/ca-cert.pem"); + port->ssl_init(); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + EXPECT_EQ(port->ssl, 1); + + network::SyncClient c(SW_SOCK_TCP); + c.enable_ssl_encrypt(); + c.get_client()->set_ssl_cert_file(test::get_ssl_dir() + "/client-cert.pem"); + c.get_client()->set_ssl_key_file(test::get_ssl_dir() + "/client-key.pem"); + c.connect(TEST_HOST, port->port); + EXPECT_EQ(c.send(packet, strlen(packet)), strlen(packet)); + + char buf[1024]; + EXPECT_GT(c.recv(buf, sizeof(buf)), 0); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->send(req->info.fd, resp.c_str(), resp.length()); + + auto conn = serv->get_connection_by_session_id(req->session_id()); + EXPECT_NE(conn->ssl_client_cert, nullptr); + EXPECT_GT(conn->ssl_client_cert->length, 16); + + char *buffer = NULL; + size_t size = 0; + FILE *stream = open_memstream(&buffer, &size); + swoole_set_stdout_stream(stream); + swoole::test::dump_cert_info(conn->ssl_client_cert->str, conn->ssl_client_cert->length); + fflush(stream); + swoole_set_stdout_stream(stdout); + + EXPECT_NE(strstr(buffer, "organizationName: swoole"), nullptr); + + fclose(stream); + free(buffer); + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; +} + +TEST(server, ssl_client_cert_1) { + test_ssl_client_cert(Server::MODE_BASE); +} + +TEST(server, ssl_client_cert_2) { + test_ssl_client_cert(Server::MODE_PROCESS); +} + +TEST(server, ssl_client_cert_3) { + test_ssl_client_cert(Server::MODE_THREAD); +} + +TEST(server, task_worker) { + Server serv; + serv.worker_num = 1; + serv.task_worker_num = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(serv->get_tasking_num(), 1); + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + serv->get_task_worker_pool()->running = 0; + serv->gs->task_count++; + serv->gs->tasking_num--; + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1([&serv]() { + auto pool = serv.get_task_worker_pool(); + pool->running = true; + pool->main_loop(pool, &pool->workers[0]); + EXPECT_EQ(serv.get_tasking_num(), 0); + serv.gs->tasking_num--; + EXPECT_EQ(serv.get_tasking_num(), 0); + EXPECT_EQ(serv.get_idle_task_worker_num(), serv.task_worker_num); + }); + + usleep(10000); + + EventData buf; + memset(&buf.info, 0, sizeof(buf.info)); + + buf.info.ext_flags = SW_TASK_NOREPLY; + buf.info.len = strlen(packet); + memcpy(buf.data, packet, strlen(packet)); + + int _dst_worker_id = 0; + + ASSERT_TRUE(serv.task(&buf, &_dst_worker_id)); + ASSERT_EQ(serv.gs->task_count, 1); + + t1.join(); + serv.get_task_worker_pool()->destroy(); + + ASSERT_EQ(serv.gs->task_count, 2); +} + +TEST(server, task_worker2) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + serv.task_worker_num = 2; + test::counter_init(); + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onPipeMessage = [](Server *serv, EventData *task) { + EXPECT_MEMEQ(task->data, TEST_STR, strlen(TEST_STR)); + test::counter_incr(7); + }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + if (worker->id == 0) { + swoole_timer_after(50, [serv](TIMER_PARAMS) { + EventData ev; + ev.info = {}; + ev.info.type = SW_SERVER_EVENT_SHUTDOWN; + ev.info.len = 0; + DEBUG() << "send SW_SERVER_EVENT_SHUTDOWN packet\n"; + ASSERT_GT(serv->send_to_worker_from_worker(1, &ev, SW_PIPE_MASTER | SW_PIPE_NONBLOCK), 0); + }); + + swoole_timer_after(60, + [serv](TIMER_PARAMS) { ASSERT_TRUE(serv->send_pipe_message(2, SW_STRL(TEST_STR))); }); + + swoole_timer_after(70, [serv](TIMER_PARAMS) { + EventData ev; + ev.info = {}; + ev.info.type = SW_SERVER_EVENT_SHUTDOWN + 99; + ev.info.len = 0; + DEBUG() << "send error type packet\n"; + ASSERT_GT(serv->send_to_worker_from_worker(0, &ev, SW_PIPE_MASTER | SW_PIPE_NONBLOCK), 0); + }); + + swoole_timer_after(100, [serv](TIMER_PARAMS) { serv->shutdown(); }); + } + test::counter_incr(1); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_EQ(test::counter_get(1), 4); // onWorkerStart + ASSERT_EQ(test::counter_get(7), 1); // onPipeMessage +} + +TEST(server, task_worker_3) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + serv.task_worker_num = 2; + test::counter_init(); + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + if (test::counter_incr(1) == 5) { + swoole_timer_after(100, [serv](TIMER_PARAMS) { serv->shutdown(); }); + } + if (worker->id == 0) { + swoole_timer_after(50, [serv](TIMER_PARAMS) { kill(serv->get_worker_pid(2), SIGTERM); }); + swoole_timer_after(60, [serv](TIMER_PARAMS) { kill(serv->get_manager_pid(), SIGRTMIN); }); + } + if (worker->id == 1 && test::counter_get(30) == 0) { + test::counter_set(30, 1); + swoole_timer_after(20, [serv](TIMER_PARAMS) { serv->kill_worker(-1); }); + } + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_EQ(test::counter_get(1), 5); // onWorkerStart +} + +TEST(server, reload_single_process) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + test::counter_init(); + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + if (worker->id == 0) { + swoole_timer_after(50, [serv](TIMER_PARAMS) { + ASSERT_FALSE(serv->reload(true)); + ASSERT_ERREQ(SW_ERROR_OPERATION_NOT_SUPPORT); + swoole_timer_after(80, [serv](TIMER_PARAMS) { serv->shutdown(); }); + }); + } + test::counter_incr(1); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_EQ(test::counter_get(1), 1); // onWorkerStart +} + +TEST(server, reload_no_task_worker) { + Server serv(Server::MODE_BASE); + serv.worker_num = 2; + test::counter_init(); + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + serv.onTask = [](Server *serv, EventData *task) -> int { return 0; }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + if (worker->id == 0) { + swoole_timer_after(50, [serv](TIMER_PARAMS) { + ASSERT_TRUE(serv->reload(false)); + swoole_timer_after(80, [serv](TIMER_PARAMS) { serv->shutdown(); }); + }); + } + test::counter_incr(1); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return 0; }; + + ASSERT_EQ(serv.create(), SW_OK); + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_EQ(test::counter_get(1), 2); // onWorkerStart +} + +static void test_task(Server::Mode mode, uint8_t task_ipc_mode = Server::TASK_IPC_UNIXSOCK) { + Server serv(mode); + serv.worker_num = 2; + serv.task_ipc_mode = task_ipc_mode; + serv.task_worker_num = 3; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + return 0; + }; + + serv.onFinish = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + if (worker->id == 1) { + int _dst_worker_id = 0; + + EventData buf{}; + memset(&buf.info, 0, sizeof(buf.info)); + buf.info.len = strlen(packet); + memcpy(buf.data, packet, strlen(packet)); + buf.info.reactor_id = worker->id; + buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_CALLBACK); + ASSERT_TRUE(serv->task(&buf, &_dst_worker_id)); + sleep(1); + serv->shutdown(); + } + }; + + ASSERT_EQ(serv.start(), 0); +} + +// PHP_METHOD(swoole_server, task) +TEST(server, task_base) { + test_task(Server::MODE_BASE); +} + +TEST(server, task_process) { + test_task(Server::MODE_PROCESS); +} + +TEST(server, task_ipc_stream) { + test_task(Server::MODE_PROCESS, Server::TASK_IPC_STREAM); +} + +// static PHP_METHOD(swoole_server, taskCo) +TEST(server, task_worker3) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 3; + serv.task_enable_coroutine = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + return 0; + }; + + serv.onFinish = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id == 1) { + int _dst_worker_id = 0; + + EventData buf{}; + memset(&buf.info, 0, sizeof(buf.info)); + buf.info.len = strlen(packet); + memcpy(buf.data, packet, strlen(packet)); + buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); + buf.info.reactor_id = worker->id; + serv->get_task_worker_pool()->dispatch(&buf, &_dst_worker_id); + sleep(1); + kill(serv->gs->master_pid, SIGTERM); + } + }; + + ASSERT_EQ(serv.start(), 0); +} + +// static PHP_METHOD(swoole_server, taskwait) +TEST(server, task_worker4) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 3; + serv.task_enable_coroutine = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + return 0; + }; + + serv.onFinish = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id == 1) { + int _dst_worker_id = 0; + + EventData buf{}; + memset(&buf.info, 0, sizeof(buf.info)); + buf.info.len = strlen(packet); + memcpy(buf.data, packet, strlen(packet)); + buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); + buf.info.reactor_id = worker->id; + serv->get_task_worker_pool()->dispatch(&buf, &_dst_worker_id); + sleep(1); + + EventData *task_result = serv->get_task_result(); + sw_memset_zero(task_result, sizeof(*task_result)); + memset(&buf.info, 0, sizeof(buf.info)); + buf.info.len = strlen(packet); + memcpy(buf.data, packet, strlen(packet)); + buf.info.reactor_id = worker->id; + sw_atomic_fetch_add(&serv->gs->tasking_num, 1); + serv->get_task_worker_pool()->dispatch(&buf, &_dst_worker_id); + sw_atomic_fetch_add(&serv->gs->tasking_num, 0); + kill(serv->gs->master_pid, SIGTERM); + } + }; + + ASSERT_EQ(serv.start(), 0); +} + +TEST(server, task_sync_multi_task) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 3; + + std::vector tasks; + std::vector results; + int n_task = 16; + constexpr size_t len_task = SW_IPC_MAX_SIZE * 2; + SW_LOOP_N(n_task) { + char data[len_task] = {}; + swoole_random_string(data, len_task - 1); + tasks.push_back(string(data, len_task - 1)); + } + + results.resize(n_task); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + PacketPtr packet{}; + String buffer(32 * 1024); + if (!Server::task_unpack(task, &buffer, &packet)) { + return -1; + } + Server::task_dump(task); + EXPECT_TRUE(serv->finish(packet.data, packet.length, 0, task)); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + SwooleG.current_task_id = 100; + + serv.onWorkerStart = [&tasks, &results](Server *serv, Worker *worker) { + if (worker->id == 1) { + Server::MultiTask mt(tasks.size()); + mt.pack = [tasks](uint16_t i, EventData *buf) -> TaskId { + auto &task = tasks.at(i); + if (!Server::task_pack(buf, task.c_str(), task.length())) { + return -1; + } else { + return buf->info.fd; + } + }; + + mt.unpack = [&tasks, &results](uint16_t i, EventData *result) { + String buffer(32 * 1024); + PacketPtr packet; + if (Server::task_unpack(result, &buffer, &packet)) { + results[i] = std::string(packet.data, packet.length); + } + }; + + mt.fail = [&results](uint16_t i) { DEBUG() << "task failed: " << i << std::endl; }; + + EXPECT_TRUE(serv->task_sync(mt, 10)); + + SW_LOOP_N(tasks.size()) { + EXPECT_EQ(tasks[i], results[i]); + } + + kill(serv->gs->master_pid, SIGTERM); + } + }; + + ASSERT_EQ(serv.start(), 0); +} + +TEST(server, task_sync) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + Server::task_dump(task); + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&](Server *serv, Worker *worker) { + if (worker->id == 1) { + int _dst_worker_id = -1; + EventData buf{}; + Server::task_pack(&buf, packet, strlen(packet)); + EXPECT_TRUE(serv->task_sync(&buf, &_dst_worker_id, 0.5)); + auto task_result = serv->get_task_result(); + EXPECT_EQ(string(task_result->data, task_result->info.len), string(packet)); + kill(serv->gs->master_pid, SIGTERM); + } + }; + + ASSERT_EQ(serv.start(), 0); +} + +static void test_task_ipc(Server &serv) { + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + EXPECT_TRUE(serv->finish(task->data, task->info.len, 0, task)); + DEBUG() << "onTask: " << task->info.len << " bytes\n"; + return 0; + }; + + serv.onFinish = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + usleep(100000); + DEBUG() << "onFinish: " << task->info.len << " bytes\n"; + serv->shutdown(); + return 0; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + if (worker->id == 1) { + int _dst_worker_id = -1; + EventData buf{}; + Server::task_pack(&buf, packet, strlen(packet)); + buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_CALLBACK); + EXPECT_TRUE(serv->task(&buf, &_dst_worker_id)); + } + + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onBeforeShutdown = [](Server *serv) { DEBUG() << "onBeforeShutdown\n"; }; + + ASSERT_EQ(serv.start(), 0); +} + +TEST(server, task_ipc_queue_1) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.task_ipc_mode = Server::TASK_IPC_MSGQUEUE; + + test_task_ipc(serv); +} + +TEST(server, task_ipc_queue_2) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.task_ipc_mode = Server::TASK_IPC_PREEMPTIVE; + + test_task_ipc(serv); +} + +TEST(server, task_ipc_queue_3) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.task_ipc_mode = Server::TASK_IPC_STREAM; + + test_task_ipc(serv); +} + +TEST(server, task_ipc_queue_4) { + Server serv(Server::MODE_BASE); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.task_ipc_mode = Server::TASK_IPC_MSGQUEUE; + + test_task_ipc(serv); +} + +TEST(server, task_ipc_queue_5) { + Server serv(Server::MODE_THREAD); + serv.worker_num = 2; + serv.task_worker_num = 2; + serv.task_ipc_mode = Server::TASK_IPC_MSGQUEUE; + + test::wait_all_child_processes(); + + test_task_ipc(serv); +} + +TEST(server, max_connection) { + Server serv; + + auto ori_max_sockets = SwooleG.max_sockets; + + serv.set_max_connection(0); + ASSERT_EQ(serv.get_max_connection(), SW_MIN(SW_MAX_CONNECTION, SwooleG.max_sockets)); + + serv.set_max_connection(SwooleG.max_sockets + 13); + ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets); + + serv.set_max_connection(SwooleG.max_sockets - 13); + ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets - 13); + + SwooleG.max_sockets = SW_SESSION_LIST_SIZE + 1024; + serv.set_max_connection(SW_SESSION_LIST_SIZE + 999); + ASSERT_EQ(serv.get_max_connection(), SW_SESSION_LIST_SIZE); + SwooleG.max_sockets = ori_max_sockets; + + uint32_t last_value = serv.get_max_connection(); + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + + serv.create(); + + serv.set_max_connection(100); + ASSERT_EQ(serv.get_max_connection(), last_value); +} + +TEST(server, min_connection) { + Server serv; + + serv.task_worker_num = 14; + serv.worker_num = 5; + + serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + + serv.set_max_connection(15); + serv.create(); + ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets); +} + +TEST(server, worker_num) { + Server serv; + + serv.worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU + 99; + serv.task_worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU + 99; + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + + serv.create(); + + ASSERT_EQ(serv.worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); + ASSERT_EQ(serv.task_worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); +} + +TEST(server, reactor_num_base) { + Server serv(Server::MODE_BASE); + serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + serv.create(); + + ASSERT_EQ(serv.reactor_num, serv.worker_num); +} + +TEST(server, reactor_num_large) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU; + serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + serv.create(); + + ASSERT_EQ(serv.reactor_num, SW_CPU_NUM * SW_MAX_THREAD_NCPU); +} + +TEST(server, reactor_num_large2) { + Server serv(Server::MODE_PROCESS); + serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + serv.create(); + + ASSERT_EQ(serv.reactor_num, serv.worker_num); +} + +TEST(server, reactor_num_zero) { + Server serv; + serv.reactor_num = 0; + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + serv.create(); + + ASSERT_EQ(serv.reactor_num, SW_CPU_NUM); +} + +void test_command(enum Server::Mode _mode) { + Server serv(_mode); + serv.worker_num = 4; + serv.task_worker_num = 4; + serv.reactor_num = 2; + swoole_set_log_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + ASSERT_EQ(serv.create(), SW_OK); + + serv.add_command("test", Server::Command::ALL_PROCESS, [](Server *, const std::string &msg) -> std::string { + return std::string("json result, ") + msg; + }); + + serv.onStart = [](Server *serv) { + static Server::Command::Callback fn = [&](Server *serv, const std::string &msg) { + usleep(50000); + if (msg == "json result, hello world [0]") { + if (serv->is_base_mode()) { + goto _send_to_event_worker; + } else { + serv->command(1, Server::Command::REACTOR_THREAD, "test", "hello world [1]", fn); + } + } else if (msg == "json result, hello world [1]") { + _send_to_event_worker: + serv->command(1, Server::Command::EVENT_WORKER, "test", "hello world [2]", fn); + } else if (msg == "json result, hello world [2]") { + serv->command(1, Server::Command::TASK_WORKER, "test", "hello world [3]", fn); + } else if (msg == "json result, hello world [3]") { + serv->command(1, Server::Command::MANAGER, "test", "hello world [4]", fn); + } else if (msg == "json result, hello world [4]") { + swoole_timer_after(50, [serv](Timer *, TimerNode *) { serv->shutdown(); }); + } else { + ASSERT_TRUE(0); + } + }; + serv->command(1, Server::Command::MASTER, "test", "hello world [0]", fn); + }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + + }; + + serv.onTask = [](Server *, EventData *) -> int { return SW_OK; }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->send(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); +} + +TEST(server, command_1) { + test_command(Server::MODE_PROCESS); +} + +TEST(server, command_2) { + test_command(Server::MODE_BASE); +} + +TEST(server, sendwait) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + mutex lock; + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + + lock.lock(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + kill(getpid(), SIGTERM); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->sendwait(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + serv.start(); + t1.join(); +} + +TEST(server, system) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + mutex lock; + lock.lock(); + + int fd = socket(AF_INET, SOCK_STREAM, 0); + int svr_port = swoole::test::get_random_port(); + struct sockaddr_in serv_addr; + bzero(&serv_addr, sizeof(serv_addr)); + serv_addr.sin_addr.s_addr = inet_addr(TEST_HOST); + serv_addr.sin_port = htons(svr_port); + serv_addr.sin_family = AF_INET; + bind(fd, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr)); + listen(fd, 1024); + + setenv("LISTEN_FDS_START", to_string(fd).c_str(), 1); + setenv("LISTEN_FDS", "1", 1); + setenv("LISTEN_PID", to_string(getpid()).c_str(), 1); + + EXPECT_GT(serv.add_systemd_socket(), 0); + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + lock.lock(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, svr_port); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + kill(getpid(), SIGTERM); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + + string resp = string("Server: ") + string(packet); + serv->sendwait(req->info.fd, resp.c_str(), resp.length()); + + return SW_OK; + }; + + serv.start(); + t1.join(); +} + +TEST(server, reopen_log) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + swoole_set_log_level(SW_LOG_WARNING); + string filename = TEST_LOG_FILE; + swoole_set_log_file(filename.c_str()); + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + ASSERT_EQ(serv.create(), SW_OK); + + serv.onWorkerStart = [&filename](Server *serv, Worker *worker) { + if (worker->id != 0) { + return; + } + EXPECT_TRUE(access(filename.c_str(), R_OK) != -1); + usleep(10000); + unlink(filename.c_str()); + EXPECT_TRUE(access(filename.c_str(), R_OK) == -1); + kill(serv->gs->master_pid, SIGRTMIN); + sleep(2); + EXPECT_TRUE(access(filename.c_str(), R_OK) != -1); + kill(serv->gs->master_pid, SIGTERM); + }; + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + ASSERT_EQ(serv.start(), 0); + remove(filename.c_str()); +} + +TEST(server, reopen_log2) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + swoole_set_log_level(SW_LOG_DEBUG); + string filename = TEST_LOG_FILE; + swoole_set_log_file(filename.c_str()); + + ASSERT_TRUE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0)); + ASSERT_EQ(serv.create(), SW_OK); + + serv.onStart = [](Server *serv) { + swoole_timer_after(50, [serv](TIMER_PARAMS) { + serv->signal_handler_reopen_logger(); + swoole_timer_after(50, [serv](TIMER_PARAMS) { serv->shutdown(); }); + }); + }; + + serv.onWorkerStart = [&filename](Server *serv, Worker *worker) { test::counter_incr(0, 1); }; + + serv.onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + ASSERT_EQ(serv.start(), 0); + remove(filename.c_str()); +} + +TEST(server, udp_packet) { + Server *server = new Server(Server::MODE_PROCESS); + server->worker_num = 2; + server->add_port(SW_SOCK_UDP, TEST_HOST, 0); + + server->create(); + server->onPacket = [](Server *serv, RecvData *req) { + DgramPacket *recv_data = (DgramPacket *) req->data; + EXPECT_EQ(string(recv_data->data, recv_data->length), string(packet)); + network::Socket *server_socket = serv->get_server_socket(req->info.server_fd); + string resp = string(packet); + server_socket->sendto(recv_data->socket_addr, resp.c_str(), resp.length(), 0); + return SW_OK; + }; + + server->onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + pid_t pid = swoole_fork(0); + + if (pid > 0) { + server->start(); + int status; + waitpid(pid, &status, 0); + } else if (pid == 0) { + sleep(1); + auto port = server->get_primary_port(); + + network::Client cli(SW_SOCK_UDP, false); + int ret = cli.connect(TEST_HOST, port->port, -1, 0); + EXPECT_EQ(ret, 0); + ret = cli.send(packet, strlen(packet), 0); + EXPECT_GT(ret, 0); + + char buf[1024]; + sleep(1); + cli.recv(buf, 128, 0); + ASSERT_MEMEQ(buf, packet, strlen(packet)); + cli.close(); + + kill(server->get_master_pid(), SIGTERM); + exit(0); + } +} + +TEST(server, protocols) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + + port->open_eof_check = true; + ASSERT_STREQ(port->get_protocols(), "eof"); + port->open_eof_check = false; + + port->open_length_check = true; + ASSERT_STREQ(port->get_protocols(), "length"); + port->open_length_check = false; + + port->open_http_protocol = true; + ASSERT_STREQ(port->get_protocols(), "http"); + port->open_http_protocol = false; + + port->open_http_protocol = true; + port->open_http2_protocol = true; + port->open_websocket_protocol = true; + ASSERT_STREQ(port->get_protocols(), "http|http2|websocket"); + port->open_http2_protocol = false; + port->open_websocket_protocol = false; + port->open_http_protocol = false; + + port->open_http_protocol = true; + port->open_http2_protocol = true; + ASSERT_STREQ(port->get_protocols(), "http|http2"); + port->open_http2_protocol = false; + port->open_http_protocol = false; + + port->open_http_protocol = true; + port->open_websocket_protocol = true; + ASSERT_STREQ(port->get_protocols(), "http|websocket"); + port->open_websocket_protocol = false; + port->open_http_protocol = false; + + port->open_mqtt_protocol = true; + ASSERT_STREQ(port->get_protocols(), "mqtt"); + port->open_mqtt_protocol = false; + + port->open_redis_protocol = true; + ASSERT_STREQ(port->get_protocols(), "redis"); + port->open_redis_protocol = false; + + port->clear_protocol(); + ASSERT_EQ(port->open_eof_check, 0); + ASSERT_EQ(port->open_length_check, 0); + ASSERT_EQ(port->open_http_protocol, 0); + ASSERT_EQ(port->open_websocket_protocol, 0); + ASSERT_EQ(port->open_http2_protocol, 0); + ASSERT_EQ(port->open_mqtt_protocol, 0); + ASSERT_EQ(port->open_redis_protocol, 0); + ASSERT_STREQ(port->get_protocols(), "raw"); +} + +TEST(server, pipe_message) { + Server *server = new Server(Server::MODE_PROCESS); + server->worker_num = 2; + server->add_port(SW_SOCK_TCP, TEST_HOST, 0); + + server->create(); + server->onPipeMessage = [](Server *serv, EventData *req) -> int { + EXPECT_EQ(string(req->data, req->info.len), string(packet)); + return SW_OK; + }; + + server->onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + server->onWorkerStart = [&](Server *server, Worker *worker) { + if (worker->id == 1) { + EventData buf{}; + string data = string(packet); + + memset(&buf.info, 0, sizeof(buf.info)); + ASSERT_TRUE(Server::task_pack(&buf, data.c_str(), data.length())); + ASSERT_TRUE(server->send_pipe_message(worker->id - 1, &buf)); + sleep(1); + + kill(server->get_master_pid(), SIGTERM); + } + }; + + server->start(); +} + +TEST(server, forward_message) { + Server serv(Server::MODE_BASE); + serv.worker_num = 2; + + swoole_set_log_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + swoole::Mutex lock(true); + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + + lock.lock(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + kill(getpid(), SIGTERM); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onPipeMessage = [](Server *serv, EventData *req) -> void { + SessionId client_fd; + memcpy(&client_fd, req->data, sizeof(client_fd)); + string resp = string("Server: ") + string(packet); + serv->send(client_fd, resp.c_str(), resp.length()); + }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + EventData msg; + SessionId client_fd = req->info.fd; + Server::task_pack(&msg, &client_fd, sizeof(client_fd)); + EXPECT_TRUE(serv->send_pipe_message(1 - swoole_get_worker_id(), &msg)); + return SW_OK; + }; + + serv.start(); + t1.join(); +} + +TEST(server, abnormal_pipeline_data) { + Server *server = new Server(Server::MODE_PROCESS); + server->worker_num = 2; + server->add_port(SW_SOCK_TCP, TEST_HOST, 0); + + uint64_t msg_id = swoole_rand(1, INT_MAX); + string filename = TEST_LOG_FILE; + swoole_set_log_file(filename.c_str()); + + server->create(); + + server->onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + server->onWorkerStart = [&](Server *server, Worker *worker) { + if (worker->id == 1) { + auto send_fn = [server](int flags, uint64_t msg_id) { + auto sock = server->get_worker_pipe_master(0); + size_t len = swoole_rand(1000, 8000); + EventData ev; + ev.info.msg_id = msg_id; + ev.info.flags = flags; + ev.info.len = len; + swoole_random_bytes(ev.data, len); + + sock->send_sync(&ev, sizeof(ev.info) + len); + }; + + send_fn(SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN, msg_id); + send_fn(SW_EVENT_DATA_CHUNK, msg_id + 9999); + + usleep(100000); + server->shutdown(); + } + }; + + server->start(); + + File fp(filename, File::READ); + auto cont = fp.read_content(); + ASSERT_TRUE(cont->contains(std::string("abnormal pipeline data, msg_id=") + std::to_string(msg_id + 9999))); + + unlink(filename.c_str()); +} + +TEST(server, startup_error) { + Server *server = new Server(Server::MODE_PROCESS); + server->task_worker_num = 2; + + ASSERT_NE(server->add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + ASSERT_NE(server->add_port(SW_SOCK_UDP, TEST_HOST, 0), nullptr); + ASSERT_EQ(server->create(), 0); + + ASSERT_EQ(server->start(), -1); + auto startup_error = String(server->get_startup_error_message()); + ASSERT_TRUE(startup_error.contains("require 'onTask' callback")); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_SERVER_INVALID_CALLBACK); + + server->onTask = [](Server *server, EventData *req) -> int { return SW_OK; }; + + ASSERT_EQ(server->start(), -1); + ASSERT_NE(strstr(server->get_startup_error_message(), "require 'onReceive' callback"), nullptr); + + auto ori_log_level = swoole_get_log_level(); + swoole_set_log_level(SW_LOG_NONE); + + ASSERT_EQ(server->start(), -1); + auto startup_error2 = std::string(server->get_startup_error_message()); + ASSERT_EQ(startup_error2, std::to_string(SW_ERROR_SERVER_INVALID_CALLBACK)); + ASSERT_EQ(swoole_get_last_error(), SW_ERROR_SERVER_INVALID_CALLBACK); + + swoole_set_log_level(ori_log_level); + + server->onReceive = [](Server *server, RecvData *req) -> int { return SW_OK; }; + + ASSERT_EQ(server->start(), -1); + ASSERT_NE(strstr(server->get_startup_error_message(), "require 'onPacket' callback"), nullptr); +} + +TEST(server, abort_worker) { + Server *server = new Server(Server::MODE_BASE); + server->worker_num = 2; + + auto port = server->add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_EQ(server->create(), 0); + + swoole::Mutex lock(true); + lock.lock(); + + std::thread t1([&]() { + swoole_signal_block_all(); + + lock.lock(); + + network::SyncClient c1(SW_SOCK_TCP); + c1.connect(TEST_HOST, port->port); + + char buf[1024]; + auto rn = c1.recv(buf, sizeof(buf), MSG_WAITALL); + ASSERT_EQ(rn, 0); + + c1.close(); + + network::SyncClient c2(SW_SOCK_TCP); + c2.connect(TEST_HOST, port->port); + c2.send(SW_STRL("info")); + auto n = c2.recv(buf, sizeof(buf)); + buf[n] = 0; + c2.close(); + + ASSERT_STREQ(buf, "OK"); + + server->shutdown(); + }); + + server->onConnect = [](Server *server, DataHead *ev) { + if (ev->fd == 1) { + swoole_timer_after(100, [server](auto r1, auto r2) { kill(getpid(), SIGKILL); }); + } + }; + + server->onReceive = [](Server *server, RecvData *req) -> int { + size_t count = 0; + SW_LOOP_N(SW_SESSION_LIST_SIZE) { + Session *session = server->get_session(i); + if (session->fd && session->id) { + count++; + } + } + EXPECT_EQ(count, 1); + if (count == 1) { + server->send(req->info.fd, "OK", 2); + } else { + server->send(req->info.fd, "ERR", 3); + } + return 0; + }; + + server->onWorkerStart = [&](Server *server, Worker *worker) { + if (worker->id == 0) { + lock.unlock(); + } + }; + + ASSERT_EQ(server->start(), 0); + t1.join(); +} + +TEST(server, reactor_thread_pipe_writable) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + + String rdata(4 * 1024 * 1024); + rdata.append_random_bytes(rdata.capacity()); + + swoole_set_log_level(SW_LOG_WARNING); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + port->open_length_check = true; + port->protocol.package_max_length = 8 * 1024 * 1024; + network::Stream::set_protocol(&port->protocol); + + Mutex lock(true); + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + + lock.lock(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.set_stream_protocol(); + c.set_package_max_length(8 * 1024 * 1024); + + uint32_t len = htonl(rdata.length); + c.send((char *) &len, sizeof(len)); + c.send(rdata.str, rdata.length); + + auto rbuf = new String(rdata.size + 1024); + + uint32_t pkt_len; + ssize_t rn; + + rn = c.recv((char *) &pkt_len, sizeof(pkt_len)); + EXPECT_EQ(rn, sizeof(pkt_len)); + + rn = c.recv(rbuf->str, ntohl(pkt_len), MSG_WAITALL); + EXPECT_EQ(rn, rdata.length); + + c.close(); + + EXPECT_MEMEQ(rbuf->str, rdata.str, rdata.length); + delete rbuf; + + serv.shutdown(); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + lock.unlock(); + usleep(300000); + }; + + serv.onReceive = [&](Server *serv, RecvData *req) -> int { + uint32_t len = htonl(rdata.length); + EXPECT_TRUE(req->info.flags & SW_EVENT_DATA_OBJ_PTR); + EXPECT_TRUE(serv->send(req->info.fd, &len, sizeof(len))); + EXPECT_TRUE(serv->send(req->info.fd, rdata.str, rdata.length)); + EXPECT_MEMEQ(req->data + 4, rdata.str, rdata.length); + + /** + * After using MessageBus::move_packet(), the data pointer will be out of the control of message_bus, + * and this part of the memory must be manually released; otherwise, a memory leak will occur. + */ + char *data = serv->get_worker_message_bus()->move_packet(); + EXPECT_NE(data, nullptr); + sw_free(data); + + return SW_OK; + }; + + serv.start(); + t1.join(); +} + +static void test_heartbeat_check(Server::Mode mode, bool single_thread = false) { + Server serv(mode); + serv.worker_num = 1; + serv.heartbeat_check_interval = 1; + serv.single_thread = single_thread; + + swoole_set_print_backtrace_on_error(true); + + std::unordered_map flags; + AsyncClient ac(SW_SOCK_TCP); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; + + serv.onStart = [port, &ac, &flags](Server *_serv) { + ac.on_connect([&](AsyncClient *ac) { flags["on_connect"] = true; }); + + ac.on_close([_serv, &flags](AsyncClient *ac) { + flags["on_close"] = true; + _serv->shutdown(); + }); + + ac.on_error([&](AsyncClient *ac) { flags["on_error"] = true; }); + + ac.on_receive([&](AsyncClient *ac, const char *data, size_t len) { flags["on_receive"] = true; }); + + bool retval = ac.connect(TEST_HOST, port->get_port()); + EXPECT_TRUE(retval); + flags["connected"] = true; + }; + + serv.start(); + + ASSERT_TRUE(flags["connected"]); + ASSERT_TRUE(flags["on_connect"]); + ASSERT_FALSE(flags["on_error"]); + ASSERT_FALSE(flags["on_receive"]); + ASSERT_TRUE(flags["on_close"]); +} + +TEST(server, heartbeat_check_1) { + test_heartbeat_check(Server::MODE_BASE); +} + +TEST(server, heartbeat_check_2) { + test_heartbeat_check(Server::MODE_PROCESS); +} + +TEST(server, heartbeat_check_3) { + test_heartbeat_check(Server::MODE_THREAD); +} + +TEST(server, heartbeat_check_4) { + test_heartbeat_check(Server::MODE_PROCESS); +} + +static void test_close(Server::Mode mode, bool close_in_client, bool single_thread = false) { + Server serv(mode); + serv.worker_num = 1; + serv.single_thread = single_thread; + + std::unordered_map flags; + AsyncClient ac(SW_SOCK_TCP); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onConnect = [&flags, close_in_client](Server *serv, DataHead *ev) { flags["server_on_connect"] = true; }; + + serv.onReceive = [&flags, close_in_client](Server *serv, RecvData *req) { + serv->send(req->session_id(), req->data, req->length()); + if (!close_in_client) { + serv->close(req->session_id()); + } + flags["server_on_receive"] = true; + return SW_OK; + }; + + serv.onClose = [&flags, close_in_client](Server *serv, DataHead *ev) { + if (!close_in_client) { + ASSERT_LT(ev->reactor_id, 0); + } + flags["server_on_close"] = true; + }; + + serv.onWorkerStop = [&flags](Server *serv, Worker *worker) { + ASSERT_TRUE(flags["server_on_connect"]); + ASSERT_TRUE(flags["server_on_receive"]); + ASSERT_TRUE(flags["server_on_close"]); + }; + + serv.onStart = [port, &ac, &flags, close_in_client](Server *_serv) { + ac.on_connect([&](AsyncClient *ac) { + flags["client_on_connect"] = true; + ac->send(SW_STRL(TEST_STR)); + }); + + ac.on_close([_serv, &flags](AsyncClient *ac) { + flags["client_on_close"] = true; + swoole_timer_after(50, [_serv, ac](TIMER_PARAMS) { _serv->shutdown(); }); + }); + + ac.on_error([&](AsyncClient *ac) { flags["client_on_error"] = true; }); + + ac.on_receive([&](AsyncClient *ac, const char *data, size_t len) { + flags["client_on_receive"] = true; + if (close_in_client) { + /** + * When a client initiates a connection to its own port in the current process, + * the epoll does not trigger a readable event upon executing close; + * it is necessary to perform a shutdown first to trigger the event. + */ + ac->get_client()->shutdown(SHUT_RDWR); + ac->close(); + } + }); + + bool retval = ac.connect(TEST_HOST, port->get_port()); + EXPECT_TRUE(retval); + flags["client_connected"] = true; + }; + + ASSERT_EQ(serv.start(), SW_OK); + + ASSERT_TRUE(flags["client_connected"]); + ASSERT_TRUE(flags["client_on_connect"]); + ASSERT_FALSE(flags["client_on_error"]); + ASSERT_TRUE(flags["client_on_receive"]); + ASSERT_TRUE(flags["client_on_close"]); +} + +TEST(server, close_1) { + test_close(Server::MODE_PROCESS, false); +} + +TEST(server, close_2) { + test_close(Server::MODE_BASE, false); +} + +TEST(server, close_3) { + test_close(Server::MODE_THREAD, false); +} + +TEST(server, close_4) { + test_close(Server::MODE_PROCESS, false, true); +} + +TEST(server, close_5) { + test_close(Server::MODE_PROCESS, true); +} + +TEST(server, close_6) { + test_close(Server::MODE_BASE, true); +} + +TEST(server, close_7) { + test_close(Server::MODE_THREAD, true); +} + +TEST(server, close_8) { + test_close(Server::MODE_PROCESS, true, true); +} + +TEST(server, eof_check) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + ASSERT_TRUE(port->is_stream()); + + port->set_eof_protocol("\r\n", true); + ASSERT_EQ(serv.create(), SW_OK); + + std::unordered_map flags; + AsyncClient ac(SW_SOCK_TCP); + + int count = 0; + + serv.onWorkerStart = [&count, &flags, port, &ac](Server *serv, Worker *worker) { + ac.on_connect([&](AsyncClient *ac) { flags["on_connect"] = true; }); + + ac.on_close([serv, &flags](AsyncClient *ac) { + flags["on_close"] = true; + serv->shutdown(); + }); + + ac.on_error([&](AsyncClient *ac) { flags["on_error"] = true; }); + + ac.on_receive([&](AsyncClient *ac, const char *data, size_t len) { + flags["on_receive"] = true; + ASSERT_MEMEQ(data, "OK", len); + count++; + + if (count == 1) { + ac->send("hello world\r\n"); + } else if (count == 2) { + ac->send("hello world\r\nhello world\r\n"); + } else if (count == 3) { + ac->send("hello world\r\nhello world\r\nhello world\r\n"); + } else if (count == 4) { + ac->close(); + } + }); + + bool retval = ac.connect(TEST_HOST, port->get_port()); + EXPECT_TRUE(retval); + flags["connected"] = true; + }; + + int recv_count = 0; + + serv.onReceive = [&](Server *serv, RecvData *req) -> int { + serv->send(req->info.fd, "OK", 2); + recv_count++; + return SW_OK; + }; + + serv.onConnect = [&](Server *serv, DataHead *ev) { serv->send(ev->fd, "OK", 2); }; + + serv.start(); + + ASSERT_TRUE(flags["connected"]); + ASSERT_TRUE(flags["on_connect"]); + ASSERT_FALSE(flags["on_error"]); + ASSERT_TRUE(flags["on_receive"]); + ASSERT_TRUE(flags["on_close"]); + ASSERT_TRUE(flags["on_close"]); + ASSERT_EQ(recv_count, 3); +} + +static void test_clean_worker(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 2; + + test::counter_init(); + + AsyncClient ac(SW_SOCK_TCP); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + ASSERT_EQ(serv.create(), SW_OK); + + serv.onConnect = [&ac](Server *serv, DataHead *ev) { + DEBUG() << "server onConnect\n"; + swoole_event_defer( + [serv, &ac](void *) { + DEBUG() << "clean_worker_connections\n"; + serv->clean_worker_connections(sw_worker()); + DEBUG() << "client shutdown\n"; + ac.get_client()->shutdown(); + serv->stop_async_worker(sw_worker()); + }, + nullptr); + }; + + serv.onReceive = [](Server *serv, RecvData *req) { + serv->send(req->info.fd, "OK", 2); + test::counter_incr(0, 1); + DEBUG() << "server onReceive\n"; + return SW_OK; + }; + + serv.onClose = [](Server *serv, DataHead *ev) { test::counter_incr(2, 1); }; + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + ASSERT_EQ(serv->get_connection_num(), 0); + DEBUG() << "worker#" << worker->id << " start\n"; + if (test::counter_incr(1, 1) == 3) { + swoole_timer_after(100, [serv](TIMER_PARAMS) { + DEBUG() << "server shutdown\n"; + serv->shutdown(); + }); + } + }; + + serv.onWorkerStop = [](Server *serv, Worker *worker) { DEBUG() << "worker#" << worker->id << " stop\n"; }; + + serv.onStart = [port, &ac](Server *_serv) { + DEBUG() << "server is started\n"; + swoole_timer_after(100, [port, _serv, &ac](TIMER_PARAMS) { + ac.on_connect([&](AsyncClient *ac) { ac->send(SW_STRL(TEST_STR)); }); + + ac.on_close([_serv](AsyncClient *ac) { DEBUG() << "client onClose\n"; }); + + ac.on_error([](AsyncClient *ac) { swoole_warning("connect failed, error=%d", swoole_get_last_error()); }); + + ac.on_receive([](AsyncClient *ac, const char *data, size_t len) { + DEBUG() << "received\n"; + test::counter_incr(3, 1); + }); + + bool retval = ac.connect(TEST_HOST, port->get_port()); + EXPECT_TRUE(retval); + DEBUG() << "client is connected\n"; + }); + }; + + ASSERT_EQ(serv.start(), SW_OK); + ASSERT_EQ(test::counter_get(0), 0); // Server on_receive + ASSERT_EQ(test::counter_get(1), 3); // worker start + ASSERT_EQ(test::counter_get(2), 0); // Server on_close + ASSERT_EQ(test::counter_get(3), 0); // Client on_receive +} + +TEST(server, clean_worker_1) { + test_clean_worker(Server::MODE_BASE); +} + +TEST(server, clean_worker_2) { + test_clean_worker(Server::MODE_THREAD); +} + +struct Options { + bool reload_async = true; + bool worker_exit_callback = false; + bool test_shutdown_event = false; +}; + +static long test_timer; + +static void test_kill_worker(Server::Mode mode, const Options &options) { + Server serv(mode); + serv.worker_num = 2; + serv.reload_async = options.reload_async; + + test::counter_init(); + int *counter = test::counter_ptr(); + + Mutex lock(true); + lock.lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onConnect = [counter](Server *serv, DataHead *ev) { + counter[4] = ev->fd; + counter[5] = sw_worker()->id; + }; + + serv.onReceive = [counter](Server *serv, RecvData *req) { + serv->send(req->info.fd, "OK", 2); + sw_atomic_fetch_add(&counter[0], 1); + + return SW_OK; + }; + + serv.onWorkerStop = [counter](Server *_serv, Worker *worker) { + _serv->close(counter[4]); + _serv->drain_worker_pipe(); + DEBUG() << "worker#" << worker->id << " stop \n"; + }; + + serv.onClose = [counter](Server *serv, DataHead *ev) { sw_atomic_fetch_add(&counter[2], 1); }; + + serv.onWorkerStart = [counter, &options](Server *serv, Worker *worker) { + auto c = sw_atomic_fetch_add(&counter[1], 1); + DEBUG() << "worker#" << worker->id << " start \n"; + if (options.worker_exit_callback) { + test_timer = swoole_timer_tick(5000, [counter](TIMER_PARAMS) {}); + } + + if (c < 2 && options.test_shutdown_event && worker->id == 0) { + EventData ev; + ev.info = {}; + ev.info.type = SW_SERVER_EVENT_SHUTDOWN; + ev.info.len = 0; + DEBUG() << "send SW_SERVER_EVENT_SHUTDOWN packet\n"; + ASSERT_GT(serv->send_to_worker_from_worker(1, &ev, SW_PIPE_MASTER | SW_PIPE_NONBLOCK), 0); + } + }; + + if (options.worker_exit_callback) { + serv.onWorkerExit = [counter](Server *_serv, Worker *worker) { + swoole_timer_clear(test_timer); + test::counter_incr(6, 1); + DEBUG() << "worker#" << worker->id << " exit \n"; + }; + } + + serv.onStart = [&lock, &options](Server *_serv) { + if (!sw_worker()) { + ASSERT_FALSE(_serv->kill_worker(-1)); + } + lock.unlock(); + }; + + std::thread t([&]() { + swoole_signal_block_all(); + + lock.lock(); + + usleep(50000); + + network::SyncClient c(SW_SOCK_TCP); + EXPECT_TRUE(c.connect(TEST_HOST, port->port)); + + EXPECT_EQ(c.send(SW_STRL(TEST_STR)), strlen(TEST_STR)); + + String rbuf(1024); + auto rn = c.recv(rbuf.str, rbuf.size); + EXPECT_EQ(rn, 2); + + serv.kill_worker(1 - counter[5]); + + rn = c.recv(rbuf.str, rbuf.size); + EXPECT_EQ(rn, 0); + + sw_atomic_fetch_add(&counter[3], 1); + + usleep(50000); + + serv.shutdown(); + }); + + ASSERT_EQ(serv.start(), SW_OK); + t.join(); + + ASSERT_EQ(counter[0], 1); // Client receive + ASSERT_EQ(counter[1], options.test_shutdown_event ? 4 : 3); // Server onWorkerStart + ASSERT_EQ(counter[2], 1); // Server onClose + ASSERT_EQ(counter[3], 1); // Client close + // counter[4] is the client fd + // counter[5] is the worker id + // counter[6] is the worker exit count + + if (options.worker_exit_callback) { + ASSERT_EQ(counter[6], 3); // Worker exit + } +} + +TEST(server, kill_worker_1) { + Options opt; + opt.reload_async = true; + test_kill_worker(Server::MODE_BASE, opt); +} + +TEST(server, kill_worker_2) { + Options opt; + opt.reload_async = true; + test_kill_worker(Server::MODE_PROCESS, opt); +} + +TEST(server, kill_worker_3) { + Options opt; + opt.reload_async = true; + test_kill_worker(Server::MODE_THREAD, opt); +} + +TEST(server, kill_worker_4) { + Options opt; + opt.reload_async = false; + test_kill_worker(Server::MODE_BASE, opt); +} + +TEST(server, kill_worker_5) { + Options opt; + opt.reload_async = false; + test_kill_worker(Server::MODE_PROCESS, opt); +} + +TEST(server, kill_worker_6) { + Options opt; + opt.reload_async = false; + test_kill_worker(Server::MODE_THREAD, opt); +} + +TEST(server, worker_exit) { + Options opt; + opt.worker_exit_callback = true; + test_kill_worker(Server::MODE_PROCESS, opt); +} + +TEST(server, shutdown_event) { + Options opt; + opt.test_shutdown_event = true; + test_kill_worker(Server::MODE_PROCESS, opt); +} + +static void test_kill_self(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 2; + + int *counter = (int *) sw_mem_pool()->alloc(sizeof(int) * 6); + + swoole::Mutex lock(true); + lock.lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + ASSERT_EQ(serv.create(), SW_OK); + + serv.onConnect = [counter](Server *serv, DataHead *ev) { + counter[4] = ev->fd; + counter[5] = sw_worker()->id; + }; + + serv.onReceive = [counter](Server *serv, RecvData *req) { + serv->send(req->info.fd, "OK", 2); + sw_atomic_fetch_add(&counter[0], 1); + + return SW_OK; + }; + + serv.onWorkerStop = [counter](Server *_serv, Worker *worker) { _serv->close(counter[4]); }; + + serv.onClose = [counter](Server *serv, DataHead *ev) { sw_atomic_fetch_add(&counter[2], 1); }; + + serv.onWorkerStart = [counter](Server *_serv, Worker *worker) { sw_atomic_fetch_add(&counter[1], 1); }; + + serv.onStart = [&lock](Server *_serv) { + if (!sw_worker()) { + ASSERT_FALSE(_serv->kill_worker(-1)); + } + lock.unlock(); + }; + + std::thread t([&]() { + swoole_signal_block_all(); + + lock.lock(); + + usleep(50000); + + network::SyncClient c(SW_SOCK_TCP); + EXPECT_TRUE(c.connect(TEST_HOST, port->port)); + + EXPECT_EQ(c.send(SW_STRL(TEST_STR)), strlen(TEST_STR)); + + String rbuf(1024); + auto rn = c.recv(rbuf.str, rbuf.size); + EXPECT_EQ(rn, 2); + + serv.kill_worker(counter[5]); + + rn = c.recv(rbuf.str, rbuf.size); + EXPECT_EQ(rn, 0); + + sw_atomic_fetch_add(&counter[3], 1); + + usleep(50000); + + serv.shutdown(); + }); + + ASSERT_EQ(serv.start(), SW_OK); + t.join(); + + ASSERT_EQ(counter[0], 1); // Client receive + ASSERT_EQ(counter[1], 3); // Server onWorkerStart + ASSERT_EQ(counter[2], 1); // Server onClose + ASSERT_EQ(counter[3], 1); // Client close +} + +TEST(server, kill_self) { + test_kill_self(Server::MODE_BASE); +} + +TEST(server, no_idle_worker) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 4; + serv.dispatch_mode = 3; + + swoole_set_log_file(TEST_LOG_FILE); + swoole_set_log_level(SW_LOG_WARNING); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + + SW_LOOP_N(1024) { + c.send(packet, strlen(packet)); + } + + sleep(3); + + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + usleep(10000); + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; + + auto log = swoole::file_get_contents(TEST_LOG_FILE); + ASSERT_TRUE(log->contains("No idle worker is available")); + + remove(TEST_LOG_FILE); +} + +TEST(server, no_idle_task_worker) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 1; + serv.task_worker_num = 4; + serv.dispatch_mode = 3; + + swoole_set_log_file(TEST_LOG_FILE); + swoole_set_log_level(SW_LOG_WARNING); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.send(packet, strlen(packet)); + + sleep(3); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + + serv.onReceive = [](Server *serv, RecvData *req) -> int { + SW_LOOP_N(1024) { + int _dst_worker_id = -1; + EventData buf{}; + Server::task_pack(&buf, packet, strlen(packet)); + buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_CALLBACK); + EXPECT_TRUE(serv->task(&buf, &_dst_worker_id)); + } + return SW_OK; + }; + + serv.onTask = [](Server *serv, EventData *task) -> int { + EXPECT_EQ(string(task->data, task->info.len), string(packet)); + usleep(10000); + return 0; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; + + auto log = swoole::file_get_contents(TEST_LOG_FILE); + ASSERT_TRUE(log->contains("No idle task worker is available")); + + remove(TEST_LOG_FILE); +} + +static void test_conn_overflow(Server::Mode mode, bool send_yield) { + Server serv(mode); + serv.worker_num = 1; + serv.send_yield = send_yield; + swoole_set_log_level(SW_LOG_WARNING); + + test::counter_init(); + auto counter = test::counter_ptr(); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.send(packet, strlen(packet)); + char buf[1024]; + c.recv(buf, sizeof(buf)); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + if (worker->id == 0) { + lock->unlock(); + } + test::counter_incr(3); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onReceive = [counter, send_yield](Server *serv, RecvData *req) -> int { + auto sid = req->session_id(); + auto conn = serv->get_connection_by_session_id(sid); + conn->overflow = 1; + + EXPECT_FALSE(serv->send(sid, SW_STRL(TEST_STR))); + EXPECT_ERREQ(send_yield ? SW_ERROR_OUTPUT_SEND_YIELD : SW_ERROR_OUTPUT_BUFFER_OVERFLOW); + + counter[0] = 1; + + swoole_timer_after(100, [serv, sid](TIMER_PARAMS) { serv->close(sid); }); + + return SW_OK; + }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; + ASSERT_EQ(counter[0], 1); + ASSERT_EQ(counter[3], 1); +} + +TEST(server, overflow_1) { + test_conn_overflow(Server::MODE_BASE, false); +} + +TEST(server, overflow_2) { + test_conn_overflow(Server::MODE_PROCESS, false); +} + +TEST(server, overflow_3) { + test_conn_overflow(Server::MODE_BASE, true); +} + +TEST(server, overflow_4) { + test_conn_overflow(Server::MODE_PROCESS, true); +} + +TEST(server, send_timeout) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + swoole_set_log_level(SW_LOG_WARNING); + + test::counter_init(); + auto counter = test::counter_ptr(); + + Mutex *lock = new Mutex(true); + lock->lock(); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + if (!port) { + swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); + exit(2); + } + + port->max_idle_time = 1; + + String wbuf(2 * 1024 * 1024); + wbuf.append_random_bytes(2 * 1024 * 1024, false); + + ASSERT_EQ(serv.create(), SW_OK); + + thread t1; + serv.onStart = [&lock, &t1, &wbuf](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.send(packet, strlen(packet)); + + String rbuf(3 * 1024 * 1024); + + auto rn = c.recv(rbuf.str, 1024); + EXPECT_EQ(rn, 1024); + rbuf.length += 1024; + + sleep(2); + + while (true) { + rn = c.recv(rbuf.str + rbuf.length, rbuf.size - rbuf.length); + if (rn <= 0) { + break; + } + rbuf.length += rn; + } + + EXPECT_MEMEQ(rbuf.str, wbuf.str, rbuf.length); + c.close(); + + kill(serv->gs->master_pid, SIGTERM); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + if (worker->id == 0) { + lock->unlock(); + } + test::counter_incr(3); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + serv.onReceive = [&wbuf](Server *serv, RecvData *req) -> int { + auto sid = req->session_id(); + auto conn = serv->get_connection_by_session_id(sid); + + swoole_timer_del(conn->socket->recv_timer); + conn->socket->recv_timer = nullptr; + conn->socket->set_buffer_size(65536); + + EXPECT_TRUE(serv->send(sid, wbuf.str, wbuf.length)); - buf.info.ext_flags = SW_TASK_NOREPLY; - buf.info.len = strlen(packet); - memcpy(buf.data, packet, strlen(packet)); + test::counter_incr(0); - int _dst_worker_id = 0; + return SW_OK; + }; - ASSERT_GE(serv.gs->task_workers.dispatch(&buf, &_dst_worker_id), 0); + ASSERT_EQ(serv.start(), 0); t1.join(); - serv.gs->task_workers.destroy(); + delete lock; + ASSERT_EQ(counter[0], 1); + ASSERT_EQ(counter[3], 1); } -TEST(server, max_connection) { - Server serv; +static void test_max_request(Server::Mode mode) { + Server serv(mode); + serv.worker_num = 2; + serv.max_request = 128; - serv.set_max_connection(0); - ASSERT_EQ(serv.get_max_connection(), SW_MIN(SW_MAX_CONNECTION, SwooleG.max_sockets)); + Mutex *lock = new Mutex(true); + lock->lock(); - serv.set_max_connection(SwooleG.max_sockets + 13); - ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets); + ASSERT_NE(serv.add_port(SW_SOCK_TCP, TEST_HOST, 0), nullptr); + ASSERT_EQ(serv.create(), SW_OK); - serv.set_max_connection(SwooleG.max_sockets - 13); - ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets - 13); + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + lock->lock(); + ListenPort *port = serv->get_primary_port(); - uint32_t last_value = serv.get_max_connection(); + auto client_fn = [&]() { + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); - serv.create(); + SW_LOOP_N(128) { + if (c.send(packet, strlen(packet)) < 0) { + break; + } + usleep(1000); + } + c.close(); + }; - serv.set_max_connection(100); - ASSERT_EQ(serv.get_max_connection(), last_value); -} + SW_LOOP_N(8) { + client_fn(); + usleep(10000); + } -TEST(server, min_connection) { - Server serv; + sleep(1); - serv.task_worker_num = 14; - serv.worker_num = 5; + serv->shutdown(); + }); + }; - serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + lock->unlock(); + test::counter_incr(0); + }; - serv.set_max_connection(15); - serv.create(); - ASSERT_EQ(serv.get_max_connection(), SwooleG.max_sockets); -} + serv.onReceive = [](Server *serv, RecvData *req) -> int { return SW_OK; }; -TEST(server, worker_num) { - Server serv; + ASSERT_EQ(serv.start(), 0); - serv.worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU + 99; - serv.task_worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU + 99; - serv.create(); + t1.join(); + delete lock; - ASSERT_EQ(serv.worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); - ASSERT_EQ(serv.task_worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); + ASSERT_GE(test::counter_get(0), 8); } -TEST(server, reactor_num_base) { - Server serv(Server::MODE_BASE); - serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; - serv.create(); +TEST(server, max_request_1) { + test_max_request(Server::MODE_PROCESS); +} - ASSERT_EQ(serv.reactor_num, serv.worker_num); +TEST(server, max_request_2) { + test_max_request(Server::MODE_THREAD); } -TEST(server, reactor_num_large) { +TEST(server, watermark) { Server serv(Server::MODE_PROCESS); - serv.worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU; - serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; - serv.create(); + serv.worker_num = 2; - ASSERT_EQ(serv.reactor_num, SW_CPU_NUM * SW_MAX_THREAD_NCPU); -} + Mutex *lock = new Mutex(true); + lock->lock(); -TEST(server, reactor_num_large2) { - Server serv(Server::MODE_PROCESS); - serv.reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU + 99; - serv.create(); + String wbuf; + wbuf.append_random_bytes(2 * 1024 * 1024); - ASSERT_EQ(serv.reactor_num, serv.worker_num); -} + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + ASSERT_EQ(serv.create(), SW_OK); -TEST(server, reactor_num_zero) { - Server serv; - serv.reactor_num = 0; - serv.create(); + port->get_socket()->set_buffer_size(65536); + port->buffer_high_watermark = 1024 * 1024; + port->buffer_low_watermark = 65536; - ASSERT_EQ(serv.reactor_num, SW_CPU_NUM); + thread t1; + serv.onStart = [&lock, &t1](Server *serv) { + t1 = thread([=]() { + swoole_signal_block_all(); + lock->lock(); + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + c.get_client()->get_socket()->set_buffer_size(65536); + c.send(packet, strlen(packet)); + usleep(1000); + + String rbuf(2 * 1024 * 1024); + while (rbuf.length < rbuf.size) { + auto rn = c.recv(rbuf.str + rbuf.length, 65536); + usleep(10000); + if (rn <= 0) { + break; + } + rbuf.length += rn; + } + + sleep(1); + c.close(); + serv->shutdown(); + }); + }; + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { + lock->unlock(); + test::counter_incr(0); + }; + + serv.onReceive = [&wbuf](Server *serv, RecvData *req) -> int { + EXPECT_TRUE(serv->send(req->session_id(), wbuf.str, wbuf.length)); + return SW_OK; + }; + + serv.onBufferEmpty = [](Server *serv, DataHead *ev) { test::counter_incr(1); }; + + serv.onBufferFull = [](Server *serv, DataHead *ev) { test::counter_incr(2); }; + + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; + + ASSERT_GE(test::counter_get(1), 1); + ASSERT_GE(test::counter_get(2), 1); } -void test_command(enum Server::Mode _mode) { - Server serv(_mode); - serv.worker_num = 4; - serv.task_worker_num = 4; - serv.reactor_num = 2; +TEST(server, discard_data) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.dispatch_mode = 3; + serv.discard_timeout_request = true; + serv.disable_notify = true; - SwooleG.running = 1; + swoole_set_log_file(TEST_LOG_FILE); + swoole_set_log_level(SW_LOG_WARNING); - sw_logger()->set_level(SW_LOG_WARNING); + Mutex *lock = new Mutex(true); + lock->lock(); ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); if (!port) { @@ -424,57 +3970,311 @@ void test_command(enum Server::Mode _mode) { ASSERT_EQ(serv.create(), SW_OK); - serv.add_command("test", Server::Command::ALL_PROCESS, [](Server *, const std::string &msg) -> std::string { - return std::string("json result, ") + msg; - }); + String rdata; + rdata.append_random_bytes(8192); - serv.onStart = [](Server *serv) { - static Server::Command::Callback fn = [&](Server *serv, const std::string &msg) { - usleep(50000); - if (msg == "json result, hello world [0]") { - if (serv->is_base_mode()) { - goto _send_to_event_worker; - } else { - serv->command(1, Server::Command::REACTOR_THREAD, "test", "hello world [1]", fn); - } - } else if (msg == "json result, hello world [1]") { - _send_to_event_worker: - serv->command(1, Server::Command::EVENT_WORKER, "test", "hello world [2]", fn); - } else if (msg == "json result, hello world [2]") { - serv->command(1, Server::Command::TASK_WORKER, "test", "hello world [3]", fn); - } else if (msg == "json result, hello world [3]") { - serv->command(1, Server::Command::MANAGER, "test", "hello world [4]", fn); - } else if (msg == "json result, hello world [4]") { - swoole_timer_after(50, [serv](Timer *, TimerNode *) { serv->shutdown(); }); - } else { - ASSERT_TRUE(0); + thread t1; + serv.onStart = [&lock, &t1, &rdata](Server *serv) { + t1 = thread([&lock, &rdata, serv]() { + swoole_signal_block_all(); + + lock->lock(); + + ListenPort *port = serv->get_primary_port(); + + network::SyncClient c(SW_SOCK_TCP); + c.connect(TEST_HOST, port->port); + + SW_LOOP_N(128) { + c.send(rdata.str, rdata.length); + usleep(10); } - }; - serv->command(1, Server::Command::MASTER, "test", "hello world [0]", fn); + + sleep(1); + + kill(serv->gs->master_pid, SIGTERM); + }); }; - serv.onWorkerStart = [](Server *serv, int worker_id) { + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock->unlock(); }; + serv.onReceive = [](Server *serv, RecvData *req) -> int { + usleep(10000); + serv->close(req->session_id()); + return SW_OK; }; - serv.onTask = [](Server *, EventData *) -> int { return SW_OK; }; + ASSERT_EQ(serv.start(), 0); + + t1.join(); + delete lock; + + auto log = file_get_contents(TEST_LOG_FILE); + DEBUG() << log->str << std::endl; + ASSERT_TRUE(log->contains("discard_data() (ERRNO 1007)")); + remove(TEST_LOG_FILE); +} + +TEST(server, pause_and_resume) { + Server serv(Server::MODE_BASE); + serv.worker_num = 2; + + swoole_set_log_level(SW_LOG_TRACE); + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + + Mutex lock(true); + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + lock.lock(); + + usleep(1000); + + network::SyncClient c(SW_SOCK_TCP); + ASSERT_TRUE(c.connect(TEST_HOST, port->port)); + ASSERT_EQ(c.send(packet, strlen(packet)), strlen(packet)); + char buf[1024]; + auto t1 = microtime(); + ASSERT_EQ(c.recv(buf, sizeof(buf)), strlen(packet) + 8); + auto t2 = microtime(); + ASSERT_GE(t2 - t1, 0.048); // Ensure that the pause and resume took some time + string resp = string("Server: ") + string(packet); + ASSERT_MEMEQ(buf, resp.c_str(), resp.length()); + c.close(); + + usleep(1000); + DEBUG() << "shutdown\n"; + serv.shutdown(); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onConnect = [](Server *serv, DataHead *ev) { + auto session_id = ev->fd; + DEBUG() << "onConnect: fd=" << session_id << ", reactor_id=" << ev->reactor_id << std::endl; + ASSERT_TRUE(serv->feedback(serv->get_connection_by_session_id(session_id), SW_SERVER_EVENT_PAUSE_RECV)); + DEBUG() << "pause recv ok, session_id=" << session_id << std::endl; + swoole_timer_after(50, [ev, serv, session_id](TIMER_PARAMS) { + ASSERT_TRUE(serv->feedback(serv->get_connection_by_session_id(session_id), SW_SERVER_EVENT_RESUME_RECV)); + DEBUG() << "resume recv ok, session_id=" << session_id << std::endl; + }); + }; serv.onReceive = [](Server *serv, RecvData *req) -> int { EXPECT_EQ(string(req->data, req->info.len), string(packet)); - string resp = string("Server: ") + string(packet); serv->send(req->info.fd, resp.c_str(), resp.length()); + return SW_OK; + }; + + serv.start(); + t1.join(); +} + +TEST(server, max_queued_bytes) { + Server serv(Server::MODE_PROCESS); + serv.worker_num = 2; + serv.max_queued_bytes = 65536; + + test::counter_init(); + swoole_set_log_level(SW_LOG_TRACE); + + int buffer_size = 65536; + int send_count = 256; + + ListenPort *port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_TRUE(port); + Mutex lock(true); + lock.lock(); + + ASSERT_EQ(serv.create(), SW_OK); + + std::thread t1([&]() { + swoole_signal_block_all(); + lock.lock(); + + usleep(1000); + + String wbuf; + wbuf.append_random_bytes(buffer_size); + + network::SyncClient c(SW_SOCK_TCP); + ASSERT_TRUE(c.connect(TEST_HOST, port->port)); + SW_LOOP_N(send_count) { + ASSERT_EQ(c.send(wbuf.str, wbuf.length), wbuf.length); + } + char buf[1024]; + ASSERT_EQ(c.recv(buf, sizeof(buf)), strlen(TEST_STR)); + c.close(); + + usleep(1000); + DEBUG() << "shutdown\n"; + serv.shutdown(); + }); + + serv.onWorkerStart = [&lock](Server *serv, Worker *worker) { lock.unlock(); }; + + serv.onConnect = [](Server *serv, DataHead *ev) { usleep(100000); }; + + serv.onReceive = [=](Server *serv, RecvData *req) -> int { + if (test::counter_incr(0, req->info.len) == send_count * buffer_size) { + serv->send(req->session_id(), SW_STRL(TEST_STR)); + } return SW_OK; }; - ASSERT_EQ(serv.start(), 0); + serv.start(); + t1.join(); + ASSERT_EQ(send_count * buffer_size, test::counter_get(0)); } -TEST(server, command_1) { - test_command(Server::MODE_PROCESS); +TEST(server, ssl_matches_wildcard_name) { + // Test exact match + { + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("example.com", "example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test.example.com", "test.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("EXAMPLE.COM", "example.com")); // Case insensitive + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("example.com", "EXAMPLE.COM")); // Case insensitive + } + + // Test no match + { + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "example.org")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "test.example.org")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("sub.example.com", "example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "sub.example.com")); + } + + // Test wildcard in leftmost component + { + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("sub.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("TEST.example.com", "*.example.com")); // Case insensitive + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*.EXAMPLE.COM")); // Case insensitive + } + + // Test wildcard with prefix + { + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("subtest.example.com", "sub*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("subthing.example.com", "sub*.example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("wrongtest.example.com", "sub*.example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "sub*.example.com")); + } + + // Test wildcard in non-leftmost component (should fail) + { + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "test.*.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.sub.example.com", "test.*.example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "example.*")); + } + + // Test wildcard with dot in prefix (should fail) + { + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "test.*.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("sub.test.example.com", "sub.*.example.com")); + } + + // Test multiple wildcards (only first one should be considered) + { + // EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*.*example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.sub.example.com", "*.*example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*.*")); + } + + // Test wildcard matching with dots between prefix and suffix + { + // These should fail because there's a dot between the prefix and suffix + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.sub.example.com", "*.example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("a.b.c.example.com", "*.example.com")); + + // This should pass because there's no dot in the wildcard portion + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("testexample.com", "*example.com")); + } + + // Test suffix length conditions + { + // Suffix longer than subject (should fail) + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.com", "*.example.com")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("short", "*.verylongdomain.com")); + + // Suffix exactly matches subject length (edge case) + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "*.example.com")); + } + + // Test empty strings and edge cases + { + // EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("", "")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("", "example.com")); + // EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("", "*")); + // EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test", "*")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("*", "*")); // Exact match + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test", "*est")); + } + + // Test wildcard at beginning with no prefix + { + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("example.com", "*example.com")); + // EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("test.example.com", "*test.example.com")); + } + + // Test wildcard at end with no suffix + { + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "example*")); + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("example.com", "example.*")); + } + + // Test practical examples from real-world scenarios + { + // Common wildcard cert patterns + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("www.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("api.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("login.example.com", "*.example.com")); + + // Subdomain matching + EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("sub.api.example.com", "*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("sub.api.example.com", "*.api.example.com")); + + // IP addresses (wildcards shouldn't work with IPs in practice) + // EXPECT_FALSE(ListenPort::ssl_matches_wildcard_name("192.168.1.1", "*.168.1.1")); + + // Partial wildcard matches + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("dev-server.example.com", "dev-*.example.com")); + EXPECT_TRUE(ListenPort::ssl_matches_wildcard_name("staging-server.example.com", "*-server.example.com")); + } } -TEST(server, command_2) { - test_command(Server::MODE_BASE); +TEST(server, wait_other_worker) { + Server serv(Server::MODE_BASE); + serv.worker_num = 1; + serv.task_worker_num = 2; + test::counter_init(); + + auto port = serv.add_port(SW_SOCK_TCP, TEST_HOST, 0); + ASSERT_NE(port, nullptr); + + serv.onWorkerStart = [](Server *serv, Worker *worker) { + test::counter_incr(1); + DEBUG() << "onWorkerStart: id=" << worker->id << "\n"; + }; + + ASSERT_EQ(serv.create(), SW_OK); + + ExitStatus fake_exit(getpid(), 0); + auto pool = serv.get_task_worker_pool(); + auto worker = serv.get_worker(2); + worker->pid = getpid(); + pool->add_worker(worker); + serv.wait_other_worker(pool, fake_exit); + + test::wait_all_child_processes(); + + ASSERT_EQ(test::counter_get(1), 1); } diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000000..767a21266c --- /dev/null +++ b/docs/API.md @@ -0,0 +1,74 @@ +## Basic C API +These functions enable you to obtain the spool version and insert some hooks into the spool, +such as `SW_GLOBAL_HOOK_BEFORE_SERVER_START`. The set function is called when the server starts. + +The header file is `swoole_api.h`. +The symbol of this file will export the function name in `C` format, not `C++`. +Similarly, functions in the `Coroutine API` are also exported as `C` functions. + +- `swoole_version()` Get the current version of Swoole +- `swoole_add_hook()` Add a hook function to the specified hook point + +## Coroutine C API + +These functions are similar to the C function in `unistd.h`, for example, read corresponds to +`swoole_coroutine_read()` in the coroutine environment. + +In addition to the `POSIX API` like functions, some additional functions can be provided. +For example, the `swoole_coroutine_get_current_id()` can obtain the ID of the coroutine, +which is equivalent to the `getpid()` of the process. + +The function similar to creating a process or thread is `swoole_coroutine_create()`, +which can be used to create a coroutine. + +The header file is `swoole_coroutine_api.h`. + +## C MACRO HOOK +Use `C/C++` macros to automatically replace read/write and other synchronously blocked unistd C functions as non blocking +coroutine functions (starts with `swoole_coroutine_`). +This method enables C++ network client code to be used in the swoole coroutine environment. + +Including network and file system: + +- `swoole_file_hook.h` Replace file system related functions, such as `open()`, `mkdir()`, etc +- `swoole_socket_hook.h` Replace network related functions, such as `socket()`, `recv()`, `send()`, etc + +## C++ API + +In addition to the above header files, others are only used in `C++` code. + +### Independent header file + +These header files have no dependencies. They can only contain C standard library header files, C++ header files, +and platform related or basic library header files. + +- `swoole_config.h`: Buffer size, string, constant, etc +- `swoole_version.h`: Swoole version information +- `swoole_atomic.h`: Atomic operations +- `swoole_asm_context.h`: Assembly context switching +- `swoole_util.h`: Common utility functions +- `swoole_log.h`: Log related functions +- `swoole_memory.h`: Memory pool related functions +- `swoole_base64.h`: Base64 encoding and decoding +- `swoole_error.h`: Error code related functions +- and more ... + +### Core header file +- `swoole.h`: Core class and function declarations +- `swoole_string.h`: String class and function declarations +- `swoole_coroutine.h`: Coroutine class and function declarations +- `swoole_async.h`: Asynchronous IO class and function declarations +- `swoole_process_pool.h`: Process pool class and function declarations +- `swoole_signal.h`: Signal handling class and function declarations +- `swoole_timer.h`: Timer class and function declarations +- `swoole_reactor.h`: Reactor class and function declarations +- and more ... + +### Facade header file +These header files depend on the core header files and provide a higher level of abstraction. + +- `swoole_server.h`: Server side class and function declarations +- `swoole_client.h`: Client side class and function declarations +- `swoole_coroutine_socket.h`: Coroutine socket class and function declarations +- `swoole_coroutine_system.h`: Coroutine system API class and function declarations +- `swoole_coroutine_channel.h`: Coroutine channel class and function declarations diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000000..55ef9fd987 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,145 @@ +# Swoole Changelog + +## 2024-12-16 v6.0.0 +# ✨ New Feature: +- Added multi-threading support, require the ZTS version of PHP. Add `--enable-swoole-thread` option to the configure command to activate it. +- Added a new thread class `Swoole\Thread`. @matyhtf +- Introduced thread lock `Swoole\Thread\Lock`. @matyhtf +- Added thread atomic counter `Swoole\Thread\Atomic`, `Swoole\Thread\Atomic\Long`. @matyhtf +- Added safe concurrent containers `Swoole\Thread\Map`, `Swoole\Thread\ArrayList`, `Swoole\Thread\Queue`. @matyhtf +- The file asynchronous operation supports using `io_uring` as the underlying engine for file asynchronous operations. When liburing is installed and Swoole is compiled with the --enable-iouring option, the asynchronous operations of functions such as file_get_contents, file_put_contents, fopen, fclose, fread, fwrite, mkdir, unlink, fsync, fdatasync, rename, fstat, lstat, and filesize will be implemented by io_uring. @matyhtf @NathanFreeman +- Upgraded `Boost Context` to version 1.84. Now, Loongson CPUs can also support coroutines. @NathanFreeman +- Added `Swoole\Thread\Map::find()` method. @matyhtf +- Added `Swoole\Thread\ArrayList::find()` method. @matyhtf +- Added `Swoole\Thread\ArrayList::offsetUnset()` method. @matyhtf +- Added `Swoole\Process::getAffinity()` method. @matyhtf +- Added `Swoole\Thread::setName()` method. @matyhtf +- Added `Swoole\Thread::setAffinity()` method. @matyhtf +- Added `Swoole\Thread::getAffinity()` method. @matyhtf +- Added `Swoole\Thread::setPriority()` method. @matyhtf +- Added `Swoole\Thread::getPriority()` method. @matyhtf +- Added `Swoole\Thread::gettid()` method. +- The file asynchronous engine `iouring` supports multi-threaded polling mode `IORING_SETUP_SQPOLL`. @NathanFreeman +- Added `iouring_workers` to modify the number of `iouring` threads. @NathanFreeman +- Added `iouring_flags` to support modifying the `iouring` working mode. @NathanFreeman +- Added `Swoole\Thread\Barrier` for multi-thread synchronization barrier. @matyhtf +- Added new function and class to set cookies. @matyhtf @NathanFreeman +- Added `non-blocking, reentrant coroutine mutex lock`, which can be used between processes/threads without blocking them. @NathanFreeman +- `Swoole\Coroutine\Socket::getOption()` supports the `TCP_INFO` option. @matyhtf +- `Swoole\Client` synchronous blocking client supports `http` proxy. @matyhtf +- Added asynchronous non-blocking `TCP/UDP/Unix socket` client `Swoole\Async\Client`. @matyhtf +- Optimized the `Swoole\Redis\Server::format()` method to support zero-copy memory, support `redis` nested structure. @matyhtf +- Supports the high-performance compression tool `Zstd`. You only need to add `--enable-zstd` when compiling `Swoole`, and then `zstd` can be used to compress or decode responses between the `http` client and server. @NathanFreeman + +# 🐛 Bug Fixed: +- Fixed the issue where installation via `pecl` was not possible. @remicollet +- Fixed the bug where setting `keepalive` was not possible for `Swoole\Coroutine\FastCGI\Client`. @NathanFreeman +- Fixed the issue where exceeding the `max_input_vars` would throw an error, causing the process to restart repeatedly. @NathanFreeman +- Fixed unknown issues caused by using `Swoole\Event::wait()` within a coroutine. @matyhtf +- Fixed the problem where `proc_open` does not support pty in coroutine mode. @matyhtf +- Fixed segmentation fault issues with `pdo_sqlite` on PHP 8.3. @NathanFreeman +- Fixed unnecessary warnings during the compilation of `Swoole`. @Appla @NathanFreeward +- Fixed the error thrown by zend_fetch_resource2_ex when `STDOUT/STDERR` are already closed. @Appla @matyhtf +- Fixed ineffective `set_tcp_nodelay` configuration. @matyhtf +- Fixed the occasional unreachable branch issue during file upload. @NathanFreeman +- Fixed the problem where setting `dispatch_func` would cause PHP's internals to throw errors. @NathanFreeman +- Fixed the deprecation of AC_PROG_CC_C99 in autoconf >= 2.70. @petk +- Capture exceptions when thread creation fails. @matyhtf +- Fixed the undefined problem with `_tsrm_ls_cache`. @jingjingxyk +- Fixed the fatal compile error with `GCC 14`. @remicollet +- Fixed the dynamic property issue in `Swoole\Http2\Request`. @guandeng +- Fixed the occasional resource unavailability issue in the `pgsql` coroutine client. @NathanFreeman +- Fixed the issue of 503 errors due to not resetting related parameters during process restart. @matyhtf +- Fixed the inconsistency between `$request->server['request_method']` and `$request->getMethod()` when `HTTP2` is enabled. @matyhtf +- Fixed incorrect `content-type` when uploading files. @matyhtf +- Fixed code errors in the `http2` coroutine client. @matyhtf +- Fixed the missing `worker_id` property in `Swoole\Server`. @cjavad +- Fixed errors related to `brotli` in `config.m4`. @fundawang +- Fixed the invalid `Swoole\Http\Response::create` under multi-threading. @matyhtf +- Fixed compilation errors in the `macos` environment. @matyhtf +- Fixed the issue of threads not being able to exit safely. @matyhtf +- Fixed the issue where the static variable for response time returned by `Swoole\Http\Response` in multi-threaded mode was not generated separately for each thread. @matyhtf @NathanFreeman +- Fixed `Fatal error` issue caused by `PHP-8.4`'s `timeout` feature in ZTS mode. @matyhtf +- Fixed compatibility issue with the `exit()` `hook` function for `PHP-8.4`. @remicollet +- Fixed the issue where `Swoole\Thread::getNativeId()` did not work in `cygwin`. @matyhtf +- Fixed the issue causing `SIGSEGV` in `Swoole\Coroutine::getaddrinfo()` method. @matyhtf +- Fixed the issue where the runtime TCP module did not support dynamically enabling SSL encryption. @matyhtf +- Fixed the issue where the HTTP client had an incorrect timeout after running for a long time. @matyhtf +- Fixed the problem where the mutex lock of `Swoole\Table` could not be used before the process exited. @matyhtf +- Fixed the failure of `Swoole\Server::stop()` when using named parameters. @matyhtf +- Fixed the crash caused by `Swoole\Thread\Map::toArray()` not copying the key. @matyhtf +- Fixed the issue of being unable to delete nested numeric keys in `Swoole\Thread\Map`. @matyhtf + +# ⭐️ Kernel optimization: +- Removed unnecessary checks for `socket structs`. @petk +- Upgraded Swoole Library. @deminy +- Added support for status code 451 in `Swoole\Http\Response`. @abnegate +- Synchronized `file` operation code across different PHP versions. @NathanFreeman +- Synchronized `pdo` operation code across different PHP versions. @NathanFreeman +- Optimized the code for `Socket::ssl_recv()`. @matyhtf +- Improved config.m4; some configurations can now set library locations via `pkg-config`. @NathanFreeman +- Optimized the use of dynamic arrays during `request header parsing`. @NathanFreeman +- Optimized file descriptor `fd` lifecycle issues in multi-threading mode. @matyhtf +- Optimized some fundamental coroutine logic. @matyhtf +- Upgraded the Oracle database version for CI testing. @gvenzl +- Optimized the underlying logic of `sendfile`. @matyhtf +- Replaced `PHP_DEF_HAVE` with `AC_DEFINE_UNQUOTED` in `config.m4`. @petk +- Optimized the logic related to `heartbeat`, `shutdown`, and `stop` for the server in multi-threaded mode. @matyhtf +- Optimized to avoid linking `librt` when `glibc` version is greater than 2.17. @matyhtf +- Enhanced the HTTP client to accept duplicate request headers. @matyhtf +- Optimized `Swoole\Http\Response::write()`. @matyhtf +- `Swoole\Http\Response::write()` can now send HTTP/2 protocol. @matyhtf +- Compatible with `PHP 8.4`. @matyhtf @NathanFreeman +- Added the ability for asynchronous writing at the underlying socket level. @matyhtf +- Optimized `Swoole\Http\Response`. @NathanFreeman +- Improved underlying error messages. @matyhtf +- Supported sharing PHP native sockets in multi-threaded mode. @matyhtf +- Optimized static file service and fixed static file path error issues. @matyhtf +- Multi-thread mode `SWOOLE_THREAD` supports restarting worker threads. @matyhtf +- Multi-thread mode `SWOOLE_THREAD` supports starting timers in the `Manager` thread. @matyhtf +- Compatible with the `curl` extension of `PHP-8.4`. @matyhtf @NathanFreeman +- Rewrite the underlying `Swoole` code using `iouring`. @matyhtf @NathanFreeman +- Optimized timers so that synchronous processes do not depend on signals. @matyhtf +- Optimized the `Swoole\Coroutine\System::waitSignal()` method to allow listening to multiple signals simultaneously. @matyhtf + +# ❌ Deprecated: +- No longer supports `PHP 8.0`. +- No longer supports `Swoole\Coroutine\MySQL` coroutine client. +- No longer supports `Swoole\Coroutine\Redis` coroutine client. +- No longer supports `Swoole\Coroutine\PostgreSQL` coroutine client. +- Removed `Swoole\Coroutine\System::fread()`, `Swoole\Coroutine\System::fwrite()`, and `Swoole\Coroutine\System::fgets()` methods. +## 2024-01-24 v5.1.2 +- Added support for embed sapi @matyhtf +- Fixed compatibility with PHP 8.3 ZEND_CHECK_STACK_LIMIT @Yurunsoft +- Fixed no Content-Range response header when the range request returns all the contents of the file @Yurunsoft +- Optimized HTTP server performance @NathanFreeman +- Fixed truncated cookie @stnguyen90 +- Fixed native-curl crash on PHP 8.3 @NathanFreeman +- Added CLOSE_SERVICE_RESTART, CLOSE_TRY_AGAIN_LATER, CLOSE_BAD_GATEWAY as valid close reasons for websocket @cjavad +- Fixed invalid errno after Server::Manager::wait() @JacobBrownAustin +- Fixed HTTP2 Typo @leocavalcante + +## 2022-07-22 v5.0.0 + +### Added +* Added `max_concurrency` option for `Server` +* Added `max_retries` option for `Coroutine\Http\Client` +* Added `name_resolver` global option +* Added `upload_max_filesize` option for `Server` +* Added `Coroutine::getExecuteTime()` +* Added `SWOOLE_DISPATCH_CONCURRENT_LB` dispatch_mode for `Server` + +### Changed +* Enhanced type system, added types for parameters and return values of all functions +* Optimized error handling, all constructors will throw exceptions when fail +* Adjusted the default mode of Server, the default is `SWOOLE_BASE` mode + +### Removed + +- Removed `PSR-0` style class names +- Removed the automatic addition of `Event::wait()` in shutdown function +- Removed `Server::tick/after/clearTimer/defer` aliases +- Removed `--enable-http`/`--enable-swoole-json`, adjusted to be enable by default + +### Deprecated +- Deprecated `Coroutine\Redis` and `Coroutine\MySQL` diff --git a/CODE-STYLE.md b/docs/CODE-STYLE.md similarity index 100% rename from CODE-STYLE.md rename to docs/CODE-STYLE.md diff --git a/CPPLINT.cfg b/docs/CPPLINT.cfg similarity index 100% rename from CPPLINT.cfg rename to docs/CPPLINT.cfg diff --git a/docs/ISSUE.md b/docs/ISSUE.md new file mode 100644 index 0000000000..2f97272d8c --- /dev/null +++ b/docs/ISSUE.md @@ -0,0 +1,93 @@ +# Bug reports + +## Instruction + +If you think you have found a bug in Swoole, please report it. +The Swoole developers probably don't know about it, +and unless you report it, chances are it won't be fixed. +You can report bugs at https://github.com/swoole/swoole-src/issues. +Please do not send bug reports in the mailing list or personal letters. +The issue page is also suitable to submit feature requests. + +Please read the **How to report a bug document** before submitting any bug reports. + +## New issue + +First, while creating an issue, the system will give the following template: + +```markdown +Please answer these questions before submitting your issue. Thanks! +1. What did you do? If possible, provide a simple script for reproducing the error. +2. What did you expect to see? +3. What did you see instead? +4. What version of Swoole are you using (`php --ri swoole`)? +5. What is your machine environment used (including the version of kernel & php & gcc)? +``` +The most important thing is to provide a simple script for reproducing the error, otherwise, you must provide as much information as possible. + +## Memory detection (recommended) + +In addition to using `gdb` analysis, you can use the `valgrind` tool to check if the program is working properly. + +```shell +USE_ZEND_ALLOC=0 valgrind --log-file=/tmp/valgrind.log php your_file.php +``` + +* After the program is executed to the wrong location, `ctrl+c` is interrupted, and upload the `/tmp/valgrind.log` file. + +## Address Sanitizer +In certain cases, crash issues only occur in complex production environments, not consistently reproducible, and with a very low probability of occurrence. The use of `Valgrind` significantly impacts performance, whereas `Address Sanitizer` allows for memory detection without affecting performance. + +We have created a `debug` version of `Swoole`, which can be compiled with the `--enable-debug` and `--enable-address-sanitizer` parameters to enable `Address Sanitizer`. The usage is as follows: + +```shell +docker pull phpswoole/swoole:dev-debug +docker run -it --rm phpswoole/swoole:dev-debug /bin/bash +php your_file.php +``` + +> To use `ASAN`, be sure to disable Address Space Layout Randomization (`ASLR`): + +```shell +echo 0 > /proc/sys/kernel/randomize_va_space +``` + +This docker image can be used directly in a production environment, and `Address Sanitizer` will print error messages and call stack information when memory errors occur. This information can assist developers in quickly pinpointing issues. + +## CoreDump + +Besides, In a special case, you can use debugging tools to help developers locate problems + +```shell +WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=11 +``` + +When a segmentation error occurs with Swoole, You can use the `gdb` tool and use `bt` command. +> Using `gdb` to track the core file need to add the `--enable-debug` parameter when compiling `swoole`. + +Enable core dump +```shell +ulimit -c unlimited +``` + +Use `gdb` to view the `core dump` information. The `core` file is usually in the current directory. If the operating system does the processing, put the `core dump` file in another directory, please replace it with the corresponding path. +``` +gdb php core +gdb php /tmp/core.4596 +``` + +Enter bt under gdb to view the call stack information. +``` +(gdb) bt +``` +Use the f command in gdb to view the code segment corresponding to the ID. +``` +(gdb)f 1 +(gdb)f 0 +``` + +If there is no function call stack information, it may be that the compiler has removed the debug information. Please manually modify the `Makefile` file in the swoole source directory and modify CFLAGS to + +```shell +CFLAGS = -Wall -pthread -g -O0 +``` diff --git a/docs/SUPPORTED.md b/docs/SUPPORTED.md new file mode 100644 index 0000000000..a2398cd1af --- /dev/null +++ b/docs/SUPPORTED.md @@ -0,0 +1,33 @@ +## Supported Versions + +| Branch | PHP Version | Initialization | Active Support Until | Security Support Until | +|------------------------------------------------------------|-------------|----------------|----------------------|------------------------| +| [v6.0.x](https://github.com/swoole/swoole-src/tree/6.0) | 8.1 - 8.4 | 2024-12-31 | 2025-12-31 | 2026-06-31 | +| [v6.1.x](https://github.com/swoole/swoole-src/tree/master) | 8.1 - 8.4 | 2025-10-31 | 2026-10-31 | 2027-04-31 | + +- **Active support**: A release that is being actively supported. Reported bugs and security issues are fixed and regular point releases are made. +- **Security fixes only**: A release that is supported for critical security issues only. Releases are only made on an as-needed basis. + +## PHP Version Support + +1. Each branch (`MINOR version`) supports a fixed range of PHP versions. The `RELEASE VERSIONS` for that branch will not increase support for higher PHP versions. +2. The upper limit is four PHP versions; any additional versions will not be supported. For example, version 6.0 only supports PHP 8.1 to 8.4. +3. No support for any DEV or RC stage of PHP + +The pace of PHP version updates is rapid, with each version introducing numerous underlying changes. The developers of Swoole have had to invest significant time and effort to support new releases, and we lack sufficient resources to keep up with PHP updates. Therefore, there will be a delay of one MINOR version before supporting new PHP versions. + +## Unsupported Branches + +> These releases that are no longer supported. Users of this release should upgrade as soon as possible, as they may be exposed to unpatched security vulnerabilities. + + +| Branch | PHP Version | Duration | +|--------------------------|-------------|-------------------------| +| `1.x.x` | 5.4 - 7.2 | 2012-07-01 ~ 2018-05-14 | +| `2.x.x` | 7.0 - 7.3 | 2016-12-30 ~ 2018-05-23 | +| `4.0.x` ~ `4.3.x` | 7.0 - 7.4 | 2018-06-14 ~ 2019-12-31 | +| `4.4.x` | 7.1 - 7.4 | 2019-04-15 ~ 2022-07-31 | +| `4.5.x`,`4.6.x`, `4.7.x` | 7.1 - 7.4 | 2019-12-20 ~ 2021-12-31 | +| `4.8.x` | 7.3 - 8.2 | 2021-10-14 ~ 2024-06-30 | +| `5.0.x` | 7.4 - 8.3 | 2022-01-20 ~ 2023-07-20 | +| `5.1.x` | 8.0 - 8.3 | 2023-11-29 ~ 2025-04-29 | diff --git a/docs/TESTS.md b/docs/TESTS.md new file mode 100644 index 0000000000..6a976d548b --- /dev/null +++ b/docs/TESTS.md @@ -0,0 +1,83 @@ +## Swoole Tests + +## Core Tests +Used to test the core code in the `src/` directory, only as a C++ library, not related to php. +The core tests depends on the googletest framework, and googletest needs to be installed. +The core test cases must be written with `C++`. + +`GCC/G++` version `8.0` or higher is required, with full support for `C++17`. + +### **Build googletest** +Since swoole core unit testing rely on googletest, we need compile googletest at first. + +```shell +wget https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz +tar zxf googletest-1.15.2.tar.gz +cd googletest-1.15.2 +mkdir ./build && cd ./build +cmake .. +make -j +sudo make install +``` + +### Build libswoole.so + +```shell +cd swoole-src +cmake . +make -j$(nproc) lib-swoole +``` +### Build core-tests +```shell +make -j$(nproc) core-tests +``` + +### Run core-tests +```shell +# run all dependency services +cd core-tests +docker compose up -d +# run all tests +./bin/core-tests +# run some test cases +./bin/core-tests --gtest_filter=server.* +# list all tests +./bin/core_tests --gtest_list_tests +``` + +## PHP Tests +Used to test the php extension code in the `ext-src/` directory. The swoole php test depends on php environment. +The `php-dev` related components must be installed. + +The php test cases must be written with `PHP`. + +### Build ext-swoole +```shell +cd swoole-src +phpize +./configure ${options} +make -j$(nproc) +make install +``` +Need to configure `php.ini`, add `extension=swoole.so` to enable `ext-swoole`. + +### Run tests +```shell +./scripts/route.sh +``` + +The automated test scripts in this directory can not only run on Github Action CI. Powered by docker container technology, it can run on any systems. You only need to run the `route.sh` script to create containers of multiple PHP environments then it will run Swoole's build tests and unit tests on multiple systems automatically. + +### With special branch + +```shell +SWOOLE_BRANCH=alpine ./scripts/route.sh +``` + +### Enter the container + +> You can cancel the unit test by `CTRL+C` + +```shell +docker exec -it -e LINES=$(tput lines) -e COLUMNS=$(tput cols) swoole /bin/bash +``` diff --git a/docs/WORKFLOW-PARAMETERS.md b/docs/WORKFLOW-PARAMETERS.md new file mode 100644 index 0000000000..91289839d1 --- /dev/null +++ b/docs/WORKFLOW-PARAMETERS.md @@ -0,0 +1,17 @@ +# Workflow Parameters +Adding parameters in the Git commit log can control the workflow. + +## --filter +This parameter specifies which workflows to run, instead of running all of them. +The command format is: `--filter=[flow1][flow2][flow...]` , +and it supports setting multiple workflows, with names matching the filename of the yml file. + +## --valgrind +Setting this parameter will cause the test program to be run with `valgrind`. + +```shell +git commit -m "commit message --filter=[core][unit] --valgrind" +``` + +## --asan +Setting this parameter will cause the test program to be run with `ASAN`. diff --git a/google-style.xml b/docs/google-style.xml similarity index 100% rename from google-style.xml rename to docs/google-style.xml diff --git a/docs/sponsors.md b/docs/sponsors.md new file mode 100644 index 0000000000..27bbfb7c10 --- /dev/null +++ b/docs/sponsors.md @@ -0,0 +1,81 @@ +
+ +# [成为 Swoole 的赞助者](#title1) +--- + +Swoole 是采用 Apache 许可的开源项目,是完全免费的。 + +维护这样一个庞大的生态系统和为项目开发新功能所需的巨大努力,只有在我们的赞助者慷慨的财务支持下才得以持续。 +捐赠募集到的资金将全部用于开源项目的维护和发展。包括但不限于: +- 向核心贡献者支付报酬 +- 服务器租赁费用 +- 网站和文档的维护 +- 举办线下活动 +- 为社区活跃者提供奖励 + +> 您可以扫码添加微信客服或通过邮件 `service@swoole.com` 与我们联系,沟通赞助事宜 + +
+ +## [赞助渠道](#title2) + +--- +来自世界各地的企业可以通过赞助来支持 `Swoole` 项目的开发。 +根据赞助商赞助金额的不同分为顶级赞助商、金牌赞助商,在获得捐赠的第一年内我们将为赞助商做以下品牌展示: + +> 顶级赞助商 +- 官网、文档、问答社区首屏顶部显著位置展示 +- GitHub 仓库首页顶部赞助商标识 +- 官方微信公众号、QQ 群、微博、知乎等官方社交平台赞助商宣传内容推送 + +> 金牌赞助商 +- 官网、文档、问答社区首屏侧边位置展示 + +## 顶级赞助商 + + + + +## 金牌赞助商 + + + + + + + + + +## 银牌赞助商 + + + + + + + + diff --git a/swoole-logo.svg b/docs/swoole-logo.svg similarity index 100% rename from swoole-logo.svg rename to docs/swoole-logo.svg diff --git a/examples/c10k.php b/examples/c10k.php deleted file mode 100644 index 371fb82f94..0000000000 --- a/examples/c10k.php +++ /dev/null @@ -1,28 +0,0 @@ - 0) - { - continue; - } - else - { - for($i = 0; $i < 9999; $i++){ - $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 - $ret = $client->connect('127.0.0.1', 9501, 0.5); - if(!$ret) - { - echo "#$i\tConnect fail. errno=".$client->errCode; - die("\n"); - } - $clients[] = $client; - usleep(10); - } - echo "Worker #".posix_getpid()." connect $i finish\n"; - sleep(1000); - exit; - } -} -sleep(1000); diff --git a/examples/client/c10k.php b/examples/client/c10k.php new file mode 100644 index 0000000000..7f4423bd76 --- /dev/null +++ b/examples/client/c10k.php @@ -0,0 +1,23 @@ + 0) { + continue; + } else { + for ($i = 0; $i < 9999; $i++) { + $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 + $ret = $client->connect('127.0.0.1', 9501, 0.5); + if (!$ret) { + echo "#$i\tConnect fail. errno=" . $client->errCode; + die("\n"); + } + $clients[] = $client; + usleep(10); + } + echo "Worker #" . posix_getpid() . " connect $i finish\n"; + sleep(1000); + exit; + } +} +sleep(1000); diff --git a/examples/recv_1m_client.php b/examples/client/recv_1m.php similarity index 100% rename from examples/recv_1m_client.php rename to examples/client/recv_1m.php diff --git a/examples/client/recv_file.php b/examples/client/recv_file.php new file mode 100644 index 0000000000..1af8d443d5 --- /dev/null +++ b/examples/client/recv_file.php @@ -0,0 +1,36 @@ +connect($server_ip, 9501, 5); +$filesize = intval($cli->recv()); +if ($filesize == 0) { + die("get file size failed.\n"); +} +echo "file_size = $filesize\n"; +$content = ''; +$cli->send("get file"); + +$use_waitall = false; + +if ($use_waitall) { + //waitall,需要一次性分配内存,适合小一点的文件 + $content = $cli->recv($filesize, true); +} else { + //循环接收,适合大型文件 + while (1) { + //超大文件接收,这里需要改成分段写磁盘 + $content .= $cli->recv(); + if (strlen($content) == $filesize) { + break; + } + } +} +file_put_contents(__DIR__ . "/recv_file_" . time() . ".jpg", $content); +echo "recv " . strlen($content) . " byte data\n"; +echo "used " . ((microtime(true) - $start_ms) * 1000) . "ms\n"; +$cli->close(); diff --git a/examples/client/select.php b/examples/client/select.php index df6013cd0c..98d23a43dd 100644 --- a/examples/client/select.php +++ b/examples/client/select.php @@ -1,30 +1,23 @@ connect('127.0.0.1', 9501, 0.5, 0); - if(!$ret) - { - echo "Connect Server fail.errCode=".$client->errCode; - } - else - { - $client->send("HELLO WORLD\n"); - $clients[$client->sock] = $client; + if (!$ret) { + echo "Connect Server fail.errCode=" . $client->errCode; + } else { + $client->send("HELLO WORLD\n"); + $clients[$client->sock] = $client; } } -while (!empty($clients)) -{ +while (!empty($clients)) { $write = $error = array(); $read = array_values($clients); $n = swoole_client_select($read, $write, $error, 0.6); - if ($n > 0) - { - foreach ($read as $index => $c) - { + if ($n > 0) { + foreach ($read as $index => $c) { echo "Recv #{$c->sock}: " . $c->recv() . "\n"; unset($clients[$c->sock]); } diff --git a/examples/client/simple.php b/examples/client/simple.php new file mode 100644 index 0000000000..2756cdc435 --- /dev/null +++ b/examples/client/simple.php @@ -0,0 +1,20 @@ +connect('127.0.0.1', 9501, 0.5, 0); + if (!$ret) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } + $clients[] = $client; +} +sleep(1); +while (1) { + foreach ($clients as $client) { + $client->send("sss"); + $data = $client->recv(); + var_dump($data); + } + sleep(1); +} diff --git a/examples/client/sync.php b/examples/client/sync.php index 95f892701b..d93713d21f 100644 --- a/examples/client/sync.php +++ b/examples/client/sync.php @@ -7,8 +7,7 @@ //if (!$client->connect(dirname(__DIR__).'/server/svr.sock', 0, -1, 1)) do_connect: -if (!$client->connect('127.0.0.1', 9501, -1)) -{ +if (!$client->connect('127.0.0.1', 9501, -1)) { exit("connect failed. Error: {$client->errCode}\n"); } @@ -23,7 +22,6 @@ $client->close(); $count++; -if ($count < 20) -{ +if ($count < 20) { goto do_connect; } diff --git a/examples/client2.php b/examples/client2.php deleted file mode 100644 index b45fc13620..0000000000 --- a/examples/client2.php +++ /dev/null @@ -1,21 +0,0 @@ -connect('127.0.0.1', 9501, 0.5, 0); - if(!$ret) - { - echo "Over flow. errno=".$client->errCode; - die("\n"); - } - $clients[] = $client; -} -sleep(1); -while (1) { - foreach ($clients as $client) { - $client->send("sss"); - $data = $client->recv(); - var_dump($data); - } - sleep(1); -} diff --git a/examples/coroutine/cancel_throw.php b/examples/coroutine/cancel_throw.php new file mode 100644 index 0000000000..e05fcb10a4 --- /dev/null +++ b/examples/coroutine/cancel_throw.php @@ -0,0 +1,17 @@ +handle('/', function ($request, $response) { - var_dump($request); - var_dump($request->get); +// var_dump($request); +// var_dump($request->get); $response->end("

hello world

"); }); diff --git a/examples/coroutine/http/write_func.php b/examples/coroutine/http/write_func.php new file mode 100644 index 0000000000..e8beabc6e4 --- /dev/null +++ b/examples/coroutine/http/write_func.php @@ -0,0 +1,14 @@ + SWOOLE_TRACE_HTTP2, + 'log_level' => 0, +]); +Co\run(function () { + $client = new Swoole\Coroutine\Http\Client('www.jd.com', 443, true); + $client->set(['write_func' => function($client, $data) { + var_dump(strlen($data)); + }]); + $client->get('/'); + var_dump(strlen($client->getBody())); + return 0; +}); diff --git a/examples/coroutine/http_download.php b/examples/coroutine/http_download.php index d5d3d21793..391d57608f 100644 --- a/examples/coroutine/http_download.php +++ b/examples/coroutine/http_download.php @@ -1,7 +1,10 @@ set(['timeout' => -1]); $cli->setHeaders([ 'Host' => $host, @@ -9,5 +12,5 @@ 'Accept' => '*', 'Accept-Encoding' => 'gzip' ]); - $cli->download('/static/files/swoole-logo.svg', __DIR__ . '/logo.svg'); + $cli->download('/dist/skin1/images/logo-white.png', '/tmp/logo.png'); }); diff --git a/examples/cpp/Makefile b/examples/cpp/Makefile index b5663c6464..b3bc0fab6c 100644 --- a/examples/cpp/Makefile +++ b/examples/cpp/Makefile @@ -1,8 +1,10 @@ -all: co repeat +all: co repeat test_server co: co.cc g++ co.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o co -g repeat: repeat.cc g++ repeat.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o repeat -g +test_server: test_server.cc + g++ test_server.cc -I../.. -I../../include -DHAVE_CONFIG_H -L../../lib -lswoole -o test_server -g clean: - rm -f co repeat + rm -f co repeat test_server diff --git a/examples/cpp/co.cc b/examples/cpp/co.cc index 8ba86cb7d1..bc9f65a1b9 100644 --- a/examples/cpp/co.cc +++ b/examples/cpp/co.cc @@ -1,12 +1,13 @@ +#include "swoole_coroutine.h" +#include "swoole_coroutine_socket.h" +#include "swoole_coroutine_system.h" +#include "swoole_signal.h" + #include #include #include #include -#include "swoole_coroutine.h" -#include "swoole_coroutine_socket.h" -#include "swoole_coroutine_system.h" - using swoole::Coroutine; using swoole::coroutine::Socket; using swoole::coroutine::System; @@ -17,94 +18,95 @@ list slaves; size_t qs; int main(int argc, char **argv) { - swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - signal(SIGPIPE, SIG_IGN); - Coroutine::create([](void *arg) { - System::sleep(2.0); - cout << "CO-1, sleep 2\n"; - }); + swoole::coroutine::run([](void *arg) { + Coroutine::create([](void *arg) { + System::sleep(2.0); + cout << "CO-1, sleep 2\n"; + }); - Coroutine::create([](void *arg) { - System::sleep(1); - cout << "CO-2, sleep 1\n"; - }); + Coroutine::create([](void *arg) { + System::sleep(1); + cout << "CO-2, sleep 1\n"; + }); - Coroutine::create([](void *arg) { - cout << "CO-3, listen tcp:0.0.0.0:9001\n"; - Socket s(SW_SOCK_TCP); - s.bind("0.0.0.0", 9001); - s.listen(); + Coroutine::create([](void *arg) { + cout << "CO-3, listen tcp:0.0.0.0:9001\n"; + Socket s(SW_SOCK_TCP); + s.bind("0.0.0.0", 9001); + s.listen(); - while (1) { - Socket *_client = s.accept(); - Coroutine::create( - [](void *arg) { - Socket *client = (Socket *) arg; - while (1) { - char buf[1024]; - auto retval = client->recv(buf, sizeof(buf)); - if (retval == 0) { - cout << "connection close\n"; - break; - } else { - if (strncasecmp("push", buf, 4) == 0) { - q.push_back(string(buf + 5, retval - 5)); - qs += retval - 5; - string resp("OK\n"); - client->send(resp.c_str(), resp.length()); + while (1) { + Socket *_client = s.accept(); + Coroutine::create( + [](void *arg) { + Socket *client = (Socket *) arg; + while (1) { + char buf[1024]; + auto retval = client->recv(buf, sizeof(buf)); + if (retval == 0) { + cout << "connection close\n"; + break; + } else { + if (strncasecmp("push", buf, 4) == 0) { + q.push_back(string(buf + 5, retval - 5)); + qs += retval - 5; + string resp("OK\n"); + client->send(resp.c_str(), resp.length()); - for (auto it = slaves.begin(); it != slaves.end();) { - auto sc = *it; - auto n = sc->send(buf, retval); - if (n <= 0) { - it = slaves.erase(it); - delete sc; + for (auto it = slaves.begin(); it != slaves.end();) { + auto sc = *it; + auto n = sc->send(buf, retval); + if (n <= 0) { + it = slaves.erase(it); + delete sc; + } else { + it++; + } + } + } else if (strncasecmp("pop", buf, 3) == 0) { + if (q.empty()) { + string resp("EMPTY\n"); + client->send(resp.c_str(), resp.length()); } else { - it++; + auto data = q.front(); + q.pop_front(); + qs -= data.length(); + client->send(data.c_str(), data.length()); } - } - } else if (strncasecmp("pop", buf, 3) == 0) { - if (q.empty()) { - string resp("EMPTY\n"); - client->send(resp.c_str(), resp.length()); + } else if (strncasecmp("stat", buf, 4) == 0) { + char stat_buf[64]; + int n = snprintf(stat_buf, sizeof(stat_buf), "count=%ld,bytes=%ld\n", q.size(), qs); + client->send(stat_buf, n); } else { - auto data = q.front(); - q.pop_front(); - qs -= data.length(); - client->send(data.c_str(), data.length()); + string resp("ERROR\n"); + client->send(resp.c_str(), resp.length()); } - } else if (strncasecmp("stat", buf, 4) == 0) { - char stat_buf[64]; - int n = snprintf(stat_buf, sizeof(stat_buf), "count=%ld,bytes=%ld\n", q.size(), qs); - client->send(stat_buf, n); - } else { - string resp("ERROR\n"); - client->send(resp.c_str(), resp.length()); } } - } - delete client; - }, - _client); - } - }); + delete client; + }, + _client); + } + }); - Coroutine::create([](void *arg) { - Socket s(SW_SOCK_TCP); - s.bind("0.0.0.0", 9002); - s.listen(); - while (1) { - Socket *_client = s.accept(); - for (auto data : q) { - _client->send(data.c_str(), data.length()); + Coroutine::create([](void *arg) { + Socket s(SW_SOCK_TCP); + s.bind("0.0.0.0", 9002); + s.listen(); + while (1) { + Socket *_client = s.accept(); + if (_client == nullptr) { + break; + } + for (auto data : q) { + _client->send(data.c_str(), data.length()); + } + slaves.push_back(_client); } - slaves.push_back(_client); - } + }); }); - swoole_event_wait(); - return 0; } diff --git a/examples/cpp/repeat.cc b/examples/cpp/repeat.cc index fb4587ef45..6d79537be4 100644 --- a/examples/cpp/repeat.cc +++ b/examples/cpp/repeat.cc @@ -21,8 +21,8 @@ int main(int argc, char **argv) { serv.onPacket = [](Server *serv, RecvData *req) { return SW_OK; }; - serv.onWorkerStart = [](Server *serv, int worker_id) { - swoole_notice("WorkerStart[%d]PID=%d, serv=%p,", worker_id, getpid(), serv); + serv.onWorkerStart = [](Server *serv, Worker *worker) { + swoole_notice("WorkerStart[%d]PID=%d, serv=%p,", worker->id, getpid(), serv); swoole_timer_after( 1000, [serv](Timer *, TimerNode *tnode) { diff --git a/examples/cpp/test_server.cc b/examples/cpp/test_server.cc index fe3fca106b..b44b88f73d 100644 --- a/examples/cpp/test_server.cc +++ b/examples/cpp/test_server.cc @@ -14,8 +14,8 @@ void my_onStart(Server *serv); void my_onShutdown(Server *serv); void my_onConnect(Server *serv, DataHead *info); void my_onClose(Server *serv, DataHead *info); -void my_onWorkerStart(Server *serv, int worker_id); -void my_onWorkerStop(Server *serv, int worker_id); +void my_onWorkerStart(Server *serv, Worker *worker); +void my_onWorkerStop(Server *serv, Worker *worker); static int g_receive_count = 0; @@ -60,7 +60,7 @@ int main(int argc, char **argv) { serv.add_port(SW_SOCK_TCP6, "::", 9503); serv.add_port(SW_SOCK_UDP6, "::", 9504); - swListenPort *port = serv.add_port(SW_SOCK_TCP, "127.0.0.1", 9501); + ListenPort *port = serv.add_port(SW_SOCK_TCP, "127.0.0.1", 9501); if (!port) { swoole_warning("listen failed, [error=%d]", swoole_get_last_error()); exit(2); @@ -83,12 +83,12 @@ int main(int argc, char **argv) { return 0; } -void my_onWorkerStart(Server *serv, int worker_id) { - swoole_notice("WorkerStart[%d]PID=%d", worker_id, getpid()); +void my_onWorkerStart(Server *serv, Worker *worker) { + swoole_notice("WorkerStart[%d]PID=%d", worker->id, worker->pid); } -void my_onWorkerStop(Server *serv, int worker_id) { - swoole_notice("WorkerStop[%d]PID=%d", worker_id, getpid()); +void my_onWorkerStop(Server *serv, Worker *worker) { + swoole_notice("WorkerStop[%d]PID=%d", worker->id, worker->pid); } int my_onReceive(Server *serv, RecvData *req) { @@ -102,11 +102,11 @@ int my_onReceive(Server *serv, RecvData *req) { memcpy(req_data, req->data, req->info.len); swoole::rtrim(req_data, req->info.len); swoole_notice("onReceive[%d]: ip=%s|port=%d Data=%s|Len=%d", - g_receive_count, - conn->info.get_ip(), - conn->info.get_port(), - req_data, - req->info.len); + g_receive_count, + conn->info.get_addr(), + conn->info.get_port(), + req_data, + req->info.len); int n = sw_snprintf(resp_data, SW_IPC_BUFFER_SIZE, "Server: %.*s\n", req->info.len, req_data); diff --git a/examples/eof/client.php b/examples/eof/client.php index 290431e106..a013c99a27 100644 --- a/examples/eof/client.php +++ b/examples/eof/client.php @@ -3,32 +3,27 @@ * 分段发送数据 * * @param Swoole\Client $client - * @param string $data - * @param int $chunk_size + * @param string $data + * @param int $chunk_size */ function send_chunk(Swoole\Client $client, $data, $chunk_size = 1024) { - $len = strlen($data); - $chunk_num = intval($len / $chunk_size) + 1; - for ($i = 0; $i < $chunk_num; $i++) - { - if ($len < ($i + 1) * $chunk_size) - { - $sendn = $len - ($i * $chunk_size); - } - else - { - $sendn = $chunk_size; - } - $client->send(substr($data, $i * $chunk_size, $sendn)); - } + $len = strlen($data); + $chunk_num = intval($len / $chunk_size) + 1; + for ($i = 0; $i < $chunk_num; $i++) { + if ($len < ($i + 1) * $chunk_size) { + $sendn = $len - ($i * $chunk_size); + } else { + $sendn = $chunk_size; + } + $client->send(substr($data, $i * $chunk_size, $sendn)); + } } $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 -if(!$client->connect('127.0.0.1', 9501, 0.5, 0)) -{ - echo "Over flow. errno=".$client->errCode; - die("\n"); +if (!$client->connect('127.0.0.1', 9501, 0.5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); } //for ($i = 0; $i < 10; $i++) @@ -39,21 +34,20 @@ function send_chunk(Swoole\Client $client, $data, $chunk_size = 1024) //exit; $data = array( - 'name' => __FILE__, - 'content' => str_repeat('A', 8192 * rand(1, 3)), //800K + 'name' => __FILE__, + 'content' => str_repeat('A', 8192 * rand(1, 3)), //800K ); $_serialize_data = serialize($data); -$_send = $_serialize_data."__doit__"; +$_send = $_serialize_data . "__doit__"; -echo "serialize_data length=".strlen($_serialize_data)."send length=".strlen($_send)."\n"; +echo "serialize_data length=" . strlen($_serialize_data) . "send length=" . strlen($_send) . "\n"; //send_chunk($client, $_send); // -if(!$client->send($_send)) -{ - die("send failed.\n"); +if (!$client->send($_send)) { + die("send failed.\n"); } //$client->send("\r\n".substr($_serialize_data, 0, 8000)); @@ -65,9 +59,8 @@ function send_chunk(Swoole\Client $client, $data, $chunk_size = 1024) //usleep(500000); -if (!$client->send("\r\n\r\n")) -{ - die("send failed.\n"); +if (!$client->send("\r\n\r\n")) { + die("send failed.\n"); } echo $client->recv(); diff --git a/examples/eof/server.php b/examples/eof/server.php index eeb876dfc3..bbf1ab21ee 100644 --- a/examples/eof/server.php +++ b/examples/eof/server.php @@ -11,21 +11,18 @@ //$serv->on('connect', function ($serv, $fd) { // //echo "[#" . posix_getpid() . "]\tClient:Connect.\n"; //}); -$serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) -{ +$serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { echo '#' . $serv->worker_id . " recv: " . strlen($data) . "\n"; - for ($i = 0; $i < 1000; $i++) - { + for ($i = 0; $i < 1000; $i++) { $resp = str_repeat('A', rand(10000, 50000)) . "\r\n\r\n"; $serv->send($fd, $resp); - if ($i % 100 == 1) - { + if ($i % 100 == 1) { sleep(1); - echo "send ".strlen($resp)." bytes\n"; + echo "send " . strlen($resp) . " bytes\n"; } } }); //$serv->on('close', function ($serv, $fd) { - //echo "[#" . posix_getpid() . "]\tClient: Close.\n"; +//echo "[#" . posix_getpid() . "]\tClient: Close.\n"; //}); $serv->start(); diff --git a/examples/event/cycle.php b/examples/event/cycle.php index 4282a6f2dd..bc4a20da26 100644 --- a/examples/event/cycle.php +++ b/examples/event/cycle.php @@ -11,3 +11,5 @@ Swoole\Event::cycle(null); }); }); + +Swoole\Event::wait(); diff --git a/examples/event/inotify.php b/examples/event/inotify.php index e492ad3018..89d9661984 100644 --- a/examples/event/inotify.php +++ b/examples/event/inotify.php @@ -13,3 +13,5 @@ } } }); + +Swoole\Event::wait(); diff --git a/examples/event/sockets.php b/examples/event/sockets.php index bd90feef48..65bf34583a 100644 --- a/examples/event/sockets.php +++ b/examples/event/sockets.php @@ -3,51 +3,48 @@ * require ./configure --enable-sockets */ +use Swoole\Event; + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Unable to create socket\n"); socket_set_nonblock($socket) or die("Unable to set nonblock on socket\n"); function socket_onRead($socket) { - static $i = 0; - - echo socket_read($socket, 8192)."\n"; - $i ++; - if ($i > 10) - { - echo "finish\n"; - Swoole\Event::del($socket); - socket_close($socket); - } - else - { - sleep(1); - Swoole\Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); - } + static $i = 0; + + echo socket_read($socket, 8192) . "\n"; + $i++; + if ($i > 10) { + echo "finish\n"; + Event::del($socket); + socket_close($socket); + } else { + sleep(1); + Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + } } function socket_onWrite($socket) { - socket_write($socket, "hi swoole"); - Swoole\Event::set($socket, null, null, SWOOLE_EVENT_READ); + socket_write($socket, "hi swoole"); + Event::set($socket, null, null, SWOOLE_EVENT_READ); } function socket_onConnect($socket) { - $err = socket_get_option($socket, SOL_SOCKET, SO_ERROR); - if ($err == 0) - { - echo "connect server success\n"; - Swoole\Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ); - socket_write($socket, "first package\n"); - } - else - { - echo "connect server failed\n"; - Swoole\Event::del($socket); - socket_close($socket); - } + $err = socket_get_option($socket, SOL_SOCKET, SO_ERROR); + if ($err == 0) { + echo "connect server success\n"; + Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ); + socket_write($socket, "first package\n"); + } else { + echo "connect server failed\n"; + Event::del($socket); + socket_close($socket); + } } -Swoole\Event::add($socket, 'socket_onRead', 'socket_onConnect', SWOOLE_EVENT_WRITE); -@socket_connect($socket, '127.0.0.1', 9501); +Event::add($socket, 'socket_onRead', 'socket_onConnect', SWOOLE_EVENT_WRITE); +socket_connect($socket, '127.0.0.1', 9501); +Event::wait(); diff --git a/examples/event/stdin.php b/examples/event/stdin.php index 06e6ee2137..bbf93cd9b2 100644 --- a/examples/event/stdin.php +++ b/examples/event/stdin.php @@ -2,3 +2,4 @@ Swoole\Event::add(STDIN, function($fp) { echo "STDIN: ".fread($fp, 8192); }); +Swoole\Event::wait(); diff --git a/examples/event/stream.php b/examples/event/stream.php index bf0e37b38f..4aed977ed1 100644 --- a/examples/event/stream.php +++ b/examples/event/stream.php @@ -19,3 +19,4 @@ function stream_onRead($fp) Swoole\Event::add($fp, 'stream_onRead'); echo "start\n"; +Swoole\Event::wait(); diff --git a/examples/event/test.php b/examples/event/test.php index 0a832a30bf..d247a9a231 100644 --- a/examples/event/test.php +++ b/examples/event/test.php @@ -7,3 +7,4 @@ Swoole\Event::del($fp); fclose($fp); }); +Swoole\Event::wait(); diff --git a/examples/get_local_ip.php b/examples/get_local_ip.php deleted file mode 100644 index 35b2c78340..0000000000 --- a/examples/get_local_ip.php +++ /dev/null @@ -1,2 +0,0 @@ -on('request', function ($req, Swoole\Http\Response $resp) use ($http) { + if ($req->server['request_uri'] == '/stream') { + $resp->header("Content-Type", "text/event-stream"); + $resp->header("Cache-Control", "no-cache"); + $resp->header("Connection", "keep-alive"); + $resp->header("X-Accel-Buffering", "no"); + $resp->header('Content-Encoding', ''); + $resp->header("Content-Length", ''); + $resp->end(); + go(function () use ($resp, $http) { + while (true) { + Co::sleep(1); + $http->send($resp->fd, 'data: ' . base64_encode(random_bytes(random_int(16, 128))). "\n\n"); + } + }); + } elseif ($req->server['request_uri'] == '/') { + $resp->end(<< + + +HTML + ); + } else { + $resp->status(404); + $resp->end(); + } +}); + +$http->start(); diff --git a/examples/http/no-compression.php b/examples/http/no-compression.php new file mode 100644 index 0000000000..b1228aa80b --- /dev/null +++ b/examples/http/no-compression.php @@ -0,0 +1,17 @@ +on('request', function ($req, Swoole\Http\Response $resp) use ($http) { + if ($req->server['request_uri'] == '/') { + $resp->header('Content-Encoding', ''); + $resp->end(str_repeat('A', 1024)); + } elseif ($req->server['request_uri'] == '/gzip') { + $resp->end(str_repeat('A', 1024)); + } else { + $resp->status(404); + $resp->end(); + } +}); + +$http->start(); diff --git a/examples/http/server.php b/examples/http/server.php index a38f8e9d8d..ac9dcaf2e3 100644 --- a/examples/http/server.php +++ b/examples/http/server.php @@ -6,6 +6,7 @@ function dump($var) $key_dir = dirname(dirname(__DIR__)) . '/tests/ssl'; $http = new Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE); +//$http = new Swoole\Http\Server("::", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP6); //$http = new Swoole\Http\Server("0.0.0.0", 9501); //$http = new Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); //https diff --git a/examples/http/url_rewrite.php b/examples/http/url_rewrite.php new file mode 100644 index 0000000000..e85a56e916 --- /dev/null +++ b/examples/http/url_rewrite.php @@ -0,0 +1,39 @@ +set([ + 'enable_static_handler' => true, + 'document_root' => realpath(__DIR__.'/../www/'), + 'http_autoindex' => true, + // URL重写规则配置 + 'url_rewrite_rules' => [ + // 普通路径重写: 将 /api 开头的请求重写到 /static/api 目录 + '/api' => '/static/api', + + // 正则表达式重写: 将 /user/123 格式的请求重写到 /static/user.html?id=123 + '~^/user/(\\d+)$~' => '/static/user.html?id=$1', + + // 正则表达式重写: 将 /article/title-slug 格式的请求重写到 /static/article.html?slug=title-slug + '~^/article/([\\w\\-]+)$~' => '/static/article.html?slug=$1' + ] +]); + +$http->on('request', function ($request, $response) { + $response->header('Content-Type', 'text/plain; charset=utf-8'); + $response->end("动态处理: " . $request->server['request_uri']); +}); + +$http->on('start', function ($server) { + echo "HTTP服务器已启动,监听 0.0.0.0:9501\n"; + echo "URL重写功能已启用\n"; + echo "测试示例:\n"; + echo "1. 普通重写: http://localhost:9501/api/test.txt -> /static/api/test.txt\n"; + echo "2. 正则重写: http://localhost:9501/user/123 -> /static/user.html?id=123\n"; + echo "3. 正则重写: http://localhost:9501/article/test-title -> /static/article.html?slug=test-title\n"; +}); + +$http->start(); \ No newline at end of file diff --git a/examples/http2/server.php b/examples/http2/server.php index 781ae316a9..195d2d9385 100644 --- a/examples/http2/server.php +++ b/examples/http2/server.php @@ -9,6 +9,7 @@ 'open_http2_protocol' => 1, 'enable_static_handler' => TRUE, 'document_root' => dirname(__DIR__), + 'package_max_length' => 1024 * 1024, 'ssl_cert_file' => $key_dir . '/ssl.crt', 'ssl_key_file' => $key_dir . '/ssl.key', ]); diff --git a/examples/http2/streaming.php b/examples/http2/streaming.php new file mode 100644 index 0000000000..f0379f80cf --- /dev/null +++ b/examples/http2/streaming.php @@ -0,0 +1,21 @@ +set([ + 'open_http2_protocol' => 1, +]); + +/** + * nghttp -v http://localhost:9501 + */ +$http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $n = 5; + while ($n--) { + $response->write("hello world, #$n
\n"); + Co\System::sleep(1); + } + $response->end("hello world"); +}); + +$http->start(); + diff --git a/examples/logo.svg b/examples/logo.svg deleted file mode 100644 index 3e06c3fdc4..0000000000 --- a/examples/logo.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/examples/misc/get_local_ip.php b/examples/misc/get_local_ip.php new file mode 100644 index 0000000000..b8c9c21dd8 --- /dev/null +++ b/examples/misc/get_local_ip.php @@ -0,0 +1,3 @@ + '224.10.20.30', 'interface' => 0) ); -if ($ret === false) -{ +if ($ret === false) { throw new RuntimeException('Unable to join multicast group'); } -$server->on('Packet', function (Swoole\Server $serv, $data, $addr) -{ +$server->on('Packet', function (Swoole\Server $serv, $data, $addr) { $serv->sendto($addr['address'], $addr['port'], "Swoole: $data"); - var_dump( $addr, strlen($data)); + var_dump($addr, strlen($data)); }); $server->start(); diff --git a/examples/namespace/README.md b/examples/namespace/README.md deleted file mode 100644 index a396a3f5c5..0000000000 --- a/examples/namespace/README.md +++ /dev/null @@ -1,24 +0,0 @@ -Enable Namespace Class ---------- -modify your `php.ini` file. - -```shell -swoole.use_namespace = on -``` - - -```php -use Swoole\Http\Server; -use Swoole\Http\Request; -use Swoole\Http\Response; - -$serv = new Server('127.0.0.1', 9501); - -$serv->on('Request', function(Request $req, Response $resp) { - var_dump($req->header, get_class($req)); - $resp->end("

Hello Swoole

"); -}); - -$serv->start(); - -``` diff --git a/examples/namespace/atomic.php b/examples/namespace/atomic.php deleted file mode 100644 index 523e333f2d..0000000000 --- a/examples/namespace/atomic.php +++ /dev/null @@ -1,5 +0,0 @@ -add(12); -echo $an->get()."\n"; diff --git a/examples/namespace/http_server.php b/examples/namespace/http_server.php deleted file mode 100644 index 6560089186..0000000000 --- a/examples/namespace/http_server.php +++ /dev/null @@ -1,13 +0,0 @@ -on('Request', function(Request $req, Response $resp) { - var_dump($req->header, get_class($req)); - $resp->end("

Hello Swoole

"); -}); - -$serv->start(); diff --git a/examples/namespace/server.php b/examples/namespace/server.php deleted file mode 100644 index 661751d5f0..0000000000 --- a/examples/namespace/server.php +++ /dev/null @@ -1,14 +0,0 @@ -on('receive', function (Server $serv, $fd, $reactor_id, $data) { - echo "[#".$serv->worker_id."]\tClient[$fd]: $data\n"; - if ($serv->send($fd, "hello\n") == false) - { - echo "error\n"; - } -}); - -$serv->start(); diff --git a/examples/namespace/timer.php b/examples/namespace/timer.php deleted file mode 100644 index e9f551745c..0000000000 --- a/examples/namespace/timer.php +++ /dev/null @@ -1,4 +0,0 @@ -connect($server_ip, 9501, 5); -$filesize = intval($cli->recv()); -if ($filesize == 0) -{ - die("get file size failed.\n"); -} -echo "file_size = $filesize\n"; -$content = ''; -$cli->send("get file"); - -$use_waitall = false; - -if ($use_waitall) -{ - //waitall,需要一次性分配内存,适合小一点的文件 - $content = $cli->recv($filesize, true); -} -else -{ - //循环接收,适合大型文件 - while(1) - { - //超大文件接收,这里需要改成分段写磁盘 - $content .= $cli->recv(); - if (strlen($content) == $filesize) - { - break; - } - } -} -file_put_contents(__DIR__."/recv_file_".time().".jpg", $content); -echo "recv ".strlen($content)." byte data\n"; -echo "used ".((microtime(true) - $start_ms)*1000)."ms\n"; -$cli->close(); diff --git a/examples/reflection_test.php b/examples/reflection_test.php deleted file mode 100644 index c5d3fd224b..0000000000 --- a/examples/reflection_test.php +++ /dev/null @@ -1,12 +0,0 @@ -getMethods(); -foreach($methods as $method) { - echo "----------------------------------------" .PHP_EOL; - echo "method name : " . $method->name . PHP_EOL; - echo "----------------------------------------" . PHP_EOL; - $method = $ref_server->getMethod($method->name); - $params = $method->getParameters(); - print_r($params); -} diff --git a/examples/runtime/curl.php b/examples/runtime/curl.php index b6c250231e..4bfb84a9bd 100644 --- a/examples/runtime/curl.php +++ b/examples/runtime/curl.php @@ -5,7 +5,7 @@ go(function () { $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, "http://www.gov.cn/xinwen/index.htm"); + curl_setopt($ch, CURLOPT_URL, "https://www.gov.cn/xinwen/index.htm"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); diff --git a/examples/runtime/file.php b/examples/runtime/file.php index 84eadc606d..317735d69c 100644 --- a/examples/runtime/file.php +++ b/examples/runtime/file.php @@ -1,7 +1,7 @@ listDatabases(); + echo "Available databases:\n"; + foreach ($list as $database) { + echo "- Name: {$database->getName()}\n"; + echo " Size: {$database->getSizeOnDisk()} bytes\n"; + echo " Empty: " . ($database->isEmpty() ? 'Yes' : 'No') . "\n"; + echo "---\n"; + } +}); diff --git a/examples/runtime/oci.php b/examples/runtime/oci.php new file mode 100644 index 0000000000..b7e046768d --- /dev/null +++ b/examples/runtime/oci.php @@ -0,0 +1,25 @@ +exec('create table test (id int)'); + $dbh->exec('insert into test values(1)'); + $dbh->exec('insert into test values(2)'); + $res = $dbh->query("select * from test"); + var_dump($res->fetchAll()); + $dbh = null; + } catch (PDOException $exception) { + echo $exception->getMessage(); + exit; + } +} + +Co::set(['hook_flags' => SWOOLE_HOOK_PDO_ORACLE]); + +Co\run(function () { + test(); +}); diff --git a/examples/runtime/odbc.php b/examples/runtime/odbc.php new file mode 100644 index 0000000000..678c2e25bb --- /dev/null +++ b/examples/runtime/odbc.php @@ -0,0 +1,22 @@ +query("select sleep(1) s"); + var_dump($res->fetchAll()); + $dbh = null; + } catch (PDOException $exception) { + echo $exception->getMessage(); + exit; + } +} + +Co::set(['trace_flags' => SWOOLE_TRACE_CO_ODBC, 'log_level' => SWOOLE_LOG_DEBUG]); + +Co\run(function () { + test(); +}); diff --git a/examples/runtime/sqlite.php b/examples/runtime/sqlite.php new file mode 100644 index 0000000000..7f31bd7110 --- /dev/null +++ b/examples/runtime/sqlite.php @@ -0,0 +1,15 @@ + SWOOLE_HOOK_PDO_SQLITE]); + +run(function() { + $db = new PDO('sqlite::memory:'); + for ($i = 0; $i < 10; $i++) { + go(function() use($i, $db) { + $db->query('select randomblob(99999999)'); + var_dump($i); + }); + } +}); diff --git a/examples/serialize.php b/examples/serialize.php deleted file mode 100644 index b3e067a0cd..0000000000 --- a/examples/serialize.php +++ /dev/null @@ -1,17 +0,0 @@ -pack($arr); - - -$ser2 = $obj->pack($arr,SWOOLE_FAST_PACK); - -var_dump($obj->unpack($ser)); -var_dump($obj->unpack($ser2)); - -?> diff --git a/examples/serialize2.php b/examples/serialize2.php deleted file mode 100644 index 3ee95c247d..0000000000 --- a/examples/serialize2.php +++ /dev/null @@ -1,30 +0,0 @@ -sub = new mySubObject(); -$arr->sub->default = new stdclass(); -$obj = new \Swoole\Serialize(); -$ser = $obj->pack($arr); - - -$ser2 = $obj->pack($arr,SWOOLE_FAST_PACK); - -var_dump($obj->unpack($ser)); -var_dump($obj->unpack($ser2)); -var_dump($obj->unpack($ser, UNSERIALIZE_OBJECT_TO_STDCLASS)); -var_dump($obj->unpack($ser2, UNSERIALIZE_OBJECT_TO_STDCLASS)); -var_dump(UNSERIALIZE_OBJECT_TO_ARRAY); -var_dump(UNSERIALIZE_OBJECT_TO_STDCLASS); -var_dump(get_class($obj->unpack($ser, UNSERIALIZE_OBJECT_TO_STDCLASS))); - -?> diff --git a/examples/server.php b/examples/server.php deleted file mode 100644 index 6c97aec81b..0000000000 --- a/examples/server.php +++ /dev/null @@ -1,537 +0,0 @@ - 16, // 线程数. 一般设置为CPU核数的1-4倍 - 'worker_num' => 2, // 工作进程数量. 设置为CPU的1-4倍最合理 - 'max_request' => 1000, // 防止 PHP 内存溢出, 一个工作进程处理 X 次任务后自动重启 (注: 0,不自动重启) - 'max_conn' => 10000, // 最大连接数 - 'task_worker_num' => 1, // 任务工作进程数量 -// 'task_ipc_mode' => 2, // 设置 Task 进程与 Worker 进程之间通信的方式。 - 'task_max_request' => 0, // 防止 PHP 内存溢出 - //'task_tmpdir' => '/tmp', - //'message_queue_key' => ftok(SYS_ROOT . 'queue.msg', 1), - 'dispatch_mode' => 2, - //'daemonize' => 1, // 设置守护进程模式 - 'backlog' => 128, - //'log_file' => '/data/logs/swoole.log', - 'heartbeat_check_interval' => 2, // 心跳检测间隔时长(秒) - 'heartbeat_idle_time' => 3, // 连接最大允许空闲的时间 - //'open_eof_check' => 1, - //'open_eof_split' => 1, - //'package_eof' => "\r\r\n", - //'open_cpu_affinity' => 1, - 'socket_buffer_size' => 1024 * 1024 * 128, - 'output_buffer_size' => 1024 * 1024 * 2, - //'enable_delay_receive' => true, - //'cpu_affinity_ignore' =>array(0,1)//如果你的网卡2个队列(或者没有多队列那么默认是cpu0来处理中断),并且绑定了core 0和core 1,那么可以通过这个设置避免swoole的线程或者进程绑定到这2个core,防止cpu0,1被耗光而造成的丢包 - ); -} - -if (isset($argv[1]) and $argv[1] == 'daemon') { - G::$config['daemonize'] = true; -} else { - G::$config['daemonize'] = false; -} - -//$mode = SWOOLE_BASE; -$mode = SWOOLE_PROCESS; - -$serv = new Swoole\Server("0.0.0.0", 9501, $mode, SWOOLE_SOCK_TCP); -$serv->listen('0.0.0.0', 9502, SWOOLE_SOCK_UDP); -$serv->listen('::', 9503, SWOOLE_SOCK_TCP6); -$serv->listen('::', 9504, SWOOLE_SOCK_UDP6); -$process1 = new Swoole\Process(function ($worker) use ($serv) { - global $argv; - swoole_set_process_name("php {$argv[0]}: my_process1"); - Swoole\Timer::tick(2000, function ($interval) use ($worker, $serv) { - echo "#{$worker->pid} child process timer $interval\n"; // 如果worker中没有定时器,则会输出 process timer xxx - foreach ($serv->connections as $conn) - { - $serv->send($conn, "heartbeat\n"); - } - }); - Swoole\Timer::tick(5000, function () use ($serv) - { - $serv->sendMessage("hello event worker", 0); - $serv->sendMessage("hello task worker", 4); - }); -}, false); - -//$serv->addprocess($process1); - -$process2 = new Swoole\Process(function ($worker) use ($serv) { - global $argv; - swoole_set_process_name("php {$argv[0]}: my_process2"); - Swoole\Timer::tick(2000, function ($interval) use ($worker, $serv) { - echo "#{$worker->pid} child process timer $interval\n"; // 如果worker中没有定时器,则会输出 process timer xxx - }); -}, false); - -//$serv->addprocess($process2); - -$serv->set(G::$config); -$serv->set(['reactor_num' => 4]); - -/** - * 使用类的静态属性,可以直接访问 - */ -G::$serv = $serv; - -function my_onStart(Swoole\Server $serv) -{ - global $argv; - swoole_set_process_name("php {$argv[0]}: master"); - my_log("Server: start.Swoole version is [".SWOOLE_VERSION."]"); - my_log("MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}"); -} - -function my_log($msg) -{ - global $serv; - if (empty($serv->worker_pid)) - { - $serv->worker_pid = posix_getpid(); - } - echo "#".$serv->worker_pid."\t[".date('H:i:s')."]\t".$msg.PHP_EOL; -} - -function forkChildInWorker() { - global $serv; - echo "on worker start\n"; - $process = new Swoole\Process( function (Swoole\Process $worker) use ($serv) { -// $serv = new Swoole\Server( "0.0.0.0", 9503 ); -// $serv->set(array( -// 'worker_num' => 1 -// )); -// $serv->on ( 'receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { -// $serv->send ( $fd, "Swoole: " . $data ); -// $serv->close ( $fd ); -// }); -// $serv->start (); -// Swoole\Event::add ($worker->pipe, function ($pipe) use ($worker) { -// echo $worker->read()."\n"; -// }); - }); - - $pid = $process->start(); - echo "Fork child process success. pid={$pid}\n"; - //保存子进程对象,这里如果不保存,那对象会被销毁,管道也会被关闭 - $serv->childprocess = $process; -} - -function processRename(Swoole\Server $serv, $worker_id) { - - global $argv; - if ( $serv->taskworker) - { - swoole_set_process_name("php {$argv[0]}: task"); - } - else - { - swoole_set_process_name("php {$argv[0]}: worker"); - } -// if ($worker_id == 0) -// { -// var_dump($serv->setting); -// } - my_log("WorkerStart: MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}|WorkerId={$serv->worker_id}|WorkerPid={$serv->worker_pid}"); -} - -function setTimerInWorker(Swoole\Server $serv, $worker_id) { - - if ($worker_id == 0) { - echo "Start: ".microtime(true)."\n"; - //$serv->addtimer(3000); -// $serv->addtimer(7000); - //var_dump($serv->gettimer()); - } -// $serv->after(2000, function(){ -// echo "Timeout: ".microtime(true)."\n"; -// }); -// $serv->after(5000, function(){ -// echo "Timeout: ".microtime(true)."\n"; -// global $serv; -// $serv->deltimer(3000); -// }); -} - -function my_onShutdown($serv) -{ - echo "Server: onShutdown\n"; -} - -function my_onClose(Swoole\Server $serv, $fd, $reactor_id) -{ - my_log("Client[$fd@$reactor_id]: fd=$fd is closed"); - var_dump($serv->getClientInfo($fd)); -} - -function my_onConnect(Swoole\Server $serv, $fd, $reactor_id) -{ - //throw new Exception("hello world"); -// var_dump($serv->connection_info($fd)); - //var_dump($serv, $fd, $reactor_id); -// echo "Worker#{$serv->worker_pid} Client[$fd@$reactor_id]: Connect.\n"; - $serv->after(2000, function() use ($serv, $fd) { - $serv->confirm($fd); - }); - my_log("Client: Connect --- {$fd}"); -} - -function timer_show($id) -{ - my_log("Timer#$id"); -} - -function my_onWorkerExit(Swoole\Server $serv, $worker_id) { - global $argv; -} - -function my_onWorkerStart(Swoole\Server $serv, $worker_id) -{ - processRename($serv, $worker_id); - - if (!$serv->taskworker) - { - Swoole\Process::signal(SIGUSR2, function($signo){ - echo "SIGNAL: $signo\n"; - }); - $serv->defer(function(){ - echo "defer call\n"; - }); -// $serv->tick(2000, function() use ($serv) { -// echo "Worker-{$serv->worker_id} tick-2000\n"; -// }); - } - else - { -// Swoole\Timer::after(2000, function() { -// echo "after 2 secends.\n"; -// }); -// $serv->tick(1000, function ($id) use ($serv) { -// if (G::$index > 10) { -// $serv->after(2500, 'timer_show', 2); -// G::$index = 0; -// } else { -// G::$index++; -// } -// timer_show($id); -// }); - } - //forkChildInWorker(); -// setTimerInWorker($serv, $worker_id); -} - -function my_onWorkerStop($serv, $worker_id) -{ - echo "WorkerStop[$worker_id]|pid=".$serv->worker_pid.".\n"; -} - -function my_onPacket($serv, $data, $clientInfo) -{ - $serv->sendto($clientInfo['address'], $clientInfo['port'], "Server " . $data); - var_dump($clientInfo); -} - -function my_onReceive(Swoole\Server $serv, $fd, $reactor_id, $data) -{ - my_log("Worker#{$serv->worker_pid} Client[$fd@$reactor_id]: received: $data"); - $cmd = trim($data); - if($cmd == "reload") - { - $serv->reload(); - } - elseif($cmd == "task") - { - $task_id = $serv->task("task ".$fd); - echo "Dispath AsyncTask: id=$task_id\n"; - } - elseif ($cmd == "taskclose") - { - $serv->task("close " . $fd); - echo "close the connection in taskworker\n"; - } - elseif ($cmd == "tasksend") - { - $serv->task("send " . $fd); - } - elseif ($cmd == "bigtask") - { - $serv->task(str_repeat('A', 8192*5)); - } - elseif($cmd == "taskwait") - { - $result = $serv->taskwait("taskwait"); - if ($result) { - $serv->send($fd, "taskwaitok"); - } - echo "SyncTask: result=".var_export($result, true)."\n"; - } - elseif($cmd == "taskWaitMulti") - { - $result = $serv->taskWaitMulti(array( - str_repeat('A', 8192 * 5), - str_repeat('B', 8192 * 6), - str_repeat('C', 8192 * 8) - )); - if ($result) - { - $resp = "taskWaitMulti ok\n"; - foreach($result as $k => $v) - { - $resp .= "result[$k] length=".strlen($v)."\n"; - } - $serv->send($fd, $resp); - } - else - { - $serv->send($fd, "taskWaitMulti error\n"); - } - } - elseif ($cmd == "hellotask") - { - $serv->task("hellotask"); - } - elseif ($cmd == "taskcallback") - { - $serv->task("taskcallback", -1, function (Swoole\Server $serv, $task_id, $data) - { - echo "Task Callback: "; - var_dump($task_id, $data); - }); - } - elseif ($cmd == "sendto") - { - $serv->sendto("127.0.0.1", 9999, "hello world"); - } - elseif($cmd == "close") - { - $serv->send($fd, "close connection\n"); - $result = $serv->close($fd); - } - elseif($cmd == "info") - { - $info = $serv->connection_info(strval($fd), $reactor_id); - var_dump($info["remote_ip"]); - $serv->send($fd, 'Info: '.var_export($info, true).PHP_EOL); - } - elseif ($cmd == 'proxy') - { - $serv->send(1, "hello world\n"); - } - elseif ($cmd == 'sleep') - { - sleep(10); - } - elseif ($cmd == 'foreach') - { - foreach($serv->connections as $fd) - { - echo "conn : $fd\n"; - } - return; - } - elseif ($cmd == 'tick') - { - $serv->tick(2000, function ($id) { - echo "tick #$id\n"; - }); - } - elseif ($cmd == 'addtimer') - { - $serv->addtimer(3000); - } - elseif($cmd == "list") - { - $start_fd = 0; - echo "broadcast\n"; - while(true) - { - $conn_list = $serv->connection_list($start_fd, 10); - if (empty($conn_list)) - { - echo "iterates finished\n"; - break; - } - $start_fd = end($conn_list); - var_dump($conn_list); - } - } - elseif($cmd == "list2") - { - foreach($serv->connections as $con) - { - var_dump($serv->connection_info($con)); - } - } - elseif($cmd == "stats") - { - $serv_stats = $serv->stats(); - $serv->send($fd, 'Stats: '.var_export($serv_stats, true)."\ncount=".count($serv->connections).PHP_EOL); - } - elseif($cmd == "broadcast") - { - broadcast($serv, $fd, "hello from $fd\n"); - } - //这里故意调用一个不存在的函数 - elseif($cmd == "error") - { - hello_no_exists(); - } - elseif($cmd == "exit") - { - exit("worker php exit.\n"); - } - //关闭fd - elseif(substr($cmd, 0, 5) == "close") - { - $close_fd = substr($cmd, 6); - $serv->close($close_fd); - } - elseif($cmd == "shutdown") - { - $serv->shutdown(); - } - elseif($cmd == "fatalerror") - { - require __DIR__.'/php/error.php'; - } - elseif($cmd == 'defer') - { - $serv->defer(function() use ($fd, $serv) { - $serv->close($fd); - $serv->defer(function(){ - echo "deferd\n"; - }); - }); - $serv->send($fd, 'Swoole: '.$data, $reactor_id); - } - else - { - $serv->send($fd, 'Swoole: '.$data, $reactor_id); - //$serv->close($fd); - } - //echo "Client:Data. fd=$fd|reactor_id=$reactor_id|data=$data"; -// $serv->after( -// 800, function () { -// echo "hello"; -// } -// ); - //Swoole\Server_send($serv, $other_fd, "Server: $data", $other_reactor_id); -} - -function my_onTask(Swoole\Server $serv, $task_id, $reactor_id, $data) -{ - if ($data == 'taskwait') - { - $fd = str_replace('task-', '', $data); - $serv->send($fd, "hello world"); - return array("task" => 'wait'); - } - elseif ($data == 'taskcallback') - { - return array("task" => 'callback'); - } - else - { - $cmd = explode(' ', $data); - if ($cmd[0] == 'send') - { - $serv->send($cmd[1], str_repeat('A', 10000)."\n"); - } - elseif ($cmd[0] == 'close') - { - $serv->close($cmd[1]); - } - else - { - echo "bigtask: length=".strlen($data)."\n"; - return $data; - } -// $serv->sendto('127.0.0.1', 9999, "hello world"); - //Swoole\Timer::after(1000, "test"); -// var_dump($data); -// $serv->send($fd, str_repeat('A', 8192 * 2)); -// $serv->send($fd, str_repeat('B', 8192 * 2)); -// $serv->send($fd, str_repeat('C', 8192 * 2)); -// $serv->send($fd, str_repeat('D', 8192 * 2)); - return; - } - - if ($data == "hellotask") - { - broadcast($serv, 0, "hellotask"); - } - else - { - echo "AsyncTask[PID=".$serv->worker_pid."]: task_id=$task_id.".PHP_EOL; - //eg: test-18 - return $data; - } -} - -function my_onFinish(Swoole\Server $serv, $task_id, $data) -{ - list($str, $fd) = explode('-', $data); - $serv->send($fd, 'taskok'); - var_dump($str, $fd); - echo "AsyncTask Finish: result={$data}. PID=".$serv->worker_pid.PHP_EOL; -} - -function my_onWorkerError(Swoole\Server $serv, $worker_id, $worker_pid, $exit_code, $signo) -{ - echo "worker abnormal exit. WorkerId=$worker_id|Pid=$worker_pid|ExitCode=$exit_code|Signal=$signo\n"; -} - -function broadcast(Swoole\Server $serv, $fd = 0, $data = "hello") -{ - $start_fd = 0; - echo "broadcast\n"; - while(true) - { - $conn_list = $serv->connection_list($start_fd, 10); - if($conn_list === false) - { - break; - } - $start_fd = end($conn_list); - foreach($conn_list as $conn) - { - if($conn === $fd) continue; - $ret1 = $serv->send($conn, $data); - //var_dump($ret1); - //$ret2 = $serv->close($conn); - //var_dump($ret2); - } - } -} - -$serv->on('PipeMessage', function($serv, $src_worker_id, $msg) { - my_log("PipeMessage: Src={$src_worker_id},Msg=".trim($msg)); - if ($serv->taskworker) - { - $serv->sendMessage("hello user process", - $src_worker_id); - } -}); - -$serv->on('Start', 'my_onStart'); -$serv->on('Connect', 'my_onConnect'); -$serv->on('Receive', 'my_onReceive'); -$serv->on('Packet', 'my_onPacket'); -$serv->on('Close', 'my_onClose'); -$serv->on('Shutdown', 'my_onShutdown'); -$serv->on('WorkerStart', 'my_onWorkerStart'); -$serv->on('WorkerStop', 'my_onWorkerStop'); -$serv->on('Task', 'my_onTask'); -$serv->on('Finish', 'my_onFinish'); -$serv->on('WorkerError', 'my_onWorkerError'); -$serv->on('WorkerExit', 'my_onWorkerExit'); -$serv->on('ManagerStart', function($serv) { - global $argv; - swoole_set_process_name("php {$argv[0]}: manager"); -}); -$serv->start(); diff --git a/examples/db_pool.php b/examples/server/db_pool.php similarity index 100% rename from examples/db_pool.php rename to examples/server/db_pool.php diff --git a/examples/server/echo.php b/examples/server/echo.php index 1ba7235a02..cdb1fa2572 100644 --- a/examples/server/echo.php +++ b/examples/server/echo.php @@ -1,29 +1,44 @@ mode === SWOOLE_THREAD ? \Swoole\Thread::getId() : posix_getpid(); +} $serv->set([ - 'worker_num' =>1, + 'worker_num' => 2, + 'task_worker_num' => 3, ]); -$serv->on('connect', function ($serv, $fd, $reactor_id){ - echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Connect.\n"; +$serv->on('workerStart', function ($serv, $worker_id) { + echo "[#" . getpid() . "]\tWorker#{$worker_id} is started.\n"; +}); + +$serv->on('workerStop', function ($serv, $worker_id) { + echo "[#" . getpid() . "]\tWorker#{$worker_id} is stopped.\n"; }); -$serv->set(array( - 'worker_num' => 1, -)); +$serv->on('connect', function ($serv, $fd, $reactor_id) { + echo "[#" . getpid() . "]\tClient@[$fd:$reactor_id]: Connect.\n"; +}); $serv->on('receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { - echo "[#".$serv->worker_id."]\tClient[$fd] receive data: $data\n"; + echo "[#" . $serv->worker_id . "]\tClient[$fd] receive data: $data\n"; if ($serv->send($fd, "hello {$data}\n") == false) { echo "error\n"; } - }); $serv->on('close', function ($serv, $fd, $reactor_id) { - echo "[#".posix_getpid()."]\tClient@[$fd:$reactor_id]: Close.\n"; + echo "[#" . getpid() . "]\tClient@[$fd:$reactor_id]: Close.\n"; +}); + +$serv->on('task', function ($serv, $src_worker_id, $task) { + var_dump($task); }); $serv->start(); diff --git a/examples/server_hot_update_opcache.php b/examples/server/host_update.php similarity index 100% rename from examples/server_hot_update_opcache.php rename to examples/server/host_update.php diff --git a/examples/hot_update_class.php b/examples/server/hot_update_class.php similarity index 100% rename from examples/hot_update_class.php rename to examples/server/hot_update_class.php diff --git a/examples/server/mixed.php b/examples/server/mixed.php new file mode 100644 index 0000000000..b3cfb8cc32 --- /dev/null +++ b/examples/server/mixed.php @@ -0,0 +1,461 @@ + 'www-data', + 'group' => 'www-data', + //'reactor_num' => 16, // 线程数. 一般设置为CPU核数的1-4倍 + 'worker_num' => 2, // 工作进程数量. 设置为CPU的1-4倍最合理 + 'max_request' => 1000, // 防止 PHP 内存溢出, 一个工作进程处理 X 次任务后自动重启 (注: 0,不自动重启) + 'max_conn' => 10000, // 最大连接数 + 'task_worker_num' => 1, // 任务工作进程数量 + // 'task_ipc_mode' => 2, // 设置 Task 进程与 Worker 进程之间通信的方式。 + 'task_max_request' => 0, // 防止 PHP 内存溢出 + //'task_tmpdir' => '/tmp', + //'message_queue_key' => ftok(SYS_ROOT . 'queue.msg', 1), + 'dispatch_mode' => 2, + //'daemonize' => 1, // 设置守护进程模式 + 'backlog' => 128, + //'log_file' => '/data/logs/swoole.log', + 'heartbeat_check_interval' => 2, // 心跳检测间隔时长(秒) + 'heartbeat_idle_time' => 3, // 连接最大允许空闲的时间 + //'open_eof_check' => 1, + //'open_eof_split' => 1, + //'package_eof' => "\r\r\n", + //'open_cpu_affinity' => 1, + 'socket_buffer_size' => 1024 * 1024 * 128, + 'output_buffer_size' => 1024 * 1024 * 2, + //'enable_delay_receive' => true, + //'cpu_affinity_ignore' =>array(0,1)//如果你的网卡2个队列(或者没有多队列那么默认是cpu0来处理中断),并且绑定了core 0和core 1,那么可以通过这个设置避免swoole的线程或者进程绑定到这2个core,防止cpu0,1被耗光而造成的丢包 + ]; +} + +if (isset($argv[1]) and $argv[1] == 'daemon') { + G::$config['daemonize'] = true; +} else { + G::$config['daemonize'] = false; +} + +//$mode = SWOOLE_BASE; +$mode = SWOOLE_PROCESS; + +$serv = new Swoole\Server('0.0.0.0', '9501', $mode, SWOOLE_SOCK_TCP); +$serv->listen('0.0.0.0', 9502, SWOOLE_SOCK_UDP); +$serv->listen('::', 9503, SWOOLE_SOCK_TCP6); +$serv->listen('::', 9504, SWOOLE_SOCK_UDP6); +$process1 = new Swoole\Process(function ($worker) use ($serv) { + global $argv; + swoole_set_process_name("php {$argv[0]}: my_process1"); + Timer::tick(2000, function ($interval) use ($worker, $serv) { + echo "#{$worker->pid} child process timer {$interval}\n"; // 如果worker中没有定时器,则会输出 process timer xxx + foreach ($serv->connections as $conn) { + $serv->send($conn, "heartbeat\n"); + } + }); + Timer::tick(5000, function () use ($serv) { + $serv->sendMessage('hello event worker', 0); + $serv->sendMessage('hello task worker', 4); + }); +}, false); + +//$serv->addprocess($process1); + +$process2 = new Swoole\Process(function ($worker) use ($serv) { + global $argv; + swoole_set_process_name("php {$argv[0]}: my_process2"); + Timer::tick(2000, function ($interval) use ($worker, $serv) { + echo "#{$worker->pid} child process timer {$interval}\n"; // 如果worker中没有定时器,则会输出 process timer xxx + }); +}, false); + +//$serv->addprocess($process2); + +$serv->set(G::$config); +$serv->set(['reactor_num' => 4]); + +/* + * 使用类的静态属性,可以直接访问 + */ +G::$serv = $serv; + +function my_onStart(Swoole\Server $serv) +{ + global $argv; + swoole_set_process_name("php {$argv[0]}: master"); + my_log('Server: start.Swoole version is [' . SWOOLE_VERSION . ']'); + my_log("MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}"); +} + +function my_log($msg) +{ + global $serv; + if (empty($serv->worker_pid)) { + $serv->worker_pid = posix_getpid(); + } + echo '#' . $serv->worker_pid . "\t[" . date('H:i:s') . "]\t" . $msg . PHP_EOL; +} + +function forkChildInWorker() +{ + global $serv; + echo "on worker start\n"; + $process = new Swoole\Process(function (Swoole\Process $worker) use ($serv) { + // $serv = new Swoole\Server( "0.0.0.0", 9503 ); + // $serv->set(array( + // 'worker_num' => 1 + // )); + // $serv->on ( 'receive', function (Swoole\Server $serv, $fd, $reactor_id, $data) { + // $serv->send ( $fd, "Swoole: " . $data ); + // $serv->close ( $fd ); + // }); + // $serv->start (); + // Swoole\Event::add ($worker->pipe, function ($pipe) use ($worker) { + // echo $worker->read()."\n"; + // }); + }); + + $pid = $process->start(); + echo "Fork child process success. pid={$pid}\n"; + //保存子进程对象,这里如果不保存,那对象会被销毁,管道也会被关闭 + $serv->childprocess = $process; +} + +function processRename(Swoole\Server $serv, $worker_id) +{ + global $argv; + if ($serv->taskworker) { + swoole_set_process_name("php {$argv[0]}: task"); + } else { + swoole_set_process_name("php {$argv[0]}: worker"); + } + // if ($worker_id == 0) + // { + // var_dump($serv->setting); + // } + my_log("WorkerStart: MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}|WorkerId={$serv->worker_id}|WorkerPid={$serv->worker_pid}"); +} + +function setTimerInWorker(Swoole\Server $serv, $worker_id) +{ + if ($worker_id == 0) { + echo 'Start: ' . microtime(true) . "\n"; + //$serv->addtimer(3000); + // $serv->addtimer(7000); + //var_dump($serv->gettimer()); + } + // $serv->after(2000, function(){ + // echo "Timeout: ".microtime(true)."\n"; + // }); + // $serv->after(5000, function(){ + // echo "Timeout: ".microtime(true)."\n"; + // global $serv; + // $serv->deltimer(3000); + // }); +} + +function my_onShutdown($serv) +{ + echo "Server: onShutdown\n"; +} + +function my_onClose(Swoole\Server $serv, $fd, $reactor_id) +{ + my_log("Client[{$fd}@{$reactor_id}]: fd={$fd} is closed"); + var_dump($serv->getClientInfo($fd)); +} + +function my_onConnect(Swoole\Server $serv, $fd, $reactor_id) +{ + //throw new Exception("hello world"); + // var_dump($serv->connection_info($fd)); + //var_dump($serv, $fd, $reactor_id); + // echo "Worker#{$serv->worker_pid} Client[$fd@$reactor_id]: Connect.\n"; + $serv->after(2000, function () use ($serv, $fd) { + $serv->confirm($fd); + }); + my_log("Client: Connect --- {$fd}"); +} + +function timer_show($id) +{ + my_log("Timer#{$id}"); +} + +function my_onWorkerExit(Swoole\Server $serv, $worker_id) +{ + global $argv; +} + +function my_onWorkerStart(Swoole\Server $serv, $worker_id) +{ + processRename($serv, $worker_id); + + if (!$serv->taskworker) { + Swoole\Process::signal(SIGUSR2, function ($signo) { + echo "SIGNAL: {$signo}\n"; + }); + Swoole\Event::defer(function () { + echo "defer call\n"; + }); + // $serv->tick(2000, function() use ($serv) { + // echo "Worker-{$serv->worker_id} tick-2000\n"; + // }); + } + // Swoole\Timer::after(2000, function() { + // echo "after 2 secends.\n"; + // }); + // $serv->tick(1000, function ($id) use ($serv) { + // if (G::$index > 10) { + // $serv->after(2500, 'timer_show', 2); + // G::$index = 0; + // } else { + // G::$index++; + // } + // timer_show($id); + // }); + + //forkChildInWorker(); + // setTimerInWorker($serv, $worker_id); +} + +function my_onWorkerStop($serv, $worker_id) +{ + echo "WorkerStop[{$worker_id}]|pid=" . $serv->worker_pid . ".\n"; +} + +function my_onPacket($serv, $data, $clientInfo) +{ + $serv->sendto($clientInfo['address'], $clientInfo['port'], 'Server ' . $data); + var_dump($clientInfo); +} + +function my_onReceive(Swoole\Server $serv, $fd, $reactor_id, $data) +{ + my_log("Worker#{$serv->worker_pid} Client[{$fd}@{$reactor_id}]: received: {$data}"); + $cmd = trim($data); + if ($cmd == 'reload') { + $serv->reload(); + } elseif ($cmd == 'task') { + $task_id = $serv->task('task ' . $fd); + echo "Dispath AsyncTask: id={$task_id}\n"; + } elseif ($cmd == 'taskclose') { + $serv->task('close ' . $fd); + echo "close the connection in taskworker\n"; + } elseif ($cmd == 'tasksend') { + $serv->task('send ' . $fd); + } elseif ($cmd == 'bigtask') { + $serv->task(str_repeat('A', 8192 * 5)); + } elseif ($cmd == 'taskwait') { + $result = $serv->taskwait('taskwait'); + if ($result) { + $serv->send($fd, 'taskwaitok'); + } + echo 'SyncTask: result=' . var_export($result, true) . "\n"; + } elseif ($cmd == 'taskWaitMulti') { + $result = $serv->taskWaitMulti([ + str_repeat('A', 8192 * 5), + str_repeat('B', 8192 * 6), + str_repeat('C', 8192 * 8), + ]); + if ($result) { + $resp = "taskWaitMulti ok\n"; + foreach ($result as $k => $v) { + $resp .= "result[{$k}] length=" . strlen($v) . "\n"; + } + $serv->send($fd, $resp); + } else { + $serv->send($fd, "taskWaitMulti error\n"); + } + } elseif ($cmd == 'hellotask') { + $serv->task('hellotask'); + } elseif ($cmd == 'taskcallback') { + $serv->task('taskcallback', -1, function (Swoole\Server $serv, $task_id, $data) { + echo 'Task Callback: '; + var_dump($task_id, $data); + }); + } elseif ($cmd == 'sendto') { + $serv->sendto('127.0.0.1', 9999, 'hello world'); + } elseif ($cmd == 'close') { + $serv->send($fd, "close connection\n"); + $result = $serv->close($fd); + } elseif ($cmd == 'info') { + $info = $serv->connection_info(strval($fd), $reactor_id); + var_dump($info['remote_ip']); + $serv->send($fd, 'Info: ' . var_export($info, true) . PHP_EOL); + } elseif ($cmd == 'proxy') { + $serv->send(1, "hello world\n"); + } elseif ($cmd == 'sleep') { + sleep(10); + } elseif ($cmd == 'foreach') { + foreach ($serv->connections as $fd) { + echo "conn : {$fd}\n"; + } + return; + } elseif ($cmd == 'tick') { + $serv->tick(2000, function ($id) { + echo "tick #{$id}\n"; + }); + } elseif ($cmd == 'addtimer') { + $serv->addtimer(3000); + } elseif ($cmd == 'list') { + $start_fd = 0; + echo "broadcast\n"; + while (true) { + $conn_list = $serv->connection_list($start_fd, 10); + if (empty($conn_list)) { + echo "iterates finished\n"; + break; + } + $start_fd = end($conn_list); + var_dump($conn_list); + } + } elseif ($cmd == 'list2') { + foreach ($serv->connections as $con) { + var_dump($serv->connection_info($con)); + } + } elseif ($cmd == 'stats') { + $serv_stats = $serv->stats(); + $serv->send($fd, 'Stats: ' . var_export($serv_stats, true) . "\ncount=" . count($serv->connections) . PHP_EOL); + } elseif ($cmd == 'broadcast') { + broadcast($serv, $fd, "hello from {$fd}\n"); + } //这里故意调用一个不存在的函数 + elseif ($cmd == 'error') { + hello_no_exists(); + } elseif ($cmd == 'exit') { + exit("worker php exit.\n"); + } //关闭fd + elseif (substr($cmd, 0, 5) == 'close') { + $close_fd = substr($cmd, 6); + $serv->close($close_fd); + } elseif ($cmd == 'shutdown') { + $serv->shutdown(); + } elseif ($cmd == 'fatalerror') { + require __DIR__ . '/php/error.php'; + } elseif ($cmd == 'defer') { + $serv->defer(function () use ($fd, $serv) { + $serv->close($fd); + $serv->defer(function () { + echo "deferd\n"; + }); + }); + $serv->send($fd, 'Swoole: ' . $data, $reactor_id); + } else { + $serv->send($fd, 'Swoole: ' . $data, $reactor_id); + //$serv->close($fd); + } + //echo "Client:Data. fd=$fd|reactor_id=$reactor_id|data=$data"; + // $serv->after( + // 800, function () { + // echo "hello"; + // } + // ); + //Swoole\Server_send($serv, $other_fd, "Server: $data", $other_reactor_id); +} + +function my_onTask(Swoole\Server $serv, $task_id, $reactor_id, $data) +{ + if ($data == 'taskwait') { + $fd = str_replace('task-', '', $data); + $serv->send($fd, 'hello world'); + return ['task' => 'wait']; + } + if ($data == 'taskcallback') { + return ['task' => 'callback']; + } + $cmd = explode(' ', $data); + if ($cmd[0] == 'send') { + $serv->send($cmd[1], str_repeat('A', 10000) . "\n"); + } elseif ($cmd[0] == 'close') { + $serv->close($cmd[1]); + } else { + echo 'bigtask: length=' . strlen($data) . "\n"; + return $data; + } + // $serv->sendto('127.0.0.1', 9999, "hello world"); + //Swoole\Timer::after(1000, "test"); + // var_dump($data); + // $serv->send($fd, str_repeat('A', 8192 * 2)); + // $serv->send($fd, str_repeat('B', 8192 * 2)); + // $serv->send($fd, str_repeat('C', 8192 * 2)); + // $serv->send($fd, str_repeat('D', 8192 * 2)); + return; + + if ($data == 'hellotask') { + broadcast($serv, 0, 'hellotask'); + } else { + echo 'AsyncTask[PID=' . $serv->worker_pid . "]: task_id={$task_id}." . PHP_EOL; + //eg: test-18 + return $data; + } +} + +function my_onFinish(Swoole\Server $serv, $task_id, $data) +{ + [$str, $fd] = explode('-', $data); + $serv->send($fd, 'taskok'); + var_dump($str, $fd); + echo "AsyncTask Finish: result={$data}. PID=" . $serv->worker_pid . PHP_EOL; +} + +function my_onWorkerError(Swoole\Server $serv, $worker_id, $worker_pid, $exit_code, $signo) +{ + echo "worker abnormal exit. WorkerId={$worker_id}|Pid={$worker_pid}|ExitCode={$exit_code}|Signal={$signo}\n"; +} + +function broadcast(Swoole\Server $serv, $fd = 0, $data = 'hello') +{ + $start_fd = 0; + echo "broadcast\n"; + while (true) { + $conn_list = $serv->connection_list($start_fd, 10); + if ($conn_list === false) { + break; + } + $start_fd = end($conn_list); + foreach ($conn_list as $conn) { + if ($conn === $fd) { + continue; + } + $ret1 = $serv->send($conn, $data); + //var_dump($ret1); + //$ret2 = $serv->close($conn); + //var_dump($ret2); + } + } +} + +$serv->on('PipeMessage', function ($serv, $src_worker_id, $msg) { + my_log("PipeMessage: Src={$src_worker_id},Msg=" . trim($msg)); + if ($serv->taskworker) { + $serv->sendMessage('hello user process', + $src_worker_id); + } +}); + +$serv->on('Start', 'my_onStart'); +$serv->on('Connect', 'my_onConnect'); +$serv->on('Receive', 'my_onReceive'); +$serv->on('Packet', 'my_onPacket'); +$serv->on('Close', 'my_onClose'); +$serv->on('Shutdown', 'my_onShutdown'); +$serv->on('WorkerStart', 'my_onWorkerStart'); +$serv->on('WorkerStop', 'my_onWorkerStop'); +$serv->on('Task', 'my_onTask'); +$serv->on('Finish', 'my_onFinish'); +$serv->on('WorkerError', 'my_onWorkerError'); +$serv->on('WorkerExit', 'my_onWorkerExit'); +$serv->on('ManagerStart', function ($serv) { + global $argv; + swoole_set_process_name("php {$argv[0]}: manager"); + Timer::after(5000, function () use ($serv) { + echo "shutdown\n"; + $serv->shutdown(); + }); +}); +$serv->start(); diff --git a/examples/multi_port_server.php b/examples/server/multi_port.php similarity index 100% rename from examples/multi_port_server.php rename to examples/server/multi_port.php diff --git a/examples/proxy_sync.php b/examples/server/proxy_sync.php similarity index 100% rename from examples/proxy_sync.php rename to examples/server/proxy_sync.php diff --git a/examples/redis_pool.php b/examples/server/redis_pool.php similarity index 100% rename from examples/redis_pool.php rename to examples/server/redis_pool.php diff --git a/examples/send_1m_svr.php b/examples/server/send_1m.php similarity index 100% rename from examples/send_1m_svr.php rename to examples/server/send_1m.php diff --git a/examples/sendfile_server.php b/examples/server/sendfile.php similarity index 100% rename from examples/sendfile_server.php rename to examples/server/sendfile.php diff --git a/examples/ssl/client.c b/examples/ssl/client.c index c818f8d54f..f8541e3a03 100644 --- a/examples/ssl/client.c +++ b/examples/ssl/client.c @@ -101,7 +101,7 @@ int main(int count, char *strings[]) hostname = strings[1]; portnum = strings[2]; ctx = InitCTX(); - server = OpenConnection(hostname, atoi(portnum)); + server = OpenConnection(hostname, sw_atoi(portnum)); ssl = SSL_new(ctx); /* create new SSL connection state */ SSL_set_fd(ssl, server); /* attach the socket descriptor */ diff --git a/examples/stdext/array.php b/examples/stdext/array.php new file mode 100644 index 0000000000..46e1099958 --- /dev/null +++ b/examples/stdext/array.php @@ -0,0 +1,76 @@ +slice(1, 2); +var_dump($arr); + +$array1 = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]; +$array2 = [6, 7, 8, 9, 10, 11, 12]; + +var_dump($array2->count()); + +// var_dump($array2->all(function ($value) { +// return $value > 10; +// })); + +$input_array = array("FirSt" => 1, "SecOnd" => 4); +print_r($input_array->changeKeyCase(CASE_UPPER)); + +$array4 = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); +$key = $array4->search('green'); +var_dump($key); + +echo "==================================[contains]===================================\n"; +$os = array("Mac", "Windows", "Linux"); +var_dump($os->contains("Windows")); +var_dump($os->contains("Unix")); + +echo "==================================[isList]===================================\n"; +var_dump($array1->isList()); +var_dump($array2->isList()); + +var_dump($array1->keys()); +var_dump($array2->values()); + +echo "==================================[join]===================================\n"; +var_dump(['a', 'b', 'c']->join(',')); + +echo "==================================[method not exists]===================================\n"; +try { + $array->notExists(); +} catch (throwable $e) { + echo "Caught exception: ", $e->getMessage(), "\n"; +} + +function odd($var) +{ + // returns whether the input integer is odd + return $var & 1; +} + +function even($var) +{ + // returns whether the input integer is even + return !($var & 1); +} + +echo "Odd :\n"; +print_r($array1->filter("odd")); + +echo "Even:\n"; +print_r($array2->filter("even")); + + +echo "==================================[array_map]===================================\n"; + +$a = [1, 2, 3, 4, 5]; +$b = $a->map(function ($n) { + return ($n * $n * $n); +}); +var_dump($b); + +echo "==================================[array_key_exists]===================================\n"; +$searchArray = ['first' => null, 'second' => 4]; + +var_dump(isset($searchArray['first'])); +var_dump($searchArray->keyExists('first')); diff --git a/examples/stdext/foreach.php b/examples/stdext/foreach.php new file mode 100644 index 0000000000..27472984e9 --- /dev/null +++ b/examples/stdext/foreach.php @@ -0,0 +1,7 @@ +'); + +$arr[] = 1; +// $arr[0] += 10; +// assert($arr[0] == 11); +$arr[0] .= "hello world"; \ No newline at end of file diff --git a/examples/stdext/ref.php b/examples/stdext/ref.php new file mode 100644 index 0000000000..731f353960 --- /dev/null +++ b/examples/stdext/ref.php @@ -0,0 +1,24 @@ + $val) { + echo "fruits[" . $key . "] = " . $val . "\n"; +} + +$b = &$fruits; +$b->sort(SORT_NATURAL | SORT_FLAG_CASE); + +echo "After sorting:\n"; +foreach ($fruits as $key => $val) { + echo "fruits[" . $key . "] = " . $val . "\n"; +} + +$stack = array("orange", "banana", "apple", "raspberry"); + +$ref = &$stack; +$fruit = $ref->shift(); +var_dump($stack); + +$ref->unshift("kiwi"); +var_dump($stack); diff --git a/examples/stdext/string.php b/examples/stdext/string.php new file mode 100644 index 0000000000..3e2a05c61c --- /dev/null +++ b/examples/stdext/string.php @@ -0,0 +1,36 @@ +upper()); + +var_dump($str->split(' ')->search('world')); +var_dump($str->length()); +var_dump('test'->length()); + +var_dump($str->indexOf('world')); +var_dump($str->substr(1, 4)); + +var_dump($str->startsWith('hello')); +var_dump($str->endsWith('world')); +var_dump($str->endsWith('.php')); + +var_dump($str->md5(), $str->sha1(), $str->crc32()); +var_dump($str->hash('sha256')); +echo "==============================hash=====================\n"; +var_dump($str->md5() === $str->hash('md5')); + +$str = 'first=value&arr[]=foo+bar&arr[]=baz'; +$output = $str->parseStr(); +echo $output['first']; // value +echo $output['arr'][0]; // foo bar +echo $output['arr'][1]; // baz + +var_dump($str->urlEncode()); diff --git a/examples/stdext/typed_array.php b/examples/stdext/typed_array.php new file mode 100644 index 0000000000..30f4b3f4a9 --- /dev/null +++ b/examples/stdext/typed_array.php @@ -0,0 +1,7 @@ +'); + +$list[] = 123; +$list[] = 345; +$list[] = 'hello'; // 异常 + diff --git a/examples/stdext/typed_array_map.php b/examples/stdext/typed_array_map.php new file mode 100644 index 0000000000..0df81adbbb --- /dev/null +++ b/examples/stdext/typed_array_map.php @@ -0,0 +1,6 @@ +'); + +$list["hello"] = 123; +$list[] = 345; +$list["hello"] = 'hello'; diff --git a/examples/test_buffer.php b/examples/test_buffer.php deleted file mode 100644 index 31fda6cac3..0000000000 --- a/examples/test_buffer.php +++ /dev/null @@ -1,39 +0,0 @@ -connect('127.0.0.1', 9501)) -{ - exit("connect fail\n"); -} - -for($i=0; $i<$loop; $i++) -{ - $client->send(str_repeat("A", 8000).$i."[0]"); - //$client->send(str_repeat("A", 20).$i."[1]"); - //$client->send(str_repeat("A", 30).$i."[2]"); - //$ret = $client->send("GET / HTTP/1.1\r\n"); - //$client->send("Host: localhost\r\n"); - //$client->send("Connection: keep-alive\r\n"); - $client->send("\r\n\r\n"); - - //$data = $client->recv(1024, 0); - //if($data === false) - //{ - // echo "#{$i} recv fail.break\n"; -// break; -// } - //echo "recv[$i]",$data,"\n"; -} - -sleep(1000); -echo "$i: ",$data,"\n"; -echo "test ok. use".((microtime(true) - $_s)*1000)."ms\n"; diff --git a/examples/thread/aio.php b/examples/thread/aio.php new file mode 100644 index 0000000000..e55972f01d --- /dev/null +++ b/examples/thread/aio.php @@ -0,0 +1,42 @@ +join(); + } + var_dump($atomic->get()); + sleep(2); + + Co\run(function () use($atomic) { + $n = 1024; + while ($n--) { + $atomic->add(); + $rs = \Swoole\Coroutine\System::readFile(__FILE__); + var_dump(strlen($rs)); + } + }); + var_dump($atomic->get()); +} else { + $atomic = $args[1]; + Co\run(function () use($atomic) { + $n = 1024; + while ($n--) { + $atomic->add(); + $rs = \Swoole\Coroutine\System::readFile(__FILE__); + var_dump(strlen($rs)); + } + }); +} diff --git a/examples/thread/argv.php b/examples/thread/argv.php new file mode 100644 index 0000000000..a224b95da7 --- /dev/null +++ b/examples/thread/argv.php @@ -0,0 +1,17 @@ +join(); + } +} else { + var_dump($args[0], $args[1], $args[2]); + sleep(1); +} diff --git a/examples/thread/array.php b/examples/thread/array.php new file mode 100644 index 0000000000..1df20fb66c --- /dev/null +++ b/examples/thread/array.php @@ -0,0 +1,20 @@ +join(); + } + var_dump($a1->get(), $a2->get()); +} else { + $a1 = $args[1]; + $a2 = $args[2]; + + $a1->add(3); + $a2->add(7); +} diff --git a/examples/thread/benchmark.php b/examples/thread/benchmark.php new file mode 100644 index 0000000000..5a52e6df5a --- /dev/null +++ b/examples/thread/benchmark.php @@ -0,0 +1,21 @@ +id); +//var_dump($t2->id); +echo Swoole\Thread::getId() . "\t" . 'gmap[uuid]' . "\t" . $map['uuid'] . "\n"; + +try { + var_dump($list[999]); +} catch (Swoole\Exception $e) { + assert(str_contains($e->getMessage(), 'out of range')); +} + +try { + unset($list[0]); +} catch (Swoole\Exception $e) { + assert(str_contains($e->getMessage(), 'unsupported')); +} + +$t1->join(); +$t2->join(); + + diff --git a/examples/thread/exit.php b/examples/thread/exit.php new file mode 100644 index 0000000000..5d1c071f6d --- /dev/null +++ b/examples/thread/exit.php @@ -0,0 +1,21 @@ +lock(); + $thread = new Thread(__FILE__, $lock); + echo "main thread\n"; + $lock->unlock(); + $thread->join(); + var_dump($thread->getExitStatus()); +} else { + $lock = $args[0]; + $lock->lock(); + sleep(1); + exit(234); +} diff --git a/examples/thread/hook.php b/examples/thread/hook.php new file mode 100644 index 0000000000..de55b63bb2 --- /dev/null +++ b/examples/thread/hook.php @@ -0,0 +1,24 @@ +lock(); + $thread = new Thread(__FILE__, $lock); + echo "main thread\n"; + $lock->unlock(); + $thread->join(); + var_dump($thread->getExitStatus()); +} else { + $lock = $args[0]; + $lock->lock(); + Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + sleep(1); + Swoole\Runtime::enableCoroutine(0); + exit(234); +} diff --git a/examples/thread/lock.php b/examples/thread/lock.php new file mode 100644 index 0000000000..e18674d631 --- /dev/null +++ b/examples/thread/lock.php @@ -0,0 +1,19 @@ +lock(); + $thread = new Thread(__FILE__, $lock); + $lock->lock(); + echo "main thread\n"; + $thread->join(); +} else { + $lock = $args[0]; + sleep(1); + $lock->unlock(); +} diff --git a/examples/thread/map.php b/examples/thread/map.php new file mode 100644 index 0000000000..fc38de69ff --- /dev/null +++ b/examples/thread/map.php @@ -0,0 +1,18 @@ + random_int(1, 999999999999999999), + 'b' => random_bytes(128), + 'c' => uniqid(), + 'd' => time(), + ]; + + $map = new Thread\Map($array); + $thread = new Thread(__FILE__, $map); +} else { + $map = $args[0]; + var_dump($map->toArray()); +} diff --git a/examples/thread/mt.php b/examples/thread/mt.php new file mode 100644 index 0000000000..ca14d152f2 --- /dev/null +++ b/examples/thread/mt.php @@ -0,0 +1,27 @@ +keys()); + +$list[] = uniqid('swoole'); +$list[count($list)] = uniqid('php'); + +var_dump($args); + +echo Swoole\Thread::getId() . "\t" . 'glist[0]' . "\t" . $list[0] . "\n"; +var_dump(count($list)); + +//if ($args[0] == 'thread-2') { +// $t3 = new Swoole\Thread('mt.php', 'thread-3', PHP_OS); +// $t3->join(); +//} + +//sleep(5); +//echo "end\n"; diff --git a/examples/thread/nested_map.php b/examples/thread/nested_map.php new file mode 100644 index 0000000000..daa0af5f9c --- /dev/null +++ b/examples/thread/nested_map.php @@ -0,0 +1,21 @@ + uniqid(), + 'b' => random_int(1000, 9999), +]; + +var_dump($map['map1']['key1']); +var_dump($map['list1'][0]); + +var_dump($map['list1']->toArray()); + +var_dump($map['map2']); diff --git a/examples/thread/pipe.php b/examples/thread/pipe.php new file mode 100644 index 0000000000..2e806cf17a --- /dev/null +++ b/examples/thread/pipe.php @@ -0,0 +1,20 @@ +recv(8192), PHP_EOL; + $thread->join(); + }); +} else { + $sockets = $args[0]; + Co\run(function () use ($sockets) { + sleep(1); + $sockets[1]->send(uniqid()); + }); +} diff --git a/examples/thread/run_test.php b/examples/thread/run_test.php new file mode 100644 index 0000000000..85a736214c --- /dev/null +++ b/examples/thread/run_test.php @@ -0,0 +1,16 @@ +join(); +} + diff --git a/examples/thread/server.php b/examples/thread/server.php new file mode 100644 index 0000000000..79435f73da --- /dev/null +++ b/examples/thread/server.php @@ -0,0 +1,25 @@ +join(); + } +} else { + $http = new Swoole\Http\Server("0.0.0.0", 9503); + $http->on('request', function ($req, Swoole\Http\Response $resp) { + $resp->end('hello world'); + }); + $http->start(); +} diff --git a/examples/thread/signal.php b/examples/thread/signal.php new file mode 100644 index 0000000000..b41930b693 --- /dev/null +++ b/examples/thread/signal.php @@ -0,0 +1,36 @@ +send('exit'); + } + Co\go(function () use ($parent_pipe, $thread) { + // 从管道中读取子线程退出的信息 + echo $parent_pipe->recv(8192), PHP_EOL; + // 回收子线程 + $thread->join(); + }); + }); +} else { + echo "child thread\n"; + $sockets = $args[0]; + $child_pipe = $sockets[0]; + Co\run(function () use ($child_pipe) { + // 收到父线程的指令,开始退出 + echo $child_pipe->recv(8192), PHP_EOL; + // 通知父线程已退出 + $child_pipe->send('child exit'); + }); +} diff --git a/examples/thread/socket.php b/examples/thread/socket.php new file mode 100644 index 0000000000..05f57cff1d --- /dev/null +++ b/examples/thread/socket.php @@ -0,0 +1,21 @@ +join(); +} else { + $map = $args[0]; + $sock = $map['socket']; + $retval = socket_connect($sock, '127.0.0.1', 80); +} diff --git a/examples/thread/test.php b/examples/thread/test.php new file mode 100644 index 0000000000..3c4b8bc1a2 --- /dev/null +++ b/examples/thread/test.php @@ -0,0 +1,17 @@ +uuid = uniqid(); +$map['obj'] = $o; + +var_dump($map['obj']); + +$s = serialize($map); +var_dump(unserialize($s)); + diff --git a/examples/thread/thread_pool.php b/examples/thread/thread_pool.php new file mode 100644 index 0000000000..226f14a5a9 --- /dev/null +++ b/examples/thread/thread_pool.php @@ -0,0 +1,37 @@ +push(base64_encode(random_bytes(16)), Queue::NOTIFY_ONE); + usleep(random_int(10000, 100000)); + } + $n = 4; + while ($n--) { + $queue->push('', Queue::NOTIFY_ONE); + } + for ($i = 0; $i < $c; $i++) { + $threads[$i]->join(); + } + var_dump($queue->count()); +} else { + $queue = $args[1]; + while (1) { + $job = $queue->pop(-1); + if (!$job) { + break; + } + var_dump($job); + } +} diff --git a/examples/thread/thread_server.php b/examples/thread/thread_server.php new file mode 100644 index 0000000000..ddb19ec32a --- /dev/null +++ b/examples/thread/thread_server.php @@ -0,0 +1,67 @@ +set([ + 'worker_num' => 2, + 'task_worker_num' => 3, + 'enable_coroutine' => true, + 'hook_flags' => SWOOLE_HOOK_ALL, +// 'trace_flags' => SWOOLE_TRACE_SERVER, +// 'log_level' => SWOOLE_LOG_TRACE, + 'init_arguments' => function () use ($http) { + $map = new Swoole\Thread\Map; + return [$map]; + } +]); + +$http->on('Request', function ($req, $resp) use ($http) { +// $resp->end("tid=" . \Swoole\Thread::getId() . ', fd=' . $req->fd); + if ($req->server['request_uri'] == '/task') { + $http->task(['code' => uniqid()]); + } elseif ($req->server['request_uri'] == '/stop') { + var_dump($http->getWorkerId()); + var_dump($req->get['worker_id']); + $http->stop($req->get['worker_id'] ?? 0); + } elseif ($req->server['request_uri'] == '/msg') { + $dstWorkerId = random_int(0, 4); + if ($dstWorkerId != $http->getWorkerId()) { + $http->sendMessage('hello ' . base64_encode(random_bytes(16)), $dstWorkerId); + echo "[worker#" . $http->getWorkerId() . "]\tsend pipe message to " . $dstWorkerId . "\n"; + } + } + $resp->end('hello world'); +}); + +$http->on('pipeMessage', function ($http, $srcWorkerId, $msg) { + echo "[worker#" . $http->getWorkerId() . "]\treceived pipe message[$msg] from " . $srcWorkerId . "\n"; +}); + +$http->addProcess(new \Swoole\Process(function () { + echo "user process, id=" . \Swoole\Thread::getId() . "\n"; + sleep(2); +})); + +$http->on('Task', function ($server, $taskId, $srcWorkerId, $data) { + var_dump($taskId, $srcWorkerId, $data); + return ['result' => uniqid()]; +}); + +$http->on('Finish', function ($server, $taskId, $data) { + var_dump($taskId, $data); +}); + +$http->on('workerStart', function ($serv, $worker_id) { + echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is started.\n"; +}); + +$http->on('workerStop', function ($serv, $worker_id) { + echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is stopped.\n"; +}); + +$http->on('workerExit', function (Server $serv, $worker_id) { + echo "[#" . Swoole\Thread::getId() . "]\tWorker#{$worker_id} is exited, event_num=" . Swoole\Coroutine::stats()['event_num'] . ".\n"; +}); + +$http->start(); diff --git a/examples/tracer/blocking.php b/examples/tracer/blocking.php new file mode 100644 index 0000000000..e2b69504ef --- /dev/null +++ b/examples/tracer/blocking.php @@ -0,0 +1,30 @@ + 0]); +// php -d swoole.blocking_detection=on -d swoole.blocking_threshold=500000 blocking.php +function sleep_test() +{ + echo "Start\n"; + sleep(1); + echo "End\n"; +} + +function redis_test() +{ + $redis = new Redis(); + $redis->connect('127.0.0.1', 6379); + $result = $redis->blPop('queue_name', 1.5); + if ($result) { + list($queueName, $value) = $result; + echo "获取到数据: {$value}\n"; + } +} + +function main() +{ + sleep_test(); + redis_test(); +} + +Co\run(function () { + main(); +}); diff --git a/examples/tracer/cli.php b/examples/tracer/cli.php new file mode 100644 index 0000000000..3bcc937d94 --- /dev/null +++ b/examples/tracer/cli.php @@ -0,0 +1,31 @@ +arr = []; + $this->str = ''; + } +} + +function foo(ClassA $obj) +{ + $str = str_repeat("big string", 1024); + $obj->arr[] = $str; + $obj->str .= $str; +} + +$obj = new ClassA(); +$usage = memory_get_usage(); +$n = 100; +while ($n--) { + foo($obj); +} + +var_dump(strlen($obj->str)); +var_dump(memory_get_usage() - $usage); +swoole_tracer_leak_detect(); diff --git a/examples/tracer/co.php b/examples/tracer/co.php new file mode 100644 index 0000000000..0eb5f1c92f --- /dev/null +++ b/examples/tracer/co.php @@ -0,0 +1,21 @@ + __DIR__]); + +function sleep_n($time) +{ + Co::sleep($time); +} + +Co\run(function () { + Co\go(function () { + sleep_n(0.1); + }); + + Co\go(function () { + sleep_n(0.2); + }); + + sleep_n(0.3); +}); + +swoole_tracer_prof_end('./test.json'); \ No newline at end of file diff --git a/examples/tracer/co_server.php b/examples/tracer/co_server.php new file mode 100644 index 0000000000..137e80b500 --- /dev/null +++ b/examples/tracer/co_server.php @@ -0,0 +1,61 @@ +set(array( +// 'worker_num' => 1, +//)); +//$http->on('request', function ($request, $response) { +//startMemleakCheck(); +// static $i=0; +// global $arr; +// $arr[] = $i++; +// print_r($arr); +//endMemleakCheck(); +// $response->end("

Hello Swoole. #".rand(1000, 9999)."

"); +//}); +//$http->start(); +// +// +// +// + +//class Test +class Test +{ + public $arr = []; + + //问题的根源是run函数无法结束,所以run的局部变量和此函数所属的对象也应该是全局变量 + function run() + { + $locals = ''; + $this->run2($locals); + } + + function run2(&$locals) + { + global $global1, $global2; + $http = new \Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE); + $http->set([ + 'worker_num' => 1 + ]); + $http->on("start", function ($server) { + + }); + $http->on("request", function ($req, $resp) use (&$global1, &$global2, &$locals) { + $global2 .= "2222222222"; + $locals .= "333333333333"; + $global1[] = random_bytes(random_int(256, 4096)); + $this->arr[] = "444444444"; + // var_dump($global1, $global2, $run2var, $this->pro); + $resp->end("hello world"); + + swoole_tracer_leak_detect(128); + }); + + $http->start(); + } +} + +(new Test())->run(); + + diff --git a/examples/tracer/co_socket.php b/examples/tracer/co_socket.php new file mode 100644 index 0000000000..0ff8aae024 --- /dev/null +++ b/examples/tracer/co_socket.php @@ -0,0 +1,22 @@ +bind("0.0.0.0", 9501); +$socket->listen(); + +go(function() use($socket) { + while (1) { + go(function() use ($socket) { + $client = $socket->accept(-1); + while (true) { + $data = $client->recv(); + if (empty($data)) { + $client->close(); + break; + } + //do business + $client->send("server" . $data); + } + }); + } +}); diff --git a/examples/tracer/fn_call.php b/examples/tracer/fn_call.php new file mode 100644 index 0000000000..279fe8cb43 --- /dev/null +++ b/examples/tracer/fn_call.php @@ -0,0 +1,44 @@ +method_test(); + call_user_func('test4'); +} + +function test4() +{ + usleep(random_int(100, 500) * 1000); + var_dump(time()); +} + +class T +{ + function method_test() + { + usleep(random_int(100, 500) * 1000); + var_dump(__METHOD__); + } +} + +swoole_tracer_prof_begin(['root_path' => __DIR__]); +main(); +var_dump(swoole_tracer_prof_end('./test.json')); \ No newline at end of file diff --git a/examples/tracer/gc.php b/examples/tracer/gc.php new file mode 100644 index 0000000000..c0581e65d6 --- /dev/null +++ b/examples/tracer/gc.php @@ -0,0 +1,22 @@ +pro = $obj; + unset($obj); + + swoole_tracer_leak_detect(64); + } + var_dump(memory_get_usage()); +} +foo(); diff --git a/ext-src/php_swoole.cc b/ext-src/php_swoole.cc index 58b42d1ddb..ef459307bc 100644 --- a/ext-src/php_swoole.cc +++ b/ext-src/php_swoole.cc @@ -16,22 +16,36 @@ #include "php_swoole_cxx.h" #include "php_swoole_library.h" #include "php_swoole_process.h" +#include "php_swoole_thread.h" +#include "swoole_iouring.h" -#if (HAVE_PCRE || HAVE_BUNDLED_PCRE) && !defined(COMPILE_DL_PCRE) -#include "ext/pcre/php_pcre.h" -#endif +BEGIN_EXTERN_C() #include "zend_exceptions.h" +#include "zend_extensions.h" -BEGIN_EXTERN_C() +#include "ext/pcre/php_pcre.h" #include "ext/json/php_json.h" +#include "php_open_temporary_file.h" #include "stubs/php_swoole_arginfo.h" #include "stubs/php_swoole_ex_arginfo.h" +#include "stubs/php_swoole_tracer_arginfo.h" +#ifdef SW_STDEXT +#include "stubs/php_swoole_stdext_arginfo.h" +#endif +#ifdef SW_HAVE_SSH2LIB +#include "stubs/php_swoole_ssh2_arginfo.h" +#endif +#ifdef SW_HAVE_FTP +#include "stubs/php_swoole_ftp_arginfo.h" +#endif END_EXTERN_C() +#include "swoole_coroutine.h" #include "swoole_mime_type.h" #include "swoole_server.h" #include "swoole_util.h" +#include "swoole_http2.h" #include #include @@ -39,6 +53,10 @@ END_EXTERN_C() #include #include +#ifdef SW_USE_CURL +#include +#endif + #if defined(__MACH__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) #include #endif @@ -46,24 +64,33 @@ END_EXTERN_C() #ifdef SW_HAVE_ZLIB #include #endif + #ifdef SW_HAVE_BROTLI #include #include #endif +#ifdef SW_HAVE_ZSTD +#include +#endif + #ifdef SW_USE_CARES #include #endif +#ifdef SW_USE_PGSQL +extern void swoole_libpq_version(char *buf, size_t len); +#endif + +using swoole::Coroutine; using swoole::Server; using swoole::network::Socket; +#ifdef SW_USE_IOURING +using swoole::Iouring; +#endif ZEND_DECLARE_MODULE_GLOBALS(swoole) -extern sapi_module_struct sapi_module; - -static swoole::CallbackManager rshutdown_callbacks; - SW_EXTERN_C_BEGIN static PHP_FUNCTION(swoole_version); static PHP_FUNCTION(swoole_cpu_num); @@ -85,8 +112,21 @@ static PHP_FUNCTION(swoole_mime_type_list); static PHP_FUNCTION(swoole_substr_unserialize); static PHP_FUNCTION(swoole_substr_json_decode); static PHP_FUNCTION(swoole_internal_call_user_shutdown_begin); +static PHP_FUNCTION(swoole_implicit_fn); SW_EXTERN_C_END +#ifdef SW_STDEXT +#include "php_swoole_stdext.h" +#endif + +#ifdef SW_HAVE_SSH2LIB +#include "php_swoole_ssh2_def.h" +#endif + +#ifdef SW_HAVE_FTP +#include "php_swoole_ftp_def.h" +#endif + // clang-format off const zend_function_entry swoole_functions[] = { PHP_FE(swoole_version, arginfo_swoole_version) @@ -124,6 +164,8 @@ const zend_function_entry swoole_functions[] = { PHP_FE(swoole_substr_unserialize, arginfo_swoole_substr_unserialize) PHP_FE(swoole_substr_json_decode, arginfo_swoole_substr_json_decode) PHP_FE(swoole_internal_call_user_shutdown_begin, arginfo_swoole_internal_call_user_shutdown_begin) + // for test + PHP_FE(swoole_implicit_fn, arginfo_swoole_implicit_fn) // for admin server ZEND_FE(swoole_get_objects, arginfo_swoole_get_objects) ZEND_FE(swoole_get_vm_status, arginfo_swoole_get_vm_status) @@ -131,6 +173,116 @@ const zend_function_entry swoole_functions[] = { ZEND_FE(swoole_name_resolver_lookup, arginfo_swoole_name_resolver_lookup) ZEND_FE(swoole_name_resolver_add, arginfo_swoole_name_resolver_add) ZEND_FE(swoole_name_resolver_remove, arginfo_swoole_name_resolver_remove) + // for stdext +#ifdef SW_STDEXT + ZEND_FE(swoole_call_array_method, arginfo_swoole_call_array_method) + ZEND_FE(swoole_call_string_method, arginfo_swoole_call_string_method) + ZEND_FE(swoole_call_stream_method, arginfo_swoole_call_stream_method) + ZEND_FE(swoole_array_search, arginfo_swoole_array_search) + ZEND_FE(swoole_array_contains, arginfo_swoole_array_contains) + ZEND_FE(swoole_array_join, arginfo_swoole_array_join) + ZEND_FE(swoole_array_key_exists, arginfo_swoole_array_key_exists) + ZEND_FE(swoole_array_map, arginfo_swoole_array_map) + ZEND_FE(swoole_str_split, arginfo_swoole_str_split) + ZEND_FE(swoole_parse_str, arginfo_swoole_parse_str) + ZEND_FE(swoole_hash, arginfo_swoole_hash) + ZEND_FE(swoole_typed_array, arginfo_swoole_typed_array) + ZEND_FE(swoole_array_is_typed, arginfo_swoole_array_is_typed) + ZEND_FE(swoole_str_is_empty, arginfo_swoole_str_is_empty) + ZEND_FE(swoole_array_is_empty, arginfo_swoole_array_is_empty) + ZEND_FE(swoole_str_match, arginfo_swoole_str_match) + ZEND_FE(swoole_str_match_all, arginfo_swoole_str_match_all) + ZEND_FE(swoole_str_json_decode, arginfo_swoole_str_json_decode) + ZEND_FE(swoole_str_json_decode_to_object, arginfo_swoole_str_json_decode_to_object) + ZEND_FE(swoole_str_replace, arginfo_swoole_str_replace) + ZEND_FE(swoole_str_ireplace, arginfo_swoole_str_ireplace) + ZEND_FE(swoole_array_replace_str, arginfo_swoole_array_replace_str) + ZEND_FE(swoole_array_ireplace_str, arginfo_swoole_array_ireplace_str) +#endif +#ifdef SW_HAVE_SSH2LIB + ZEND_FE(ssh2_connect, arginfo_ssh2_connect) + ZEND_FE(ssh2_disconnect, arginfo_ssh2_disconnect) + ZEND_FE(ssh2_methods_negotiated, arginfo_ssh2_methods_negotiated) + ZEND_FE(ssh2_fingerprint, arginfo_ssh2_fingerprint) + ZEND_FE(ssh2_auth_none, arginfo_ssh2_auth_none) + ZEND_FE(ssh2_auth_password, arginfo_ssh2_auth_password) + ZEND_FE(ssh2_auth_pubkey_file, arginfo_ssh2_auth_pubkey_file) + ZEND_FE(ssh2_auth_pubkey, arginfo_ssh2_auth_pubkey) + ZEND_FE(ssh2_auth_hostbased_file, arginfo_ssh2_auth_hostbased_file) + ZEND_FE(ssh2_forward_listen, arginfo_ssh2_forward_listen) + ZEND_FE(ssh2_forward_accept, arginfo_ssh2_forward_accept) + ZEND_FE(ssh2_shell, arginfo_ssh2_shell) + ZEND_FE(ssh2_shell_resize, arginfo_ssh2_shell_resize) + ZEND_FE(ssh2_exec, arginfo_ssh2_exec) + ZEND_FE(ssh2_tunnel, arginfo_ssh2_tunnel) + ZEND_FE(ssh2_scp_recv, arginfo_ssh2_scp_recv) + ZEND_FE(ssh2_scp_send, arginfo_ssh2_scp_send) + ZEND_FE(ssh2_fetch_stream, arginfo_ssh2_fetch_stream) + ZEND_FE(ssh2_send_eof, arginfo_ssh2_send_eof) + ZEND_FE(ssh2_sftp, arginfo_ssh2_sftp) + ZEND_FE(ssh2_sftp_rename, arginfo_ssh2_sftp_rename) + ZEND_FE(ssh2_sftp_unlink, arginfo_ssh2_sftp_unlink) + ZEND_FE(ssh2_sftp_mkdir, arginfo_ssh2_sftp_mkdir) + ZEND_FE(ssh2_sftp_rmdir, arginfo_ssh2_sftp_rmdir) + ZEND_FE(ssh2_sftp_chmod, arginfo_ssh2_sftp_chmod) + ZEND_FE(ssh2_sftp_stat, arginfo_ssh2_sftp_stat) + ZEND_FE(ssh2_sftp_lstat, arginfo_ssh2_sftp_lstat) + ZEND_FE(ssh2_sftp_symlink, arginfo_ssh2_sftp_symlink) + ZEND_FE(ssh2_sftp_readlink, arginfo_ssh2_sftp_readlink) + ZEND_FE(ssh2_sftp_realpath, arginfo_ssh2_sftp_realpath) + ZEND_FE(ssh2_publickey_init, arginfo_ssh2_publickey_init) + ZEND_FE(ssh2_publickey_add, arginfo_ssh2_publickey_add) + ZEND_FE(ssh2_publickey_remove, arginfo_ssh2_publickey_remove) + ZEND_FE(ssh2_publickey_list, arginfo_ssh2_publickey_list) + ZEND_FE(ssh2_auth_agent, arginfo_ssh2_auth_agent) +#endif +#ifdef SW_HAVE_FTP + ZEND_FE(ftp_connect, arginfo_ftp_connect) +#if defined(SW_HAVE_FTP_SSL) + ZEND_FE(ftp_ssl_connect, arginfo_ftp_ssl_connect) +#endif + ZEND_FE(ftp_login, arginfo_ftp_login) + ZEND_FE(ftp_pwd, arginfo_ftp_pwd) + ZEND_FE(ftp_cdup, arginfo_ftp_cdup) + ZEND_FE(ftp_chdir, arginfo_ftp_chdir) + ZEND_FE(ftp_exec, arginfo_ftp_exec) + ZEND_FE(ftp_raw, arginfo_ftp_raw) + ZEND_FE(ftp_mkdir, arginfo_ftp_mkdir) + ZEND_FE(ftp_rmdir, arginfo_ftp_rmdir) + ZEND_FE(ftp_chmod, arginfo_ftp_chmod) + ZEND_FE(ftp_alloc, arginfo_ftp_alloc) + ZEND_FE(ftp_nlist, arginfo_ftp_nlist) + ZEND_FE(ftp_rawlist, arginfo_ftp_rawlist) + ZEND_FE(ftp_mlsd, arginfo_ftp_mlsd) + ZEND_FE(ftp_systype, arginfo_ftp_systype) + ZEND_FE(ftp_fget, arginfo_ftp_fget) + ZEND_FE(ftp_nb_fget, arginfo_ftp_nb_fget) + ZEND_FE(ftp_pasv, arginfo_ftp_pasv) + ZEND_FE(ftp_get, arginfo_ftp_get) + ZEND_FE(ftp_nb_get, arginfo_ftp_nb_get) + ZEND_FE(ftp_nb_continue, arginfo_ftp_nb_continue) + ZEND_FE(ftp_fput, arginfo_ftp_fput) + ZEND_FE(ftp_nb_fput, arginfo_ftp_nb_fput) + ZEND_FE(ftp_put, arginfo_ftp_put) + ZEND_FE(ftp_append, arginfo_ftp_append) + ZEND_FE(ftp_nb_put, arginfo_ftp_nb_put) + ZEND_FE(ftp_size, arginfo_ftp_size) + ZEND_FE(ftp_mdtm, arginfo_ftp_mdtm) + ZEND_FE(ftp_rename, arginfo_ftp_rename) + ZEND_FE(ftp_delete, arginfo_ftp_delete) + ZEND_FE(ftp_site, arginfo_ftp_site) + ZEND_FE(ftp_close, arginfo_ftp_close) +#if PHP_VERSION_ID >= 80400 + ZEND_RAW_FENTRY("ftp_quit", zif_ftp_close, arginfo_ftp_quit, 0, NULL, NULL) +#else + ZEND_RAW_FENTRY("ftp_quit", zif_ftp_close, arginfo_ftp_quit, 0) +#endif + ZEND_FE(ftp_set_option, arginfo_ftp_set_option) + ZEND_FE(ftp_get_option, arginfo_ftp_get_option) +#endif + ZEND_FE(swoole_tracer_leak_detect, arginfo_swoole_tracer_leak_detect) + ZEND_FE(swoole_tracer_prof_begin, arginfo_swoole_tracer_prof_begin) + ZEND_FE(swoole_tracer_prof_end, arginfo_swoole_tracer_prof_end) PHP_FE_END /* Must be the last line in swoole_functions[] */ }; @@ -145,6 +297,16 @@ static const zend_module_dep swoole_deps[] = { #ifdef SW_USE_CURL ZEND_MOD_REQUIRED("curl") #endif +#if defined(SW_USE_PGSQL) || defined(SW_USE_ORACLE) || defined(SW_USE_SQLITE) || defined(SW_USE_FIREBIRD) + ZEND_MOD_REQUIRED("pdo") +#endif +#ifdef SW_HAVE_FTP + ZEND_MOD_CONFLICTS("ftp") +#endif +#ifdef SW_HAVE_SSH2LIB + ZEND_MOD_CONFLICTS("ssh2") +#endif + ZEND_MOD_END }; @@ -171,62 +333,67 @@ zend_class_entry *swoole_error_ce; zend_object_handlers swoole_error_handlers; #ifdef COMPILE_DL_SWOOLE +#ifdef ZTS +ZEND_TSRMLS_CACHE_DEFINE() +#endif ZEND_GET_MODULE(swoole) #endif // clang-format off -/* {{{ PHP_INI - */ - PHP_INI_BEGIN() -/** - * enable swoole coroutine - */ -STD_ZEND_INI_BOOLEAN("swoole.enable_coroutine", "On", PHP_INI_ALL, OnUpdateBool, enable_coroutine, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.enable_library", "On", PHP_INI_ALL, OnUpdateBool, enable_library, zend_swoole_globals, swoole_globals) -/** - * enable swoole coroutine epreemptive scheduler - */ +STD_ZEND_INI_BOOLEAN("swoole.enable_fiber_mock", "Off", PHP_INI_ALL, OnUpdateBool, enable_fiber_mock, zend_swoole_globals, swoole_globals) STD_ZEND_INI_BOOLEAN("swoole.enable_preemptive_scheduler", "Off", PHP_INI_ALL, OnUpdateBool, enable_preemptive_scheduler, zend_swoole_globals, swoole_globals) -/** - * display error - */ STD_ZEND_INI_BOOLEAN("swoole.display_errors", "On", PHP_INI_ALL, OnUpdateBool, display_errors, zend_swoole_globals, swoole_globals) -/** - * use short class name - */ STD_ZEND_INI_BOOLEAN("swoole.use_shortname", "On", PHP_INI_SYSTEM, OnUpdateBool, use_shortname, zend_swoole_globals, swoole_globals) -/** - * unix socket buffer size - */ -STD_PHP_INI_ENTRY("swoole.unixsock_buffer_size", ZEND_TOSTR(SW_SOCKET_BUFFER_SIZE), PHP_INI_ALL, OnUpdateLong, socket_buffer_size, zend_swoole_globals, swoole_globals) +STD_PHP_INI_ENTRY("swoole.socket_buffer_size", ZEND_TOSTR(SW_SOCKET_BUFFER_SIZE), PHP_INI_ALL, OnUpdateLong, socket_buffer_size, zend_swoole_globals, swoole_globals) +STD_ZEND_INI_BOOLEAN("swoole.blocking_detection", "Off", PHP_INI_SYSTEM, OnUpdateBool, blocking_detection, zend_swoole_globals, swoole_globals) +STD_PHP_INI_ENTRY("swoole.blocking_threshold", "100000", PHP_INI_SYSTEM, OnUpdateLong, blocking_threshold, zend_swoole_globals, swoole_globals) +STD_ZEND_INI_BOOLEAN("swoole.profile", "Off", PHP_INI_SYSTEM, OnUpdateBool, profile, zend_swoole_globals, swoole_globals) +STD_ZEND_INI_BOOLEAN("swoole.leak_detection", "Off", PHP_INI_SYSTEM, OnUpdateBool, leak_detection, zend_swoole_globals, swoole_globals) PHP_INI_END() // clang-format on static void php_swoole_init_globals(zend_swoole_globals *swoole_globals) { - swoole_globals->enable_coroutine = 1; - swoole_globals->enable_library = 1; - swoole_globals->enable_preemptive_scheduler = 0; + swoole_globals->enable_library = true; + swoole_globals->enable_fiber_mock = false; + swoole_globals->enable_preemptive_scheduler = false; swoole_globals->socket_buffer_size = SW_SOCKET_BUFFER_SIZE; - swoole_globals->display_errors = 1; - swoole_globals->use_shortname = 1; + swoole_globals->display_errors = true; + swoole_globals->use_shortname = true; + swoole_globals->in_autoload = nullptr; + swoole_globals->blocking_detection = false; + swoole_globals->blocking_threshold = 100000; + swoole_globals->profile = false; + swoole_globals->leak_detection = false; + + if (strcmp("cli", sapi_module.name) == 0 || strcmp("phpdbg", sapi_module.name) == 0 || + strcmp("embed", sapi_module.name) == 0 || strcmp("micro", sapi_module.name) == 0) { + swoole_globals->cli = true; + } } void php_swoole_register_shutdown_function(const char *function) { +#if PHP_VERSION_ID >= 80500 + php_shutdown_function_entry shutdown_function_entry = { + .fci_cache = empty_fcall_info_cache, + .params = NULL, + .param_count = 0, + }; + auto fn_len = strlen(function); + auto fn_entry = (zend_function *) zend_hash_str_find_ptr(EG(function_table), function, fn_len); + assert(fn_entry); + shutdown_function_entry.fci_cache.function_handler = fn_entry; + register_user_shutdown_function(function, fn_len, &shutdown_function_entry); +#else php_shutdown_function_entry shutdown_function_entry; -#if PHP_VERSION_ID >= 80100 zval function_name; + // In the user_shutdown_function_dtor function, the memory for the function name will be released, + // so must not free it manually here. ZVAL_STRING(&function_name, function); zend_fcall_info_init( - &function_name, 0, &shutdown_function_entry.fci, &shutdown_function_entry.fci_cache, NULL, NULL); + &function_name, 0, &shutdown_function_entry.fci, &shutdown_function_entry.fci_cache, nullptr, nullptr); register_user_shutdown_function(Z_STRVAL(function_name), Z_STRLEN(function_name), &shutdown_function_entry); -#else - zval *function_name; - shutdown_function_entry.arg_count = 0; - shutdown_function_entry.arguments = NULL; - function_name = &shutdown_function_entry.function_name; - ZVAL_STRING(function_name, function); - register_user_shutdown_function(Z_STRVAL_P(function_name), Z_STRLEN_P(function_name), &shutdown_function_entry); #endif } @@ -235,17 +402,27 @@ void php_swoole_set_global_option(HashTable *vht) { #ifdef SW_DEBUG if (php_swoole_array_get_value(vht, "debug_mode", ztmp) && zval_is_true(ztmp)) { - sw_logger()->set_level(0); + swoole_set_log_level(0); } #endif + // [EventLoop] + // ====================================================================== + if (php_swoole_array_get_value(vht, "enable_signalfd", ztmp)) { + SwooleG.enable_signalfd = zval_is_true(ztmp); + } + if (php_swoole_array_get_value(vht, "enable_kqueue", ztmp)) { + SwooleG.enable_kqueue = zval_is_true(ztmp); + } + // [Logger] + // ====================================================================== if (php_swoole_array_get_value(vht, "trace_flags", ztmp)) { - SwooleG.trace_flags = (uint32_t) SW_MAX(0, zval_get_long(ztmp)); + swoole_set_trace_flags(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "log_file", ztmp)) { - sw_logger()->open(zend::String(ztmp).val()); + swoole_set_log_file(zend::String(ztmp).val()); } if (php_swoole_array_get_value(vht, "log_level", ztmp)) { - sw_logger()->set_level(zval_get_long(ztmp)); + swoole_set_log_level(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "log_date_format", ztmp)) { sw_logger()->set_date_format(zend::String(ztmp).val()); @@ -259,10 +436,23 @@ void php_swoole_set_global_option(HashTable *vht) { if (php_swoole_array_get_value(vht, "display_errors", ztmp)) { SWOOLE_G(display_errors) = zval_is_true(ztmp); } + if (php_swoole_array_get_value(vht, "print_backtrace_on_error", ztmp)) { +#if !defined(HAVE_BOOST_STACKTRACE) && !defined(HAVE_EXECINFO) + zend_throw_exception( + swoole_error_ce, + "The `print_backtrace_on_error` option requires `boost stacktrace` or `execinfo.h` to be installed", + SW_ERROR_OPERATION_NOT_SUPPORT); +#else + SwooleG.print_backtrace_on_error = zval_is_true(ztmp); +#endif + } + // [DNS] + // ====================================================================== if (php_swoole_array_get_value(vht, "dns_server", ztmp)) { swoole_set_dns_server(zend::String(ztmp).to_std_string()); } - + // [Socket] + // ====================================================================== auto timeout_format = [](zval *v) -> double { double timeout = zval_get_double(v); if (timeout <= 0 || timeout > INT_MAX) { @@ -271,7 +461,6 @@ void php_swoole_set_global_option(HashTable *vht) { return timeout; } }; - if (php_swoole_array_get_value(vht, "socket_dns_timeout", ztmp)) { Socket::default_dns_timeout = timeout_format(ztmp); } @@ -287,23 +476,86 @@ void php_swoole_set_global_option(HashTable *vht) { Socket::default_read_timeout = timeout_format(ztmp); } if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { - Socket::default_buffer_size = zval_get_long(ztmp); + Socket::default_buffer_size = php_swoole_parse_to_size(ztmp); } if (php_swoole_array_get_value(vht, "socket_timeout", ztmp)) { Socket::default_read_timeout = Socket::default_write_timeout = timeout_format(ztmp); } -} - -void php_swoole_register_rshutdown_callback(swoole::Callback cb, void *private_data) { - rshutdown_callbacks.append(cb, private_data); + // [HTTP2] + // ====================================================================== + if (php_swoole_array_get_value(vht, "http2_header_table_size", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE, php_swoole_parse_to_size(ztmp)); + } + if (php_swoole_array_get_value(vht, "http2_enable_push", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH, zval_get_long(ztmp)); + } + if (php_swoole_array_get_value(vht, "http2_max_concurrent_streams", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, zval_get_long(ztmp)); + } + if (php_swoole_array_get_value(vht, "http2_init_window_size", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, php_swoole_parse_to_size(ztmp)); + } + if (php_swoole_array_get_value(vht, "http2_max_frame_size", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, php_swoole_parse_to_size(ztmp)); + } + if (php_swoole_array_get_value(vht, "http2_max_header_list_size", ztmp)) { + swoole::http2::put_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, php_swoole_parse_to_size(ztmp)); + } } SW_API bool php_swoole_is_enable_coroutine() { if (sw_server()) { return sw_server()->is_enable_coroutine(); } else { - return SWOOLE_G(enable_coroutine); + return SwooleG.enable_coroutine; + } +} + +SW_API zend_long php_swoole_parse_to_size(zval *zv) { + if (ZVAL_IS_STRING(zv)) { + zend_string *errstr; + auto size = zend_ini_parse_quantity(Z_STR_P(zv), &errstr); + if (errstr) { + php_swoole_fatal_error( + E_ERROR, "failed to parse '%s' to size, Error: %s", Z_STRVAL_P(zv), ZSTR_VAL(errstr)); + zend_string_release(errstr); + } + return size; + } else { + return zval_get_long(zv); + } +} + +SW_API zend_string *php_swoole_serialize(zval *zdata) { + php_serialize_data_t var_hash; + smart_str serialized_data = {}; + + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&serialized_data, zdata, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + + zend_string *result = nullptr; + if (!EG(exception)) { + result = zend_string_init(serialized_data.s->val, serialized_data.s->len, true); } + smart_str_free(&serialized_data); + return result; +} + +SW_API bool php_swoole_unserialize(const zend_string *data, zval *zv) { + php_unserialize_data_t var_hash; + const char *p = ZSTR_VAL(data); + size_t l = ZSTR_LEN(data); + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + zend_bool unserialized = php_var_unserialize(zv, (const uchar **) &p, (const uchar *) (p + l), &var_hash); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + if (!unserialized) { + swoole_warning("unserialize() failed, Error at offset " ZEND_LONG_FMT " of %zd bytes", + (zend_long) ((char *) p - ZSTR_VAL(data)), + l); + } + return unserialized; } static void fatal_error(int code, const char *format, ...) { @@ -313,25 +565,48 @@ static void fatal_error(int code, const char *format, ...) { zend_throw_exception(swoole_error_ce, swoole::std_string::vformat(format, args).c_str(), code); va_end(args); - zend_try { - zend_exception_error(exception, E_ERROR); + zend::print_error(exception, E_ERROR); + + if (code == SW_ERROR_CO_HAS_BEEN_BOUND) { + fprintf(stderr, + "\n [Coroutine-%ld] Stack trace:" + "\n -------------------------------------------------------------------" + "\n", + Coroutine::get_socket_bound_cid()); + sw_php_print_backtrace(Coroutine::get_socket_bound_cid()); } - zend_catch { - exit(255); + +#ifdef SW_THREAD + if (!tsrm_is_main_thread()) { + php_swoole_thread_bailout(); } - zend_end_try(); +#endif + swoole_exit(255); +} + +static void print_backtrace() { + fprintf(stderr, "\nStack trace:\n"); + sw_php_print_backtrace_impl(0); } static void bug_report_message_init() { SwooleG.bug_report_message += swoole::std_string::format("PHP_VERSION : %s\n", PHP_VERSION); } +static int g_module_number_; + +int sw_module_number() { + return g_module_number_; +} + /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(swoole) { ZEND_INIT_MODULE_GLOBALS(swoole, php_swoole_init_globals, nullptr); REGISTER_INI_ENTRIES(); + g_module_number_ = module_number; + // clang-format off // MUST be on the same line for the inspection tool to recognize correctly SW_REGISTER_STRING_CONSTANT("SWOOLE_VERSION", SWOOLE_VERSION); @@ -367,6 +642,8 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UDP6", SW_SOCK_UDP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UNIX_DGRAM", SW_SOCK_UNIX_DGRAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_UNIX_STREAM", SW_SOCK_UNIX_STREAM); + SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_RAW", SW_SOCK_RAW); + SW_REGISTER_LONG_CONSTANT("SWOOLE_SOCK_RAW6", SW_SOCK_RAW6); /** * simple socket type alias @@ -377,6 +654,8 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_UDP6", SW_SOCK_UDP6); SW_REGISTER_LONG_CONSTANT("SWOOLE_UNIX_DGRAM", SW_SOCK_UNIX_DGRAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_UNIX_STREAM", SW_SOCK_UNIX_STREAM); + SW_REGISTER_LONG_CONSTANT("SWOOLE_RAW", SW_SOCK_RAW); + SW_REGISTER_LONG_CONSTANT("SWOOLE_RAW6", SW_SOCK_RAW6); /** * simple api @@ -384,11 +663,6 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_BOOL_CONSTANT("SWOOLE_SOCK_SYNC", 0); SW_REGISTER_BOOL_CONSTANT("SWOOLE_SOCK_ASYNC", 1); - SW_REGISTER_LONG_CONSTANT("SWOOLE_SYNC", SW_FLAG_SYNC); - SW_REGISTER_LONG_CONSTANT("SWOOLE_ASYNC", SW_FLAG_ASYNC); - SW_REGISTER_LONG_CONSTANT("SWOOLE_KEEP", SW_FLAG_KEEP); - -#ifdef SW_USE_OPENSSL SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL", SW_SOCK_SSL); /** @@ -434,26 +708,23 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_SSLv3", SW_SSL_SSLv3); #endif SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1", SW_SSL_TLSv1); - #ifdef TLS1_1_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_1", SW_SSL_TLSv1_1); #endif - #ifdef TLS1_2_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_2", SW_SSL_TLSv1_2); #endif - #ifdef TLS1_3_VERSION SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_TLSv1_3", SW_SSL_TLSv1_3); #endif - #ifdef SW_SUPPORT_DTLS SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_DTLS", SW_SSL_DTLS); #endif - SW_REGISTER_LONG_CONSTANT("SWOOLE_SSL_SSLv2", SW_SSL_SSLv2); -#endif + /** + * Register event constants + */ SW_REGISTER_LONG_CONSTANT("SWOOLE_EVENT_READ", SW_EVENT_READ); SW_REGISTER_LONG_CONSTANT("SWOOLE_EVENT_WRITE", SW_EVENT_WRITE); @@ -477,9 +748,15 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_OPERATION_NOT_SUPPORT", SW_ERROR_OPERATION_NOT_SUPPORT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PROTOCOL_ERROR", SW_ERROR_PROTOCOL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WRONG_OPERATION", SW_ERROR_WRONG_OPERATION); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PHP_RUNTIME_NOTICE", SW_ERROR_PHP_RUNTIME_NOTICE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FOR_TEST", SW_ERROR_FOR_TEST); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_NO_PAYLOAD", SW_ERROR_NO_PAYLOAD); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_UNDEFINED_BEHAVIOR", SW_ERROR_UNDEFINED_BEHAVIOR); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_NOT_THREAD_SAFETY", SW_ERROR_NOT_THREAD_SAFETY); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_NOT_EXIST", SW_ERROR_FILE_NOT_EXIST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_TOO_LARGE", SW_ERROR_FILE_TOO_LARGE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_FILE_EMPTY", SW_ERROR_FILE_EMPTY); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DIR_NOT_EXIST", SW_ERROR_DIR_NOT_EXIST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_DUPLICATE_REQUEST", SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED", SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT", SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT); @@ -487,7 +764,13 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DNSLOOKUP_NO_SERVER", SW_ERROR_DNSLOOKUP_NO_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_IPV6_ADDRESS", SW_ERROR_BAD_IPV6_ADDRESS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_UNREGISTERED_SIGNAL", SW_ERROR_UNREGISTERED_SIGNAL); - SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_SOCKET_REMOVED", SW_ERROR_EVENT_SOCKET_REMOVED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_HOST_ADDR", SW_ERROR_BAD_HOST_ADDR); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_PORT", SW_ERROR_BAD_PORT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_BAD_SOCKET_TYPE", SW_ERROR_BAD_SOCKET_TYPE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_REMOVE_FAILED", SW_ERROR_EVENT_REMOVE_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_ADD_FAILED", SW_ERROR_EVENT_ADD_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_UPDATE_FAILED", SW_ERROR_EVENT_UPDATE_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_EVENT_UNKNOWN_DATA", SW_ERROR_EVENT_UNKNOWN_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSED_BY_SERVER", SW_ERROR_SESSION_CLOSED_BY_SERVER); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSED_BY_CLIENT", SW_ERROR_SESSION_CLOSED_BY_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SESSION_CLOSING", SW_ERROR_SESSION_CLOSING); @@ -506,6 +789,8 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_BAD_PROTOCOL", SW_ERROR_SSL_BAD_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_RESET", SW_ERROR_SSL_RESET); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_HANDSHAKE_FAILED", SW_ERROR_SSL_HANDSHAKE_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_CREATE_CONTEXT_FAILED", SW_ERROR_SSL_CREATE_CONTEXT_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SSL_CREATE_SESSION_FAILED", SW_ERROR_SSL_CREATE_SESSION_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PACKAGE_LENGTH_TOO_LARGE", SW_ERROR_PACKAGE_LENGTH_TOO_LARGE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_PACKAGE_LENGTH_NOT_FOUND", SW_ERROR_PACKAGE_LENGTH_NOT_FOUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_DATA_LENGTH_TOO_LARGE", SW_ERROR_DATA_LENGTH_TOO_LARGE); @@ -518,21 +803,27 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_NOT_FOUND", SW_ERROR_HTTP2_STREAM_NOT_FOUND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_STREAM_IGNORE", SW_ERROR_HTTP2_STREAM_IGNORE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED", SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP2_INTERNAL_ERROR", SW_ERROR_HTTP2_INTERNAL_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_BAD_REQUEST", SW_ERROR_AIO_BAD_REQUEST); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_CANCELED", SW_ERROR_AIO_CANCELED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_AIO_TIMEOUT", SW_ERROR_AIO_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CLIENT_NO_CONNECTION", SW_ERROR_CLIENT_NO_CONNECTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_CLOSED", SW_ERROR_SOCKET_CLOSED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_POLL_TIMEOUT", SW_ERROR_SOCKET_POLL_TIMEOUT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKET_NOT_EXISTS", SW_ERROR_SOCKET_NOT_EXISTS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_UNSUPPORT_VERSION", SW_ERROR_SOCKS5_UNSUPPORT_VERSION); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_UNSUPPORT_METHOD", SW_ERROR_SOCKS5_UNSUPPORT_METHOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_AUTH_FAILED", SW_ERROR_SOCKS5_AUTH_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_SERVER_ERROR", SW_ERROR_SOCKS5_SERVER_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_HANDSHAKE_FAILED", SW_ERROR_SOCKS5_HANDSHAKE_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SOCKS5_CONNECT_FAILED", SW_ERROR_SOCKS5_CONNECT_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_HANDSHAKE_ERROR", SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_INVALID_PROTOCOL", SW_ERROR_HTTP_INVALID_PROTOCOL); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_HANDSHAKE_FAILED", SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_PROXY_BAD_RESPONSE", SW_ERROR_HTTP_PROXY_BAD_RESPONSE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_CONFLICT_HEADER", SW_ERROR_HTTP_CONFLICT_HEADER); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_CONTEXT_UNAVAILABLE", SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_HTTP_COOKIE_UNAVAILABLE", SW_ERROR_HTTP_COOKIE_UNAVAILABLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_BAD_CLIENT", SW_ERROR_WEBSOCKET_BAD_CLIENT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_BAD_OPCODE", SW_ERROR_WEBSOCKET_BAD_OPCODE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_WEBSOCKET_UNCONNECTED", SW_ERROR_WEBSOCKET_UNCONNECTED); @@ -554,6 +845,8 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_COMMAND", SW_ERROR_SERVER_INVALID_COMMAND); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_IS_NOT_REGULAR_FILE", SW_ERROR_SERVER_IS_NOT_REGULAR_FILE); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT", SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_INVALID_CALLBACK", SW_ERROR_SERVER_INVALID_CALLBACK); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_UNRELATED_THREAD", SW_ERROR_SERVER_UNRELATED_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_EXIT_TIMEOUT", SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA", SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_SERVER_WORKER_UNPROCESSED_DATA", SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA); @@ -575,6 +868,7 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_NOT_EXISTS", SW_ERROR_CO_NOT_EXISTS); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_CANCELED", SW_ERROR_CO_CANCELED); SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_TIMEDOUT", SW_ERROR_CO_TIMEDOUT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_ERROR_CO_SOCKET_CLOSE_WAIT", SW_ERROR_CO_SOCKET_CLOSE_WAIT); /** * trace log @@ -609,6 +903,14 @@ PHP_MINIT_FUNCTION(swoole) { SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_TABLE", SW_TRACE_TABLE); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_CURL", SW_TRACE_CO_CURL); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CARES", SW_TRACE_CARES); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_ZLIB", SW_TRACE_ZLIB); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_PGSQL", SW_TRACE_CO_PGSQL); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_ODBC", SW_TRACE_CO_ODBC); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_ORACLE", SW_TRACE_CO_ORACLE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_SQLITE", SW_TRACE_CO_SQLITE); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_FIREBIRD", SW_TRACE_CO_FIREBIRD); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_CO_SSH2", SW_TRACE_CO_SSH2); + SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_THREAD", SW_TRACE_THREAD); SW_REGISTER_LONG_CONSTANT("SWOOLE_TRACE_ALL", SW_TRACE_ALL); /** @@ -637,20 +939,31 @@ PHP_MINIT_FUNCTION(swoole) { */ SW_REGISTER_LONG_CONSTANT("SWOOLE_IOV_MAX", IOV_MAX); + /** + * iouring + */ +#ifdef SW_USE_IOURING + SW_REGISTER_LONG_CONSTANT("SWOOLE_IOURING_DEFAULT", SW_IOURING_DEFAULT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_IOURING_SQPOLL", SW_IOURING_SQPOLL); +#endif + // clang-format on if (SWOOLE_G(use_shortname)) { - SW_FUNCTION_ALIAS(CG(function_table), "swoole_coroutine_create", CG(function_table), "go"); - SW_FUNCTION_ALIAS(CG(function_table), "swoole_coroutine_defer", CG(function_table), "defer"); + SW_FUNCTION_ALIAS( + CG(function_table), "swoole_coroutine_create", CG(function_table), "go", arginfo_swoole_coroutine_create); + SW_FUNCTION_ALIAS( + CG(function_table), "swoole_coroutine_defer", CG(function_table), "defer", arginfo_swoole_coroutine_defer); +#ifdef SW_STDEXT + SW_FUNCTION_ALIAS( + CG(function_table), "swoole_typed_array", CG(function_table), "typed_array", arginfo_swoole_typed_array); +#endif } swoole_init(); // init bug report message bug_report_message_init(); - if (strcmp("cli", sapi_module.name) == 0 || strcmp("phpdbg", sapi_module.name) == 0) { - SWOOLE_G(cli) = 1; - } SW_INIT_CLASS_ENTRY_EX2( swoole_exception, "Swoole\\Exception", nullptr, nullptr, zend_ce_exception, zend_get_std_object_handlers()); @@ -671,35 +984,69 @@ PHP_MINIT_FUNCTION(swoole) { php_swoole_coroutine_minit(module_number); php_swoole_coroutine_system_minit(module_number); php_swoole_coroutine_scheduler_minit(module_number); + php_swoole_coroutine_lock_minit(module_number); php_swoole_channel_coro_minit(module_number); php_swoole_runtime_minit(module_number); // client php_swoole_socket_coro_minit(module_number); php_swoole_client_minit(module_number); + php_swoole_client_async_minit(module_number); php_swoole_client_coro_minit(module_number); php_swoole_http_client_coro_minit(module_number); php_swoole_http2_client_coro_minit(module_number); - php_swoole_mysql_coro_minit(module_number); - php_swoole_redis_coro_minit(module_number); +#ifdef SW_HAVE_SSH2LIB + php_swoole_ssh2_minit(module_number); +#endif +#ifdef SW_HAVE_FTP + PHP_MINIT(ftp)(type, module_number); +#endif // server php_swoole_server_minit(module_number); php_swoole_server_port_minit(module_number); php_swoole_http_request_minit(module_number); php_swoole_http_response_minit(module_number); + php_swoole_http_cookie_minit(module_number); php_swoole_http_server_minit(module_number); php_swoole_http_server_coro_minit(module_number); php_swoole_websocket_server_minit(module_number); php_swoole_redis_server_minit(module_number); php_swoole_name_resolver_minit(module_number); #ifdef SW_USE_PGSQL - php_swoole_postgresql_coro_minit(module_number); + php_swoole_pgsql_minit(module_number); +#endif +#ifdef SW_USE_ODBC + php_swoole_odbc_minit(module_number); +#endif +#ifdef SW_USE_ORACLE + php_swoole_oracle_minit(module_number); +#endif +#ifdef SW_USE_SQLITE + php_swoole_sqlite_minit(module_number); #endif +#ifdef SW_USE_FIREBIRD + php_swoole_firebird_minit(module_number); +#endif +#ifdef SW_THREAD + php_swoole_thread_minit(module_number); + php_swoole_thread_atomic_minit(module_number); + php_swoole_thread_lock_minit(module_number); + php_swoole_thread_barrier_minit(module_number); + php_swoole_thread_queue_minit(module_number); + php_swoole_thread_map_minit(module_number); + php_swoole_thread_arraylist_minit(module_number); +#endif +#ifdef SW_STDEXT + php_swoole_stdext_minit(module_number); +#endif + php_swoole_tracer_minit(module_number); SwooleG.fatal_error = fatal_error; + SwooleG.print_backtrace = print_backtrace; + Socket::default_buffer_size = SWOOLE_G(socket_buffer_size); SwooleG.dns_cache_refresh_time = 60; - // enable pcre.jit and use swoole extension on MacOS will lead to coredump, disable it temporarily + // enable pcre.jit and use swoole extension on macOS will lead to core dump, disable it temporarily #if defined(PHP_PCRE_VERSION) && defined(HAVE_PCRE_JIT_SUPPORT) && __MACH__ && !defined(SW_DEBUG) PCRE_G(jit) = 0; #endif @@ -716,7 +1063,21 @@ PHP_MSHUTDOWN_FUNCTION(swoole) { zend::known_strings_dtor(); php_swoole_runtime_mshutdown(); - php_swoole_websocket_server_mshutdown(); +#ifdef SW_USE_PGSQL + php_swoole_pgsql_mshutdown(); +#endif +#ifdef SW_USE_ORACLE + php_swoole_oracle_mshutdown(); +#endif +#ifdef SW_USE_SQLITE + php_swoole_sqlite_mshutdown(); +#endif +#ifdef SW_USE_FIREBIRD + php_swoole_firebird_mshutdown(); +#endif +#ifdef SW_HAVE_SSH2LIB + php_swoole_ssh2_mshutdown(); +#endif swoole_clean(); @@ -734,6 +1095,13 @@ PHP_MINFO_FUNCTION(swoole) { php_info_print_table_row(2, "Version", SWOOLE_VERSION); snprintf(buf, sizeof(buf), "%s %s", __DATE__, __TIME__); php_info_print_table_row(2, "Built", buf); + +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + php_info_print_table_row(2, "host byte order", "little endian"); +#else + php_info_print_table_row(2, "host byte order", "big endian"); +#endif + #if defined(SW_USE_THREAD_CONTEXT) php_info_print_table_row(2, "coroutine", "enabled with thread context"); #elif defined(SW_USE_ASM_CONTEXT) @@ -753,8 +1121,8 @@ PHP_MINFO_FUNCTION(swoole) { #ifdef HAVE_EVENTFD php_info_print_table_row(2, "eventfd", "enabled"); #endif -#ifdef HAVE_KQUEUE - php_info_print_table_row(2, "kqueue", "enabled"); +#ifdef SW_THREAD + php_info_print_table_row(2, "thread", "enabled"); #endif #ifdef HAVE_SIGNALFD php_info_print_table_row(2, "signalfd", "enabled"); @@ -774,7 +1142,6 @@ PHP_MINFO_FUNCTION(swoole) { #ifdef SW_SOCKETS php_info_print_table_row(2, "sockets", "enabled"); #endif -#ifdef SW_USE_OPENSSL #ifdef OPENSSL_VERSION_TEXT php_info_print_table_row(2, "openssl", OPENSSL_VERSION_TEXT); #else @@ -782,15 +1149,13 @@ PHP_MINFO_FUNCTION(swoole) { #endif #ifdef SW_SUPPORT_DTLS php_info_print_table_row(2, "dtls", "enabled"); -#endif #endif php_info_print_table_row(2, "http2", "enabled"); php_info_print_table_row(2, "json", "enabled"); #ifdef SW_USE_CURL php_info_print_table_row(2, "curl-native", "enabled"); -#endif -#ifdef HAVE_PCRE - php_info_print_table_row(2, "pcre", "enabled"); + curl_version_info_data *d = curl_version_info(CURLVERSION_NOW); + php_info_print_table_row(2, "curl-version", d->version); #endif #ifdef SW_USE_CARES php_info_print_table_row(2, "c-ares", ares_version(nullptr)); @@ -806,6 +1171,13 @@ PHP_MINFO_FUNCTION(swoole) { snprintf(buf, sizeof(buf), "E%u/D%u", BrotliEncoderVersion(), BrotliDecoderVersion()); php_info_print_table_row(2, "brotli", buf); #endif +#ifdef SW_HAVE_ZSTD +#ifdef ZSTD_VERSION_NUMBER + php_info_print_table_row(2, "zstd", ZSTD_VERSION_STRING); +#else + php_info_print_table_row(2, "zstd", "enabled"); +#endif +#endif #ifdef HAVE_MUTEX_TIMEDLOCK php_info_print_table_row(2, "mutex_timedlock", "enabled"); #endif @@ -824,9 +1196,40 @@ PHP_MINFO_FUNCTION(swoole) { #ifdef SW_USE_TCMALLOC php_info_print_table_row(2, "tcmalloc", "enabled"); #endif - php_info_print_table_row(2, "async_redis", "enabled"); #ifdef SW_USE_PGSQL - php_info_print_table_row(2, "coroutine_postgresql", "enabled"); + char libpq_version[16]; + swoole_libpq_version(libpq_version, sizeof(libpq_version)); + php_info_print_table_row(2, "postgresql(libpq) version", libpq_version); + php_info_print_table_row(2, "coroutine_pgsql", "enabled"); +#endif +#ifdef SW_USE_ODBC + php_info_print_table_row(2, "coroutine_odbc", "enabled"); +#endif +#ifdef SW_USE_ORACLE + php_info_print_table_row(2, "coroutine_oracle", "enabled"); +#endif +#ifdef SW_USE_SQLITE + php_info_print_table_row(2, "coroutine_sqlite", "enabled"); +#endif +#ifdef SW_USE_FIREBIRD + php_info_print_table_row(2, "coroutine_firebird", "enabled"); +#endif +#ifdef SW_USE_IOURING + php_info_print_table_row(2, "io_uring", "enabled"); +#endif +#ifdef SW_USE_URING_SOCKET + php_info_print_table_row(2, "uring_socket", "enabled"); +#endif +#ifdef HAVE_BOOST_STACKTRACE + php_info_print_table_row(2, "boost stacktrace", "enabled"); +#elif defined(HAVE_EXECINFO) + php_info_print_table_row(2, "execinfo", "enabled"); +#endif +#ifdef SW_HAVE_SSH2LIB + php_swoole_ssh2_minfo(); +#endif +#ifdef SW_HAVE_FTP + PHP_MINFO(ftp)(zend_module); #endif php_info_print_table_end(); @@ -834,62 +1237,62 @@ PHP_MINFO_FUNCTION(swoole) { } /* }}} */ -static void *_sw_emalloc(size_t size) { +static void *sw_emalloc(size_t size) { return emalloc(size); } -static void *_sw_ecalloc(size_t nmemb, size_t size) { +static void *sw_ecalloc(size_t nmemb, size_t size) { return ecalloc(nmemb, size); } -static void *_sw_erealloc(void *address, size_t size) { +static void *sw_erealloc(void *address, size_t size) { return erealloc(address, size); } -static void _sw_efree(void *address) { +static void sw_efree(void *address) { efree(address); } -static void *_sw_zend_string_malloc(size_t size) { - zend_string *str = zend_string_alloc(size, 0); +static void *sw_zend_string_malloc(size_t size) { + zend_string *str = zend_string_alloc(size, false); if (str == nullptr) { return nullptr; } return str->val; } -static void *_sw_zend_string_calloc(size_t nmemb, size_t size) { - void *mem = _sw_zend_string_malloc(nmemb * size); +static void *sw_zend_string_calloc(size_t nmemb, size_t size) { + void *mem = sw_zend_string_malloc(nmemb * size); if (mem) { sw_memset_zero(mem, size); } return mem; } -static void *_sw_zend_string_realloc(void *address, size_t size) { - zend_string *str = zend_string_realloc(zend::fetch_zend_string_by_val(address), size, 0); +static void *sw_zend_string_realloc(void *address, size_t size) { + zend_string *str = zend_string_realloc(zend::fetch_zend_string_by_val(address), size, false); if (str == nullptr) { return nullptr; } return str->val; } -static void _sw_zend_string_free(void *address) { - zend_string_free(zend::fetch_zend_string_by_val(address)); +static void sw_zend_string_free(void *address) { + zend_string_release_ex(zend::fetch_zend_string_by_val(address), false); } static swoole::Allocator php_allocator{ - _sw_emalloc, - _sw_ecalloc, - _sw_erealloc, - _sw_efree, + sw_emalloc, + sw_ecalloc, + sw_erealloc, + sw_efree, }; static swoole::Allocator zend_string_allocator{ - _sw_zend_string_malloc, - _sw_zend_string_calloc, - _sw_zend_string_realloc, - _sw_zend_string_free, + sw_zend_string_malloc, + sw_zend_string_calloc, + sw_zend_string_realloc, + sw_zend_string_free, }; const swoole::Allocator *sw_php_allocator() { @@ -900,13 +1303,64 @@ const swoole::Allocator *sw_zend_string_allocator() { return &zend_string_allocator; } +void sw_php_exit(int status) { + EG(exit_status) = status; +#ifdef SW_THREAD + php_swoole_thread_bailout(); +#else + zend_bailout(); +#endif +} + +bool sw_zval_is_serializable(const zval *struc) { +again: + switch (Z_TYPE_P(struc)) { + case IS_OBJECT: { + if (Z_OBJCE_P(struc)->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { + return false; + } + break; + } + case IS_ARRAY: { + zval *elem; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(struc), elem) { + if (!sw_zval_is_serializable(elem)) { + return false; + } + } + ZEND_HASH_FOREACH_END(); + break; + } + case IS_REFERENCE: + struc = Z_REFVAL_P(struc); + goto again; + default: + break; + } + return true; +} + +static void sw_after_fork(void *args) { +#if PHP_VERSION_ID >= 80500 + refresh_memory_manager(); +#endif +#ifdef ZEND_MAX_EXECUTION_TIMERS + zend_max_execution_timer_init(); +#endif +} + PHP_RINIT_FUNCTION(swoole) { if (!SWOOLE_G(cli)) { return SUCCESS; } SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_BEGIN; - SwooleG.running = 1; + + // Use `sys_get_temp_dir` to obtain the system temporary file directory. + // The default temporary directory is `/tmp`, which does not exist on the Android platform. + // The system root path is a read-only mapping. + // The temporary file directory under termux is `/data/data/com.termux/files/usr/tmp` + swoole_set_task_tmpdir(php_get_temporary_directory()); php_swoole_register_shutdown_function("swoole_internal_call_user_shutdown_begin"); @@ -916,17 +1370,43 @@ PHP_RINIT_FUNCTION(swoole) { && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) #endif ) { + // https://github.com/swoole/swoole-src/issues/5182 + /** + * xdebug will hook zend_execute_ex to xdebug_execute_ex. + * This would cause php_swoole_load_library function not to execute correctly, so it must be replaced + * with the execute_ex function. + */ + void (*old_zend_execute_ex)(zend_execute_data * execute_data) = nullptr; + if (UNEXPECTED(zend_execute_ex != execute_ex)) { + old_zend_execute_ex = zend_execute_ex; + zend_execute_ex = execute_ex; + } + php_swoole_load_library(); + + if (UNEXPECTED(old_zend_execute_ex)) { + zend_execute_ex = old_zend_execute_ex; + old_zend_execute_ex = nullptr; + } } #ifdef ZEND_SIGNALS /* Disable warning even in ZEND_DEBUG because we may register our own signal handlers */ - SIGG(check) = 0; + SIGG(check) = false; #endif + swoole_add_hook(SW_GLOBAL_HOOK_AFTER_FORK, sw_after_fork, 0); + php_swoole_http_server_rinit(); php_swoole_coroutine_rinit(); php_swoole_runtime_rinit(); +#ifdef SW_USE_ORACLE + php_swoole_oracle_rinit(); +#endif +#ifdef SW_THREAD + php_swoole_thread_rinit(); +#endif + php_swoole_tracer_rinit(); SWOOLE_G(req_status) = PHP_SWOOLE_RINIT_END; @@ -940,33 +1420,33 @@ PHP_RSHUTDOWN_FUNCTION(swoole) { SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_BEGIN; - rshutdown_callbacks.execute(); - - swoole_event_free(); - php_swoole_server_rshutdown(); + php_swoole_http_server_rshutdown(); + php_swoole_http_response_rshutdown(); php_swoole_async_coro_rshutdown(); php_swoole_redis_server_rshutdown(); php_swoole_coroutine_rshutdown(); php_swoole_coroutine_scheduler_rshutdown(); + php_swoole_timer_rshutdown(); php_swoole_runtime_rshutdown(); + php_swoole_process_rshutdown(); +#ifdef SW_THREAD + php_swoole_thread_rshutdown(); +#endif + php_swoole_tracer_rshutdown(); - php_swoole_process_clean(); + swoole_event_free(); - SwooleG.running = 0; SWOOLE_G(req_status) = PHP_SWOOLE_RSHUTDOWN_END; #ifdef PHP_STREAM_FLAG_NO_CLOSE auto php_swoole_set_stdio_no_close = [](const char *name, size_t name_len) { - zval *zstream; - php_stream *stream; - - zstream = zend_get_constant_str(name, name_len); + zval *zstream = zend_get_constant_str(name, name_len); if (!zstream) { return; } - stream = - (php_stream *) zend_fetch_resource2_ex((zstream), "stream", php_file_le_stream(), php_file_le_pstream()); + auto *stream = + (php_stream *) zend_fetch_resource2_ex((zstream), nullptr, php_file_le_stream(), php_file_le_pstream()); if (!stream) { return; } @@ -989,7 +1469,7 @@ static uint32_t hashkit_one_at_a_time(const char *key, size_t key_length) { uint32_t value = 0; while (key_length--) { - uint32_t val = (uint32_t) *ptr++; + auto val = (uint32_t) *ptr++; value += val; value += (value << 10); value ^= (value >> 6); @@ -1158,8 +1638,7 @@ static PHP_FUNCTION(swoole_errno) { } PHP_FUNCTION(swoole_set_process_name) { - zend_function *cli_set_process_title = - (zend_function *) zend_hash_str_find_ptr(EG(function_table), ZEND_STRL("cli_set_process_title")); + auto *cli_set_process_title = zend::get_function(ZEND_STRL("cli_set_process_title")); if (!cli_set_process_title) { php_swoole_fatal_error(E_WARNING, "swoole_set_process_name only support in CLI mode"); RETURN_FALSE; @@ -1168,42 +1647,45 @@ PHP_FUNCTION(swoole_set_process_name) { } static PHP_FUNCTION(swoole_get_local_ip) { - struct sockaddr_in *s4; - struct ifaddrs *ipaddrs, *ifa; - void *in_addr; - char ip[64]; + struct ifaddrs *ipaddrs; if (getifaddrs(&ipaddrs) != 0) { php_swoole_sys_error(E_WARNING, "getifaddrs() failed"); RETURN_FALSE; } + + zend_long family = AF_INET; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(family) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + array_init(return_value); - for (ifa = ipaddrs; ifa != nullptr; ifa = ifa->ifa_next) { + for (struct ifaddrs *ifa = ipaddrs; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr || !(ifa->ifa_flags & IFF_UP)) { continue; } + if (ifa->ifa_addr->sa_family != family) { + continue; + } + + swoole::network::Address addr{}; switch (ifa->ifa_addr->sa_family) { case AF_INET: - s4 = (struct sockaddr_in *) ifa->ifa_addr; - in_addr = &s4->sin_addr; + addr.type = SW_SOCK_TCP; + memcpy(&addr.addr.inet_v4, ifa->ifa_addr, sizeof(addr.addr.inet_v4)); break; case AF_INET6: - // struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)ifa->ifa_addr; - // in_addr = &s6->sin6_addr; - continue; + addr.type = SW_SOCK_TCP6; + memcpy(&addr.addr.inet_v6, ifa->ifa_addr, sizeof(addr.addr.inet_v6)); + break; default: continue; } - if (!inet_ntop(ifa->ifa_addr->sa_family, in_addr, ip, sizeof(ip))) { - php_error_docref(nullptr, E_WARNING, "%s: inet_ntop failed", ifa->ifa_name); - } else { - // if (ifa->ifa_addr->sa_family == AF_INET && ntohl(((struct in_addr *) in_addr)->s_addr) == - // INADDR_LOOPBACK) - if (strcmp(ip, "127.0.0.1") == 0) { - continue; - } - add_assoc_string(return_value, ifa->ifa_name, ip); + + if (!addr.is_loopback_addr()) { + add_assoc_string(return_value, ifa->ifa_name, addr.get_addr()); } } freeifaddrs(ipaddrs); @@ -1216,46 +1698,74 @@ static PHP_FUNCTION(swoole_get_local_mac) { SW_STRS(buf), "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); add_assoc_string(zv, name, buf); }; + #ifdef SIOCGIFHWADDR + char buffer[4096]; struct ifconf ifc; - struct ifreq buf[16]; - + struct ifreq *ifr; int sock; - int i = 0, num = 0; + int num_interfaces; + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + php_swoole_sys_error(E_WARNING, "socket creation failed"); + RETURN_FALSE; + } - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - php_swoole_sys_error(E_WARNING, "new socket failed"); + ifc.ifc_len = sizeof(buffer); + ifc.ifc_buf = buffer; + + if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { + php_swoole_sys_error(E_WARNING, "ioctl SIOCGIFCONF failed"); + close(sock); RETURN_FALSE; } + array_init(return_value); - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = (caddr_t) buf; - if (!ioctl(sock, SIOCGIFCONF, (char *) &ifc)) { - num = ifc.ifc_len / sizeof(struct ifreq); - while (i < num) { - if (!(ioctl(sock, SIOCGIFHWADDR, (char *) &buf[i]))) { - add_assoc_address(return_value, buf[i].ifr_name, (unsigned char *) buf[i].ifr_hwaddr.sa_data); - } - i++; + ifr = ifc.ifc_req; + num_interfaces = ifc.ifc_len / sizeof(struct ifreq); + + for (int i = 0; i < num_interfaces; i++) { + struct ifreq temp_ifr; + strncpy(temp_ifr.ifr_name, ifr[i].ifr_name, IFNAMSIZ - 1); + temp_ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + + if (ioctl(sock, SIOCGIFFLAGS, &temp_ifr) < 0) { + continue; + } + + if (temp_ifr.ifr_flags & IFF_LOOPBACK) { + continue; + } + + if (ioctl(sock, SIOCGIFHWADDR, &temp_ifr) == 0) { + add_assoc_address(return_value, temp_ifr.ifr_name, (unsigned char *) temp_ifr.ifr_hwaddr.sa_data); } } close(sock); #else #ifdef LLADDR ifaddrs *ifas, *ifa; - if (getifaddrs(&ifas) == 0) { - array_init(return_value); - for (ifa = ifas; ifa; ifa = ifa->ifa_next) { - if ((ifa->ifa_addr->sa_family == AF_LINK) && ifa->ifa_addr) { - add_assoc_address( - return_value, ifa->ifa_name, (unsigned char *) (LLADDR((struct sockaddr_dl *) ifa->ifa_addr))); + + if (getifaddrs(&ifas) != 0) { + php_swoole_sys_error(E_WARNING, "getifaddrs failed"); + RETURN_FALSE; + } + + array_init(return_value); + + for (ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && !(ifa->ifa_flags & IFF_LOOPBACK) && ifa->ifa_addr->sa_family == AF_LINK) { + struct sockaddr_dl *sdl = (struct sockaddr_dl *) ifa->ifa_addr; + if (sdl->sdl_alen == 6) { + add_assoc_address(return_value, ifa->ifa_name, (unsigned char *) LLADDR(sdl)); } } - freeifaddrs(ifas); } + + freeifaddrs(ifas); #else - php_error_docref(nullptr, E_WARNING, "swoole_get_local_mac is not supported"); + php_error_docref(nullptr, E_WARNING, "swoole_get_local_mac is not supported on this platform"); RETURN_FALSE; #endif #endif @@ -1273,9 +1783,9 @@ static PHP_FUNCTION(swoole_internal_call_user_shutdown_begin) { static PHP_FUNCTION(swoole_substr_unserialize) { zend_long offset, length = 0; - char *buf = NULL; + char *buf = nullptr; size_t buf_len; - zval *options = NULL; + zval *options = nullptr; ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STRING(buf, buf_len) @@ -1290,22 +1800,25 @@ static PHP_FUNCTION(swoole_substr_unserialize) { } if (offset < 0) { offset = buf_len + offset; + if (offset < 0) { + RETURN_FALSE; + } } if ((zend_long) buf_len <= offset) { RETURN_FALSE; } - if (length <= 0) { + if (length <= 0 || length > (zend_long) (buf_len - offset)) { length = buf_len - offset; } - zend::unserialize(return_value, buf + offset, length, options ? Z_ARRVAL_P(options) : NULL); + zend::unserialize(return_value, buf + offset, length, options ? Z_ARRVAL_P(options) : nullptr); } static PHP_FUNCTION(swoole_substr_json_decode) { zend_long offset, length = 0; char *str; size_t str_len; - zend_bool assoc = 0; /* return JS objects as PHP objects by default */ - zend_bool assoc_null = 1; + zend_bool assoc = false; /* return JS objects as PHP objects by default */ + zend_bool assoc_null = true; zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; zend_long options = 0; @@ -1320,15 +1833,21 @@ static PHP_FUNCTION(swoole_substr_json_decode) { ZEND_PARSE_PARAMETERS_END(); if (str_len == 0) { - RETURN_FALSE; + php_error_docref(nullptr, E_WARNING, "Non-empty string required"); + RETURN_NULL(); } if (offset < 0) { offset = str_len + offset; + if (offset < 0) { + php_error_docref(nullptr, E_WARNING, "Offset must be not less than the negative length of the string"); + RETURN_NULL(); + } } if ((zend_long) str_len <= offset) { - RETURN_FALSE; + php_error_docref(nullptr, E_WARNING, "Offset must be less than the length of the string"); + RETURN_NULL(); } - if (length <= 0) { + if (length <= 0 || length > (zend_long) (str_len - offset)) { length = str_len - offset; } /* For BC reasons, the bool $assoc overrides the long $options bit for PHP_JSON_OBJECT_AS_ARRAY */ @@ -1341,3 +1860,40 @@ static PHP_FUNCTION(swoole_substr_json_decode) { } zend::json_decode(return_value, str + offset, length, options, depth); } + +/** + * The implicit functions are intended solely for internal testing and will not be documented. + * These functions are unsafe, do not use if you are not an internal developer. + */ +static PHP_FUNCTION(swoole_implicit_fn) { + char *fn; + size_t l_fn; + zval *zargs = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STRING(fn, l_fn) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(zargs) + ZEND_PARSE_PARAMETERS_END(); + + if (SW_STRCASEEQ(fn, l_fn, "fatal_error")) { + swoole_fatal_error(SW_ERROR_FOR_TEST, "test"); + php_printf("never be executed here\n"); + } else if (SW_STRCASEEQ(fn, l_fn, "bailout")) { + sw_php_exit(zargs ? zval_get_long(zargs) : 95); + } else if (SW_STRCASEEQ(fn, l_fn, "abort")) { + abort(); + } else if (SW_STRCASEEQ(fn, l_fn, "refcount")) { + RETURN_LONG(zval_refcount_p(zargs)); + } else if (SW_STRCASEEQ(fn, l_fn, "func_handler")) { + auto fn_str = zval_get_string(zargs); + auto *zf = zend::get_function(fn_str); + zend_string_release(fn_str); + if (zf == nullptr) { + RETURN_FALSE; + } + printf("zif_handler=%p\n", zf->internal_function.handler); + } else { + zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "unknown fn '%s'", fn); + } +} diff --git a/ext-src/php_swoole_call_stack.h b/ext-src/php_swoole_call_stack.h new file mode 100644 index 0000000000..14d1555e80 --- /dev/null +++ b/ext-src/php_swoole_call_stack.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | 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: Yurun | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole.h" + +#ifdef ZEND_CHECK_STACK_LIMIT +#define HOOK_PHP_CALL_STACK(callback) \ + void *__stack_limit = EG(stack_limit); \ + void *__stack_base = EG(stack_base); \ + EG(stack_base) = (void *) 0; \ + EG(stack_limit) = (void *) 0; \ + callback EG(stack_limit) = __stack_limit; \ + EG(stack_base) = __stack_base; +#else +#define HOOK_PHP_CALL_STACK(callback) callback +#endif diff --git a/ext-src/php_swoole_client.h b/ext-src/php_swoole_client.h index 007c1476a9..8f247a9f90 100644 --- a/ext-src/php_swoole_client.h +++ b/ext-src/php_swoole_client.h @@ -21,7 +21,53 @@ #include "php_swoole_cxx.h" #include "swoole_client.h" -bool php_swoole_client_check_setting(swoole::network::Client *cli, zval *zset); -#ifdef SW_USE_OPENSSL -void php_swoole_client_check_ssl_setting(swoole::network::Client *cli, zval *zset); +enum ClientFlag { + SW_FLAG_KEEP = 1u << 12, + SW_FLAG_ASYNC = 1u << 10, + SW_FLAG_SYNC = 1u << 11, +}; + +struct AsyncClientObject { + zend::Callable *onConnect; + zend::Callable *onReceive; + zend::Callable *onClose; + zend::Callable *onError; + zend::Callable *onBufferFull; + zend::Callable *onBufferEmpty; + zend::Callable *onSSLReady; + zval _zobject; +}; + +typedef swoole::network::Client NetClient; + +struct ClientObject { + NetClient *cli; +#ifdef SWOOLE_SOCKETS_SUPPORT + zval *zsocket; #endif + AsyncClientObject *async; + zend_object std; +}; + +static inline ClientObject *php_swoole_client_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_client_handlers.offset); +} + +static inline ClientObject *php_swoole_client_fetch_object(const zval *zobj) { + return php_swoole_client_fetch_object(Z_OBJ_P(zobj)); +} + +static inline NetClient *php_swoole_client_get_cli(const zval *zobject) { + return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cli; +} + +static inline enum swSocketType php_swoole_client_get_type(long type) { + return (enum swSocketType)(type & (~SW_FLAG_SYNC) & (~SW_FLAG_ASYNC) & (~SW_FLAG_KEEP) & (~SW_SOCK_SSL)); +} + +NetClient *php_swoole_client_get_cli_safe(const zval *zobject); +void php_swoole_client_free(const zval *zobject, NetClient *cli); +void php_swoole_client_async_free_object(const ClientObject *client_obj); +bool php_swoole_client_check_setting(NetClient *cli, const zval *zset); +void php_swoole_client_check_ssl_setting(const NetClient *cli, const zval *zset); +bool php_swoole_client_enable_ssl_encryption(NetClient *cli, zval *zobject); diff --git a/ext-src/php_swoole_coroutine.h b/ext-src/php_swoole_coroutine.h index 735216a0a6..7839fb5615 100644 --- a/ext-src/php_swoole_coroutine.h +++ b/ext-src/php_swoole_coroutine.h @@ -20,11 +20,14 @@ #pragma once #include "swoole_coroutine.h" -#include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" + #include "zend_vm.h" #include "zend_closures.h" +#include "zend_fibers.h" +#include "zend_observer.h" + #include #include @@ -58,16 +61,34 @@ struct PHPContext { zend_class_entry *exception_class; zend_object *exception; zend_output_globals *output_ptr; -#if PHP_VERSION_ID < 80100 - /* for array_walk non-reentrancy */ - zend::Function *array_walk_fci; -#endif + /* + * for var serialize/unserialize, + * coroutine switching may occur in the __sleep/__wakeup magic method of the object + */ + unsigned serialize_lock; + struct { + struct php_serialize_data *data; + unsigned level; + } serialize; + struct { + struct php_unserialize_data *data; + unsigned level; + } unserialize; /* for error control `@` */ bool in_silence; bool enable_scheduler; int ori_error_reporting; int tmp_error_reporting; Coroutine *co; + zend_fcall_info fci; + zend_fcall_info_cache fci_cache; + zval return_value; + zend_fiber_context *fiber_context; + bool fiber_init_notified; +#ifdef ZEND_CHECK_STACK_LIMIT + void *stack_base; + void *stack_limit; +#endif std::stack *defer_tasks; SwapCallback *on_yield; SwapCallback *on_resume; @@ -75,6 +96,7 @@ struct PHPContext { long pcid; zend_object *context; int64_t last_msec; + size_t switch_count; }; class PHPCoroutine { @@ -83,53 +105,58 @@ class PHPCoroutine { zend_fcall_info_cache *fci_cache; zval *argv; uint32_t argc; + zval *callable; }; struct Config { uint64_t max_num; - uint32_t max_concurrency; uint32_t hook_flags; bool enable_preemptive_scheduler; bool enable_deadlock_check; }; - static zend_array *options; + static SW_THREAD_LOCAL zend_array *options; enum HookType { - HOOK_NONE = 0, - HOOK_TCP = 1u << 1, - HOOK_UDP = 1u << 2, - HOOK_UNIX = 1u << 3, - HOOK_UDG = 1u << 4, - HOOK_SSL = 1u << 5, - HOOK_TLS = 1u << 6, - HOOK_STREAM_FUNCTION = 1u << 7, - HOOK_FILE = 1u << 8, - HOOK_SLEEP = 1u << 9, - HOOK_PROC = 1u << 10, - HOOK_CURL = 1u << 11, - HOOK_NATIVE_CURL = 1u << 12, - HOOK_BLOCKING_FUNCTION = 1u << 13, - HOOK_SOCKETS = 1u << 14, - HOOK_STDIO = 1u << 15, + HOOK_NONE = 0, + HOOK_TCP = 1u << 1, + HOOK_UDP = 1u << 2, + HOOK_UNIX = 1u << 3, + HOOK_UDG = 1u << 4, + HOOK_SSL = 1u << 5, + HOOK_TLS = 1u << 6, + HOOK_STREAM_FUNCTION = 1u << 7, + HOOK_FILE = 1u << 8, + HOOK_SLEEP = 1u << 9, + HOOK_PROC = 1u << 10, + HOOK_CURL = 1u << 11, + HOOK_NATIVE_CURL = 1u << 12, + HOOK_SOCKETS = 1u << 14, + HOOK_STDIO = 1u << 15, + HOOK_PDO_PGSQL = 1u << 16, + HOOK_PDO_ODBC = 1u << 17, + HOOK_PDO_ORACLE = 1u << 18, + HOOK_PDO_SQLITE = 1u << 19, + HOOK_PDO_FIREBIRD = 1u << 20, + HOOK_NET_FUNCTION = 1u << 21, + HOOK_MONGODB = 1u << 22, #ifdef SW_USE_CURL - HOOK_ALL = 0x7fffffff ^ HOOK_CURL, + HOOK_ALL = 0x7fffffff & ~HOOK_CURL & ~HOOK_MONGODB, #else - HOOK_ALL = 0x7fffffff ^ HOOK_NATIVE_CURL, + HOOK_ALL = 0x7fffffff & ~HOOK_NATIVE_CURL & ~HOOK_MONGODB, #endif }; static const uint8_t MAX_EXEC_MSEC = 10; - static void init(); static void shutdown(); - static long create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv); + static long create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *callable); + static PHPContext *create_context(const Args *args); static void defer(zend::Function *fci); static void deadlock_check(); static bool enable_hook(uint32_t flags); static bool disable_hook(); static void disable_unsafe_function(); static void enable_unsafe_function(); - static void error_cb(int type, error_filename_t *error_filename, const uint32_t error_lineno, ZEND_ERROR_CB_LAST_ARG_D); static void interrupt_thread_stop(); static inline long get_cid() { @@ -137,8 +164,8 @@ class PHPCoroutine { } static inline long get_pcid(long cid = 0) { - PHPContext *task = cid == 0 ? get_context() : get_context_by_cid(cid); - return sw_likely(task) ? task->pcid : 0; + PHPContext *ctx = cid == 0 ? get_context() : get_context_by_cid(cid); + return sw_likely(ctx) ? ctx->pcid : 0; } static inline long get_elapsed(long cid = 0) { @@ -146,17 +173,37 @@ class PHPCoroutine { } static inline PHPContext *get_context() { - PHPContext *task = (PHPContext *) Coroutine::get_current_task(); - return task ? task : &main_task; + PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); + return ctx ? ctx : &main_context; } - static inline PHPContext *get_origin_context(PHPContext *task) { - Coroutine *co = task->co->get_origin(); - return co ? (PHPContext *) co->get_task() : &main_task; + static inline PHPContext *get_origin_context(PHPContext *ctx) { + Coroutine *co = ctx->co->get_origin(); + return co ? (PHPContext *) co->get_task() : &main_context; } static inline PHPContext *get_context_by_cid(long cid) { - return cid == -1 ? &main_task : (PHPContext *) Coroutine::get_task_by_cid(cid); + return cid == -1 ? &main_context : (PHPContext *) Coroutine::get_task_by_cid(cid); + } + + static inline ssize_t get_stack_usage(long cid) { + zend_long current_cid = PHPCoroutine::get_cid(); + if (cid == 0) { + cid = current_cid; + } + PHPContext *ctx = (PHPContext *) PHPCoroutine::get_context_by_cid(cid); + if (UNEXPECTED(!ctx)) { + swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); + return -1; + } + zend_vm_stack stack = cid == current_cid ? EG(vm_stack) : ctx->vm_stack; + size_t usage = 0; + + while (stack) { + usage += (stack->end - stack->top) * sizeof(zval); + stack = stack->prev; + } + return usage; } static inline uint64_t get_max_num() { @@ -171,23 +218,23 @@ class PHPCoroutine { config.enable_deadlock_check = value; } - static inline bool is_schedulable(PHPContext *task) { - return task->enable_scheduler && (Timer::get_absolute_msec() - task->last_msec > MAX_EXEC_MSEC); + static inline bool is_schedulable(PHPContext *ctx) { + return ctx->enable_scheduler && (Timer::get_absolute_msec() - ctx->last_msec > MAX_EXEC_MSEC); } static inline bool enable_scheduler() { - PHPContext *task = (PHPContext *) Coroutine::get_current_task(); - if (task && !task->enable_scheduler) { - task->enable_scheduler = true; + PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); + if (ctx && !ctx->enable_scheduler) { + ctx->enable_scheduler = true; return true; } return false; } static inline bool disable_scheduler() { - PHPContext *task = (PHPContext *) Coroutine::get_current_task(); - if (task && task->enable_scheduler) { - task->enable_scheduler = false; + PHPContext *ctx = (PHPContext *) Coroutine::get_current_task(); + if (ctx && ctx->enable_scheduler) { + ctx->enable_scheduler = false; return true; } return false; @@ -203,10 +250,6 @@ class PHPCoroutine { config.enable_preemptive_scheduler = value; } - static inline void set_max_concurrency(uint32_t value) { - config.max_concurrency = value; - } - static inline bool is_activated() { return activated; } @@ -215,36 +258,56 @@ class PHPCoroutine { return sw_likely(activated) ? Coroutine::get_execute_time(cid) : -1; } + static inline void init_main_context() { + main_context.co = nullptr; + main_context.fiber_context = EG(main_fiber_context); + main_context.fiber_init_notified = true; + save_context(&main_context); + } + + static inline void free_main_context() { + main_context = {}; + } + protected: - static bool activated; - static PHPContext main_task; - static Config config; - static uint32_t concurrency; + static SW_THREAD_LOCAL bool activated; + static SW_THREAD_LOCAL PHPContext main_context; + static SW_THREAD_LOCAL Config config; - static bool interrupt_thread_running; - static std::thread interrupt_thread; + static SW_THREAD_LOCAL bool interrupt_thread_running; + static SW_THREAD_LOCAL std::thread interrupt_thread; static void activate(); static void deactivate(void *ptr); - static void vm_stack_init(void); - static void vm_stack_destroy(void); - static void save_vm_stack(PHPContext *task); - static void restore_vm_stack(PHPContext *task); - static void save_og(PHPContext *task); - static void restore_og(PHPContext *task); - static void save_task(PHPContext *task); - static void restore_task(PHPContext *task); - static void catch_exception(); + static void save_vm_stack(PHPContext *ctx); + static void restore_vm_stack(PHPContext *ctx); + static void save_og(PHPContext *ctx); + static void restore_og(PHPContext *ctx); + static void save_bg(PHPContext *ctx); + static void restore_bg(PHPContext *ctx); + static void save_context(PHPContext *ctx); + static void restore_context(PHPContext *ctx); + static void destroy_context(PHPContext *ctx); + static bool catch_exception(); + static void bailout(); static void on_yield(void *arg); static void on_resume(void *arg); static void on_close(void *arg); static void main_func(void *arg); - + static zend_fiber_status fiber_get_status(const PHPContext *ctx); + static void fiber_context_init(PHPContext *ctx); + static void fiber_context_try_destroy(const PHPContext *ctx, PHPContext *origin_ctx); + static void fiber_context_switch_notify(const PHPContext *from, PHPContext *to); + static void fiber_context_switch_try_notify(const PHPContext *from, PHPContext *to); +#ifdef ZEND_CHECK_STACK_LIMIT + static void *stack_limit(PHPContext *ctx); + static void *stack_base(PHPContext *ctx); +#endif static void interrupt_thread_start(); - static void record_last_msec(PHPContext *task) { + static void record_last_msec(PHPContext *ctx) { if (interrupt_thread_running) { - task->last_msec = Timer::get_absolute_msec(); + ctx->last_msec = Timer::get_absolute_msec(); } } }; diff --git a/ext-src/php_swoole_coroutine_system.h b/ext-src/php_swoole_coroutine_system.h index f4a75506e8..5c62e40175 100644 --- a/ext-src/php_swoole_coroutine_system.h +++ b/ext-src/php_swoole_coroutine_system.h @@ -16,6 +16,8 @@ +----------------------------------------------------------------------+ */ +#pragma once + #include "php_swoole_cxx.h" // clang-format off diff --git a/ext-src/php_swoole_curl.h b/ext-src/php_swoole_curl.h index 8134635fe9..d53c3128a2 100644 --- a/ext-src/php_swoole_curl.h +++ b/ext-src/php_swoole_curl.h @@ -24,33 +24,63 @@ SW_EXTERN_C_BEGIN #include #include +#if PHP_VERSION_ID >= 80400 +#include "thirdparty/php84/curl/curl_private.h" +#else #include "thirdparty/php/curl/curl_private.h" +#endif SW_EXTERN_C_END #if LIBCURL_VERSION_NUM < 0x073800 #error "require cURL version 7.56.0 or later" #endif -namespace swoole { +#include -using network::Socket; +CURLcode swoole_curl_easy_perform(CURL *cp); +php_curl *swoole_curl_get_handle(zval *zid, bool exclusive = true, bool required = true); +void swoole_curl_easy_reset(CURL *curl); +namespace swoole { namespace curl { class Multi; +struct Socket { + Multi *multi; + network::Socket *socket; + int bitmask; + int sockfd; + int action; + bool deleted; +}; + struct Handle { CURL *cp; - Socket *socket; Multi *multi; - int event_bitmask; - int event_fd; - int action; + /** + * This is only for the swoole_curl_easy_perform function, + * and it has a one-to-one relationship with the curl handle. + * It must be destroyed when the curl handle is released. + */ + Multi *easy_multi; + + explicit Handle(CURL *_cp) { + cp = _cp; + multi = nullptr; + easy_multi = nullptr; + } }; +Handle *get_handle(CURL *cp); +Handle *create_handle(CURL *ch); +void destroy_handle(CURL *ch); + struct Selector { bool timer_callback = false; - std::set active_handles; + bool executing = false; + std::unordered_set active_sockets; + std::unordered_set release_sockets; }; class Multi { @@ -59,31 +89,28 @@ class Multi { long timeout_ms_ = 0; Coroutine *co = nullptr; int running_handles_ = 0; - int last_sockfd; - int event_count_ = 0; bool defer_callback = false; - std::unique_ptr selector; + Selector selector; + std::unordered_map sockets; - CURLcode read_info(); + CURLcode read_info() const; - Socket *create_socket(CURL *cp, curl_socket_t sockfd); + int set_event(void *socket_ptr, curl_socket_t sockfd, int action); + int del_event(void *socket_ptr, curl_socket_t sockfd); + void selector_finish(); + void selector_prepare(); - Handle *get_handle(CURL *cp) { - Handle *handle = nullptr; - curl_easy_getinfo(cp, CURLINFO_PRIVATE, &handle); - return handle; + bool wait_event() const { + return timer || !sockets.empty(); } - void set_event(CURL *easy, void *socket_ptr, curl_socket_t sockfd, int action); - void del_event(CURL *easy, void *socket_ptr, curl_socket_t sockfd); - void add_timer(long timeout_ms) { if (timer && swoole_timer_is_available()) { swoole_timer_del(timer); } timeout_ms_ = timeout_ms; - timer = swoole_timer_add(timeout_ms, false, [this](Timer *timer, TimerNode *tnode) { - this->timer = nullptr; + timer = swoole_timer_add(timeout_ms, false, [this](Timer *_timer, TimerNode *tnode) { + timer = nullptr; callback(nullptr, 0); }); } @@ -103,33 +130,19 @@ class Multi { } public: - Multi() { - multi_handle_ = curl_multi_init(); - co = nullptr; - curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETFUNCTION, handle_socket); - curl_multi_setopt(multi_handle_, CURLMOPT_TIMERFUNCTION, handle_timeout); - curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETDATA, this); - curl_multi_setopt(multi_handle_, CURLMOPT_TIMERDATA, this); - } + Multi(); + ~Multi(); - ~Multi() { - curl_multi_cleanup(multi_handle_); - } - - CURLM *get_multi_handle() { + CURLM *get_multi_handle() const { return multi_handle_; } - int get_running_handles() { + int get_running_handles() const { return running_handles_; } - void set_selector(Selector *_selector) { - selector.reset(_selector); - } - - CURLMcode add_handle(CURL *cp); - CURLMcode remove_handle(CURL *cp); + CURLMcode add_handle(Handle *handle); + CURLMcode remove_handle(Handle *handle) const; CURLMcode perform() { return curl_multi_perform(multi_handle_, &running_handles_); @@ -141,15 +154,18 @@ class Multi { Coroutine *check_bound_co() { if (co) { - swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, "cURL is executing, cannot be operated"); + Coroutine::set_socket_bound_cid(co->get_cid()); + swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, + "This cURL handle is currently executing in coroutine#%ld, cannot be operated", + co->get_cid()); return nullptr; } return Coroutine::get_current_safe(); } - CURLcode exec(php_curl *ch); + CURLcode exec(Handle *handle); long select(php_curlm *mh, double timeout = -1); - void callback(Handle *handle, int event_bitmask); + void callback(Socket *curl_socket, int bitmask, int sockfd = -1); static int cb_readable(Reactor *reactor, Event *event); static int cb_writable(Reactor *reactor, Event *event); diff --git a/ext-src/php_swoole_cxx.cc b/ext-src/php_swoole_cxx.cc index 4699087e7a..cbb5970046 100644 --- a/ext-src/php_swoole_cxx.cc +++ b/ext-src/php_swoole_cxx.cc @@ -10,58 +10,91 @@ static const char *sw_known_strings[] = { SW_API zend_string **sw_zend_known_strings = nullptr; +SW_API zend_refcounted *sw_refcount_ptr; + +zend_refcounted *sw_get_refcount_ptr(zval *value) { + return (sw_refcount_ptr = value->value.counted); +} + //----------------------------------known string------------------------------------ +namespace zend { +void known_strings_init() { + sw_zend_known_strings = static_cast( + pemalloc(sizeof(zend_string *) * ((sizeof(sw_known_strings) / sizeof(sw_known_strings[0]) - 1)), 1)); + for (unsigned int i = 0; i < (sizeof(sw_known_strings) / sizeof(sw_known_strings[0])) - 1; i++) { + zend_string *str = zend_string_init(sw_known_strings[i], strlen(sw_known_strings[i]), true); + sw_zend_known_strings[i] = zend_new_interned_string(str); + } +} + +void known_strings_dtor() { + pefree(sw_zend_known_strings, 1); + sw_zend_known_strings = nullptr; +} -#if PHP_VERSION_ID < 80200 -#define ZEND_COMPILE_POSITION_DC -#define ZEND_COMPILE_POSITION_RELAY_C -#else -#define ZEND_COMPILE_POSITION_DC , zend_compile_position position -#define ZEND_COMPILE_POSITION_RELAY_C , position -#endif +zend_function *get_function(const zend_array *function_table, const char *name, size_t name_len) { + return static_cast(zend_hash_str_find_ptr(function_table, name, name_len)); +} -// for compatibly with dis_eval -static zend_op_array *(*old_compile_string)(zend_string *source_string, const char *filename ZEND_COMPILE_POSITION_DC); +zend_function *get_function(const char *fname, size_t fname_len) { + return get_function(EG(function_table), fname, fname_len); +} -static zend_op_array *swoole_compile_string(zend_string *source_string, const char *filename ZEND_COMPILE_POSITION_DC) { - if (UNEXPECTED(EG(exception))) { - zend_exception_error(EG(exception), E_ERROR); - return nullptr; - } - zend_op_array *opa = old_compile_string(source_string, filename ZEND_COMPILE_POSITION_RELAY_C); - opa->type = ZEND_USER_FUNCTION; - return opa; +zend_function *get_function(const std::string &fname) { + return get_function(fname.c_str(), fname.length()); } -namespace zend { -bool eval(const std::string &code, std::string const &filename) { - if (!old_compile_string) { - old_compile_string = zend_compile_string; +zend_function *get_function(const zend_string *fname) { + return get_function(ZSTR_VAL(fname), ZSTR_LEN(fname)); +} + +static zend_always_inline zval *sw_zend_symtable_str_add( + HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key, zval *pData) { + if (numeric_key) { + return zend_hash_index_add(ht, idx, pData); + } else { + return zend_hash_str_add(ht, str, len, pData); } - // overwrite - zend_compile_string = swoole_compile_string; - int ret = (zend_eval_stringl((char *) code.c_str(), code.length(), nullptr, (char *) filename.c_str()) == SUCCESS); - // recover - zend_compile_string = old_compile_string; - return ret; } -void known_strings_init(void) { - zend_string *str; - sw_zend_known_strings = nullptr; +static zend_always_inline zval *sw_zend_symtable_str_find( + HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key) { + if (numeric_key) { + return zend_hash_index_find(ht, idx); + } else { + return zend_hash_str_find(ht, str, len); + } +} - /* known strings */ - sw_zend_known_strings = (zend_string **) pemalloc( - sizeof(zend_string *) * ((sizeof(sw_known_strings) / sizeof(sw_known_strings[0]) - 1)), 1); - for (unsigned int i = 0; i < (sizeof(sw_known_strings) / sizeof(sw_known_strings[0])) - 1; i++) { - str = zend_string_init(sw_known_strings[i], strlen(sw_known_strings[i]), 1); - sw_zend_known_strings[i] = zend_new_interned_string(str); +static zend_always_inline zval *sw_zend_symtable_str_update( + HashTable *ht, const char *str, size_t len, zend_ulong idx, bool numeric_key, zval *pData) { + if (numeric_key) { + return zend_hash_index_update(ht, idx, pData); + } else { + return zend_hash_str_update(ht, str, len, pData); } } -void known_strings_dtor(void) { - pefree(sw_zend_known_strings, 1); - sw_zend_known_strings = nullptr; +void array_add_or_merge(zval *zarray, const char *key, size_t key_len, zval *new_element) { + zend_ulong idx; + bool numeric_key = ZEND_HANDLE_NUMERIC_STR(key, key_len, idx); + + zend_array *array = Z_ARRVAL_P(zarray); + zval *zresult = sw_zend_symtable_str_add(array, key, key_len, idx, numeric_key, new_element); + // Adding element failed, indicating that this key already exists and must be converted to an array + if (!zresult) { + zval *current_elements = sw_zend_symtable_str_find(array, key, key_len, idx, numeric_key); + if (ZVAL_IS_ARRAY(current_elements)) { + add_next_index_zval(current_elements, new_element); + } else { + zval zvalue_array; + array_init_size(&zvalue_array, 2); + Z_ADDREF_P(current_elements); + add_next_index_zval(&zvalue_array, current_elements); + add_next_index_zval(&zvalue_array, new_element); + sw_zend_symtable_str_update(array, key, key_len, idx, numeric_key, &zvalue_array); + } + } } namespace function { @@ -73,7 +106,7 @@ bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *ret /* the coroutine has no return value */ ZVAL_NULL(retval); } - success = swoole::PHPCoroutine::create(fci_cache, argc, argv) >= 0; + success = swoole::PHPCoroutine::create(fci_cache, argc, argv, nullptr) >= 0; } else { success = sw_zend_call_function_ex(nullptr, fci_cache, argc, argv, retval) == SUCCESS; } @@ -84,11 +117,11 @@ bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *ret return success; } -ReturnValue call(const std::string &func_name, int argc, zval *argv) { +Variable call(const std::string &func_name, int argc, zval *argv) { zval function_name; ZVAL_STRINGL(&function_name, func_name.c_str(), func_name.length()); - ReturnValue retval; - if (call_user_function(EG(function_table), NULL, &function_name, &retval.value, argc, argv) != SUCCESS) { + Variable retval; + if (call_user_function(EG(function_table), nullptr, &function_name, &retval.value, argc, argv) != SUCCESS) { ZVAL_NULL(&retval.value); } zval_dtor(&function_name); @@ -100,4 +133,31 @@ ReturnValue call(const std::string &func_name, int argc, zval *argv) { } } // namespace function + +Callable::Callable(zval *_zfn) { + ZVAL_UNDEF(&zfn); + if (!zval_is_true(_zfn)) { + php_swoole_fatal_error(E_WARNING, "illegal callback function"); + return; + } + if (!sw_zend_is_callable_ex(_zfn, nullptr, 0, &fn_name, nullptr, &fcc, nullptr)) { + php_swoole_fatal_error(E_WARNING, "function '%s' is not callable", fn_name); + return; + } + zfn = *_zfn; + zval_add_ref(&zfn); +} + +Callable::~Callable() { + if (!ZVAL_IS_UNDEF(&zfn)) { + zval_ptr_dtor(&zfn); + } + if (fn_name) { + efree(fn_name); + } +} + +uint32_t Callable::refcount() const { + return zval_refcount_p(&zfn); +} } // namespace zend diff --git a/ext-src/php_swoole_cxx.h b/ext-src/php_swoole_cxx.h index dabd0c7787..32b8a0fcea 100644 --- a/ext-src/php_swoole_cxx.h +++ b/ext-src/php_swoole_cxx.h @@ -18,6 +18,9 @@ #include "php_swoole_private.h" #include "php_swoole_coroutine.h" + +#include "swoole_socket_impl.h" +#include "swoole_protocol.h" #include "swoole_util.h" #include @@ -28,6 +31,13 @@ #define SW_ZEND_KNOWN_STRINGS(_) \ _(SW_ZEND_STR_TYPE, "type") \ _(SW_ZEND_STR_HOST, "host") \ + _(SW_ZEND_STR_USER_AGENT, "user-agent") \ + _(SW_ZEND_STR_ACCEPT, "accept") \ + _(SW_ZEND_STR_CONTENT_TYPE, "content-type") \ + _(SW_ZEND_STR_CONTENT_LENGTH, "content-length") \ + _(SW_ZEND_STR_AUTHORIZATION, "authorization") \ + _(SW_ZEND_STR_CONNECTION, "connection") \ + _(SW_ZEND_STR_ACCEPT_ENCODING, "accept-encoding") \ _(SW_ZEND_STR_PORT, "port") \ _(SW_ZEND_STR_SETTING, "setting") \ _(SW_ZEND_STR_ID, "id") \ @@ -35,7 +45,6 @@ _(SW_ZEND_STR_SOCK, "sock") \ _(SW_ZEND_STR_PIPE, "pipe") \ _(SW_ZEND_STR_HEADERS, "headers") \ - _(SW_ZEND_STR_SET_COOKIE_HEADERS, "set_cookie_headers") \ _(SW_ZEND_STR_REQUEST_METHOD, "requestMethod") \ _(SW_ZEND_STR_REQUEST_HEADERS, "requestHeaders") \ _(SW_ZEND_STR_REQUEST_BODY, "requestBody") \ @@ -43,8 +52,12 @@ _(SW_ZEND_STR_COOKIES, "cookies") \ _(SW_ZEND_STR_DOWNLOAD_FILE, "downloadFile") \ _(SW_ZEND_STR_DOWNLOAD_OFFSET, "downloadOffset") \ - _(SW_ZEND_STR_TMPFILES, "tmpfiles") \ + _(SW_ZEND_STR_SERVER, "server") \ _(SW_ZEND_STR_HEADER, "header") \ + _(SW_ZEND_STR_GET, "get") \ + _(SW_ZEND_STR_POST, "post") \ + _(SW_ZEND_STR_FILES, "files") \ + _(SW_ZEND_STR_TMPFILES, "tmpfiles") \ _(SW_ZEND_STR_COOKIE, "cookie") \ _(SW_ZEND_STR_METHOD, "method") \ _(SW_ZEND_STR_PATH, "path") \ @@ -54,8 +67,6 @@ _(SW_ZEND_STR_TRAILER, "trailer") \ _(SW_ZEND_STR_MASTER_PID, "master_pid") \ _(SW_ZEND_STR_CALLBACK, "callback") \ - _(SW_ZEND_STR_VALUE, "value") \ - _(SW_ZEND_STR_KEY, "key") \ _(SW_ZEND_STR_OPCODE, "opcode") \ _(SW_ZEND_STR_CODE, "code") \ _(SW_ZEND_STR_REASON, "reason") \ @@ -65,6 +76,23 @@ _(SW_ZEND_STR_PRIVATE_DATA, "private_data") \ _(SW_ZEND_STR_CLASS_NAME_RESOLVER, "Swoole\\NameResolver") \ _(SW_ZEND_STR_SOCKET, "socket") \ + _(SW_ZEND_STR_ADDR_LOOPBACK_V4, "127.0.0.1") \ + _(SW_ZEND_STR_ADDR_LOOPBACK_V6, "::1") \ + _(SW_ZEND_STR_REQUEST_METHOD2, "request_method") \ + _(SW_ZEND_STR_REQUEST_URI, "request_uri") \ + _(SW_ZEND_STR_PATH_INFO, "path_info") \ + _(SW_ZEND_STR_REQUEST_TIME, "request_time") \ + _(SW_ZEND_STR_REQUEST_TIME_FLOAT, "request_time_float") \ + _(SW_ZEND_STR_SERVER_PROTOCOL, "server_protocol") \ + _(SW_ZEND_STR_SERVER_PORT, "server_port") \ + _(SW_ZEND_STR_SERVER_ADDR, "server_addr") \ + _(SW_ZEND_STR_REMOTE_PORT, "remote_port") \ + _(SW_ZEND_STR_REMOTE_ADDR, "remote_addr") \ + _(SW_ZEND_STR_MASTER_TIME, "master_time") \ + _(SW_ZEND_STR_QUERY_STRING, "query_string") \ + _(SW_ZEND_STR_HTTP10, "HTTP/1.0") \ + _(SW_ZEND_STR_HTTP11, "HTTP/1.1") \ + _(SW_ZEND_STR_HTTP2, "HTTP/2") \ typedef enum sw_zend_known_string_id { #define _SW_ZEND_STR_ID(id, str) id, @@ -83,66 +111,61 @@ extern zend_string **sw_zend_known_strings; #define SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(module) \ module##_ce->create_object = [](zend_class_entry *ce) { return sw_zend_create_object(ce, &module##_handlers); } +/** + * It is safe across coroutines, + * add reference count, prevent the socket pointer being released + */ +#define SW_CLIENT_GET_SOCKET_SAFE(__sock, __zsocket) \ + SocketImpl *__sock = nullptr; \ + zend::Variable tmp_socket; \ + if (ZVAL_IS_OBJECT(__zsocket)) { \ + __sock = php_swoole_get_socket(__zsocket); \ + tmp_socket.assign(__zsocket); \ + } + +#define SW_CLIENT_PRESERVE_SOCKET(__zsocket) \ + zend::Variable tmp_socket; \ + if (ZVAL_IS_OBJECT(__zsocket)) { \ + tmp_socket.assign(__zsocket); \ + } + SW_API bool php_swoole_is_enable_coroutine(); +SW_API zend_object *php_swoole_create_socket(enum swSocketType type); SW_API zend_object *php_swoole_create_socket_from_fd(int fd, enum swSocketType type); -SW_API bool php_swoole_export_socket(zval *zobject, swoole::coroutine::Socket *_socket); +SW_API zend_object *php_swoole_create_socket_from_fd(int fd, int _domain, int _type, int _protocol); +SW_API bool php_swoole_export_socket(zval *zobject, SocketImpl *_socket); SW_API zend_object *php_swoole_dup_socket(int fd, enum swSocketType type); -SW_API void php_swoole_init_socket_object(zval *zobject, swoole::coroutine::Socket *socket); -SW_API swoole::coroutine::Socket *php_swoole_get_socket(zval *zobject); -SW_API bool php_swoole_socket_is_closed(zval *zobject); -#ifdef SW_USE_OPENSSL -SW_API bool php_swoole_socket_set_ssl(swoole::coroutine::Socket *sock, zval *zset); -#endif -SW_API bool php_swoole_socket_set_protocol(swoole::coroutine::Socket *sock, zval *zset); -SW_API bool php_swoole_client_set(swoole::coroutine::Socket *cli, zval *zset); +SW_API void php_swoole_init_socket_object(zval *zobject, SocketImpl *socket); +SW_API SocketImpl *php_swoole_get_socket(const zval *zobject); +SW_API bool php_swoole_socket_is_closed(const zval *zobject); +SW_API bool php_swoole_socket_set_ssl(SocketImpl *sock, const zval *zset); +SW_API bool php_swoole_socket_set_protocol(SocketImpl *sock, const zval *zset); +SW_API bool php_swoole_socket_set(SocketImpl *cli, const zval *zset); +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code); +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code, const char *msg); +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, const SocketImpl *socket); +#define php_swoole_client_set php_swoole_socket_set SW_API php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, int type, int protocol STREAMS_DC); +SW_API php_stream *php_swoole_create_stream_from_pipe(int fd, const char *mode, const char *persistent_id STREAMS_DC); SW_API php_stream_ops *php_swoole_get_ori_php_stream_stdio_ops(); -SW_API void php_swoole_register_rshutdown_callback(swoole::Callback cb, void *private_data); +SW_API zif_handler php_swoole_get_original_handler(const char *name, size_t len); +SW_API bool php_swoole_call_original_handler(const char *name, size_t len, INTERNAL_FUNCTION_PARAMETERS); // timer SW_API bool php_swoole_timer_clear(swoole::TimerNode *tnode); SW_API bool php_swoole_timer_clear_all(); static inline bool php_swoole_is_fatal_error() { - if (PG(last_error_message)) { - switch (PG(last_error_type)) { - case E_ERROR: - case E_CORE_ERROR: - case E_USER_ERROR: - case E_COMPILE_ERROR: - return true; - default: - break; - } - } - return false; + return PG(last_error_message) && (PG(last_error_type) & E_FATAL_ERRORS); } -ssize_t php_swoole_length_func(const swoole::Protocol *, swoole::network::Socket *, swoole::PacketLength *); - -#ifdef SW_HAVE_ZLIB -#define php_swoole_websocket_frame_pack php_swoole_websocket_frame_pack_ex -#define php_swoole_websocket_frame_object_pack php_swoole_websocket_frame_object_pack_ex -#else -#define php_swoole_websocket_frame_pack(buffer, zdata, opcode, flags, mask, allow_compress) \ - php_swoole_websocket_frame_pack_ex(buffer, zdata, opcode, flags, mask, 0) -#define php_swoole_websocket_frame_object_pack(buffer, zdata, mask, allow_compress) \ - php_swoole_websocket_frame_object_pack_ex(buffer, zdata, mask, 0) -#endif -int php_swoole_websocket_frame_pack_ex( - swoole::String *buffer, zval *zdata, zend_long opcode, uint8_t flags, zend_bool mask, zend_bool allow_compress); -int php_swoole_websocket_frame_object_pack_ex(swoole::String *buffer, - zval *zdata, - zend_bool mask, - zend_bool allow_compress); -void php_swoole_websocket_frame_unpack(swoole::String *data, zval *zframe); -void php_swoole_websocket_frame_unpack_ex(swoole::String *data, zval *zframe, uchar allow_uncompress); - -swoole::TaskId php_swoole_task_pack(swoole::EventData *task, zval *data); -zval *php_swoole_task_unpack(swoole::EventData *task_result); +ssize_t php_swoole_length_func(const swoole::Protocol *, NetSocket *, swoole::PacketLength *); +SW_API zend_long php_swoole_parse_to_size(zval *zv); +SW_API zend_string *php_swoole_serialize(zval *zdata); +SW_API bool php_swoole_unserialize(const zend_string *data, zval *zv); #ifdef SW_HAVE_ZLIB int php_swoole_zlib_decompress(z_stream *stream, swoole::String *buffer, char *body, int length); @@ -157,6 +180,28 @@ bool php_swoole_name_resolver_add(zval *zresolver); const swoole::Allocator *sw_php_allocator(); const swoole::Allocator *sw_zend_string_allocator(); +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +#define SOL_TCP IPPROTO_TCP +#endif + +#ifdef __APPLE__ +#define TCP_INFO TCP_CONNECTION_INFO +using tcp_info = tcp_connection_info; +#endif + +#ifdef TCP_INFO +std::unordered_map sw_socket_parse_tcp_info(tcp_info *info); +#endif + +static inline bool php_swoole_async(bool blocking, const std::function &fn) { + if (!blocking && swoole_coroutine_is_in()) { + return swoole::coroutine::async(fn); + } else { + fn(); + return true; + } +} + namespace zend { //-----------------------------------namespace begin-------------------------------------------- class String { @@ -166,11 +211,11 @@ class String { } String(const char *_str, size_t len) { - str = zend_string_init(_str, len, 0); + str = zend_string_init(_str, len, false); } String(const std::string &_str) { - str = zend_string_init(_str.c_str(), _str.length(), 0); + str = zend_string_init(_str.c_str(), _str.length(), false); } String(zval *v) { @@ -189,58 +234,62 @@ class String { str = zend_string_copy(o.str); } - String(String &&o) { + String(String &&o) noexcept { str = o.str; o.str = nullptr; } - void operator=(zval *v) { - if (str) { - zend_string_release(str); - } + String &operator=(zval *v) { + release(); str = zval_get_string(v); + return *this; } - String &operator=(String &&o) { + String &operator=(String &&o) noexcept { + release(); str = o.str; o.str = nullptr; return *this; } String &operator=(const String &o) { + if (&o == this) { + return *this; + } + release(); str = zend_string_copy(o.str); return *this; } - inline char *val() { + char *val() const { return ZSTR_VAL(str); } - inline size_t len() { + size_t len() const { return ZSTR_LEN(str); } - inline zend_string *get() { + zend_string *get() const { return str; } - void rtrim() { + void rtrim() const { ZSTR_LEN(str) = swoole::rtrim(val(), len()); } - inline const std::string to_std_string() { - return std::string(val(), len()); + std::string to_std_string() const { + return {val(), len()}; } - inline char *dup() { + char *dup() const { return sw_likely(len() > 0) ? sw_strndup(val(), len()) : nullptr; } - inline char *edup() { + char *edup() const { return sw_likely(len() > 0) ? estrndup(val(), len()) : nullptr; } - inline void release() { + void release() { if (str) { zend_string_release(str); str = nullptr; @@ -269,7 +318,7 @@ class KeyValue { Z_TRY_ADDREF(zvalue); } - inline void add_to(zval *zarray) { + void add_to(zval *zarray) { HashTable *ht = Z_ARRVAL_P(zarray); zval *dest_elem = !key ? zend_hash_index_update(ht, index, &zvalue) : zend_hash_update(ht, key, &zvalue); Z_TRY_ADDREF_P(dest_elem); @@ -285,13 +334,14 @@ class KeyValue { class ArrayIterator { public: - ArrayIterator(Bucket *p) { + explicit ArrayIterator(Bucket *p) { _ptr = p; _key = _ptr->key; _val = &_ptr->val; _index = _ptr->h; pe = p; } + ArrayIterator(Bucket *p, Bucket *_pe) { _ptr = p; _key = _ptr->key; @@ -300,23 +350,29 @@ class ArrayIterator { pe = _pe; skipUndefBucket(); } + void operator++(int i) { ++_ptr; skipUndefBucket(); } - bool operator!=(ArrayIterator b) { + + bool operator!=(ArrayIterator b) const { return b.ptr() != _ptr; } - std::string key() { - return std::string(_key->val, _key->len); + + std::string key() const { + return {_key->val, _key->len}; } - zend_ulong index() { + + zend_ulong index() const { return _index; } - zval *value() { + + zval *value() const { return _val; } - Bucket *ptr() { + + Bucket *ptr() const { return _ptr; } @@ -358,87 +414,282 @@ class Array { arr = _arr; } - inline size_t count() { + size_t count() const { return zend_hash_num_elements(Z_ARRVAL_P(arr)); } - inline bool set(zend_ulong index, zval *value) { + bool set(zend_ulong index, zval *value) const { return add_index_zval(arr, index, value) == SUCCESS; } - inline bool append(zval *value) { + bool append(zval *value) const { return add_next_index_zval(arr, value) == SUCCESS; } - inline bool set(zend_ulong index, zend_resource *res) { + bool set(zend_ulong index, zend_resource *res) const { zval tmp; ZVAL_RES(&tmp, res); return set(index, &tmp); } - ArrayIterator begin() { - return ArrayIterator(Z_ARRVAL_P(arr)->arData, Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed); + ArrayIterator begin() const { + return {Z_ARRVAL_P(arr)->arData, Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed}; } - ArrayIterator end() { + ArrayIterator end() const { return ArrayIterator(Z_ARRVAL_P(arr)->arData + Z_ARRVAL_P(arr)->nNumUsed); } }; -enum PipeType { - PIPE_TYPE_NONE = 0, - PIPE_TYPE_STREAM = 1, - PIPE_TYPE_DGRAM = 2, +class Variable { + public: + zval value; + + Variable() { + value = {}; + } + + Variable(zval *zvalue) { + assign(zvalue); + } + + Variable(const char *str, size_t l_str) { + ZVAL_STRINGL(&value, str, l_str); + } + + Variable(const char *str) { + ZVAL_STRING(&value, str); + } + + Variable(const std::string &str) { + ZVAL_STRINGL(&value, str.c_str(), str.length()); + } + + Variable(const Variable &&src) noexcept { + value = src.value; + add_ref(); + } + + Variable(Variable &&src) noexcept { + value = src.value; + src.reset(); + } + + Variable &operator=(zval *zvalue) { + assign(zvalue); + return *this; + } + + Variable &operator=(const Variable &src) { + value = src.value; + add_ref(); + return *this; + } + + void assign(zval *zvalue) { + value = *zvalue; + add_ref(); + } + + zval *ptr() { + return &value; + } + + void reset() { + ZVAL_UNDEF(&value); + } + + void add_ref() { + Z_TRY_ADDREF_P(&value); + } + + void del_ref() { + Z_TRY_DELREF_P(&value); + } + + ~Variable() { + zval_ptr_dtor(&value); + } }; -class Process { +class CharPtr { + private: + char *str_; + public: - zend_object *zsocket = nullptr; - enum PipeType pipe_type; - bool enable_coroutine; + CharPtr() { + str_ = nullptr; + } + + CharPtr(const char *str) { + str_ = estrndup(str, strlen(str)); + } + + CharPtr(const char *str, size_t len) { + str_ = estrndup(str, len); + } - Process(enum PipeType pipe_type, bool enable_coroutine) - : pipe_type(pipe_type), enable_coroutine(enable_coroutine) {} + CharPtr &operator=(const char *str) { + assign(str, strlen(str)); + return *this; + } - ~Process() { - if (zsocket) { - OBJ_RELEASE(zsocket); + void release() { + if (str_) { + efree(str_); + str_ = nullptr; } } + + void assign(const char *str, size_t len) { + release(); + str_ = estrndup(str, len); + } + + void assign_tolower(const char *str, size_t len) { + release(); + str_ = zend_str_tolower_dup(str, len); + } + + ~CharPtr() { + release(); + } + + char *get() const { + return str_; + } }; -namespace function { -/* must use this API to call event callbacks to ensure that exceptions are handled correctly */ -bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine); +class Callable { + private: + zval zfn; + zend_fcall_info_cache fcc; + char *fn_name = nullptr; + + Callable() {} -class ReturnValue { public: - zval value; - ReturnValue() { - value = {}; + Callable(zval *_zfn); + ~Callable(); + uint32_t refcount() const; + + zend_refcounted *refcount_ptr() { + return sw_get_refcount_ptr(&zfn); + } + + zend_fcall_info_cache *ptr() { + return &fcc; + } + + bool ready() const { + return !ZVAL_IS_UNDEF(&zfn); + } + + Callable *dup() const { + auto copy = new Callable(); + copy->fcc = fcc; + copy->zfn = zfn; + zval_add_ref(©->zfn); + if (fn_name) { + copy->fn_name = estrdup(fn_name); + } + return copy; } - ~ReturnValue() { - zval_dtor(&value); + + bool call(uint32_t argc, zval *argv, zval *retval) { + return sw_zend_call_function_ex(&zfn, &fcc, argc, argv, retval) == SUCCESS; } }; -ReturnValue call(const std::string &func_name, int argc, zval *argv); +#define SW_CONCURRENCY_HASHMAP_LOCK(code) \ + if (locked_) { \ + code; \ + } else { \ + lock_.lock(); \ + code; \ + lock_.unlock(); \ + } + +template +class ConcurrencyHashMap { + std::unordered_map map_; + std::mutex lock_; + bool locked_; + ValueT default_value_; + + public: + ConcurrencyHashMap(ValueT _default_value) : map_(), lock_() { + default_value_ = _default_value; + locked_ = false; + } + + void set(const KeyT &key, const ValueT &value) { + SW_CONCURRENCY_HASHMAP_LOCK(map_[key] = value); + } + + ValueT get(const KeyT &key) { + ValueT value; + auto fn = [&]() -> ValueT { + auto iter = map_.find(key); + if (iter == map_.end()) { + return default_value_; + } + return iter->second; + }; + SW_CONCURRENCY_HASHMAP_LOCK(value = fn()); + return value; + } + + bool exists(const KeyT &key) { + std::unique_lock _lock(lock_); + return map_.find(key) != map_.end(); + } + + void del(const KeyT &key) { + SW_CONCURRENCY_HASHMAP_LOCK(map_.erase(key)); + } + + void clear() { + SW_CONCURRENCY_HASHMAP_LOCK(map_.clear()); + } + + void each(const std::function &cb) { + std::unique_lock _lock(lock_); + locked_ = true; + for (auto &iter : map_) { + cb(iter.first, iter.second); + } + locked_ = false; + } +}; + +namespace function { +/* must use this API to call event callbacks to ensure that exceptions are handled correctly */ +bool call(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine); +Variable call(const std::string &func_name, int argc, zval *argv); + +static inline bool call(Callable *cb, uint32_t argc, zval *argv, zval *retval, const bool enable_coroutine) { + return call(cb->ptr(), argc, argv, retval, enable_coroutine); +} } // namespace function struct Function { zend_fcall_info fci; zend_fcall_info_cache fci_cache; - inline bool call(zval *retval, const bool enable_coroutine) { + bool call(zval *retval, const bool enable_coroutine) { return function::call(&fci_cache, fci.param_count, fci.params, retval, enable_coroutine); } }; -bool eval(const std::string &code, const std::string &filename = ""); -void known_strings_init(void); -void known_strings_dtor(void); +void known_strings_init(); +void known_strings_dtor(); void unserialize(zval *return_value, const char *buf, size_t buf_len, HashTable *options); void json_decode(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long zend_long); +zend_function *get_function(const char *fname, size_t fname_len); +zend_function *get_function(const std::string &fname); +zend_function *get_function(const zend_string *fname); +zend_function *get_function(const zend_array *function_table, const char *name, size_t name_len); static inline zend_string *fetch_zend_string_by_val(void *val) { return (zend_string *) ((char *) val - XtOffsetOf(zend_string, val)); @@ -451,5 +702,157 @@ static inline void assign_zend_string_by_val(zval *zdata, char *addr, size_t len ZVAL_STR(zdata, zstr); } +static inline void array_set(zval *arg, const char *key, size_t l_key, zval *zvalue) { + Z_TRY_ADDREF_P(zvalue); + add_assoc_zval_ex(arg, key, l_key, zvalue); +} + +static inline void array_set(zval *arg, const char *key, size_t l_key, const char *value, size_t l_value) { + zval ztmp; + ZVAL_STRINGL(&ztmp, value, l_value); + add_assoc_zval_ex(arg, key, l_key, &ztmp); +} + +static inline void array_set(zval *arg, const char *key, size_t l_key, bool value) { + add_assoc_bool_ex(arg, key, l_key, value); +} + +static inline void array_set(zval *arg, const char *key, size_t l_key, const char *value) { + add_assoc_string_ex(arg, key, l_key, value); +} + +static inline void array_set(zval *arg, zend_ulong index, zval *zvalue) { + Z_TRY_ADDREF_P(zvalue); + zend_hash_index_add(Z_ARRVAL_P(arg), index, zvalue); +} + +static inline void array_add(zval *arg, zval *zvalue) { + Z_TRY_ADDREF_P(zvalue); + add_next_index_zval(arg, zvalue); +} + +/** + * return reference + */ +static inline zval *array_get(zval *arg, const char *key, size_t l_key) { + return zend_hash_str_find(Z_ARRVAL_P(arg), key, l_key); +} + +static inline zval *array_get(zval *arg, zend_ulong index) { + return zend_hash_index_find(Z_ARRVAL_P(arg), index); +} + +static inline void array_unset(zval *arg, const char *key, size_t l_key) { + zend_hash_str_del(Z_ARRVAL_P(arg), key, l_key); +} + +/** + * Add new element to the associative array or merge with existing elements. + * If the key does not exist, add it to the array. + * If the key already exists, merge all into a two-dimensional array. + */ +void array_add_or_merge(zval *zarray, const char *key, size_t key_len, zval *new_element); + +static inline zend_long object_get_long(zval *obj, zend_string *key) { + static zval rv; + zval *property = zend_read_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), key, true, &rv); + return property ? zval_get_long(property) : 0; +} + +static inline zend_long object_get_long(zval *obj, const char *key, size_t l_key) { + static zval rv; + zval *property = zend_read_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), key, l_key, true, &rv); + return property ? zval_get_long(property) : 0; +} + +static inline zend_long object_get_long(zend_object *obj, const char *key, size_t l_key) { + static zval rv; + zval *property = zend_read_property(obj->ce, obj, key, l_key, true, &rv); + return property ? zval_get_long(property) : 0; +} + +static inline void object_set(zval *obj, const char *name, size_t l_name, zval *zvalue) { + zend_update_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, zvalue); +} + +static inline void object_set(zval *obj, const char *name, size_t l_name, const char *value) { + zend_update_property_string(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, value); +} + +static inline void object_set(zval *obj, const char *name, size_t l_name, zend_long value) { + zend_update_property_long(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, value); +} + +static inline void object_set(zend_object *obj, zend_string *name, zval *zvalue) { + zend_update_property_ex(obj->ce, obj, name, zvalue); +} + +static inline void object_set(zend_object *obj, zend_string *name, zend_string *value) { + zval tmp; + ZVAL_STR(&tmp, value); + zend_update_property_ex(obj->ce, obj, name, &tmp); +} + +static inline void object_set(zend_object *obj, zend_string *name, zend_long value) { + zval tmp; + ZVAL_LONG(&tmp, value); + zend_update_property_ex(obj->ce, obj, name, &tmp); +} + +static inline zval *object_get(zval *obj, const char *name, size_t l_name) { + static zval rv; + return zend_read_property(Z_OBJCE_P(obj), Z_OBJ_P(obj), name, l_name, true, &rv); +} + +/** + * print exception, The virtual machine will not be terminated. + */ +static inline void print_error(zend_object *exception, int severity) { + zend_exception_error(exception, severity); +} + +static inline void add_constant(const char *name, zend_long value) { + zend_register_long_constant(name, strlen(name), value, CONST_CS | CONST_PERSISTENT, sw_module_number()); +} + +static inline void add_constant(const char *name, const char *value) { + zend_register_string_constant(name, strlen(name), value, CONST_CS | CONST_PERSISTENT, sw_module_number()); +} + //-----------------------------------namespace end-------------------------------------------- } // namespace zend + +/* use void* to match some C callback function pointers */ +static inline void sw_callable_free(void *ptr) { + delete (zend::Callable *) ptr; +} + +static inline zend::Callable *sw_callable_create(zval *zfn) { + auto fn = new zend::Callable(zfn); + if (fn->ready()) { + return fn; + } else { + delete fn; + return nullptr; + } +} + +static inline zend::Callable *sw_callable_create_ex(zval *zfn, const char *fname, bool allow_null = true) { + if (zfn == nullptr || ZVAL_IS_NULL(zfn)) { + if (!allow_null) { + zend_throw_exception_ex( + swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "%s must be of type callable, null given", fname); + } + return nullptr; + } + auto cb = sw_callable_create(zfn); + if (!cb) { + zend_throw_exception_ex(swoole_exception_ce, + SW_ERROR_INVALID_PARAMS, + "%s must be of type callable, %s given", + fname, + zend_zval_type_name(zfn)); + return nullptr; + } + return cb; +} diff --git a/ext-src/php_swoole_firebird.h b/ext-src/php_swoole_firebird.h new file mode 100644 index 0000000000..9f24b87423 --- /dev/null +++ b/ext-src/php_swoole_firebird.h @@ -0,0 +1,90 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ +#ifndef SWOOLE_SRC_PHP_SWOOLE_FIREBIRD_H +#define SWOOLE_SRC_PHP_SWOOLE_FIREBIRD_H + +#include "php_swoole.h" + +#ifdef SW_USE_FIREBIRD + +BEGIN_EXTERN_C() + +#include "ext/pdo/php_pdo_driver.h" + +#if PHP_VERSION_ID < 80500 +#include "thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h" +#else +#include "thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h" +#endif + +extern const pdo_driver_t swoole_pdo_firebird_driver; +void swoole_firebird_set_blocking(bool blocking); + +ISC_STATUS swoole_isc_attach_database( + ISC_STATUS *, short, const ISC_SCHAR *, isc_db_handle *, short, const ISC_SCHAR *); +ISC_STATUS swoole_isc_detach_database(ISC_STATUS *, isc_db_handle *); +ISC_STATUS swoole_isc_dsql_execute(ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const XSQLDA *); +ISC_STATUS swoole_isc_dsql_execute2( + ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const XSQLDA *, const XSQLDA *); +ISC_STATUS swoole_isc_dsql_sql_info(ISC_STATUS *, isc_stmt_handle *, short, const ISC_SCHAR *, short, ISC_SCHAR *); +ISC_STATUS swoole_isc_dsql_free_statement(ISC_STATUS *, isc_stmt_handle *, unsigned short); +ISC_STATUS swoole_isc_start_transaction(ISC_STATUS *, isc_tr_handle *, short, isc_db_handle *, size_t, char *); +ISC_STATUS swoole_isc_commit_retaining(ISC_STATUS *, isc_tr_handle *); +ISC_STATUS swoole_isc_commit_transaction(ISC_STATUS *, isc_tr_handle *); +ISC_STATUS swoole_isc_rollback_transaction(ISC_STATUS *, isc_tr_handle *); +ISC_STATUS swoole_isc_dsql_allocate_statement(ISC_STATUS *, isc_db_handle *, isc_stmt_handle *); +ISC_STATUS swoole_isc_dsql_prepare( + ISC_STATUS *, isc_tr_handle *, isc_stmt_handle *, unsigned short, const ISC_SCHAR *, unsigned short, XSQLDA *); +ISC_STATUS swoole_isc_dsql_fetch(ISC_STATUS *, isc_stmt_handle *, unsigned short, const XSQLDA *); +ISC_STATUS swoole_isc_open_blob(ISC_STATUS *, isc_db_handle *, isc_tr_handle *, isc_blob_handle *, ISC_QUAD *); +ISC_STATUS swoole_isc_blob_info(ISC_STATUS *, isc_blob_handle *, short, const ISC_SCHAR *, short, ISC_SCHAR *); +ISC_STATUS swoole_isc_get_segment(ISC_STATUS *, isc_blob_handle *, unsigned short *, unsigned short, ISC_SCHAR *); +ISC_STATUS swoole_isc_put_segment(ISC_STATUS *, isc_blob_handle *, unsigned short, const ISC_SCHAR *); +ISC_STATUS swoole_isc_close_blob(ISC_STATUS *, isc_blob_handle *); +ISC_STATUS swoole_isc_create_blob(ISC_STATUS *, isc_db_handle *, isc_tr_handle *, isc_blob_handle *, ISC_QUAD *); +ISC_STATUS swoole_isc_dsql_set_cursor_name(ISC_STATUS *, isc_stmt_handle *, const ISC_SCHAR *, unsigned short); +ISC_STATUS swoole_fb_ping(ISC_STATUS *, isc_db_handle *); +int swoole_isc_version(isc_db_handle *, ISC_VERSION_CALLBACK, void *); + +#ifdef SW_USE_FIREBIRD_HOOK +#define isc_attach_database swoole_isc_attach_database +#define isc_detach_database swoole_isc_detach_database +#define isc_dsql_execute swoole_isc_dsql_execute +#define isc_dsql_execute2 swoole_isc_dsql_execute2 +#define isc_dsql_sql_info swoole_isc_dsql_sql_info +#define isc_dsql_free_statement swoole_isc_dsql_free_statement +#define isc_start_transaction swoole_isc_start_transaction +#define isc_commit_retaining swoole_isc_commit_retaining +#define isc_commit_transaction swoole_isc_commit_transaction +#define isc_rollback_transaction swoole_isc_rollback_transaction +#define isc_dsql_allocate_statement swoole_isc_dsql_allocate_statement +#define isc_dsql_prepare swoole_isc_dsql_prepare +#define isc_dsql_fetch swoole_isc_dsql_fetch +#define isc_open_blob swoole_isc_open_blob +#define isc_blob_info swoole_isc_blob_info +#define isc_get_segment swoole_isc_get_segment +#define isc_put_segment swoole_isc_put_segment +#define isc_create_blob swoole_isc_create_blob +#define isc_close_blob swoole_isc_close_blob +#define isc_dsql_set_cursor_name swoole_isc_dsql_set_cursor_name +#define fb_ping swoole_fb_ping +#define isc_version swoole_isc_version +#endif +END_EXTERN_C() +#endif +#endif diff --git a/ext-src/php_swoole_ftp_def.h b/ext-src/php_swoole_ftp_def.h new file mode 100644 index 0000000000..8b57668182 --- /dev/null +++ b/ext-src/php_swoole_ftp_def.h @@ -0,0 +1,53 @@ +#ifndef EXT_SRC_PHP_SWOOLE_FTP_DEF_H_ +#define EXT_SRC_PHP_SWOOLE_FTP_DEF_H_ + +#include "php.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +BEGIN_EXTERN_C() +ZEND_FUNCTION(ftp_connect); +#if defined(SW_HAVE_FTP_SSL) +ZEND_FUNCTION(ftp_ssl_connect); +#endif +ZEND_FUNCTION(ftp_login); +ZEND_FUNCTION(ftp_pwd); +ZEND_FUNCTION(ftp_cdup); +ZEND_FUNCTION(ftp_chdir); +ZEND_FUNCTION(ftp_exec); +ZEND_FUNCTION(ftp_raw); +ZEND_FUNCTION(ftp_mkdir); +ZEND_FUNCTION(ftp_rmdir); +ZEND_FUNCTION(ftp_chmod); +ZEND_FUNCTION(ftp_alloc); +ZEND_FUNCTION(ftp_nlist); +ZEND_FUNCTION(ftp_rawlist); +ZEND_FUNCTION(ftp_mlsd); +ZEND_FUNCTION(ftp_systype); +ZEND_FUNCTION(ftp_fget); +ZEND_FUNCTION(ftp_nb_fget); +ZEND_FUNCTION(ftp_pasv); +ZEND_FUNCTION(ftp_get); +ZEND_FUNCTION(ftp_nb_get); +ZEND_FUNCTION(ftp_nb_continue); +ZEND_FUNCTION(ftp_fput); +ZEND_FUNCTION(ftp_nb_fput); +ZEND_FUNCTION(ftp_put); +ZEND_FUNCTION(ftp_append); +ZEND_FUNCTION(ftp_nb_put); +ZEND_FUNCTION(ftp_size); +ZEND_FUNCTION(ftp_mdtm); +ZEND_FUNCTION(ftp_rename); +ZEND_FUNCTION(ftp_delete); +ZEND_FUNCTION(ftp_site); +ZEND_FUNCTION(ftp_close); +ZEND_FUNCTION(ftp_set_option); +ZEND_FUNCTION(ftp_get_option); + +PHP_MINIT_FUNCTION(ftp); +PHP_MINFO_FUNCTION(ftp); +END_EXTERN_C() + +#endif diff --git a/ext-src/php_swoole_http.h b/ext-src/php_swoole_http.h index 13573e09b6..a3dff82981 100644 --- a/ext-src/php_swoole_http.h +++ b/ext-src/php_swoole_http.h @@ -20,7 +20,9 @@ #include "swoole_http.h" #include "swoole_http2.h" -#include "thirdparty/swoole_http_parser.h" +#include "swoole_llhttp.h" +#include "swoole_websocket.h" + #include "thirdparty/multipart_parser.h" #include @@ -32,9 +34,25 @@ #define SW_ZLIB_ENCODING_GZIP 0x1f #define SW_ZLIB_ENCODING_DEFLATE 0x0f #define SW_ZLIB_ENCODING_ANY 0x2f +#if MAX_MEM_LEVEL >= 8 +#define SW_ZLIB_DEF_MEM_LEVEL 8 +#else +#define SW_ZLIB_DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif + +#ifdef SW_HAVE_BROTLI +#include +#include +#endif + +#ifdef SW_HAVE_ZSTD +#include #endif -#include "thirdparty/nghttp2/nghttp2.h" +#define HTTP_API + +#include enum swHttpHeaderFlag { HTTP_HEADER_SERVER = 1u << 1, @@ -44,6 +62,7 @@ enum swHttpHeaderFlag { HTTP_HEADER_CONTENT_TYPE = 1u << 5, HTTP_HEADER_TRANSFER_ENCODING = 1u << 6, HTTP_HEADER_ACCEPT_ENCODING = 1u << 7, + HTTP_HEADER_CONTENT_ENCODING = 1u << 8, }; enum swHttpCompressMethod { @@ -51,18 +70,26 @@ enum swHttpCompressMethod { HTTP_COMPRESS_GZIP, HTTP_COMPRESS_DEFLATE, HTTP_COMPRESS_BR, + HTTP_COMPRESS_ZSTD, +}; + +enum swHttpErrorStatusCode { + HTTP_ESTATUS_CONNECT_FAILED = -1, + HTTP_ESTATUS_REQUEST_TIMEOUT = -2, + HTTP_ESTATUS_SERVER_RESET = -3, + HTTP_ESTATUS_SEND_FAILED = -4, }; namespace swoole { class Server; class Coroutine; + namespace http2 { class Stream; class Session; } // namespace http2 namespace http { - struct Request { int version; char *path; @@ -97,7 +124,7 @@ struct Request { }; struct Response { - enum swoole_http_method method; + enum llhttp_method method; int version; int status; char *reason; @@ -128,19 +155,17 @@ struct Context { uchar send_trailer_ : 1; uchar keepalive : 1; uchar websocket : 1; -#ifdef SW_HAVE_ZLIB uchar websocket_compression : 1; -#endif uchar upgrade : 1; uchar detached : 1; uchar parse_cookie : 1; uchar parse_body : 1; uchar parse_files : 1; - uchar co_socket : 1; uchar http2 : 1; - http2::Stream *stream; - std::shared_ptr write_buffer; + zval zsocket; + uint32_t stream_id; + String *write_buffer; #ifdef SW_HAVE_COMPRESSION int8_t compression_level; @@ -150,66 +175,141 @@ struct Context { std::shared_ptr zlib_buffer; #endif + std::shared_ptr continue_frame_buffer; + WebSocketSettings websocket_settings; + Request request; Response response; - swoole_http_parser parser; + llhttp_t parser; multipart_parser *mt_parser; uint16_t input_var_num; - char *current_header_name; + const char *current_header_name; size_t current_header_name_len; char *current_input_name; size_t current_input_name_len; char *current_form_data_name; size_t current_form_data_name_len; zval *current_multipart_header; + const char *tmp_content_type; + size_t tmp_content_type_len; String *form_data_buffer; std::string upload_tmp_dir; + // The `private_data` pointer is used to store Server or CoSocket object void *private_data; + // The `private_data_2` pointer is used to save callback function void *private_data_2; bool (*send)(Context *ctx, const char *data, size_t length); - bool (*sendfile)(Context *ctx, const char *file, uint32_t l_file, off_t offset, size_t length); + bool (*sendfile)(Context *ctx, zend_string *file, off_t offset, size_t length); bool (*close)(Context *ctx); bool (*onBeforeRequest)(Context *ctx); void (*onAfterResponse)(Context *ctx); void init(Server *server); - void init(coroutine::Socket *socket); + void init(zval *zsock); void bind(Server *server); - void bind(coroutine::Socket *socket); - void copy(Context *ctx); + void bind(zval *zsock); + void copy(const Context *ctx); bool init_multipart_parser(const char *boundary_str, int boundary_len); bool get_multipart_boundary( const char *at, size_t length, size_t offset, char **out_boundary_str, int *out_boundary_len); size_t parse(const char *data, size_t length); - bool parse_multipart_data(const char *at, size_t length); - bool set_header(const char *, size_t, zval *, bool); - bool set_header(const char *, size_t, const char *, size_t, bool); - 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); + bool parse_multipart_data(const char *at, size_t length) const; + + HTTP_API bool set_header(const char *, size_t, zval *, bool); + HTTP_API bool set_header(const char *, size_t, const char *, size_t, bool); + HTTP_API bool set_header(const char *, size_t, const std::string &, bool); + HTTP_API void end(zend_string *sdata, zval *return_value); + HTTP_API void write(zend_string *sdata, zval *return_value); + HTTP_API bool send_file(zend_string *file, off_t offset, size_t length); + String *get_write_buffer(); - void build_header(String *http_buffer, const char *body, size_t length); - ssize_t build_trailer(String *http_buffer); - size_t get_content_length() { + String *get_http2_data_buffer() { + String *buffer = request.h2_data_buffer; + if (!buffer) { + buffer = new String(SW_HTTP2_DATA_BUFFER_SIZE); + request.h2_data_buffer = buffer; + } + return buffer; + } + + size_t get_http2_data_length() { + if (request.h2_data_buffer) { + return request.h2_data_buffer->length; + } else { + return 0; + } + } + + size_t get_content_length() const { return parser.content_length; } + bool is_co_socket() const { + return !ZVAL_IS_NULL(&zsocket); + } + + Server *get_async_server() const { + return static_cast(private_data); + } + + SocketImpl *get_co_socket() const { + return static_cast(private_data); + } + #ifdef SW_HAVE_COMPRESSION void set_compression_method(const char *accept_encoding, size_t length); - const char *get_content_encoding(); + const char *get_content_encoding() const; bool compress(const char *data, size_t length); #endif - void http2_end(zval *zdata, zval *return_value); - bool http2_send_file(const char *file, uint32_t l_file, off_t offset, size_t length); - - bool is_available(); + bool is_available() const; void free(); + + private: + void build_header(String *http_buffer, const char *body, size_t length); + ssize_t build_trailer(String *http_buffer) const; + void send_trailer(zval *return_value); +}; + +class Cookie { + bool encode_; + smart_str buffer_ = {}; + + protected: + zend_string *name = nullptr; + zend_string *value = nullptr; + zend_string *path = nullptr; + zend_string *domain = nullptr; + zend_string *sameSite = nullptr; + zend_string *priority = nullptr; + zend_long expires = 0; + zend_bool secure = false; + zend_bool httpOnly = false; + zend_bool partitioned = false; + + public: + explicit Cookie(bool _encode = true) { + encode_ = _encode; + } + Cookie *withName(zend_string *); + Cookie *withExpires(zend_long); + Cookie *withSecure(zend_bool); + Cookie *withHttpOnly(zend_bool); + Cookie *withPartitioned(zend_bool); + Cookie *withValue(zend_string *); + Cookie *withPath(zend_string *); + Cookie *withDomain(zend_string *); + Cookie *withSameSite(zend_string *); + Cookie *withPriority(zend_string *); + void reset(); + void toArray(zval *return_value) const; + zend_string *toString(); + ~Cookie(); }; } // namespace http @@ -225,27 +325,37 @@ class Stream { uint32_t local_window_size; Coroutine *waiting_coroutine = nullptr; - Stream(Session *client, uint32_t _id); + Stream(const Session *client, uint32_t _id); ~Stream(); - bool send_header(const String *body, bool end_stream); - bool send_body(const String *body, bool end_stream, size_t max_frame_size, off_t offset = 0, size_t length = 0); - bool send_trailer(); + bool send_header(const String *body, bool end_stream) const; + bool send_body(const String *body, + bool end_stream, + const std::shared_ptr &session, + off_t offset = 0, + size_t length = 0); + bool send_end_stream_data_frame() const; + bool send_trailer() const; - void reset(uint32_t error_code); + void reset(uint32_t error_code) const; }; class Session { public: - int fd; - std::unordered_map streams; + SessionId fd; + std::unordered_map> streams; nghttp2_hd_inflater *inflater = nullptr; nghttp2_hd_deflater *deflater = nullptr; - http2::Settings local_settings = {}; - http2::Settings remote_settings = {}; + Settings local_settings = {}; + Settings remote_settings = {}; + // flow control + uint32_t remote_window_size; + uint32_t local_window_size; + + uint32_t max_body_size; uint32_t last_stream_id; bool shutting_down; bool is_coro; @@ -253,28 +363,49 @@ class Session { http::Context *default_ctx = nullptr; void *private_data = nullptr; - void (*handle)(Session *, Stream *) = nullptr; + void (*handle)(const std::shared_ptr &, const std::shared_ptr &) = nullptr; - Session(SessionId _fd); + explicit Session(SessionId _fd); ~Session(); + std::shared_ptr create_stream(uint32_t stream_id); + std::shared_ptr get_stream(uint32_t stream_id); + bool remove_stream(uint32_t stream_id); }; } // namespace http2 - } // namespace swoole -extern zend_class_entry *swoole_http_server_ce; extern zend_class_entry *swoole_http_request_ce; extern zend_class_entry *swoole_http_response_ce; +extern zend_class_entry *swoole_http_cookie_ce; swoole::http::Context *swoole_http_context_new(swoole::SessionId fd); -swoole::http::Context *php_swoole_http_request_get_and_check_context(zval *zobject); -swoole::http::Context *php_swoole_http_response_get_and_check_context(zval *zobject); +swoole::http::Cookie *php_swoole_http_get_cooke_safety(const zval *zobject); + +/** + * These class properties cannot be modified by the user before assignment, such as Swoole\\Http\\Request. + * So we can use this function to init property. + */ +static sw_inline zval *swoole_http_init_and_read_property(const zend_class_entry *ce, + const zval *zobject, + zval **zproperty_store_pp, + zend_string *name, + int size = HT_MIN_SIZE) { + if (UNEXPECTED(!*zproperty_store_pp)) { + zval *zv = zend_hash_find(&ce->properties_info, name); + auto *property_info = (zend_property_info *) Z_PTR_P(zv); + zval *property = OBJ_PROP(SW_Z8_OBJ_P(zobject), property_info->offset); + array_init_size(property, size); + *zproperty_store_pp = (zval *) (zproperty_store_pp + 1); + **zproperty_store_pp = *property; + } + return *zproperty_store_pp; +} static sw_inline zval *swoole_http_init_and_read_property( - zend_class_entry *ce, zval *zobject, zval **zproperty_store_pp, const char *name, size_t name_len) { + zend_class_entry *ce, const zval *zobject, zval **zproperty_store_pp, const char *name, size_t name_len) { if (UNEXPECTED(!*zproperty_store_pp)) { // Notice: swoole http server properties can not be unset anymore, so we can read it without checking - zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(zobject), name, name_len, 0, &rv); + zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(zobject), name, name_len, false, &rv); array_init(property); *zproperty_store_pp = (zval *) (zproperty_store_pp + 1); **zproperty_store_pp = *property; @@ -282,7 +413,7 @@ static sw_inline zval *swoole_http_init_and_read_property( return *zproperty_store_pp; } -static inline bool swoole_http_has_crlf(const char *value, size_t length) { +static sw_inline bool swoole_http_has_crlf(const char *value, size_t length) { /* new line/NUL character safety check */ for (size_t i = 0; i < length; i++) { /* RFC 7230 ch. 3.2.4 deprecates folding support */ @@ -300,11 +431,13 @@ static inline bool swoole_http_has_crlf(const char *value, size_t length) { } void swoole_http_parse_cookie(zval *array, const char *at, size_t length); +bool swoole_http_token_list_contains_value(const char *at, size_t length, const char *value); -swoole::http::Context *php_swoole_http_request_get_context(zval *zobject); -void php_swoole_http_request_set_context(zval *zobject, swoole::http::Context *context); -swoole::http::Context *php_swoole_http_response_get_context(zval *zobject); -void php_swoole_http_response_set_context(zval *zobject, swoole::http::Context *context); +swoole::http::Context *php_swoole_http_request_get_context(const zval *zobject); +void php_swoole_http_request_set_context(const zval *zobject, swoole::http::Context *ctx); +swoole::http::Context *php_swoole_http_response_get_context(const zval *zobject); +void php_swoole_http_response_set_context(const zval *zobject, swoole::http::Context *ctx); +zend_string *php_swoole_http_get_date(); #ifdef SW_HAVE_ZLIB voidpf php_zlib_alloc(voidpf opaque, uInt items, uInt size); @@ -330,15 +463,15 @@ namespace http2 { //-----------------------------------namespace begin-------------------------------------------- class HeaderSet { public: - HeaderSet(size_t size) : size(size), index(0) { + explicit HeaderSet(size_t size) : size(size), index(0) { nvs = (nghttp2_nv *) ecalloc(size, sizeof(nghttp2_nv)); } - inline nghttp2_nv *get() { + nghttp2_nv *get() const { return nvs; } - inline size_t len() { + size_t len() const { return index; } @@ -346,12 +479,12 @@ class HeaderSet { index++; } - inline void add(size_t index, - const char *name, - size_t name_len, - const char *value, - size_t value_len, - const uint8_t flags = NGHTTP2_NV_FLAG_NONE) { + void add(size_t index, + const char *name, + size_t name_len, + const char *value, + size_t value_len, + const uint8_t flags = NGHTTP2_NV_FLAG_NONE) const { if (sw_likely(index < size || nvs[index].name == nullptr)) { nghttp2_nv *nv = &nvs[index]; name = zend_str_tolower_dup(name, name_len); // auto to lower @@ -375,11 +508,11 @@ class HeaderSet { } } - inline void add(const char *name, - size_t name_len, - const char *value, - size_t value_len, - const uint8_t flags = NGHTTP2_NV_FLAG_NONE) { + void add(const char *name, + size_t name_len, + const char *value, + size_t value_len, + const uint8_t flags = NGHTTP2_NV_FLAG_NONE) { add(index++, name, name_len, value, value_len, flags); } diff --git a/ext-src/php_swoole_http_server.h b/ext-src/php_swoole_http_server.h index a9dca864ee..e12a2af3fe 100644 --- a/ext-src/php_swoole_http_server.h +++ b/ext-src/php_swoole_http_server.h @@ -21,13 +21,12 @@ #include "php_swoole_server.h" #include "php_swoole_http.h" -#include "swoole_http.h" -#include "swoole_websocket.h" #include "swoole_mime_type.h" -#include "swoole_http2.h" bool swoole_http_server_onBeforeRequest(swoole::http::Context *ctx); void swoole_http_server_onAfterResponse(swoole::http::Context *ctx); +void swoole_http_server_populate_ip_and_port( + swoole::Server *server, HashTable *ht, swoole::Connection *conn, swoole::SessionId session_id, bool keepalive); int swoole_websocket_onMessage(swoole::Server *serv, swoole::RecvData *req); int swoole_websocket_onHandshake(swoole::Server *serv, swoole::ListenPort *port, swoole::http::Context *ctx); @@ -36,11 +35,55 @@ void swoole_websocket_onOpen(swoole::http::Context *ctx); void swoole_websocket_onRequest(swoole::http::Context *ctx); bool swoole_websocket_handshake(swoole::http::Context *ctx); -int swoole_http2_server_parse(swoole::http2::Session *client, const char *buf); +int swoole_http2_server_parse(const std::shared_ptr &client, const char *buf); int swoole_http2_server_onReceive(swoole::Server *serv, swoole::Connection *conn, swoole::RecvData *req); -void swoole_http2_server_session_free(swoole::Connection *conn); -int swoole_http2_server_ping(swoole::http::Context *ctx); -int swoole_http2_server_goaway(swoole::http::Context *ctx, - zend_long error_code, - const char *debug_data, - size_t debug_data_len); + +std::shared_ptr swoole_http2_server_session_new(swoole::SessionId fd); +void swoole_http2_server_session_free(swoole::SessionId fd); + +bool swoole_http2_server_end(swoole::http::Context *ctx, zend_string *sdata); +bool swoole_http2_server_write(swoole::http::Context *ctx, zend_string *sdata); +bool swoole_http2_server_send_file(swoole::http::Context *ctx, zend_string *file, off_t offset, size_t length); +bool swoole_http2_server_ping(swoole::http::Context *ctx); +bool swoole_http2_server_goaway(swoole::http::Context *ctx, zend_long error_code, zend_string *sdata); + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, const char *value) { + zval tmp; + ZVAL_STRING(&tmp, value); + zend_hash_add_new(ht, key, &tmp); +} + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, const char *value, size_t length) { + zval tmp; + ZVAL_STRINGL(&tmp, value, length); + zend_hash_add_new(ht, key, &tmp); +} + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zend_long value) { + zval tmp; + ZVAL_LONG(&tmp, value); + zend_hash_add_new(ht, key, &tmp); +} + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, double value) { + zval tmp; + ZVAL_DOUBLE(&tmp, value); + zend_hash_add_new(ht, key, &tmp); +} + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zend_string *value) { + zval tmp; + ZVAL_STR(&tmp, value); + zend_hash_add_new(ht, key, &tmp); +} + +static inline void http_server_add_server_array(HashTable *ht, zend_string *key, zval *value) { + zend_hash_add_new(ht, key, value); +} + +static inline void http_server_set_object_fd_property(zend_object *object, const zend_class_entry *ce, long fd) { + auto *zv = zend_hash_find(&ce->properties_info, SW_ZSTR_KNOWN(SW_ZEND_STR_FD)); + auto *property_info = static_cast(Z_PTR_P(zv)); + auto property = OBJ_PROP(object, property_info->offset); + ZVAL_LONG(property, fd); +} diff --git a/ext-src/php_swoole_library.h b/ext-src/php_swoole_library.h index 049b79f532..4cc7fbda84 100644 --- a/ext-src/php_swoole_library.h +++ b/ext-src/php_swoole_library.h @@ -1,6 +1,6 @@ /** * ----------------------------------------------------------------------- - * Generated by tools/build-library.php, Please DO NOT modify! + * Generated by build-library.php, Please DO NOT modify! +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ @@ -11,14 +11,70 @@ | 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. | - +----------------------------------------------------------------------+ + +----------------------------------------------------------------------+ */ -/* $Id: cc2a5d35674acd1609d060529ea4298b4fcde292 */ +/* $Id: 54ad6d15fb4330384673e6d6a8317c46d0ca78a3 */ + +#ifndef SWOOLE_LIBRARY_H +#define SWOOLE_LIBRARY_H + +#if PHP_VERSION_ID < 80000 +typedef zval zend_source_string_t; +#else +typedef zend_string zend_source_string_t; +#endif + +#if PHP_VERSION_ID < 80200 +#define ZEND_COMPILE_POSITION_DC +#define ZEND_COMPILE_POSITION_RELAY_C +#else +#define ZEND_COMPILE_POSITION_DC , zend_compile_position position +#define ZEND_COMPILE_POSITION_RELAY_C , position +#endif + +#if PHP_VERSION_ID < 80000 +#define ZEND_STR_CONST +#else +#define ZEND_STR_CONST const +#endif + + +static zend_op_array *(*old_compile_string)(zend_source_string_t *source_string, ZEND_STR_CONST char *filename ZEND_COMPILE_POSITION_DC); + +static inline zend_op_array *_compile_string(zend_source_string_t *source_string, ZEND_STR_CONST char *filename ZEND_COMPILE_POSITION_DC) { + if (UNEXPECTED(EG(exception))) { + zend_exception_error(EG(exception), E_ERROR); + return NULL; + } + zend_op_array *opa = old_compile_string(source_string, filename ZEND_COMPILE_POSITION_RELAY_C); + opa->type = ZEND_USER_FUNCTION; + return opa; +} + +static inline zend_bool _eval(const char *code, const char *filename) { + if (!old_compile_string) { + old_compile_string = zend_compile_string; + } + // overwrite + zend_compile_string = _compile_string; + int ret = (zend_eval_stringl((char *) code, strlen(code), NULL, (char *) filename) == SUCCESS); + // recover + zend_compile_string = old_compile_string; + return ret; +} + +#endif static const char* swoole_library_source_constants = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -31,13 +87,21 @@ static const char* swoole_library_source_constants = static const char* swoole_library_source_std_exec = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" + "use Swoole\\Coroutine\\System;\n" + "\n" "function swoole_exec(string $command, &$output = null, &$returnVar = null)\n" "{\n" - " $result = Swoole\\Coroutine::exec($command);\n" + " $result = System::exec($command);\n" " if ($result) {\n" " $outputList = explode(PHP_EOL, $result['output']);\n" " foreach ($outputList as &$value) {\n" @@ -60,7 +124,7 @@ static const char* swoole_library_source_std_exec = "\n" "function swoole_shell_exec(string $cmd)\n" "{\n" - " $result = Swoole\\Coroutine::exec($cmd);\n" + " $result = System::exec($cmd);\n" " if ($result && $result['output'] !== '') {\n" " return $result['output'];\n" " }\n" @@ -69,7 +133,13 @@ static const char* swoole_library_source_std_exec = static const char* swoole_library_source_core_constant = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -77,7 +147,7 @@ static const char* swoole_library_source_core_constant = "\n" "class Constant\n" "{\n" - " \n" + " /* {{{ EVENT */\n" " public const EVENT_START = 'start';\n" "\n" " public const EVENT_BEFORE_SHUTDOWN = 'beforeShutdown';\n" @@ -122,7 +192,7 @@ static const char* swoole_library_source_core_constant = "\n" " public const EVENT_HANDSHAKE = 'handshake';\n" "\n" - " public const EVENT_BEFORE_HAND_SHAKE_RESPONSE = 'beforeHandShakeResponse';\n" + " public const EVENT_BEFORE_HANDSHAKE_RESPONSE = 'beforeHandshakeResponse';\n" "\n" " public const EVENT_OPEN = 'open';\n" "\n" @@ -130,11 +200,11 @@ static const char* swoole_library_source_core_constant = "\n" " public const EVENT_DISCONNECT = 'disconnect';\n" "\n" - " \n" + " /* }}} EVENT */\n" "\n" " public const EVENT_ERROR = 'error';\n" "\n" - " \n" + " /* {{{ OPTION */\n" " public const OPTION_DEBUG_MODE = 'debug_mode';\n" "\n" " public const OPTION_TRACE_FLAGS = 'trace_flags';\n" @@ -153,21 +223,81 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_DNS_SERVER = 'dns_server';\n" "\n" + " /**\n" + " * Socket DNS timeout in seconds.\n" + " */\n" " public const OPTION_SOCKET_DNS_TIMEOUT = 'socket_dns_timeout';\n" "\n" + " /**\n" + " * Default socket connect timeout in seconds.\n" + " */\n" " public const OPTION_SOCKET_CONNECT_TIMEOUT = 'socket_connect_timeout';\n" "\n" + " /**\n" + " * Default socket write timeout in seconds.\n" + " *\n" + " * This one works the same as option \"socket_send_timeout\", but has higher priority.\n" + " *\n" + " * @see Constant::OPTION_SOCKET_SEND_TIMEOUT\n" + " */\n" " public const OPTION_SOCKET_WRITE_TIMEOUT = 'socket_write_timeout';\n" "\n" + " /**\n" + " * Default socket write timeout in seconds.\n" + " *\n" + " * This one works the same as option \"socket_write_timeout\", but has lower priority.\n" + " *\n" + " * @see Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" + " */\n" " public const OPTION_SOCKET_SEND_TIMEOUT = 'socket_send_timeout';\n" "\n" + " /**\n" + " * Default socket read timeout in seconds.\n" + " *\n" + " * This one works the same as option \"socket_recv_timeout\", but has higher priority.\n" + " *\n" + " * @see Constant::OPTION_SOCKET_RECV_TIMEOUT\n" + " */\n" " public const OPTION_SOCKET_READ_TIMEOUT = 'socket_read_timeout';\n" "\n" + " /**\n" + " * Default socket read timeout in seconds.\n" + " *\n" + " * This one works the same as option \"socket_read_timeout\", but has lower priority.\n" + " *\n" + " * @see Constant::OPTION_SOCKET_READ_TIMEOUT\n" + " */\n" " public const OPTION_SOCKET_RECV_TIMEOUT = 'socket_recv_timeout';\n" "\n" + " /**\n" + " * Default socket read/write timeout in seconds.\n" + " *\n" + " * This one has the highest priority than the other read/write timeout options:\n" + " * - Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" + " * - Constant::OPTION_SOCKET_SEND_TIMEOUT\n" + " * - Constant::OPTION_SOCKET_READ_TIMEOUT\n" + " * - Constant::OPTION_SOCKET_RECV_TIMEOUT\n" + " *\n" + " * @see Constant::OPTION_SOCKET_SEND_TIMEOUT\n" + " * @see Constant::OPTION_SOCKET_WRITE_TIMEOUT\n" + " * @see Constant::OPTION_SOCKET_RECV_TIMEOUT\n" + " * @see Constant::OPTION_SOCKET_READ_TIMEOUT\n" + " */\n" + " public const OPTION_SOCKET_TIMEOUT = 'socket_timeout';\n" + "\n" " public const OPTION_SOCKET_BUFFER_SIZE = 'socket_buffer_size';\n" "\n" - " public const OPTION_SOCKET_TIMEOUT = 'socket_timeout';\n" + " public const OPTION_HTTP2_HEADER_TABLE_SIZE = 'http2_header_table_size';\n" + "\n" + " public const OPTION_HTTP2_ENABLE_PUSH = 'http2_enable_push';\n" + "\n" + " public const OPTION_HTTP2_MAX_CONCURRENT_STREAMS = 'http2_max_concurrent_streams';\n" + "\n" + " public const OPTION_HTTP2_INIT_WINDOW_SIZE = 'http2_init_window_size';\n" + "\n" + " public const OPTION_HTTP2_MAX_FRAME_SIZE = 'http2_max_frame_size';\n" + "\n" + " public const OPTION_HTTP2_MAX_HEADER_LIST_SIZE = 'http2_max_header_list_size';\n" "\n" " public const OPTION_AIO_CORE_WORKER_NUM = 'aio_core_worker_num';\n" "\n" @@ -177,8 +307,28 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_AIO_MAX_IDLE_TIME = 'aio_max_idle_time';\n" "\n" + " /**\n" + " * @since 6.0.0-beta\n" + " */\n" + " public const OPTION_IOURING_ENTRIES = 'iouring_entries';\n" + "\n" + " /**\n" + " * @since 6.0.0-rc1\n" + " */\n" + " public const OPTION_IOURING_WORKERS = 'iouring_workers';\n" + "\n" + " /**\n" + " * @since 6.0.0-rc1\n" + " */\n" + " public const OPTION_IOURING_FLAG = 'iouring_flag';\n" + "\n" " public const OPTION_ENABLE_SIGNALFD = 'enable_signalfd';\n" "\n" + " /**\n" + " * @since 6.1.0\n" + " */\n" + " public const OPTION_ENABLE_KQUEUE = 'enable_kqueue';\n" + "\n" " public const OPTION_WAIT_SIGNAL = 'wait_signal';\n" "\n" " public const OPTION_DNS_CACHE_REFRESH_TIME = 'dns_cache_refresh_time';\n" @@ -189,6 +339,9 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_MAX_THREAD_NUM = 'max_thread_num';\n" "\n" + " /**\n" + " * @removed 6.1.0\n" + " */\n" " public const OPTION_SOCKET_DONTWAIT = 'socket_dontwait';\n" "\n" " public const OPTION_DNS_LOOKUP_RANDOM = 'dns_lookup_random';\n" @@ -269,20 +422,6 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_HTTP_PROXY_PASSWORD = 'http_proxy_password';\n" "\n" - " public const OPTION_TIMEOUT = 'timeout';\n" - "\n" - " public const OPTION_CONNECT_TIMEOUT = 'connect_timeout';\n" - "\n" - " public const OPTION_READ_TIMEOUT = 'read_timeout';\n" - "\n" - " public const OPTION_WRITE_TIMEOUT = 'write_timeout';\n" - "\n" - " public const OPTION_SSL_DISABLE_COMPRESSION = 'ssl_disable_compression';\n" - "\n" - " public const OPTION_SSL_ECDH_CURVE = 'ssl_ecdh_curve';\n" - "\n" - " public const OPTION_SSL_GREASE = 'ssl_grease';\n" - "\n" " public const OPTION_MAX_CORO_NUM = 'max_coro_num';\n" "\n" " public const OPTION_MAX_COROUTINE = 'max_coroutine';\n" @@ -303,7 +442,9 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_DNS_CACHE_CAPACITY = 'dns_cache_capacity';\n" "\n" - " public const OPTION_MAX_CONCURRENCY = 'max_concurrency';\n" + " public const OPTION_CONNECT_TIMEOUT = 'connect_timeout';\n" + "\n" + " public const OPTION_TIMEOUT = 'timeout';\n" "\n" " public const OPTION_MAX_RETRIES = 'max_retries';\n" "\n" @@ -321,6 +462,8 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_WEBSOCKET_COMPRESSION = 'websocket_compression';\n" "\n" + " public const OPTION_WRITE_FUNC = 'write_func';\n" + "\n" " public const OPTION_HTTP_PARSE_COOKIE = 'http_parse_cookie';\n" "\n" " public const OPTION_HTTP_PARSE_POST = 'http_parse_post';\n" @@ -343,32 +486,16 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_UPLOAD_TMP_DIR = 'upload_tmp_dir';\n" "\n" - " public const OPTION_HOST = 'host';\n" + " public const OPTION_ENABLE_MESSAGE_BUS = 'enable_message_bus';\n" "\n" - " public const OPTION_PORT = 'port';\n" + " public const OPTION_MAX_PACKAGE_SIZE = 'max_package_size';\n" "\n" " public const OPTION_SSL = 'ssl';\n" "\n" - " public const OPTION_USER = 'user';\n" - "\n" - " public const OPTION_PASSWORD = 'password';\n" - "\n" - " public const OPTION_DATABASE = 'database';\n" - "\n" - " public const OPTION_CHARSET = 'charset';\n" - "\n" - " public const OPTION_STRICT_TYPE = 'strict_type';\n" - "\n" - " public const OPTION_FETCH_MODE = 'fetch_mode';\n" - "\n" - " public const OPTION_SERIALIZE = 'serialize';\n" - "\n" - " public const OPTION_RECONNECT = 'reconnect';\n" - "\n" - " public const OPTION_COMPATIBILITY_MODE = 'compatibility_mode';\n" - "\n" " public const OPTION_CHROOT = 'chroot';\n" "\n" + " public const OPTION_USER = 'user';\n" + "\n" " public const OPTION_GROUP = 'group';\n" "\n" " public const OPTION_DAEMONIZE = 'daemonize';\n" @@ -385,6 +512,8 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_MAX_QUEUED_BYTES = 'max_queued_bytes';\n" "\n" + " public const OPTION_MAX_CONCURRENCY = 'max_concurrency';\n" + "\n" " public const OPTION_WORKER_MAX_CONCURRENCY = 'worker_max_concurrency';\n" "\n" " public const OPTION_SEND_TIMEOUT = 'send_timeout';\n" @@ -453,6 +582,11 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_STATIC_HANDLER_LOCATIONS = 'static_handler_locations';\n" "\n" + " /**\n" + " * @since 6.2.0\n" + " */\n" + " public const OPTION_URL_REWRITE_RULES = 'url_rewrite_rules';\n" + "\n" " public const OPTION_INPUT_BUFFER_SIZE = 'input_buffer_size';\n" "\n" " public const OPTION_BUFFER_INPUT_SIZE = 'buffer_input_size';\n" @@ -463,6 +597,16 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_MESSAGE_QUEUE_KEY = 'message_queue_key';\n" "\n" + " /**\n" + " * @since 6.0.0-beta\n" + " */\n" + " public const OPTION_BOOTSTRAP = 'bootstrap';\n" + "\n" + " /**\n" + " * @since 6.0.0-beta\n" + " */\n" + " public const OPTION_INIT_ARGUMENTS = 'init_arguments';\n" + "\n" " public const OPTION_BACKLOG = 'backlog';\n" "\n" " public const OPTION_KERNEL_SOCKET_RECV_BUFFER_SIZE = 'kernel_socket_recv_buffer_size';\n" @@ -507,6 +651,8 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_SSL_PREFER_SERVER_CIPHERS = 'ssl_prefer_server_ciphers';\n" "\n" + " public const OPTION_SSL_ECDH_CURVE = 'ssl_ecdh_curve';\n" + "\n" " public const OPTION_SSL_DHPARAM = 'ssl_dhparam';\n" "\n" " public const OPTION_SSL_SNI_CERTS = 'ssl_sni_certs';\n" @@ -515,6 +661,14 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_OPEN_FASTCGI_PROTOCOL = 'open_fastcgi_protocol';\n" "\n" + " public const OPTION_READ_TIMEOUT = 'read_timeout';\n" + "\n" + " public const OPTION_WRITE_TIMEOUT = 'write_timeout';\n" + "\n" + " public const OPTION_SSL_DISABLE_COMPRESSION = 'ssl_disable_compression';\n" + "\n" + " public const OPTION_SSL_GREASE = 'ssl_grease';\n" + "\n" " public const OPTION_EXIT_CONDITION = 'exit_condition';\n" "\n" " public const OPTION_DEADLOCK_CHECK_DISABLE_TRACE = 'deadlock_check_disable_trace';\n" @@ -529,28 +683,32 @@ static const char* swoole_library_source_core_constant = "\n" " public const OPTION_ADMIN_SERVER = 'admin_server';\n" "\n" - " \n" + " /* }}} OPTION */\n" "\n" " public const OPTION_HTTP_CLIENT_DRIVER = 'http_client_driver';\n" "}\n"; static const char* swoole_library_source_core_string_object = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "class StringObject\n" + "class StringObject implements \\Stringable\n" "{\n" - " \n" - " protected $string;\n" - "\n" - " \n" - " public function __construct(string $string = '')\n" + " /**\n" + " * StringObject constructor.\n" + " */\n" + " public function __construct(protected string $string = '')\n" " {\n" - " $this->string = $string;\n" " }\n" "\n" " public function __toString(): string\n" @@ -558,9 +716,9 @@ static const char* swoole_library_source_core_string_object = " return $this->string;\n" " }\n" "\n" - " public static function from(string $string = ''): self\n" + " public static function from(string $string = ''): static\n" " {\n" - " return new static($string);\n" + " return new static($string); // @phpstan-ignore new.static\n" " }\n" "\n" " public function length(): int\n" @@ -568,96 +726,102 @@ static const char* swoole_library_source_core_string_object = " return strlen($this->string);\n" " }\n" "\n" - " \n" - " public function indexOf(string $needle, int $offset = 0)\n" + " public function indexOf(string $needle, int $offset = 0): false|int\n" " {\n" - " return strpos($this->string, ...func_get_args());\n" + " return strpos($this->string, $needle, $offset);\n" " }\n" "\n" - " \n" - " public function lastIndexOf(string $needle, int $offset = 0)\n" + " public function lastIndexOf(string $needle, int $offset = 0): false|int\n" " {\n" - " return strrpos($this->string, ...func_get_args());\n" + " return strrpos($this->string, $needle, $offset);\n" " }\n" "\n" - " \n" - " public function pos(string $needle, int $offset = 0)\n" + " public function pos(string $needle, int $offset = 0): false|int\n" " {\n" - " return strpos($this->string, ...func_get_args());\n" + " return strpos($this->string, $needle, $offset);\n" " }\n" "\n" - " \n" - " public function rpos(string $needle, int $offset = 0)\n" + " public function rpos(string $needle, int $offset = 0): false|int\n" " {\n" - " return strrpos($this->string, ...func_get_args());\n" + " return strrpos($this->string, $needle, $offset);\n" " }\n" "\n" - " \n" + " public function reverse(): static\n" + " {\n" + " return new static(strrev($this->string)); // @phpstan-ignore new.static\n" + " }\n" + "\n" + " /**\n" + " * @return false|int\n" + " */\n" " public function ipos(string $needle)\n" " {\n" " return stripos($this->string, $needle);\n" " }\n" "\n" - " \n" - " public function lower(): self\n" + " public function lower(): static\n" " {\n" - " return new static(strtolower($this->string));\n" + " return new static(strtolower($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function upper(): self\n" + " public function upper(): static\n" " {\n" - " return new static(strtoupper($this->string));\n" + " return new static(strtoupper($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function trim($characters = ''): self\n" + " public function trim(string $characters = ''): static\n" " {\n" " if ($characters) {\n" - " return new static(trim($this->string, $characters));\n" + " return new static(trim($this->string, $characters)); // @phpstan-ignore new.static\n" " }\n" - " return new static(trim($this->string));\n" + " return new static(trim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * @return static\n" + " */\n" " public function ltrim(): self\n" " {\n" - " return new static(ltrim($this->string));\n" + " return new static(ltrim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * @return static\n" + " */\n" " public function rtrim(): self\n" " {\n" - " return new static(rtrim($this->string));\n" + " return new static(rtrim($this->string)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * @return static\n" + " */\n" " public function substr(int $offset, ?int $length = null)\n" " {\n" - " return new static(substr($this->string, ...func_get_args()));\n" + " return new static(substr($this->string, $offset, $length)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function repeat(int $times): self\n" + " public function repeat(int $times): static\n" " {\n" - " return new static(str_repeat($this->string, $times));\n" + " return new static(str_repeat($this->string, $times)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function append($str): self\n" + " public function append(mixed $str): static\n" " {\n" - " return new static($this->string .= $str);\n" + " return new static($this->string .= $str); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function replace(string $search, string $replace, &$count = null): self\n" + " /**\n" + " * @param int|null $count\n" + " */\n" + " public function replace(string $search, string $replace, &$count = null): static\n" " {\n" - " return new static(str_replace($search, $replace, $this->string, $count));\n" + " return new static(str_replace($search, $replace, $this->string, $count)); // @phpstan-ignore new.static\n" " }\n" "\n" " public function startsWith(string $needle): bool\n" " {\n" - " return strpos($this->string, $needle) === 0;\n" + " return str_starts_with($this->string, $needle);\n" " }\n" "\n" " public function endsWith(string $needle): bool\n" @@ -678,7 +842,7 @@ static const char* swoole_library_source_core_string_object = "\n" " public function contains(string $subString): bool\n" " {\n" - " return strpos($this->string, $subString) !== false;\n" + " return str_contains($this->string, $subString);\n" " }\n" "\n" " public function split(string $delimiter, int $limit = PHP_INT_MAX): ArrayObject\n" @@ -694,15 +858,27 @@ static const char* swoole_library_source_core_string_object = " return $this->string[$index];\n" " }\n" "\n" - " \n" - " public function chunkSplit(int $chunkLength = 76, string $chunkEnd = ''): self\n" + " /**\n" + " * Get a new string object by splitting the string of current object into smaller chunks.\n" + " *\n" + " * @param int $length The chunk length.\n" + " * @param string $separator The line ending sequence.\n" + " * @see https://www.php.net/chunk_split\n" + " */\n" + " public function chunkSplit(int $length = 76, string $separator = \"\\r\\n\"): static\n" " {\n" - " return new static(chunk_split($this->string, ...func_get_args()));\n" + " return new static(chunk_split($this->string, $length, $separator)); // @phpstan-ignore new.static\n" " }\n" "\n" - " public function chunk(int $splitLength = 1): ArrayObject\n" + " /**\n" + " * Convert a string to an array object of class \\Swoole\\ArrayObject.\n" + " *\n" + " * @param int $length Maximum length of the chunk.\n" + " * @see https://www.php.net/str_split\n" + " */\n" + " public function chunk(int $length = 1): ArrayObject\n" " {\n" - " return static::detectArrayType(str_split($this->string, ...func_get_args()));\n" + " return static::detectArrayType(str_split($this->string, $length));\n" " }\n" "\n" " public function toString(): string\n" @@ -718,7 +894,13 @@ static const char* swoole_library_source_core_string_object = static const char* swoole_library_source_core_multibyte_string_object = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -731,51 +913,58 @@ static const char* swoole_library_source_core_multibyte_string_object = " return mb_strlen($this->string);\n" " }\n" "\n" - " \n" - " public function indexOf(string $needle, int $offset = 0, ?string $encoding = null)\n" + " public function indexOf(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" - " return mb_strpos($this->string, ...func_get_args());\n" + " return mb_strpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" - " \n" - " public function lastIndexOf(string $needle, int $offset = 0, ?string $encoding = null)\n" + " public function lastIndexOf(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" - " return mb_strrpos($this->string, ...func_get_args());\n" + " return mb_strrpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" - " \n" - " public function pos(string $needle, int $offset = 0, ?string $encoding = null)\n" + " public function pos(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" - " return mb_strpos($this->string, ...func_get_args());\n" + " return mb_strpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" - " \n" - " public function rpos(string $needle, int $offset = 0, ?string $encoding = null)\n" + " public function rpos(string $needle, int $offset = 0, ?string $encoding = null): false|int\n" " {\n" - " return mb_strrpos($this->string, ...func_get_args());\n" + " return mb_strrpos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" - " \n" - " public function ipos(string $needle, ?string $encoding = null)\n" + " public function ipos(string $needle, int $offset = 0, ?string $encoding = null): int|false\n" " {\n" - " return mb_stripos($this->string, ...func_get_args());\n" + " return mb_stripos($this->string, $needle, $offset, $encoding);\n" " }\n" "\n" - " \n" - " public function substr(int $offset, ?int $length = null, ?string $encoding = null)\n" + " /**\n" + " * @see https://www.php.net/mb_substr\n" + " */\n" + " public function substr(int $start, ?int $length = null, ?string $encoding = null): static\n" " {\n" - " return new static(mb_substr($this->string, ...func_get_args()));\n" + " return new static(mb_substr($this->string, $start, $length, $encoding)); // @phpstan-ignore new.static\n" " }\n" "\n" - " public function chunk(int $splitLength = 1, ?int $limit = null): ArrayObject\n" + " /**\n" + " * {@inheritDoc}\n" + " * @see https://www.php.net/mb_str_split\n" + " */\n" + " public function chunk(int $length = 1): ArrayObject\n" " {\n" - " return static::detectArrayType(mb_split($this->string, ...func_get_args()));\n" + " return static::detectArrayType(mb_str_split($this->string, $length));\n" " }\n" "}\n"; static const char* swoole_library_source_core_exception_array_key_not_exists = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -787,25 +976,30 @@ static const char* swoole_library_source_core_exception_array_key_not_exists = static const char* swoole_library_source_core_array_object = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "use ArrayAccess;\n" - "use Countable;\n" - "use Iterator;\n" - "use RuntimeException;\n" - "use Serializable;\n" "use Swoole\\Exception\\ArrayKeyNotExists;\n" "\n" - "class ArrayObject implements ArrayAccess, Serializable, Countable, Iterator\n" + "class ArrayObject implements \\ArrayAccess, \\Serializable, \\Countable, \\Iterator\n" "{\n" - " \n" + " /**\n" + " * @var array\n" + " */\n" " protected $array;\n" "\n" - " \n" + " /**\n" + " * ArrayObject constructor.\n" + " */\n" " public function __construct(array $array = [])\n" " {\n" " $this->array = $array;\n" @@ -826,9 +1020,9 @@ static const char* swoole_library_source_core_array_object = " $this->array = $data;\n" " }\n" "\n" - " public static function from(array $array = []): self\n" + " public static function from(array $array = []): static\n" " {\n" - " return new static($array);\n" + " return new static($array); // @phpstan-ignore new.static\n" " }\n" "\n" " public function toArray(): array\n" @@ -846,14 +1040,18 @@ static const char* swoole_library_source_core_array_object = " return count($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " #[\\ReturnTypeWillChange]\n" " public function current()\n" " {\n" " return current($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " #[\\ReturnTypeWillChange]\n" " public function key()\n" " {\n" @@ -865,22 +1063,28 @@ static const char* swoole_library_source_core_array_object = " return array_key_exists($this->key(), $this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " #[\\ReturnTypeWillChange]\n" " public function rewind()\n" " {\n" " return reset($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " #[\\ReturnTypeWillChange]\n" " public function next()\n" " {\n" " return next($this->array);\n" " }\n" "\n" - " \n" - " public function get($key)\n" + " /**\n" + " * @return ArrayObject|StringObject\n" + " */\n" + " public function get(mixed $key)\n" " {\n" " if (!$this->exists($key)) {\n" " throw new ArrayKeyNotExists($key);\n" @@ -888,8 +1092,10 @@ static const char* swoole_library_source_core_array_object = " return static::detectType($this->array[$key]);\n" " }\n" "\n" - " \n" - " public function getOr($key, $default = null)\n" + " /**\n" + " * @return ArrayObject|StringObject\n" + " */\n" + " public function getOr(mixed $key, mixed $default = null)\n" " {\n" " if (!$this->exists($key)) {\n" " return $default;\n" @@ -897,7 +1103,9 @@ static const char* swoole_library_source_core_array_object = " return static::detectType($this->array[$key]);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " public function last()\n" " {\n" " $key = array_key_last($this->array);\n" @@ -907,19 +1115,25 @@ static const char* swoole_library_source_core_array_object = " return $this->get($key);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return int|string|null\n" + " */\n" " public function firstKey()\n" " {\n" " return array_key_first($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return int|string|null\n" + " */\n" " public function lastKey()\n" " {\n" " return array_key_last($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " public function first()\n" " {\n" " $key = array_key_first($this->array);\n" @@ -929,22 +1143,28 @@ static const char* swoole_library_source_core_array_object = " return $this->get($key);\n" " }\n" "\n" - " \n" - " public function set($key, $value): self\n" + " /**\n" + " * @return $this\n" + " */\n" + " public function set(mixed $key, mixed $value): self\n" " {\n" " $this->array[$key] = $value;\n" " return $this;\n" " }\n" "\n" - " \n" - " public function delete($key): self\n" + " /**\n" + " * @return $this\n" + " */\n" + " public function delete(mixed $key): self\n" " {\n" " unset($this->array[$key]);\n" " return $this;\n" " }\n" "\n" - " \n" - " public function remove($value, bool $strict = true, bool $loop = false): self\n" + " /**\n" + " * @return $this\n" + " */\n" + " public function remove(mixed $value, bool $strict = true, bool $loop = false): self\n" " {\n" " do {\n" " $key = $this->search($value, $strict);\n" @@ -957,16 +1177,20 @@ static const char* swoole_library_source_core_array_object = " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return $this\n" + " */\n" " public function clear(): self\n" " {\n" " $this->array = [];\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed|null\n" + " */\n" " #[\\ReturnTypeWillChange]\n" - " public function offsetGet($key)\n" + " public function offsetGet(mixed $key)\n" " {\n" " if (!array_key_exists($key, $this->array)) {\n" " return null;\n" @@ -974,45 +1198,47 @@ static const char* swoole_library_source_core_array_object = " return $this->array[$key];\n" " }\n" "\n" - " \n" - " public function offsetSet($key, $value): void\n" + " public function offsetSet(mixed $key, mixed $value): void\n" " {\n" " $this->array[$key] = $value;\n" " }\n" "\n" - " \n" - " public function offsetUnset($key): void\n" + " public function offsetUnset(mixed $key): void\n" " {\n" " unset($this->array[$key]);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return bool\n" + " */\n" " #[\\ReturnTypeWillChange]\n" - " public function offsetExists($key)\n" + " public function offsetExists(mixed $key)\n" " {\n" " return isset($this->array[$key]);\n" " }\n" "\n" - " \n" - " public function exists($key): bool\n" + " public function exists(mixed $key): bool\n" " {\n" " return array_key_exists($key, $this->array);\n" " }\n" "\n" - " \n" - " public function contains($value, bool $strict = true): bool\n" + " public function contains(mixed $value, bool $strict = true): bool\n" " {\n" " return in_array($value, $this->array, $strict);\n" " }\n" "\n" - " \n" - " public function indexOf($value, bool $strict = true)\n" + " /**\n" + " * @return mixed\n" + " */\n" + " public function indexOf(mixed $value, bool $strict = true)\n" " {\n" " return $this->search($value, $strict);\n" " }\n" "\n" - " \n" - " public function lastIndexOf($value, bool $strict = true)\n" + " /**\n" + " * @return mixed\n" + " */\n" + " public function lastIndexOf(mixed $value, bool $strict = true)\n" " {\n" " $array = $this->array;\n" " for (end($array); ($currentKey = key($array)) !== null; prev($array)) {\n" @@ -1027,49 +1253,58 @@ static const char* swoole_library_source_core_array_object = " return $currentKey;\n" " }\n" "\n" - " \n" - " public function search($needle, bool $strict = true)\n" + " /**\n" + " * @return mixed\n" + " */\n" + " public function search(mixed $needle, bool $strict = true)\n" " {\n" " return array_search($needle, $this->array, $strict);\n" " }\n" "\n" " public function join(string $glue = ''): StringObject\n" " {\n" - " return static::detectStringType(implode($glue, $this->array));\n" + " return self::detectStringType(implode($glue, $this->array));\n" " }\n" "\n" - " public function serialize(): StringObject\n" + " public function serialize(): string\n" " {\n" - " return static::detectStringType(serialize($this->array));\n" + " return serialize($this->array);\n" " }\n" "\n" - " \n" - " public function unserialize($string): self\n" + " public function unserialize(string|\\Stringable|StringObject $string): self\n" " {\n" " $this->array = (array) unserialize((string) $string);\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return float|int\n" + " */\n" " public function sum()\n" " {\n" " return array_sum($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return float|int\n" + " */\n" " public function product()\n" " {\n" " return array_product($this->array);\n" " }\n" "\n" - " \n" - " public function push($value)\n" + " /**\n" + " * @return int\n" + " */\n" + " public function push(mixed $value)\n" " {\n" " return $this->pushBack($value);\n" " }\n" "\n" - " \n" - " public function pushFront($value)\n" + " /**\n" + " * @return int\n" + " */\n" + " public function pushFront(mixed $value)\n" " {\n" " return array_unshift($this->array, $value);\n" " }\n" @@ -1080,14 +1315,18 @@ static const char* swoole_library_source_core_array_object = " return $this;\n" " }\n" "\n" - " \n" - " public function pushBack($value)\n" + " /**\n" + " * @return int\n" + " */\n" + " public function pushBack(mixed $value)\n" " {\n" " return array_push($this->array, $value);\n" " }\n" "\n" - " \n" - " public function insert(int $offset, $value): self\n" + " /**\n" + " * @return $this\n" + " */\n" + " public function insert(int $offset, mixed $value): self\n" " {\n" " if (is_array($value) || is_object($value) || is_null($value)) {\n" " $value = [$value];\n" @@ -1096,217 +1335,234 @@ static const char* swoole_library_source_core_array_object = " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " public function pop()\n" " {\n" " return $this->popBack();\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " public function popFront()\n" " {\n" " return array_shift($this->array);\n" " }\n" "\n" - " \n" + " /**\n" + " * @return mixed\n" + " */\n" " public function popBack()\n" " {\n" " return array_pop($this->array);\n" " }\n" "\n" - " \n" - " public function slice($offset, int $length = null, bool $preserve_keys = false): self\n" + " public function slice(int $offset, ?int $length = null, bool $preserve_keys = false): static\n" " {\n" - " return new static(array_slice($this->array, ...func_get_args()));\n" + " return new static(array_slice($this->array, $offset, $length, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * @return ArrayObject|mixed|StringObject\n" + " */\n" " public function randomGet()\n" " {\n" " return static::detectType($this->array[array_rand($this->array, 1)]);\n" " }\n" "\n" - " \n" " public function each(callable $fn): self\n" " {\n" - " if (array_walk($this->array, $fn) === false) {\n" - " throw new RuntimeException('array_walk() failed');\n" - " }\n" + " array_walk($this->array, $fn);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" - " public function map(callable $fn, ...$args): self\n" + " /**\n" + " * @param array $args\n" + " */\n" + " public function map(callable $fn, ...$args): static\n" " {\n" - " return new static(array_map($fn, $this->array, ...$args));\n" + " return new static(array_map($fn, $this->array, ...$args)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * @param null $initial\n" + " * @return mixed\n" + " */\n" " public function reduce(callable $fn, $initial = null)\n" " {\n" " return array_reduce($this->array, $fn, $initial);\n" " }\n" "\n" - " \n" - " public function keys(...$args): self\n" + " /**\n" + " * @param array $args\n" + " */\n" + " public function keys(...$args): static\n" " {\n" - " return new static(array_keys($this->array, ...$args));\n" + " return new static(array_keys($this->array, ...$args)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function values(): self\n" + " public function values(): static\n" " {\n" - " return new static(array_values($this->array));\n" + " return new static(array_values($this->array)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function column($column_key, $index = null): self\n" + " public function column(mixed $column_key, mixed $index = null): static\n" " {\n" - " return new static(array_column($this->array, $column_key, $index));\n" + " return new static(array_column($this->array, $column_key, $index)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function unique(int $sort_flags = SORT_STRING): self\n" + " public function unique(int $sort_flags = SORT_STRING): static\n" " {\n" - " return new static(array_unique($this->array, $sort_flags));\n" + " return new static(array_unique($this->array, $sort_flags)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function reverse(bool $preserve_keys = false): self\n" + " public function reverse(bool $preserve_keys = false): static\n" " {\n" - " return new static(array_reverse($this->array, $preserve_keys));\n" + " return new static(array_reverse($this->array, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function chunk(int $size, bool $preserve_keys = false): self\n" + " public function chunk(int $size, bool $preserve_keys = false): static\n" " {\n" - " return new static(array_chunk($this->array, $size, $preserve_keys));\n" + " return new static(array_chunk($this->array, $size, $preserve_keys)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function flip(): self\n" + " /**\n" + " * Swap keys and values in an array.\n" + " */\n" + " public function flip(): static\n" " {\n" - " return new static(array_flip($this->array));\n" + " return new static(array_flip($this->array)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" - " public function filter(callable $fn, int $flag = 0): self\n" + " public function filter(callable $fn, int $flag = 0): static\n" " {\n" - " return new static(array_filter($this->array, $fn, $flag));\n" + " return new static(array_filter($this->array, $fn, $flag)); // @phpstan-ignore new.static\n" " }\n" "\n" - " \n" + " /**\n" + " * | Function name | Sorts by | Maintains key association | Order of sort | Related functions |\n" + " * | :---------------- | :------- | :-------------------------- | :-------------------------- | :---------------- |\n" + " * | array_multisort() | value | associative yes, numeric no | first array or sort options | array_walk() |\n" + " * | asort() | value | yes | low to high | arsort() |\n" + " * | arsort() | value | yes | high to low | asort() |\n" + " * | krsort() | key | yes | high to low | ksort() |\n" + " * | ksort() | key | yes | low to high | asort() |\n" + " * | natcasesort() | value | yes | natural, case insensitive | natsort() |\n" + " * | natsort() | value | yes | natural | natcasesort() |\n" + " * | rsort() | value | no | high to low | sort() |\n" + " * | shuffle() | value | no | random | array_rand() |\n" + " * | sort() | value | no | low to high | rsort() |\n" + " * | uasort() | value | yes | user defined | uksort() |\n" + " * | uksort() | key | yes | user defined | uasort() |\n" + " * | usort() | value | no | user defined | uasort() |\n" + " */\n" "\n" - " \n" + " /**\n" + " * @return $this\n" + " */\n" " public function asort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (asort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('asort() failed');\n" - " }\n" + " asort($this->array, $sort_flags);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function arsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (arsort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('arsort() failed');\n" - " }\n" + " arsort($this->array, $sort_flags);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function krsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (krsort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('krsort() failed');\n" - " }\n" + " krsort($this->array, $sort_flags);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function ksort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (ksort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('ksort() failed');\n" - " }\n" + " ksort($this->array, $sort_flags);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return $this\n" + " */\n" " public function natcasesort(): self\n" " {\n" - " if (natcasesort($this->array) !== true) {\n" - " throw new RuntimeException('natcasesort() failed');\n" + " if (natcasesort($this->array) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" + " throw new \\RuntimeException('natcasesort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return $this\n" + " */\n" " public function natsort(): self\n" " {\n" - " if (natsort($this->array) !== true) {\n" - " throw new RuntimeException('natsort() failed');\n" + " if (natsort($this->array) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" + " throw new \\RuntimeException('natsort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return $this\n" + " */\n" " public function rsort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (rsort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('rsort() failed');\n" + " if (rsort($this->array, $sort_flags) !== true) { // @phpstan-ignore notIdentical.alwaysFalse\n" + " throw new \\RuntimeException('rsort() failed');\n" " }\n" " return $this;\n" " }\n" "\n" - " \n" " public function shuffle(): self\n" " {\n" - " if (shuffle($this->array) !== true) {\n" - " throw new RuntimeException('shuffle() failed');\n" - " }\n" + " shuffle($this->array);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function sort(int $sort_flags = SORT_REGULAR): self\n" " {\n" - " if (sort($this->array, $sort_flags) !== true) {\n" - " throw new RuntimeException('sort() failed');\n" - " }\n" + " sort($this->array, $sort_flags);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function uasort(callable $value_compare_func): self\n" " {\n" - " if (uasort($this->array, $value_compare_func) !== true) {\n" - " throw new RuntimeException('uasort() failed');\n" - " }\n" + " uasort($this->array, $value_compare_func);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function uksort(callable $value_compare_func): self\n" " {\n" - " if (uksort($this->array, $value_compare_func) !== true) {\n" - " throw new RuntimeException('uksort() failed');\n" - " }\n" + " uksort($this->array, $value_compare_func);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" " public function usort(callable $value_compare_func): self\n" " {\n" - " if (usort($this->array, $value_compare_func) !== true) {\n" - " throw new RuntimeException('usort() failed');\n" - " }\n" + " usort($this->array, $value_compare_func);\n" + "\n" " return $this;\n" " }\n" "\n" - " \n" - " protected static function detectType($value)\n" + " /**\n" + " * @return ArrayObject|mixed|StringObject\n" + " */\n" + " protected static function detectType(mixed $value)\n" " {\n" " if (is_string($value)) {\n" " return static::detectStringType($value);\n" @@ -1322,33 +1578,33 @@ static const char* swoole_library_source_core_array_object = " return new StringObject($value);\n" " }\n" "\n" - " \n" - " protected static function detectArrayType(array $value): self\n" + " protected static function detectArrayType(array $value): static\n" " {\n" - " return new static($value);\n" + " return new static($value); // @phpstan-ignore new.static\n" " }\n" "}\n"; static const char* swoole_library_source_core_object_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "use TypeError;\n" - "\n" "class ObjectProxy\n" "{\n" - " \n" + " /** @var object */\n" " protected $__object;\n" "\n" - " public function __construct($object)\n" + " public function __construct(object $object)\n" " {\n" - " if (!is_object($object)) {\n" - " throw new TypeError('Non-object given');\n" - " }\n" " $this->__object = $object;\n" " }\n" "\n" @@ -1384,7 +1640,7 @@ static const char* swoole_library_source_core_object_proxy = "\n" " public function __invoke(...$arguments)\n" " {\n" - " \n" + " /** @var mixed $object */\n" " $object = $this->__object;\n" " return $object(...$arguments);\n" " }\n" @@ -1392,22 +1648,25 @@ static const char* swoole_library_source_core_object_proxy = static const char* swoole_library_source_core_coroutine_wait_group = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" - "use BadMethodCallException;\n" - "use InvalidArgumentException;\n" - "\n" "class WaitGroup\n" "{\n" - " protected $chan;\n" + " protected Channel $chan;\n" "\n" - " protected $count = 0;\n" + " protected int $count = 0;\n" "\n" - " protected $waiting = false;\n" + " protected bool $waiting = false;\n" "\n" " public function __construct(int $delta = 0)\n" " {\n" @@ -1420,11 +1679,11 @@ static const char* swoole_library_source_core_coroutine_wait_group = " public function add(int $delta = 1): void\n" " {\n" " if ($this->waiting) {\n" - " throw new BadMethodCallException('WaitGroup misuse: add called concurrently with wait');\n" + " throw new \\BadMethodCallException('WaitGroup misuse: add called concurrently with wait');\n" " }\n" " $count = $this->count + $delta;\n" " if ($count < 0) {\n" - " throw new InvalidArgumentException('WaitGroup misuse: negative counter');\n" + " throw new \\InvalidArgumentException('WaitGroup misuse: negative counter');\n" " }\n" " $this->count = $count;\n" " }\n" @@ -1433,7 +1692,7 @@ static const char* swoole_library_source_core_coroutine_wait_group = " {\n" " $count = $this->count - 1;\n" " if ($count < 0) {\n" - " throw new BadMethodCallException('WaitGroup misuse: negative counter');\n" + " throw new \\BadMethodCallException('WaitGroup misuse: negative counter');\n" " }\n" " $this->count = $count;\n" " if ($count === 0 && $this->waiting) {\n" @@ -1444,11 +1703,11 @@ static const char* swoole_library_source_core_coroutine_wait_group = " public function wait(float $timeout = -1): bool\n" " {\n" " if ($this->waiting) {\n" - " throw new BadMethodCallException('WaitGroup misuse: reused before previous wait has returned');\n" + " throw new \\BadMethodCallException('WaitGroup misuse: reused before previous wait has returned');\n" " }\n" " if ($this->count > 0) {\n" " $this->waiting = true;\n" - " $done = $this->chan->pop($timeout);\n" + " $done = $this->chan->pop($timeout);\n" " $this->waiting = false;\n" " return $done;\n" " }\n" @@ -1463,56 +1722,63 @@ static const char* swoole_library_source_core_coroutine_wait_group = static const char* swoole_library_source_core_coroutine_server = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine;\n" "\n" + "use Swoole\\Constant;\n" "use Swoole\\Coroutine;\n" "use Swoole\\Coroutine\\Server\\Connection;\n" "use Swoole\\Exception;\n" "\n" - "\n" - "define('SWOOLE_COROUTINE_SOCKET_HAVE_SSL_HANDSHAKE', method_exists(Socket::class, 'sslHandshake'));\n" - "\n" "class Server\n" "{\n" - " \n" + " /** @var string */\n" " public $host = '';\n" "\n" - " \n" + " /** @var int */\n" " public $port = 0;\n" "\n" - " \n" + " /** @var int */\n" " public $type = AF_INET;\n" "\n" - " \n" + " /** @var int */\n" " public $fd = -1;\n" "\n" - " \n" + " /** @var int */\n" " public $errCode = 0;\n" "\n" - " \n" + " /** @var array */\n" " public $setting = [];\n" "\n" - " \n" + " /** @var bool */\n" " protected $running = false;\n" "\n" - " \n" + " /** @var callable|null */\n" " protected $fn;\n" "\n" - " \n" + " /** @var Socket */\n" " protected $socket;\n" "\n" - " \n" + " /**\n" + " * Server constructor.\n" + " * @throws Exception\n" + " */\n" " public function __construct(string $host, int $port = 0, bool $ssl = false, bool $reuse_port = false)\n" " {\n" " $_host = swoole_string($host);\n" " if ($_host->contains('::')) {\n" " $this->type = AF_INET6;\n" " } elseif ($_host->startsWith('unix:/')) {\n" - " $host = $_host->substr(5)->__toString();\n" + " $host = $_host->substr(5)->__toString();\n" " $this->type = AF_UNIX;\n" " } else {\n" " $this->type = AF_INET;\n" @@ -1529,9 +1795,9 @@ static const char* swoole_library_source_core_coroutine_server = " if (!$socket->listen()) {\n" " throw new Exception('listen() failed', $socket->errCode);\n" " }\n" - " $this->port = $socket->getsockname()['port'] ?? 0;\n" - " $this->fd = $socket->fd;\n" - " $this->socket = $socket;\n" + " $this->port = $socket->getsockname()['port'] ?? 0;\n" + " $this->fd = $socket->fd;\n" + " $this->socket = $socket;\n" " $this->setting['open_ssl'] = $ssl;\n" " }\n" "\n" @@ -1564,15 +1830,15 @@ static const char* swoole_library_source_core_coroutine_server = " return false;\n" " }\n" "\n" - " while ($this->running) {\n" - " \n" + " while ($this->running) { // @phpstan-ignore while.alwaysTrue\n" " $conn = null;\n" + " /** @var Socket $conn */\n" " $conn = $socket->accept();\n" - " if ($conn) {\n" + " if ($conn) { // @phpstan-ignore if.alwaysTrue\n" " $conn->setProtocol($this->setting);\n" - " if (SWOOLE_COROUTINE_SOCKET_HAVE_SSL_HANDSHAKE && $this->setting['open_ssl'] ?? false) {\n" + " if (!empty($this->setting[Constant::OPTION_OPEN_SSL])) {\n" " $fn = static function ($fn, $connection) {\n" - " \n" + " /* @var $connection Connection */\n" " if (!$connection->exportSocket()->sslHandshake()) {\n" " return;\n" " }\n" @@ -1580,7 +1846,7 @@ static const char* swoole_library_source_core_coroutine_server = " };\n" " $arguments = [$this->fn, new Connection($conn)];\n" " } else {\n" - " $fn = $this->fn;\n" + " $fn = $this->fn;\n" " $arguments = [new Connection($conn)];\n" " }\n" " if (Coroutine::create($fn, ...$arguments) < 0) {\n" @@ -1603,13 +1869,19 @@ static const char* swoole_library_source_core_coroutine_server = " }\n" " }\n" "\n" - " return true;\n" + " return true; // @phpstan-ignore deadCode.unreachable\n" " }\n" "}\n"; static const char* swoole_library_source_core_coroutine_server_connection = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -1649,7 +1921,13 @@ static const char* swoole_library_source_core_coroutine_server_connection = static const char* swoole_library_source_core_coroutine_barrier = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -1661,11 +1939,11 @@ static const char* swoole_library_source_core_coroutine_barrier = "\n" "class Barrier\n" "{\n" - " private $cid = -1;\n" + " private int $cid = -1;\n" "\n" " private $timer = -1;\n" "\n" - " private static $cancel_list = [];\n" + " private static array $cancel_list = [];\n" "\n" " public function __destruct()\n" " {\n" @@ -1683,18 +1961,20 @@ static const char* swoole_library_source_core_coroutine_barrier = " }\n" " }\n" "\n" - " public static function make()\n" + " public static function make(): self\n" " {\n" - " return new static();\n" + " return new self();\n" " }\n" "\n" - " \n" - " public static function wait(Barrier &$barrier, float $timeout = -1)\n" + " /**\n" + " * @param-out null $barrier\n" + " */\n" + " public static function wait(Barrier &$barrier, float $timeout = -1): void\n" " {\n" " if ($barrier->cid !== -1) {\n" " throw new Exception('The barrier is waiting, cannot wait again.');\n" " }\n" - " $cid = Coroutine::getCid();\n" + " $cid = Coroutine::getCid();\n" " $barrier->cid = $cid;\n" " if ($timeout > 0 && ($timeout_ms = (int) ($timeout * 1000)) > 0) {\n" " $barrier->timer = Timer::after($timeout_ms, function () use ($cid) {\n" @@ -1713,7 +1993,13 @@ static const char* swoole_library_source_core_coroutine_barrier = static const char* swoole_library_source_core_coroutine_http_client_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -1721,38 +2007,32 @@ static const char* swoole_library_source_core_coroutine_http_client_proxy = "\n" "class ClientProxy\n" "{\n" - " private $body;\n" - "\n" - " private $statusCode;\n" + " private array $headers;\n" "\n" - " private $headers;\n" + " private array $cookies;\n" "\n" - " private $cookies;\n" - "\n" - " public function __construct($body, $statusCode, $headers, $cookies)\n" + " public function __construct(private string $body, private int $statusCode, ?array $headers, ?array $cookies)\n" " {\n" - " $this->body = $body;\n" - " $this->statusCode = $statusCode;\n" - " $this->headers = $headers;\n" - " $this->cookies = $cookies;\n" + " $this->headers = $headers ?? [];\n" + " $this->cookies = $cookies ?? [];\n" " }\n" "\n" - " public function getBody()\n" + " public function getBody(): string\n" " {\n" " return $this->body;\n" " }\n" "\n" - " public function getStatusCode()\n" + " public function getStatusCode(): int\n" " {\n" " return $this->statusCode;\n" " }\n" "\n" - " public function getHeaders()\n" + " public function getHeaders(): array\n" " {\n" " return $this->headers;\n" " }\n" "\n" - " public function getCookies()\n" + " public function getCookies(): array\n" " {\n" " return $this->cookies;\n" " }\n" @@ -1760,7 +2040,13 @@ static const char* swoole_library_source_core_coroutine_http_client_proxy = static const char* swoole_library_source_core_coroutine_http_functions = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -1768,35 +2054,35 @@ static const char* swoole_library_source_core_coroutine_http_functions = "\n" "use Swoole\\Coroutine\\Http\\Client\\Exception;\n" "\n" - "\n" + "/**\n" + " * @throws Exception\n" + " */\n" "function request(\n" " string $url,\n" " string $method,\n" - " $data = null,\n" - " array $options = null,\n" - " array $headers = null,\n" - " array $cookies = null\n" + " mixed $data = null,\n" + " ?array $options = null,\n" + " ?array $headers = null,\n" + " ?array $cookies = null,\n" "): ClientProxy {\n" " $driver = swoole_library_get_option('http_client_driver');\n" - " switch ($driver) {\n" - " case 'curl':\n" - " return request_with_curl($url, $method, $data, $options, $headers, $cookies);\n" - " case 'stream':\n" - " return request_with_stream($url, $method, $data, $options, $headers, $cookies);\n" - " case 'swoole':\n" - " default:\n" - " return request_with_http_client($url, $method, $data, $options, $headers, $cookies);\n" - " }\n" + " return match ($driver) {\n" + " 'curl' => request_with_curl($url, $method, $data, $options, $headers, $cookies),\n" + " 'stream' => request_with_stream($url, $method, $data, $options, $headers, $cookies),\n" + " default => request_with_http_client($url, $method, $data, $options, $headers, $cookies),\n" + " };\n" "}\n" "\n" - "\n" + "/**\n" + " * @throws Exception\n" + " */\n" "function request_with_http_client(\n" " string $url,\n" " string $method,\n" - " $data = null,\n" - " array $options = null,\n" - " array $headers = null,\n" - " array $cookies = null\n" + " mixed $data = null,\n" + " ?array $options = null,\n" + " ?array $headers = null,\n" + " ?array $cookies = null,\n" "): ClientProxy {\n" " $info = parse_url($url);\n" " if (empty($info['scheme'])) {\n" @@ -1813,15 +2099,9 @@ static const char* swoole_library_source_core_coroutine_http_functions = " if ($data) {\n" " $client->setData($data);\n" " }\n" - " if (is_array($options)) {\n" - " $client->set($options);\n" - " }\n" - " if (is_array($headers)) {\n" - " $client->setHeaders($headers);\n" - " }\n" - " if (is_array($cookies)) {\n" - " $client->setCookies($cookies);\n" - " }\n" + " $client->set($options ?: []);\n" + " $client->setHeaders($headers ?: []);\n" + " $client->setCookies($cookies ?: []);\n" " $request_url = swoole_array_default_value($info, 'path', '/');\n" " if (!empty($info['query'])) {\n" " $request_url .= '?' . $info['query'];\n" @@ -1830,21 +2110,23 @@ static const char* swoole_library_source_core_coroutine_http_functions = " return new ClientProxy(\n" " $client->getBody(),\n" " $client->getStatusCode(),\n" - " $client->getHeaders(),\n" - " $client->getCookies()\n" + " $client->getHeaders() ?: [],\n" + " $client->getCookies() ?: []\n" " );\n" " }\n" " throw new Exception($client->errMsg, $client->errCode);\n" "}\n" "\n" - "\n" + "/**\n" + " * @throws Exception\n" + " */\n" "function request_with_curl(\n" " string $url,\n" " string $method,\n" - " $data = null,\n" - " array $options = null,\n" - " array $headers = null,\n" - " array $cookies = null\n" + " mixed $data = null,\n" + " ?array $options = null,\n" + " ?array $headers = null,\n" + " ?array $cookies = null,\n" "): ClientProxy {\n" " $ch = curl_init($url);\n" " if (empty($ch)) {\n" @@ -1854,14 +2136,14 @@ static const char* swoole_library_source_core_coroutine_http_functions = " curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));\n" " $responseHeaders = $responseCookies = [];\n" " curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$responseHeaders, &$responseCookies) {\n" - " $len = strlen($header);\n" + " $len = strlen($header);\n" " $header = explode(':', $header, 2);\n" " if (count($header) < 2) {\n" " return $len;\n" " }\n" " $headerKey = strtolower(trim($header[0]));\n" " if ($headerKey == 'set-cookie') {\n" - " [$k, $v] = explode('=', $header[1]);\n" + " [$k, $v] = explode('=', $header[1]);\n" " $responseCookies[$k] = $v;\n" " } else {\n" " $responseHeaders[$headerKey][] = trim($header[1]);\n" @@ -1899,19 +2181,21 @@ static const char* swoole_library_source_core_coroutine_http_functions = " }\n" " $body = curl_exec($ch);\n" " if ($body !== false) {\n" - " return new ClientProxy($body, curl_getinfo($ch, CURLINFO_HTTP_CODE), $responseHeaders, $responseCookies);\n" + " return new ClientProxy($body, curl_getinfo($ch, CURLINFO_RESPONSE_CODE), $responseHeaders, $responseCookies);\n" " }\n" " throw new Exception(curl_error($ch), curl_errno($ch));\n" "}\n" "\n" - "\n" + "/**\n" + " * @throws Exception\n" + " */\n" "function request_with_stream(\n" " string $url,\n" " string $method,\n" - " $data = null,\n" - " array $options = null,\n" - " array $headers = null,\n" - " array $cookies = null\n" + " mixed $data = null,\n" + " ?array $options = null,\n" + " ?array $headers = null,\n" + " ?array $cookies = null,\n" "): ClientProxy {\n" " $stream_options = [\n" " 'http' => [\n" @@ -1951,55 +2235,55 @@ static const char* swoole_library_source_core_coroutine_http_functions = " throw new Exception($error['message']);\n" "}\n" "\n" - "\n" - "function post(string $url, $data, array $options = null, array $headers = null, array $cookies = null): ClientProxy\n" + "/**\n" + " * @throws Exception\n" + " */\n" + "function post(string $url, mixed $data, ?array $options = null, ?array $headers = null, ?array $cookies = null): ClientProxy\n" "{\n" " return request($url, 'POST', $data, $options, $headers, $cookies);\n" "}\n" "\n" - "\n" - "function get(string $url, array $options = null, array $headers = null, array $cookies = null): ClientProxy\n" + "/**\n" + " * @throws Exception\n" + " */\n" + "function get(string $url, ?array $options = null, ?array $headers = null, ?array $cookies = null): ClientProxy\n" "{\n" " return request($url, 'GET', null, $options, $headers, $cookies);\n" "}\n"; static const char* swoole_library_source_core_connection_pool = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "use RuntimeException;\n" "use Swoole\\Coroutine\\Channel;\n" - "use Throwable;\n" "\n" "class ConnectionPool\n" "{\n" " public const DEFAULT_SIZE = 64;\n" "\n" - " \n" - " protected $pool;\n" + " protected ?Channel $pool;\n" "\n" - " \n" + " /** @var callable */\n" " protected $constructor;\n" "\n" - " \n" - " protected $size;\n" + " protected int $size;\n" "\n" - " \n" - " protected $num;\n" + " protected int $num = 0;\n" "\n" - " \n" - " protected $proxy;\n" - "\n" - " public function __construct(callable $constructor, int $size = self::DEFAULT_SIZE, ?string $proxy = null)\n" + " public function __construct(callable $constructor, int $size = self::DEFAULT_SIZE, protected ?string $proxy = null)\n" " {\n" - " $this->pool = new Channel($this->size = $size);\n" + " $this->pool = new Channel($this->size = $size);\n" " $this->constructor = $constructor;\n" - " $this->num = 0;\n" - " $this->proxy = $proxy;\n" " }\n" "\n" " public function fill(): void\n" @@ -2009,10 +2293,16 @@ static const char* swoole_library_source_core_connection_pool = " }\n" " }\n" "\n" + " /**\n" + " * Get a connection from the pool.\n" + " *\n" + " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" + " * @return mixed|false Returns a connection object from the pool, or false if the pool is full and the timeout is reached.\n" + " */\n" " public function get(float $timeout = -1)\n" " {\n" " if ($this->pool === null) {\n" - " throw new RuntimeException('Pool has been closed');\n" + " throw new \\RuntimeException('Pool has been closed');\n" " }\n" " if ($this->pool->isEmpty() && $this->num < $this->size) {\n" " $this->make();\n" @@ -2028,7 +2318,7 @@ static const char* swoole_library_source_core_connection_pool = " if ($connection !== null) {\n" " $this->pool->push($connection);\n" " } else {\n" - " \n" + " /* connection broken */\n" " $this->num -= 1;\n" " $this->make();\n" " }\n" @@ -2038,7 +2328,7 @@ static const char* swoole_library_source_core_connection_pool = " {\n" " $this->pool->close();\n" " $this->pool = null;\n" - " $this->num = 0;\n" + " $this->num = 0;\n" " }\n" "\n" " protected function make(): void\n" @@ -2049,9 +2339,9 @@ static const char* swoole_library_source_core_connection_pool = " $connection = new $this->proxy($this->constructor);\n" " } else {\n" " $constructor = $this->constructor;\n" - " $connection = $constructor();\n" + " $connection = $constructor();\n" " }\n" - " } catch (Throwable $throwable) {\n" + " } catch (\\Throwable $throwable) {\n" " $this->num--;\n" " throw $throwable;\n" " }\n" @@ -2061,25 +2351,35 @@ static const char* swoole_library_source_core_connection_pool = static const char* swoole_library_source_core_database_object_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use Error;\n" - "\n" "class ObjectProxy extends \\Swoole\\ObjectProxy\n" "{\n" - " public function __clone()\n" + " final public function __clone(): void\n" " {\n" - " throw new Error('Trying to clone an uncloneable database proxy object');\n" + " throw new \\Error('Trying to clone an uncloneable database proxy object');\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_config = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -2087,36 +2387,28 @@ static const char* swoole_library_source_core_database_mysqli_config = "\n" "class MysqliConfig\n" "{\n" - " \n" - " protected $host = '127.0.0.1';\n" + " protected string $host = '127.0.0.1';\n" "\n" - " \n" - " protected $port = 3306;\n" + " protected int $port = 3306;\n" "\n" - " \n" - " protected $unixSocket = '';\n" + " protected ?string $unixSocket;\n" "\n" - " \n" - " protected $dbname = 'test';\n" + " protected string $dbname = 'test';\n" "\n" - " \n" - " protected $charset = 'utf8mb4';\n" + " protected string $charset = 'utf8mb4';\n" "\n" - " \n" - " protected $username = 'root';\n" + " protected string $username = 'root';\n" "\n" - " \n" - " protected $password = 'root';\n" + " protected string $password = 'root';\n" "\n" - " \n" - " protected $options = [];\n" + " protected array $options = [];\n" "\n" " public function getHost(): string\n" " {\n" " return $this->host;\n" " }\n" "\n" - " public function withHost($host): self\n" + " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" @@ -2127,9 +2419,9 @@ static const char* swoole_library_source_core_database_mysqli_config = " return $this->port;\n" " }\n" "\n" - " public function getUnixSocket(): string\n" + " public function getUnixSocket(): ?string\n" " {\n" - " return $this->unixSocket;\n" + " return $this->unixSocket ?? null;\n" " }\n" "\n" " public function withUnixSocket(?string $unixSocket): self\n" @@ -2202,21 +2494,31 @@ static const char* swoole_library_source_core_database_mysqli_config = static const char* swoole_library_source_core_database_mysqli_exception = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use Exception;\n" - "\n" - "class MysqliException extends Exception\n" + "class MysqliException extends \\Exception\n" "{\n" "}\n"; static const char* swoole_library_source_core_database_mysqli_pool = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -2225,17 +2527,16 @@ static const char* swoole_library_source_core_database_mysqli_pool = "use mysqli;\n" "use Swoole\\ConnectionPool;\n" "\n" - "\n" + "/**\n" + " * @method \\mysqli|MysqliProxy get()\n" + " * @method void put(mysqli|MysqliProxy $connection)\n" + " */\n" "class MysqliPool extends ConnectionPool\n" "{\n" - " \n" - " protected $config;\n" - "\n" - " public function __construct(MysqliConfig $config, int $size = self::DEFAULT_SIZE)\n" + " public function __construct(protected MysqliConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" - " $this->config = $config;\n" " parent::__construct(function () {\n" - " $mysqli = new mysqli();\n" + " $mysqli = new \\mysqli();\n" " foreach ($this->config->getOptions() as $option => $value) {\n" " $mysqli->set_opt($option, $value);\n" " }\n" @@ -2250,6 +2551,7 @@ static const char* swoole_library_source_core_database_mysqli_pool = " if ($mysqli->connect_errno) {\n" " throw new MysqliException($mysqli->connect_error, $mysqli->connect_errno);\n" " }\n" + " $mysqli->set_charset($this->config->getCharset());\n" " return $mysqli;\n" " }, $size, MysqliProxy::class);\n" " }\n" @@ -2257,41 +2559,44 @@ static const char* swoole_library_source_core_database_mysqli_pool = static const char* swoole_library_source_core_database_mysqli_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use mysqli;\n" - "\n" + "/**\n" + " * @method \\mysqli __getObject()\n" + " */\n" "class MysqliProxy extends ObjectProxy\n" "{\n" " public const IO_METHOD_REGEX = '/^autocommit|begin_transaction|change_user|close|commit|kill|multi_query|ping|prepare|query|real_connect|real_query|reap_async_query|refresh|release_savepoint|rollback|savepoint|select_db|send_query|set_charset|ssl_set$/i';\n" "\n" " public const IO_ERRORS = [\n" - " 2002, \n" - " 2006, \n" - " 2013, \n" + " 2002, // MYSQLND_CR_CONNECTION_ERROR\n" + " 2006, // MYSQLND_CR_SERVER_GONE_ERROR\n" + " 2013, // MYSQLND_CR_SERVER_LOST\n" " ];\n" "\n" - " \n" + " /** @var \\mysqli */\n" " protected $__object;\n" "\n" - " \n" - " protected $charsetContext;\n" + " protected string $charsetContext;\n" "\n" - " \n" - " protected $setOptContext;\n" + " protected array $setOptContext = [];\n" "\n" - " \n" - " protected $changeUserContext;\n" + " protected array $changeUserContext;\n" "\n" - " \n" + " /** @var callable */\n" " protected $constructor;\n" "\n" - " \n" - " protected $round = 0;\n" + " protected int $round = 0;\n" "\n" " public function __construct(callable $constructor)\n" " {\n" @@ -2304,11 +2609,11 @@ static const char* swoole_library_source_core_database_mysqli_proxy = " for ($n = 3; $n--;) {\n" " $ret = @$this->__object->{$name}(...$arguments);\n" " if ($ret === false) {\n" - " \n" + " /* non-IO method */\n" " if (!preg_match(static::IO_METHOD_REGEX, $name)) {\n" " break;\n" " }\n" - " \n" + " /* no more chances or non-IO failures */\n" " if (!in_array($this->__object->errno, static::IO_ERRORS, true) || ($n === 0)) {\n" " throw new MysqliException($this->__object->error, $this->__object->errno);\n" " }\n" @@ -2322,7 +2627,7 @@ static const char* swoole_library_source_core_database_mysqli_proxy = " }\n" " break;\n" " }\n" - " \n" + " /* @noinspection PhpUndefinedVariableInspection */\n" " return $ret;\n" " }\n" "\n" @@ -2336,16 +2641,14 @@ static const char* swoole_library_source_core_database_mysqli_proxy = " $constructor = $this->constructor;\n" " parent::__construct($constructor());\n" " $this->round++;\n" - " \n" - " if ($this->charsetContext) {\n" + " /* restore context */\n" + " if (!empty($this->charsetContext)) {\n" " $this->__object->set_charset($this->charsetContext);\n" " }\n" - " if ($this->setOptContext) {\n" - " foreach ($this->setOptContext as $opt => $val) {\n" - " $this->__object->set_opt($opt, $val);\n" - " }\n" + " foreach ($this->setOptContext as $opt => $val) {\n" + " $this->__object->set_opt($opt, $val);\n" " }\n" - " if ($this->changeUserContext) {\n" + " if (!empty($this->changeUserContext)) {\n" " $this->__object->change_user(...$this->changeUserContext);\n" " }\n" " }\n" @@ -2367,7 +2670,7 @@ static const char* swoole_library_source_core_database_mysqli_proxy = " return $this->__object->set_charset($charset);\n" " }\n" "\n" - " public function change_user(string $user, string $password, string $database): bool\n" + " public function change_user(string $user, string $password, ?string $database): bool\n" " {\n" " $this->changeUserContext = [$user, $password, $database];\n" " return $this->__object->change_user($user, $password, $database);\n" @@ -2376,45 +2679,42 @@ static const char* swoole_library_source_core_database_mysqli_proxy = static const char* swoole_library_source_core_database_mysqli_statement_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use mysqli;\n" - "use mysqli_stmt;\n" - "\n" "class MysqliStatementProxy extends ObjectProxy\n" "{\n" " public const IO_METHOD_REGEX = '/^close|execute|fetch|prepare$/i';\n" "\n" - " \n" + " /** @var \\mysqli_stmt */\n" " protected $__object;\n" "\n" - " \n" - " protected $queryString;\n" + " protected ?string $queryString;\n" "\n" - " \n" - " protected $attrSetContext;\n" + " protected array $attrSetContext = [];\n" "\n" - " \n" - " protected $bindParamContext;\n" + " protected array $bindParamContext;\n" "\n" - " \n" - " protected $bindResultContext;\n" + " protected array $bindResultContext;\n" "\n" - " \n" - " protected $parent;\n" + " protected MysqliProxy $parent;\n" "\n" - " \n" - " protected $parentRound;\n" + " protected int $parentRound;\n" "\n" - " public function __construct(mysqli_stmt $object, ?string $queryString, MysqliProxy $parent)\n" + " public function __construct(\\mysqli_stmt $object, ?string $queryString, MysqliProxy $parent)\n" " {\n" " parent::__construct($object);\n" " $this->queryString = $queryString;\n" - " $this->parent = $parent;\n" + " $this->parent = $parent;\n" " $this->parentRound = $parent->getRound();\n" " }\n" "\n" @@ -2423,33 +2723,31 @@ static const char* swoole_library_source_core_database_mysqli_statement_proxy = " for ($n = 3; $n--;) {\n" " $ret = @$this->__object->{$name}(...$arguments);\n" " if ($ret === false) {\n" - " \n" + " /* non-IO method */\n" " if (!preg_match(static::IO_METHOD_REGEX, $name)) {\n" " break;\n" " }\n" - " \n" + " /* no more chances or non-IO failures or in transaction */\n" " if (!in_array($this->__object->errno, $this->parent::IO_ERRORS, true) || ($n === 0)) {\n" " throw new MysqliException($this->__object->error, $this->__object->errno);\n" " }\n" " if ($this->parent->getRound() === $this->parentRound) {\n" - " \n" + " /* if not equal, parent has reconnected */\n" " $this->parent->reconnect();\n" " }\n" - " $parent = $this->parent->__getObject();\n" + " $parent = $this->parent->__getObject();\n" " $this->__object = $this->queryString ? @$parent->prepare($this->queryString) : @$parent->stmt_init();\n" " if ($this->__object === false) {\n" " throw new MysqliException($parent->error, $parent->errno);\n" " }\n" - " if ($this->bindParamContext) {\n" + " if (!empty($this->bindParamContext)) {\n" " $this->__object->bind_param($this->bindParamContext[0], ...$this->bindParamContext[1]);\n" " }\n" - " if ($this->bindResultContext) {\n" + " if (!empty($this->bindResultContext)) {\n" " $this->__object->bind_result($this->bindResultContext);\n" " }\n" - " if ($this->attrSetContext) {\n" - " foreach ($this->attrSetContext as $attr => $value) {\n" - " $this->__object->attr_set($attr, $value);\n" - " }\n" + " foreach ($this->attrSetContext as $attr => $value) {\n" + " $this->__object->attr_set($attr, $value);\n" " }\n" " continue;\n" " }\n" @@ -2458,7 +2756,7 @@ static const char* swoole_library_source_core_database_mysqli_statement_proxy = " }\n" " break;\n" " }\n" - " \n" + " /* @noinspection PhpUndefinedVariableInspection */\n" " return $ret;\n" " }\n" "\n" @@ -2481,9 +2779,102 @@ static const char* swoole_library_source_core_database_mysqli_statement_proxy = " }\n" "}\n"; -static const char* swoole_library_source_core_database_pdo_config = +static const char* swoole_library_source_core_database_detects_lost_connections = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\Database;\n" + "\n" + "class DetectsLostConnections\n" + "{\n" + " /**\n" + " * @var array\n" + " */\n" + " private const ERROR_MESSAGES = [\n" + " 'server has gone away',\n" + " 'no connection to the server',\n" + " 'Lost connection',\n" + " 'is dead or not enabled',\n" + " 'Error while sending',\n" + " 'decryption failed or bad record mac',\n" + " 'server closed the connection unexpectedly',\n" + " 'SSL connection has been closed unexpectedly',\n" + " 'Error writing data to the connection',\n" + " 'Resource deadlock avoided',\n" + " 'Transaction() on null',\n" + " 'child connection forced to terminate due to client_idle_limit',\n" + " 'query_wait_timeout',\n" + " 'reset by peer',\n" + " 'Physical connection is not usable',\n" + " 'TCP Provider: Error code 0x68',\n" + " 'ORA-03113',\n" + " 'ORA-03114',\n" + " 'Packets out of order. Expected',\n" + " 'Adaptive Server connection failed',\n" + " 'Communication link failure',\n" + " 'connection is no longer usable',\n" + " 'Login timeout expired',\n" + " 'SQLSTATE[HY000] [2002] Connection refused',\n" + " 'running with the --read-only option so it cannot execute this statement',\n" + " 'The connection is broken and recovery is not possible. The connection is marked by the client driver as unrecoverable. No attempt was made to restore the connection.',\n" + " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again',\n" + " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name or service not known',\n" + " 'SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo for',\n" + " 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: EOF detected',\n" + " 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.',\n" + " 'Temporary failure in name resolution',\n" + " 'SQLSTATE[08S01]: Communication link failure',\n" + " 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host',\n" + " 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host',\n" + " 'The client was disconnected by the server because of inactivity. See wait_timeout and interactive_timeout for configuring this behavior.',\n" + " 'SQLSTATE[08006] [7] could not translate host name',\n" + " 'TCP Provider: Error code 0x274C',\n" + " 'SQLSTATE[HY000] [2002] No such file or directory',\n" + " 'Reason: Server is in script upgrade mode. Only administrator can connect at this time.',\n" + " 'Unknown $curl_error_code: 77',\n" + " 'SQLSTATE[08006] [7] SSL error: sslv3 alert unexpected message',\n" + " 'SQLSTATE[08006] [7] unrecognized SSL error code:',\n" + " 'SQLSTATE[HY000] [2002] No connection could be made because the target machine actively refused it',\n" + " 'Broken pipe',\n" + " // PDO::prepare(): Send of 77 bytes failed with errno=110 Operation timed out\n" + " // SSL: Handshake timed out\n" + " // SSL: Operation timed out\n" + " // SSL: Connection timed out\n" + " // SQLSTATE[HY000] [2002] Connection timed out\n" + " 'timed out',\n" + " 'Error reading result',\n" + " ];\n" + "\n" + " public static function causedByLostConnection(\\Throwable $e): bool\n" + " {\n" + " $message = $e->getMessage();\n" + " foreach (self::ERROR_MESSAGES as $needle) {\n" + " if (mb_strpos($message, $needle) !== false) {\n" + " return true;\n" + " }\n" + " }\n" "\n" + " return false;\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_database_pdo_config = "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -2493,32 +2884,23 @@ static const char* swoole_library_source_core_database_pdo_config = "{\n" " public const DRIVER_MYSQL = 'mysql';\n" "\n" - " \n" - " protected $driver = self::DRIVER_MYSQL;\n" + " protected string $driver = self::DRIVER_MYSQL;\n" "\n" - " \n" - " protected $host = '127.0.0.1';\n" + " protected string $host = '127.0.0.1';\n" "\n" - " \n" - " protected $port = 3306;\n" + " protected int $port = 3306;\n" "\n" - " \n" - " protected $unixSocket;\n" + " protected ?string $unixSocket;\n" "\n" - " \n" - " protected $dbname = 'test';\n" + " protected string $dbname = 'test';\n" "\n" - " \n" - " protected $charset = 'utf8mb4';\n" + " protected string $charset = 'utf8mb4';\n" "\n" - " \n" - " protected $username = 'root';\n" + " protected string $username = 'root';\n" "\n" - " \n" - " protected $password = 'root';\n" + " protected string $password = 'root';\n" "\n" - " \n" - " protected $options = [];\n" + " protected array $options = [];\n" "\n" " public function getDriver(): string\n" " {\n" @@ -2536,7 +2918,7 @@ static const char* swoole_library_source_core_database_pdo_config = " return $this->host;\n" " }\n" "\n" - " public function withHost($host): self\n" + " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" @@ -2549,12 +2931,12 @@ static const char* swoole_library_source_core_database_pdo_config = "\n" " public function hasUnixSocket(): bool\n" " {\n" - " return isset($this->unixSocket);\n" + " return !empty($this->unixSocket);\n" " }\n" "\n" - " public function getUnixSocket(): string\n" + " public function getUnixSocket(): ?string\n" " {\n" - " return $this->unixSocket;\n" + " return $this->unixSocket ?? null;\n" " }\n" "\n" " public function withUnixSocket(?string $unixSocket): self\n" @@ -2624,8 +3006,12 @@ static const char* swoole_library_source_core_database_pdo_config = " return $this;\n" " }\n" "\n" - " \n" - " public static function getAvailableDrivers()\n" + " /**\n" + " * Returns the list of available drivers\n" + " *\n" + " * @return string[]\n" + " */\n" + " public static function getAvailableDrivers(): array\n" " {\n" " return [\n" " self::DRIVER_MYSQL,\n" @@ -2635,7 +3021,13 @@ static const char* swoole_library_source_core_database_pdo_config = static const char* swoole_library_source_core_database_pdo_pool = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -2644,107 +3036,146 @@ static const char* swoole_library_source_core_database_pdo_pool = "use PDO;\n" "use Swoole\\ConnectionPool;\n" "\n" - "\n" + "/**\n" + " * @method void put(PDO|PDOProxy $connection)\n" + " */\n" "class PDOPool extends ConnectionPool\n" "{\n" - " \n" - " protected $size = 64;\n" - "\n" - " \n" - " protected $config;\n" - "\n" - " public function __construct(PDOConfig $config, int $size = self::DEFAULT_SIZE)\n" + " public function __construct(protected PDOConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" - " $this->config = $config;\n" " parent::__construct(function () {\n" - " return new PDO(\n" - " \"{$this->config->getDriver()}:\" .\n" - " (\n" - " $this->config->hasUnixSocket() ?\n" - " \"unix_socket={$this->config->getUnixSocket()};\" :\n" - " \"host={$this->config->getHost()};\" . \"port={$this->config->getPort()};\"\n" - " ) .\n" - " \"dbname={$this->config->getDbname()};\" .\n" - " \"charset={$this->config->getCharset()}\",\n" - " $this->config->getUsername(),\n" - " $this->config->getPassword(),\n" - " $this->config->getOptions()\n" - " );\n" + " $driver = $this->config->getDriver();\n" + " if ($driver === 'sqlite') {\n" + " return new \\PDO($this->createDSN('sqlite'));\n" + " }\n" + "\n" + " return new \\PDO($this->createDSN($driver), $this->config->getUsername(), $this->config->getPassword(), $this->config->getOptions());\n" " }, $size, PDOProxy::class);\n" " }\n" + "\n" + " /**\n" + " * Get a PDO connection from the pool. The PDO connection (a PDO object) is wrapped in a PDOProxy object returned.\n" + " *\n" + " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" + " * @return PDOProxy|false Returns a PDOProxy object from the pool, or false if the pool is full and the timeout is reached.\n" + " * {@inheritDoc}\n" + " */\n" + " public function get(float $timeout = -1)\n" + " {\n" + " /* @var \\Swoole\\Database\\PDOProxy|false $pdo */\n" + " $pdo = parent::get($timeout);\n" + " if ($pdo === false) {\n" + " return false;\n" + " }\n" + "\n" + " $pdo->reset();\n" + "\n" + " return $pdo;\n" + " }\n" + "\n" + " /**\n" + " * @purpose create DSN\n" + " * @throws \\Exception\n" + " */\n" + " private function createDSN(string $driver): string\n" + " {\n" + " switch ($driver) {\n" + " case 'mysql':\n" + " if ($this->config->hasUnixSocket()) {\n" + " $dsn = \"mysql:unix_socket={$this->config->getUnixSocket()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}\";\n" + " } else {\n" + " $dsn = \"mysql:host={$this->config->getHost()};port={$this->config->getPort()};dbname={$this->config->getDbname()};charset={$this->config->getCharset()}\";\n" + " }\n" + " break;\n" + " case 'pgsql':\n" + " $dsn = 'pgsql:host=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . \";port={$this->config->getPort()};dbname={$this->config->getDbname()}\";\n" + " break;\n" + " case 'oci':\n" + " $dsn = 'oci:dbname=' . ($this->config->hasUnixSocket() ? $this->config->getUnixSocket() : $this->config->getHost()) . ':' . $this->config->getPort() . '/' . $this->config->getDbname() . ';charset=' . $this->config->getCharset();\n" + " break;\n" + " case 'sqlite':\n" + " // There are three types of SQLite databases: databases on disk, databases in memory, and temporary\n" + " // databases (which are deleted when the connections are closed). It doesn't make sense to use\n" + " // connection pool for the latter two types of databases, because each connection connects to a\n" + " //different in-memory or temporary SQLite database.\n" + " if ($this->config->getDbname() === '') {\n" + " throw new \\Exception('Connection pool in Swoole does not support temporary SQLite databases.');\n" + " }\n" + " if ($this->config->getDbname() === ':memory:') {\n" + " throw new \\Exception('Connection pool in Swoole does not support creating SQLite databases in memory.');\n" + " }\n" + " $dsn = 'sqlite:' . $this->config->getDbname();\n" + " break;\n" + " default:\n" + " throw new \\Exception('Unsupported Database Driver:' . $driver);\n" + " }\n" + " return $dsn;\n" + " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use PDO;\n" - "use PDOException;\n" - "\n" + "/**\n" + " * @method \\PDO __getObject()\n" + " */\n" "class PDOProxy extends ObjectProxy\n" "{\n" - " public const IO_ERRORS = [\n" - " 2002, \n" - " 2006, \n" - " 2013, \n" - " ];\n" - "\n" - " \n" + " /** @var \\PDO */\n" " protected $__object;\n" "\n" - " \n" - " protected $setAttributeContext;\n" + " protected array $setAttributeContext = [];\n" "\n" - " \n" + " /** @var callable */\n" " protected $constructor;\n" "\n" - " \n" - " protected $round = 0;\n" + " protected int $round = 0;\n" + "\n" + " protected int $inTransaction = 0;\n" "\n" " public function __construct(callable $constructor)\n" " {\n" " parent::__construct($constructor());\n" - " $this->__object->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);\n" + " $this->__object->setAttribute(\\PDO::ATTR_ERRMODE, \\PDO::ERRMODE_EXCEPTION);\n" " $this->constructor = $constructor;\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" - " for ($n = 3; $n--;) {\n" - " $ret = @$this->__object->{$name}(...$arguments);\n" - " if ($ret === false) {\n" - " $errorInfo = $this->__object->errorInfo();\n" - " if (empty($errorInfo)) {\n" - " break;\n" - " }\n" - " \n" - " if (\n" - " !in_array($errorInfo[1], static::IO_ERRORS, true)\n" - " || $n === 0\n" - " || $this->__object->inTransaction()\n" - " ) {\n" - " \n" - " if (!empty($errorInfo) && $errorInfo[0] !== '00000') {\n" - " $exception = new PDOException($errorInfo[2], $errorInfo[1]);\n" - " $exception->errorInfo = $errorInfo;\n" - " throw $exception;\n" - " }\n" - " \n" - " break;\n" - " }\n" + " try {\n" + " $ret = $this->__object->{$name}(...$arguments);\n" + " } catch (\\PDOException $e) {\n" + " if (!$this->__object->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {\n" " $this->reconnect();\n" - " continue;\n" - " }\n" - " if ((strcasecmp($name, 'prepare') === 0) || (strcasecmp($name, 'query') === 0)) {\n" - " $ret = new PDOStatementProxy($ret, $this);\n" + " $ret = $this->__object->{$name}(...$arguments);\n" + " } else {\n" + " throw $e;\n" " }\n" - " break;\n" " }\n" - " \n" + "\n" + " if (strcasecmp($name, 'beginTransaction') === 0) {\n" + " $this->inTransaction++;\n" + " }\n" + "\n" + " if ((strcasecmp($name, 'commit') === 0 || strcasecmp($name, 'rollback') === 0) && $this->inTransaction > 0) {\n" + " $this->inTransaction--;\n" + " }\n" + "\n" + " if ((strcasecmp($name, 'prepare') === 0) || (strcasecmp($name, 'query') === 0)) {\n" + " $ret = new PDOStatementProxy($ret, $this);\n" + " }\n" + "\n" " return $ret;\n" " }\n" "\n" @@ -2757,12 +3188,11 @@ static const char* swoole_library_source_core_database_pdo_proxy = " {\n" " $constructor = $this->constructor;\n" " parent::__construct($constructor());\n" + " $this->__object->setAttribute(\\PDO::ATTR_ERRMODE, \\PDO::ERRMODE_EXCEPTION);\n" " $this->round++;\n" - " \n" - " if ($this->setAttributeContext) {\n" - " foreach ($this->setAttributeContext as $attribute => $value) {\n" - " $this->__object->setAttribute($attribute, $value);\n" - " }\n" + " /* restore context */\n" + " foreach ($this->setAttributeContext as $attribute => $value) {\n" + " $this->__object->setAttribute($attribute, $value);\n" " }\n" " }\n" "\n" @@ -2774,119 +3204,95 @@ static const char* swoole_library_source_core_database_pdo_proxy = "\n" " public function inTransaction(): bool\n" " {\n" - " return $this->__object->inTransaction();\n" + " return $this->inTransaction > 0;\n" + " }\n" + "\n" + " public function reset(): void\n" + " {\n" + " $this->inTransaction = 0;\n" " }\n" "}\n"; static const char* swoole_library_source_core_database_pdo_statement_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Database;\n" "\n" - "use PDO;\n" - "use PDOException;\n" - "use PDOStatement;\n" - "\n" + "/**\n" + " * The proxy class for PHP class PDOStatement.\n" + " *\n" + " * @see https://www.php.net/PDOStatement The PDOStatement class\n" + " */\n" "class PDOStatementProxy extends ObjectProxy\n" "{\n" - " \n" + " /** @var \\PDOStatement */\n" " protected $__object;\n" "\n" - " \n" - " protected $setAttributeContext;\n" + " protected array $setAttributeContext = [];\n" "\n" - " \n" - " protected $setFetchModeContext;\n" + " protected array $setFetchModeContext;\n" "\n" - " \n" - " protected $bindParamContext;\n" + " protected array $bindParamContext = [];\n" "\n" - " \n" - " protected $bindColumnContext;\n" + " protected array $bindColumnContext = [];\n" "\n" - " \n" - " protected $bindValueContext;\n" + " protected array $bindValueContext = [];\n" "\n" - " \n" - " protected $parent;\n" + " protected PDOProxy $parent;\n" "\n" - " \n" + " /** @var int */\n" " protected $parentRound;\n" "\n" - " public function __construct(PDOStatement $object, PDOProxy $parent)\n" + " public function __construct(\\PDOStatement $object, PDOProxy $parent)\n" " {\n" " parent::__construct($object);\n" - " $this->parent = $parent;\n" + " $this->parent = $parent;\n" " $this->parentRound = $parent->getRound();\n" " }\n" "\n" " public function __call(string $name, array $arguments)\n" " {\n" - " for ($n = 3; $n--;) {\n" - " $ret = @$this->__object->{$name}(...$arguments);\n" - " if ($ret === false) {\n" - " $errorInfo = $this->__object->errorInfo();\n" - " if (empty($errorInfo)) {\n" - " break;\n" - " }\n" - " \n" - " if (\n" - " !in_array($errorInfo[1], $this->parent::IO_ERRORS, true)\n" - " || $n === 0\n" - " || $this->parent->inTransaction()\n" - " ) {\n" - " \n" - " if (!empty($errorInfo) && $errorInfo[0] !== '00000') {\n" - " $exception = new PDOException($errorInfo[2], $errorInfo[1]);\n" - " $exception->errorInfo = $errorInfo;\n" - " throw $exception;\n" - " }\n" - " \n" - " break;\n" - " }\n" + " try {\n" + " $ret = $this->__object->{$name}(...$arguments);\n" + " } catch (\\PDOException $e) {\n" + " if (!$this->parent->inTransaction() && DetectsLostConnections::causedByLostConnection($e)) {\n" " if ($this->parent->getRound() === $this->parentRound) {\n" - " \n" + " /* if not equal, parent has reconnected */\n" " $this->parent->reconnect();\n" " }\n" - " $parent = $this->parent->__getObject();\n" + " $parent = $this->parent->__getObject();\n" " $this->__object = $parent->prepare($this->__object->queryString);\n" - " if ($this->__object === false) {\n" - " $errorInfo = $parent->errorInfo();\n" - " $exception = new PDOException($errorInfo[2], $errorInfo[1]);\n" - " $exception->errorInfo = $errorInfo;\n" - " throw $exception;\n" - " }\n" - " if ($this->setAttributeContext) {\n" - " foreach ($this->setAttributeContext as $attribute => $value) {\n" - " $this->__object->setAttribute($attribute, $value);\n" - " }\n" + "\n" + " foreach ($this->setAttributeContext as $attribute => $value) {\n" + " $this->__object->setAttribute($attribute, $value);\n" " }\n" - " if ($this->setFetchModeContext) {\n" + " if (!empty($this->setFetchModeContext)) {\n" " $this->__object->setFetchMode(...$this->setFetchModeContext);\n" " }\n" - " if ($this->bindParamContext) {\n" - " foreach ($this->bindParamContext as $param => $item) {\n" - " $this->__object->bindParam($param, ...$item);\n" - " }\n" + " foreach ($this->bindParamContext as $param => $item) {\n" + " $this->__object->bindParam($param, ...$item);\n" " }\n" - " if ($this->bindColumnContext) {\n" - " foreach ($this->bindColumnContext as $column => $item) {\n" - " $this->__object->bindColumn($column, ...$item);\n" - " }\n" + " foreach ($this->bindColumnContext as $column => $item) {\n" + " $this->__object->bindColumn($column, ...$item);\n" " }\n" - " if ($this->bindValueContext) {\n" - " foreach ($this->bindValueContext as $value => $item) {\n" - " $this->__object->bindParam($value, ...$item);\n" - " }\n" + " foreach ($this->bindValueContext as $value => $item) {\n" + " $this->__object->bindParam($value, ...$item);\n" " }\n" - " continue;\n" + " $ret = $this->__object->{$name}(...$arguments);\n" + " } else {\n" + " throw $e;\n" " }\n" - " break;\n" " }\n" - " \n" + "\n" " return $ret;\n" " }\n" "\n" @@ -2896,13 +3302,18 @@ static const char* swoole_library_source_core_database_pdo_statement_proxy = " return $this->__object->setAttribute($attribute, $value);\n" " }\n" "\n" - " public function setFetchMode(int $mode, ...$args): bool\n" + " /**\n" + " * Set the default fetch mode for this statement.\n" + " *\n" + " * @see https://www.php.net/manual/en/pdostatement.setfetchmode.php\n" + " */\n" + " public function setFetchMode(int $mode, ...$params): bool\n" " {\n" " $this->setFetchModeContext = func_get_args();\n" " return $this->__object->setFetchMode(...$this->setFetchModeContext);\n" " }\n" "\n" - " public function bindParam($parameter, &$variable, $data_type = PDO::PARAM_STR, $length = 0, $driver_options = null): bool\n" + " public function bindParam($parameter, &$variable, $data_type = \\PDO::PARAM_STR, $length = 0, $driver_options = null): bool\n" " {\n" " $this->bindParamContext[$parameter] = [$variable, $data_type, $length, $driver_options];\n" " return $this->__object->bindParam($parameter, $variable, $data_type, $length, $driver_options);\n" @@ -2914,7 +3325,7 @@ static const char* swoole_library_source_core_database_pdo_statement_proxy = " return $this->__object->bindColumn($column, $param, $type, $maxlen, $driverdata);\n" " }\n" "\n" - " public function bindValue($parameter, $value, $data_type = PDO::PARAM_STR): bool\n" + " public function bindValue($parameter, $value, $data_type = \\PDO::PARAM_STR): bool\n" " {\n" " $this->bindValueContext[$parameter] = [$value, $data_type];\n" " return $this->__object->bindValue($parameter, $value, $data_type);\n" @@ -2923,7 +3334,13 @@ static const char* swoole_library_source_core_database_pdo_statement_proxy = static const char* swoole_library_source_core_database_redis_config = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -2931,36 +3348,33 @@ static const char* swoole_library_source_core_database_redis_config = "\n" "class RedisConfig\n" "{\n" - " \n" - " protected $host = '127.0.0.1';\n" + " protected string $host = '127.0.0.1';\n" "\n" - " \n" - " protected $port = 6379;\n" + " protected int $port = 6379;\n" "\n" - " \n" - " protected $timeout = 0.0;\n" + " protected float $timeout = 0.0;\n" "\n" - " \n" - " protected $reserved = '';\n" + " protected string $reserved = '';\n" "\n" - " \n" - " protected $retry_interval = 0;\n" + " protected int $retry_interval = 0;\n" "\n" - " \n" - " protected $read_timeout = 0.0;\n" + " protected float $read_timeout = 0.0;\n" "\n" - " \n" - " protected $auth = '';\n" + " protected string $auth = '';\n" "\n" - " \n" - " protected $dbIndex = 0;\n" + " protected int $dbIndex = 0;\n" "\n" - " public function getHost()\n" + " /**\n" + " * @var array\n" + " */\n" + " protected array $options = [];\n" + "\n" + " public function getHost(): string\n" " {\n" " return $this->host;\n" " }\n" "\n" - " public function withHost($host): self\n" + " public function withHost(string $host): self\n" " {\n" " $this->host = $host;\n" " return $this;\n" @@ -3042,11 +3456,47 @@ static const char* swoole_library_source_core_database_redis_config = " $this->dbIndex = $dbIndex;\n" " return $this;\n" " }\n" + "\n" + " /**\n" + " * Add a configurable option.\n" + " */\n" + " public function withOption(int $option, mixed $value): self\n" + " {\n" + " $this->options[$option] = $value;\n" + " return $this;\n" + " }\n" + "\n" + " /**\n" + " * Add/override configurable options.\n" + " *\n" + " * @param array $options\n" + " */\n" + " public function setOptions(array $options): self\n" + " {\n" + " $this->options = $options;\n" + " return $this;\n" + " }\n" + "\n" + " /**\n" + " * Get configurable options.\n" + " *\n" + " * @return array\n" + " */\n" + " public function getOptions(): array\n" + " {\n" + " return $this->options;\n" + " }\n" "}\n"; static const char* swoole_library_source_core_database_redis_pool = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -3055,18 +3505,17 @@ static const char* swoole_library_source_core_database_redis_pool = "use Redis;\n" "use Swoole\\ConnectionPool;\n" "\n" - "\n" + "/**\n" + " * @method \\Redis get(float $timeout = -1)\n" + " * @method void put(Redis $connection)\n" + " */\n" "class RedisPool extends ConnectionPool\n" "{\n" - " \n" - " protected $config;\n" - "\n" - " public function __construct(RedisConfig $config, int $size = self::DEFAULT_SIZE)\n" + " public function __construct(protected RedisConfig $config, int $size = self::DEFAULT_SIZE)\n" " {\n" - " $this->config = $config;\n" " parent::__construct(function () {\n" - " $redis = new Redis();\n" - " \n" + " $redis = new \\Redis();\n" + " /* Compatible with different versions of Redis extension as much as possible */\n" " $arguments = [\n" " $this->config->getHost(),\n" " $this->config->getPort(),\n" @@ -3075,7 +3524,7 @@ static const char* swoole_library_source_core_database_redis_pool = " $arguments[] = $this->config->getTimeout();\n" " }\n" " if ($this->config->getRetryInterval() !== 0) {\n" - " \n" + " /* reserved should always be NULL */\n" " $arguments[] = null;\n" " $arguments[] = $this->config->getRetryInterval();\n" " }\n" @@ -3089,6 +3538,12 @@ static const char* swoole_library_source_core_database_redis_pool = " if ($this->config->getDbIndex() !== 0) {\n" " $redis->select($this->config->getDbIndex());\n" " }\n" + "\n" + " /* Set Redis options. */\n" + " foreach ($this->config->getOptions() as $key => $value) {\n" + " $redis->setOption($key, $value);\n" + " }\n" + "\n" " return $redis;\n" " }, $size);\n" " }\n" @@ -3096,7 +3551,13 @@ static const char* swoole_library_source_core_database_redis_pool = static const char* swoole_library_source_core_http_status = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -3227,66 +3688,66 @@ static const char* swoole_library_source_core_http_status = " public const NETWORK_AUTHENTICATION_REQUIRED = 511;\n" "\n" " protected static $reasonPhrases = [\n" - " self::CONTINUE => 'Continue',\n" - " self::SWITCHING_PROTOCOLS => 'Switching Protocols',\n" - " self::PROCESSING => 'Processing',\n" - " self::OK => 'OK',\n" - " self::CREATED => 'Created',\n" - " self::ACCEPTED => 'Accepted',\n" - " self::NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information',\n" - " self::NO_CONTENT => 'No Content',\n" - " self::RESET_CONTENT => 'Reset Content',\n" - " self::PARTIAL_CONTENT => 'Partial Content',\n" - " self::MULTI_STATUS => 'Multi-status',\n" - " self::ALREADY_REPORTED => 'Already Reported',\n" - " self::IM_USED => 'IM Used',\n" - " self::MULTIPLE_CHOICES => 'Multiple Choices',\n" - " self::MOVED_PERMANENTLY => 'Moved Permanently',\n" - " self::FOUND => 'Found',\n" - " self::SEE_OTHER => 'See Other',\n" - " self::NOT_MODIFIED => 'Not Modified',\n" - " self::USE_PROXY => 'Use Proxy',\n" - " self::SWITCH_PROXY => 'Switch Proxy',\n" - " self::TEMPORARY_REDIRECT => 'Temporary Redirect',\n" - " self::PERMANENT_REDIRECT => 'Permanent Redirect',\n" - " self::BAD_REQUEST => 'Bad Request',\n" - " self::UNAUTHORIZED => 'Unauthorized',\n" - " self::PAYMENT_REQUIRED => 'Payment Required',\n" - " self::FORBIDDEN => 'Forbidden',\n" - " self::NOT_FOUND => 'Not Found',\n" - " self::METHOD_NOT_ALLOWED => 'Method Not Allowed',\n" - " self::NOT_ACCEPTABLE => 'Not Acceptable',\n" - " self::PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required',\n" - " self::REQUEST_TIME_OUT => 'Request Time-out',\n" - " self::CONFLICT => 'Conflict',\n" - " self::GONE => 'Gone',\n" - " self::LENGTH_REQUIRED => 'Length Required',\n" - " self::PRECONDITION_FAILED => 'Precondition Failed',\n" - " self::REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large',\n" - " self::REQUEST_URI_TOO_LARGE => 'Request-URI Too Large',\n" - " self::UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type',\n" + " self::CONTINUE => 'Continue',\n" + " self::SWITCHING_PROTOCOLS => 'Switching Protocols',\n" + " self::PROCESSING => 'Processing',\n" + " self::OK => 'OK',\n" + " self::CREATED => 'Created',\n" + " self::ACCEPTED => 'Accepted',\n" + " self::NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information',\n" + " self::NO_CONTENT => 'No Content',\n" + " self::RESET_CONTENT => 'Reset Content',\n" + " self::PARTIAL_CONTENT => 'Partial Content',\n" + " self::MULTI_STATUS => 'Multi-status',\n" + " self::ALREADY_REPORTED => 'Already Reported',\n" + " self::IM_USED => 'IM Used',\n" + " self::MULTIPLE_CHOICES => 'Multiple Choices',\n" + " self::MOVED_PERMANENTLY => 'Moved Permanently',\n" + " self::FOUND => 'Found',\n" + " self::SEE_OTHER => 'See Other',\n" + " self::NOT_MODIFIED => 'Not Modified',\n" + " self::USE_PROXY => 'Use Proxy',\n" + " self::SWITCH_PROXY => 'Switch Proxy',\n" + " self::TEMPORARY_REDIRECT => 'Temporary Redirect',\n" + " self::PERMANENT_REDIRECT => 'Permanent Redirect',\n" + " self::BAD_REQUEST => 'Bad Request',\n" + " self::UNAUTHORIZED => 'Unauthorized',\n" + " self::PAYMENT_REQUIRED => 'Payment Required',\n" + " self::FORBIDDEN => 'Forbidden',\n" + " self::NOT_FOUND => 'Not Found',\n" + " self::METHOD_NOT_ALLOWED => 'Method Not Allowed',\n" + " self::NOT_ACCEPTABLE => 'Not Acceptable',\n" + " self::PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required',\n" + " self::REQUEST_TIME_OUT => 'Request Time-out',\n" + " self::CONFLICT => 'Conflict',\n" + " self::GONE => 'Gone',\n" + " self::LENGTH_REQUIRED => 'Length Required',\n" + " self::PRECONDITION_FAILED => 'Precondition Failed',\n" + " self::REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large',\n" + " self::REQUEST_URI_TOO_LARGE => 'Request-URI Too Large',\n" + " self::UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type',\n" " self::REQUESTED_RANGE_NOT_SATISFIABLE => 'Requested range not satisfiable',\n" - " self::EXPECTATION_FAILED => 'Expectation Failed',\n" - " self::MISDIRECTED_REQUEST => 'Misdirected Request',\n" - " self::UNPROCESSABLE_ENTITY => 'Unprocessable Entity',\n" - " self::LOCKED => 'Locked',\n" - " self::FAILED_DEPENDENCY => 'Failed Dependency',\n" - " self::UNORDERED_COLLECTION => 'Unordered Collection',\n" - " self::UPGRADE_REQUIRED => 'Upgrade Required',\n" - " self::PRECONDITION_REQUIRED => 'Precondition Required',\n" - " self::TOO_MANY_REQUESTS => 'Too Many Requests',\n" + " self::EXPECTATION_FAILED => 'Expectation Failed',\n" + " self::MISDIRECTED_REQUEST => 'Misdirected Request',\n" + " self::UNPROCESSABLE_ENTITY => 'Unprocessable Entity',\n" + " self::LOCKED => 'Locked',\n" + " self::FAILED_DEPENDENCY => 'Failed Dependency',\n" + " self::UNORDERED_COLLECTION => 'Unordered Collection',\n" + " self::UPGRADE_REQUIRED => 'Upgrade Required',\n" + " self::PRECONDITION_REQUIRED => 'Precondition Required',\n" + " self::TOO_MANY_REQUESTS => 'Too Many Requests',\n" " self::REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large',\n" - " self::UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons',\n" - " self::INTERNAL_SERVER_ERROR => 'Internal Server Error',\n" - " self::NOT_IMPLEMENTED => 'Not Implemented',\n" - " self::BAD_GATEWAY => 'Bad Gateway',\n" - " self::SERVICE_UNAVAILABLE => 'Service Unavailable',\n" - " self::GATEWAY_TIME_OUT => 'Gateway Time-out',\n" - " self::HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported',\n" - " self::VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates',\n" - " self::INSUFFICIENT_STORAGE => 'Insufficient Storage',\n" - " self::LOOP_DETECTED => 'Loop Detected',\n" - " self::NOT_EXTENDED => 'Not Extended',\n" + " self::UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons',\n" + " self::INTERNAL_SERVER_ERROR => 'Internal Server Error',\n" + " self::NOT_IMPLEMENTED => 'Not Implemented',\n" + " self::BAD_GATEWAY => 'Bad Gateway',\n" + " self::SERVICE_UNAVAILABLE => 'Service Unavailable',\n" + " self::GATEWAY_TIME_OUT => 'Gateway Time-out',\n" + " self::HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported',\n" + " self::VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates',\n" + " self::INSUFFICIENT_STORAGE => 'Insufficient Storage',\n" + " self::LOOP_DETECTED => 'Loop Detected',\n" + " self::NOT_EXTENDED => 'Not Extended',\n" " self::NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required',\n" " ];\n" "\n" @@ -3303,7 +3764,13 @@ static const char* swoole_library_source_core_http_status = static const char* swoole_library_source_core_curl_exception = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -3317,59 +3784,65 @@ static const char* swoole_library_source_core_curl_exception = static const char* swoole_library_source_core_curl_handler = "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" - "\n" - "\n" + "/* @noinspection PhpComposerExtensionStubsInspection, PhpDuplicateSwitchCaseBodyInspection, PhpInconsistentReturnPointsInspection */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Curl;\n" "\n" - "use CURLFile;\n" - "use ReflectionClass;\n" - "use Swoole;\n" "use Swoole\\Constant;\n" "use Swoole\\Coroutine\\Http\\Client;\n" + "use Swoole\\Coroutine\\System;\n" "use Swoole\\Curl\\Exception as CurlException;\n" "use Swoole\\Http\\Status;\n" "\n" - "final class Handler\n" + "final class Handler implements \\Stringable\n" "{\n" - " \n" + " /**\n" + " * @var Client|null\n" + " */\n" " private $client;\n" "\n" " private $info = [\n" - " 'url' => '',\n" - " 'content_type' => '',\n" - " 'http_code' => 0,\n" - " 'header_size' => 0,\n" - " 'request_size' => 0,\n" - " 'filetime' => -1,\n" - " 'ssl_verify_result' => 0,\n" - " 'redirect_count' => 0,\n" - " 'total_time' => 5.3E-5,\n" - " 'namelookup_time' => 0.0,\n" - " 'connect_time' => 0.0,\n" - " 'pretransfer_time' => 0.0,\n" - " 'size_upload' => 0.0,\n" - " 'size_download' => 0.0,\n" - " 'speed_download' => 0.0,\n" - " 'speed_upload' => 0.0,\n" + " 'url' => '',\n" + " 'content_type' => '',\n" + " 'http_code' => 0,\n" + " 'header_size' => 0,\n" + " 'request_size' => 0,\n" + " 'filetime' => -1,\n" + " 'ssl_verify_result' => 0,\n" + " 'redirect_count' => 0,\n" + " 'total_time' => 5.3E-5,\n" + " 'namelookup_time' => 0.0,\n" + " 'connect_time' => 0.0,\n" + " 'pretransfer_time' => 0.0,\n" + " 'size_upload' => 0.0,\n" + " 'size_download' => 0.0,\n" + " 'speed_download' => 0.0,\n" + " 'speed_upload' => 0.0,\n" " 'download_content_length' => -1.0,\n" - " 'upload_content_length' => -1.0,\n" - " 'starttransfer_time' => 0.0,\n" - " 'redirect_time' => 0.0,\n" - " 'redirect_url' => '',\n" - " 'primary_ip' => '',\n" - " 'certinfo' => [],\n" - " 'primary_port' => 0,\n" - " 'local_ip' => '',\n" - " 'local_port' => 0,\n" - " 'http_version' => 0,\n" - " 'protocol' => 0,\n" - " 'ssl_verifyresult' => 0,\n" - " 'scheme' => '',\n" - " 'private' => '',\n" + " 'upload_content_length' => -1.0,\n" + " 'starttransfer_time' => 0.0,\n" + " 'redirect_time' => 0.0,\n" + " 'redirect_url' => '',\n" + " 'primary_ip' => '',\n" + " 'certinfo' => [],\n" + " 'primary_port' => 0,\n" + " 'local_ip' => '',\n" + " 'local_port' => 0,\n" + " 'http_version' => 0,\n" + " 'protocol' => 0,\n" + " 'ssl_verifyresult' => 0,\n" + " 'scheme' => '',\n" + " 'private' => '',\n" " ];\n" "\n" " private $withHeaderOut = false;\n" @@ -3408,18 +3881,18 @@ static const char* swoole_library_source_core_curl_handler = "\n" " private $nobody = false;\n" "\n" - " \n" + " /** @var callable|null */\n" " private $headerFunction;\n" "\n" - " \n" + " /** @var callable|null */\n" " private $readFunction;\n" "\n" - " \n" + " /** @var callable|null */\n" " private $writeFunction;\n" "\n" " private $noProgress = true;\n" "\n" - " \n" + " /** @var callable */\n" " private $progressFunction;\n" "\n" " private $returnTransfer = false;\n" @@ -3453,13 +3926,13 @@ static const char* swoole_library_source_core_curl_handler = " }\n" " }\n" "\n" - " public function __toString()\n" + " public function __toString(): string\n" " {\n" " $id = spl_object_id($this);\n" " return \"Object({$id}) of type (curl)\";\n" " }\n" "\n" - " \n" + " /* ====== Public APIs ====== */\n" "\n" " public function isAvailable(): bool\n" " {\n" @@ -3488,14 +3961,14 @@ static const char* swoole_library_source_core_curl_handler = " return $this->isAvailable() ? $this->info : false;\n" " }\n" "\n" - " public function errno()\n" + " public function errno(): int\n" " {\n" - " return $this->isAvailable() ? $this->errCode : false;\n" + " return $this->isAvailable() ? $this->errCode : 0;\n" " }\n" "\n" - " public function error()\n" + " public function error(): string\n" " {\n" - " return $this->isAvailable() ? $this->errMsg : false;\n" + " return $this->isAvailable() ? $this->errMsg : '';\n" " }\n" "\n" " public function reset()\n" @@ -3503,7 +3976,7 @@ static const char* swoole_library_source_core_curl_handler = " if (!$this->isAvailable()) {\n" " return false;\n" " }\n" - " foreach ((new ReflectionClass(static::class))->getDefaultProperties() as $name => $value) {\n" + " foreach ((new \\ReflectionClass(self::class))->getDefaultProperties() as $name => $value) {\n" " $this->{$name} = $value;\n" " }\n" " }\n" @@ -3516,12 +3989,12 @@ static const char* swoole_library_source_core_curl_handler = " return $this->transfer;\n" " }\n" "\n" - " public function close()\n" + " public function close(): void\n" " {\n" " if (!$this->isAvailable()) {\n" - " return false;\n" + " return;\n" " }\n" - " foreach ($this as &$property) {\n" + " foreach ($this as &$property) { // @phpstan-ignore foreach.nonIterable\n" " $property = null;\n" " }\n" " $this->closed = true;\n" @@ -3572,7 +4045,7 @@ static const char* swoole_library_source_core_curl_handler = " $this->setError(CURLE_URL_MALFORMAT, 'No URL set!');\n" " return false;\n" " }\n" - " if (strpos($url, '://') === false && $this->unix_socket_path === '') {\n" + " if (!str_contains($url, '://') && $this->unix_socket_path === '') {\n" " $url = 'http://' . $url;\n" " }\n" " if ($setInfo) {\n" @@ -3609,7 +4082,7 @@ static const char* swoole_library_source_core_curl_handler = " }\n" " $host = $urlInfo['host'];\n" " if ($this->info['primary_port'] !== 0) {\n" - " \n" + " /* keep same with cURL, primary_port has the highest priority */\n" " $urlInfo['port'] = $this->info['primary_port'];\n" " } elseif (empty($urlInfo['port'])) {\n" " $urlInfo['port'] = $scheme === 'https' ? 443 : 80;\n" @@ -3617,10 +4090,10 @@ static const char* swoole_library_source_core_curl_handler = " $urlInfo['port'] = intval($urlInfo['port']);\n" " }\n" " $port = $urlInfo['port'];\n" - " if ($this->client) {\n" + " if (isset($this->client)) {\n" " $oldUrlInfo = $this->urlInfo;\n" " if (($host !== $oldUrlInfo['host']) || ($port !== $oldUrlInfo['port']) || ($scheme !== $oldUrlInfo['scheme'])) {\n" - " \n" + " /* target changed */\n" " $this->create($urlInfo);\n" " }\n" " }\n" @@ -3633,8 +4106,8 @@ static const char* swoole_library_source_core_curl_handler = " $this->info['primary_port'] = $port;\n" " if (!isset($this->urlInfo['port']) || $this->urlInfo['port'] !== $port) {\n" " $this->urlInfo['port'] = $port;\n" - " if ($this->client) {\n" - " \n" + " if (isset($this->client)) {\n" + " /* target changed */\n" " $this->create();\n" " }\n" " }\n" @@ -3643,7 +4116,7 @@ static const char* swoole_library_source_core_curl_handler = " private function setError($code, $msg = ''): void\n" " {\n" " $this->errCode = $code;\n" - " $this->errMsg = $msg ?: curl_strerror($code);\n" + " $this->errMsg = $msg ?: curl_strerror($code);\n" " }\n" "\n" " private function hasHeader(string $headerName): bool\n" @@ -3660,20 +4133,22 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " if ($value !== '') {\n" - " $this->headers[$headerName] = $value;\n" + " $this->headers[$headerName] = $value;\n" " $this->headerMap[$lowerCaseHeaderName] = $headerName;\n" " } else {\n" - " \n" + " // remove empty headers (keep same with raw cURL)\n" " unset($this->headerMap[$lowerCaseHeaderName]);\n" " }\n" " }\n" "\n" - " \n" - " private function setOption(int $opt, $value): bool\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function setOption(int $opt, mixed $value): bool\n" " {\n" " switch ($opt) {\n" - " \n" - " \n" + " // case CURLOPT_STDERR:\n" + " // case CURLOPT_WRITEHEADER:\n" " case CURLOPT_FILE:\n" " case CURLOPT_INFILE:\n" " if (!is_resource($value)) {\n" @@ -3684,7 +4159,9 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " switch ($opt) {\n" - " \n" + " /*\n" + " * Basic\n" + " */\n" " case CURLOPT_URL:\n" " return $this->setUrl((string) $value);\n" " case CURLOPT_PORT:\n" @@ -3694,8 +4171,8 @@ static const char* swoole_library_source_core_curl_handler = " $this->clientOptions[Constant::OPTION_KEEP_ALIVE] = !$value;\n" " break;\n" " case CURLOPT_RETURNTRANSFER:\n" - " $this->returnTransfer = $value;\n" - " $this->transfer = '';\n" + " $this->returnTransfer = (bool) $value;\n" + " $this->transfer = '';\n" " break;\n" " case CURLOPT_ENCODING:\n" " if (empty($value)) {\n" @@ -3717,9 +4194,7 @@ static const char* swoole_library_source_core_curl_handler = " break;\n" " case CURLOPT_PROXYTYPE:\n" " if ($value !== CURLPROXY_HTTP and $value !== CURLPROXY_SOCKS5) {\n" - " throw new Swoole\\Curl\\Exception(\n" - " 'swoole_curl_setopt(): Only support following CURLOPT_PROXYTYPE values: CURLPROXY_HTTP, CURLPROXY_SOCKS5'\n" - " );\n" + " throw new CurlException('swoole_curl_setopt(): Only support following CURLOPT_PROXYTYPE values: CURLPROXY_HTTP, CURLPROXY_SOCKS5');\n" " }\n" " $this->proxyType = $value;\n" " break;\n" @@ -3736,12 +4211,12 @@ static const char* swoole_library_source_core_curl_handler = " $this->proxyPassword = $value;\n" " break;\n" " case CURLOPT_PROXYUSERPWD:\n" - " $usernamePassword = explode(':', $value);\n" + " $usernamePassword = explode(':', $value);\n" " $this->proxyUsername = urldecode($usernamePassword[0]);\n" " $this->proxyPassword = urldecode($usernamePassword[1] ?? null);\n" " break;\n" " case CURLOPT_PROXYAUTH:\n" - " \n" + " /* ignored temporarily */\n" " break;\n" " case CURLOPT_UNIX_SOCKET_PATH:\n" " $realpath = realpath((string) $value);\n" @@ -3759,26 +4234,24 @@ static const char* swoole_library_source_core_curl_handler = " foreach ((array) $value as $resolve) {\n" " $flag = substr($resolve, 0, 1);\n" " if ($flag === '+' || $flag === '-') {\n" - " \n" + " // TODO: [+]HOST:PORT:ADDRESS\n" " $resolve = substr($resolve, 1);\n" " }\n" " $tmpResolve = explode(':', $resolve, 3);\n" - " $host = $tmpResolve[0] ?? '';\n" - " $port = $tmpResolve[1] ?? 0;\n" - " $ip = $tmpResolve[2] ?? '';\n" + " $host = $tmpResolve[0];\n" + " $port = $tmpResolve[1] ?? 0;\n" + " $ip = $tmpResolve[2] ?? '';\n" " if ($flag === '-') {\n" " unset($this->resolve[$host][$port]);\n" " } else {\n" - " \n" + " // TODO: HOST:PORT:ADDRESS[,ADDRESS]...\n" " $this->resolve[$host][$port] = explode(',', $ip)[0];\n" " }\n" " }\n" " break;\n" " case CURLOPT_IPRESOLVE:\n" " if ($value !== CURL_IPRESOLVE_WHATEVER and $value !== CURL_IPRESOLVE_V4) {\n" - " throw new Swoole\\Curl\\Exception(\n" - " 'swoole_curl_setopt(): Only support following CURLOPT_IPRESOLVE values: CURL_IPRESOLVE_WHATEVER, CURL_IPRESOLVE_V4'\n" - " );\n" + " throw new CurlException('swoole_curl_setopt(): Only support following CURLOPT_IPRESOLVE values: CURL_IPRESOLVE_WHATEVER, CURL_IPRESOLVE_V4');\n" " }\n" " break;\n" " case CURLOPT_TCP_NODELAY:\n" @@ -3787,14 +4260,14 @@ static const char* swoole_library_source_core_curl_handler = " case CURLOPT_PRIVATE:\n" " $this->info['private'] = $value;\n" " break;\n" - " \n" + " /*\n" + " * Ignore options\n" + " */\n" " case CURLOPT_VERBOSE:\n" - " \n" + " // trigger_error('swoole_curl_setopt(): CURLOPT_VERBOSE is not supported', E_USER_WARNING);\n" " case CURLOPT_SSLVERSION:\n" " case CURLOPT_NOSIGNAL:\n" " case CURLOPT_FRESH_CONNECT:\n" - " \n" - " case CURLOPT_BINARYTRANSFER: \n" " case CURLOPT_DNS_USE_GLOBAL_CACHE:\n" " case CURLOPT_DNS_CACHE_TIMEOUT:\n" " case CURLOPT_STDERR:\n" @@ -3808,7 +4281,9 @@ static const char* swoole_library_source_core_curl_handler = " case CURLOPT_PROXYHEADER:\n" " case CURLOPT_HTTPPROXYTUNNEL:\n" " break;\n" - " \n" + " /*\n" + " * SSL\n" + " */\n" " case CURLOPT_SSL_VERIFYHOST:\n" " break;\n" " case CURLOPT_SSL_VERIFYPEER:\n" @@ -3831,7 +4306,9 @@ static const char* swoole_library_source_core_curl_handler = " case CURLOPT_SSLKEYPASSWD:\n" " $this->clientOptions[Constant::OPTION_SSL_PASSPHRASE] = $value;\n" " break;\n" - " \n" + " /*\n" + " * Http POST\n" + " */\n" " case CURLOPT_POST:\n" " $this->method = 'POST';\n" " break;\n" @@ -3841,22 +4318,26 @@ static const char* swoole_library_source_core_curl_handler = " $this->method = 'POST';\n" " }\n" " break;\n" - " \n" + " /*\n" + " * Upload\n" + " */\n" " case CURLOPT_SAFE_UPLOAD:\n" " if (!$value) {\n" " trigger_error('swoole_curl_setopt(): Disabling safe uploads is no longer supported', E_USER_WARNING);\n" " return false;\n" " }\n" " break;\n" - " \n" + " /*\n" + " * Http Header\n" + " */\n" " case CURLOPT_HTTPHEADER:\n" " if (!is_array($value) and !is_iterable($value)) {\n" " trigger_error('swoole_curl_setopt(): You must pass either an object or an array with the CURLOPT_HTTPHEADER argument', E_USER_WARNING);\n" " return false;\n" " }\n" " foreach ($value as $header) {\n" - " $header = explode(':', $header, 2);\n" - " $headerName = $header[0];\n" + " $header = explode(':', $header, 2);\n" + " $headerName = $header[0];\n" " $headerValue = trim($header[1] ?? '');\n" " $this->setHeader($headerName, $headerValue);\n" " }\n" @@ -3895,7 +4376,9 @@ static const char* swoole_library_source_core_curl_handler = " case CURLOPT_FAILONERROR:\n" " $this->failOnError = $value;\n" " break;\n" - " \n" + " /*\n" + " * Http Cookie\n" + " */\n" " case CURLOPT_COOKIE:\n" " $this->setHeader('Cookie', $value);\n" " break;\n" @@ -3932,7 +4415,13 @@ static const char* swoole_library_source_core_curl_handler = " $this->readFunction = $value;\n" " break;\n" " case CURLOPT_WRITEFUNCTION:\n" - " $this->writeFunction = $value;\n" + " if (SWOOLE_VERSION_ID >= 50100) {\n" + " $this->clientOptions[Constant::OPTION_WRITE_FUNC] = function ($client, $data) use ($value) {\n" + " return $value($this, $data);\n" + " };\n" + " } else {\n" + " $this->writeFunction = $value;\n" + " }\n" " break;\n" " case CURLOPT_NOPROGRESS:\n" " $this->noProgress = $value;\n" @@ -3960,7 +4449,7 @@ static const char* swoole_library_source_core_curl_handler = " break;\n" " case CURLOPT_PUT:\n" " case CURLOPT_UPLOAD:\n" - " \n" + " /* after libcurl 7.12, CURLOPT_PUT is replaced by CURLOPT_UPLOAD */\n" " $this->method = 'PUT';\n" " break;\n" " case CURLOPT_INFILE:\n" @@ -3970,11 +4459,11 @@ static const char* swoole_library_source_core_curl_handler = " $this->infileSize = $value;\n" " break;\n" " case CURLOPT_HTTPGET:\n" - " \n" + " /* Since GET is the default, this is only necessary if the request method has been changed. */\n" " $this->method = 'GET';\n" " break;\n" " default:\n" - " throw new Swoole\\Curl\\Exception(\"swoole_curl_setopt(): option[{$opt}] is not supported\");\n" + " throw new CurlException(\"swoole_curl_setopt(): option[{$opt}] is not supported\");\n" " }\n" " return true;\n" " }\n" @@ -3982,26 +4471,30 @@ static const char* swoole_library_source_core_curl_handler = " private function execute()\n" " {\n" " $this->info['redirect_count'] = $this->info['starttransfer_time'] = 0;\n" - " $this->info['redirect_url'] = '';\n" - " $timeBegin = microtime(true);\n" - " \n" + " $this->info['redirect_url'] = '';\n" + " $timeBegin = microtime(true);\n" + " /*\n" + " * Socket\n" + " */\n" " if (!$this->urlInfo) {\n" " $this->setError(CURLE_URL_MALFORMAT, 'No URL set or URL using bad/illegal format');\n" " return false;\n" " }\n" - " if (!$this->client) {\n" + " if (!isset($this->client)) {\n" " $this->create();\n" " }\n" " while (true) {\n" " $client = $this->client;\n" - " \n" + " /*\n" + " * Http Proxy\n" + " */\n" " if ($this->proxy) {\n" - " $parse = parse_url($this->proxy);\n" - " $proxy = $parse['host'] ?? $parse['path'];\n" - " $proxyPort = $parse['port'] ?? $this->proxyPort;\n" + " $parse = parse_url($this->proxy);\n" + " $proxy = $parse['host'] ?? $parse['path'];\n" + " $proxyPort = $parse['port'] ?? $this->proxyPort;\n" " $proxyUsername = $parse['user'] ?? $this->proxyUsername;\n" " $proxyPassword = $parse['pass'] ?? $this->proxyPassword;\n" - " $proxyType = $parse['scheme'] ?? $this->proxyType;\n" + " $proxyType = $parse['scheme'] ?? $this->proxyType;\n" " if (is_string($proxyType)) {\n" " if ($proxyType === 'socks5') {\n" " $proxyType = CURLPROXY_SOCKS5;\n" @@ -4011,47 +4504,48 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " if (!filter_var($proxy, FILTER_VALIDATE_IP)) {\n" - " $ip = Swoole\\Coroutine::gethostbyname($proxy, AF_INET, $this->clientOptions['connect_timeout'] ?? -1);\n" + " $ip = System::gethostbyname($proxy, AF_INET, $this->clientOptions['connect_timeout'] ?? -1);\n" " if (!$ip) {\n" " $this->setError(CURLE_COULDNT_RESOLVE_PROXY, 'Could not resolve proxy: ' . $proxy);\n" " return false;\n" " }\n" " $this->proxy = $proxy = $ip;\n" " }\n" - " switch ($proxyType) {\n" - " case CURLPROXY_HTTP:\n" - " $proxyOptions = [\n" - " 'http_proxy_host' => $proxy,\n" - " 'http_proxy_port' => $proxyPort,\n" - " 'http_proxy_username' => $proxyUsername,\n" - " 'http_proxy_password' => $proxyPassword,\n" - " ];\n" - " break;\n" - " case CURLPROXY_SOCKS5:\n" - " $proxyOptions = [\n" - " 'socks5_host' => $proxy,\n" - " 'socks5_port' => $proxyPort,\n" - " 'socks5_username' => $proxyUsername,\n" - " 'socks5_password' => $proxyPassword,\n" - " ];\n" - " break;\n" - " default:\n" - " throw new CurlException(\"Unexpected proxy type [{$proxyType}]\");\n" - " }\n" + " $proxyOptions = match ($proxyType) {\n" + " CURLPROXY_HTTP => [\n" + " 'http_proxy_host' => $proxy,\n" + " 'http_proxy_port' => $proxyPort,\n" + " 'http_proxy_username' => $proxyUsername,\n" + " 'http_proxy_password' => $proxyPassword,\n" + " ],\n" + " CURLPROXY_SOCKS5 => [\n" + " 'socks5_host' => $proxy,\n" + " 'socks5_port' => $proxyPort,\n" + " 'socks5_username' => $proxyUsername,\n" + " 'socks5_password' => $proxyPassword,\n" + " ],\n" + " default => throw new CurlException(\"Unexpected proxy type [{$proxyType}]\"),\n" + " };\n" " }\n" - " \n" + " /*\n" + " * Client Options\n" + " */\n" " $client->set(\n" " $this->clientOptions +\n" " ($proxyOptions ?? [])\n" " );\n" - " \n" + " /*\n" + " * Method\n" + " */\n" " if ($this->method) {\n" " $client->setMethod($this->method);\n" " }\n" - " \n" + " /*\n" + " * Data\n" + " */\n" " if ($this->infile) {\n" - " \n" - " \n" + " // Infile\n" + " // Notice: we make its priority higher than postData but raw cURL will send both of them\n" " $data = '';\n" " while (true) {\n" " $nLength = $this->infileSize - strlen($data);\n" @@ -4064,11 +4558,11 @@ static const char* swoole_library_source_core_curl_handler = " $data .= fread($this->infile, $nLength);\n" " }\n" " $client->setData($data);\n" - " \n" - " $this->infile = null;\n" + " // Notice: although we reset it, raw cURL never do this\n" + " $this->infile = null;\n" " $this->infileSize = PHP_INT_MAX;\n" " } else {\n" - " \n" + " // POST data\n" " if ($this->postData) {\n" " if (is_string($this->postData)) {\n" " if (!$this->hasHeader('content-type')) {\n" @@ -4076,21 +4570,25 @@ static const char* swoole_library_source_core_curl_handler = " }\n" " } elseif (is_array($this->postData)) {\n" " foreach ($this->postData as $k => $v) {\n" - " if ($v instanceof CURLFile) {\n" + " if ($v instanceof \\CURLFile) {\n" " $client->addFile($v->getFilename(), $k, $v->getMimeType() ?: 'application/octet-stream', $v->getPostFilename());\n" " unset($this->postData[$k]);\n" " }\n" " }\n" " }\n" + " $client->setData($this->postData);\n" " }\n" - " $client->setData($this->postData);\n" " }\n" - " \n" - " \n" - " \n" - " \n" + " /*\n" + " * Headers\n" + " */\n" + " // Notice: setHeaders must be placed last, because headers may be changed by other parts\n" + " // As much as possible to ensure that Host is the first header.\n" + " // See: http://tools.ietf.org/html/rfc7230#section-5.4\n" " $client->setHeaders($this->headers);\n" - " \n" + " /**\n" + " * Execute.\n" + " */\n" " $executeResult = $client->execute($this->getUrl());\n" " if (!$executeResult) {\n" " $errCode = $client->errCode;\n" @@ -4104,13 +4602,13 @@ static const char* swoole_library_source_core_curl_handler = " }\n" " if ($client->statusCode >= 300 and $client->statusCode < 400 and isset($client->headers['location'])) {\n" " $redirectParsedUrl = $this->getRedirectUrl($client->headers['location']);\n" - " $redirectUrl = static::unparseUrl($redirectParsedUrl);\n" + " $redirectUrl = self::unparseUrl($redirectParsedUrl);\n" " if ($this->followLocation and ($this->maxRedirects === null or $this->info['redirect_count'] < $this->maxRedirects)) {\n" " if ($this->info['redirect_count'] === 0) {\n" " $this->info['starttransfer_time'] = microtime(true) - $timeBegin;\n" - " $redirectBeginTime = microtime(true);\n" + " $redirectBeginTime = microtime(true);\n" " }\n" - " \n" + " // force GET\n" " if (in_array($client->statusCode, [Status::MOVED_PERMANENTLY, Status::FOUND, Status::SEE_OTHER])) {\n" " $this->method = 'GET';\n" " }\n" @@ -4131,10 +4629,10 @@ static const char* swoole_library_source_core_curl_handler = " break;\n" " }\n" " }\n" - " $this->info['total_time'] = microtime(true) - $timeBegin;\n" - " $this->info['http_code'] = $client->statusCode;\n" - " $this->info['content_type'] = $client->headers['content-type'] ?? '';\n" - " $this->info['size_download'] = $this->info['download_content_length'] = strlen($client->body);\n" + " $this->info['total_time'] = microtime(true) - $timeBegin;\n" + " $this->info['http_code'] = $client->statusCode;\n" + " $this->info['content_type'] = $client->headers['content-type'] ?? '';\n" + " $this->info['size_download'] = $this->info['download_content_length'] = strlen($client->body);\n" " $this->info['speed_download'] = 1 / $this->info['total_time'] * $this->info['size_download'];\n" " if (isset($redirectBeginTime)) {\n" " $this->info['redirect_time'] = microtime(true) - $redirectBeginTime;\n" @@ -4145,7 +4643,7 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " if ($this->unix_socket_path) {\n" - " $this->info['primary_ip'] = $this->unix_socket_path;\n" + " $this->info['primary_ip'] = $this->unix_socket_path;\n" " $this->info['primary_port'] = $this->urlInfo['port'];\n" " }\n" "\n" @@ -4160,11 +4658,14 @@ static const char* swoole_library_source_core_curl_handler = " $headerContent .= $row;\n" " }\n" " foreach ($client->headers as $k => $v) {\n" - " $row = \"{$k}: {$v}\\r\\n\";\n" - " if ($cb) {\n" - " $cb($this, $row);\n" + " $list = is_array($v) ? $v : [$v];\n" + " foreach ($list as $_v) {\n" + " $row = \"{$k}: {$_v}\\r\\n\";\n" + " if ($cb) {\n" + " $cb($this, $row);\n" + " }\n" + " $headerContent .= $row;\n" " }\n" - " $headerContent .= $row;\n" " }\n" " $headerContent .= \"\\r\\n\";\n" " $this->info['header_size'] = strlen($headerContent);\n" @@ -4187,7 +4688,7 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " if ($this->withHeaderOut) {\n" - " $headerOutContent = $client->getHeaderOut();\n" + " $headerOutContent = $client->getHeaderOut();\n" " $this->info['request_header'] = $headerOutContent ? $headerOutContent . \"\\r\\n\\r\\n\" : '';\n" " }\n" " if ($this->withFileTime) {\n" @@ -4198,7 +4699,7 @@ static const char* swoole_library_source_core_curl_handler = " }\n" " }\n" "\n" - " if ($this->cookieJar && $this->cookieJar !== '') {\n" + " if (!empty($this->cookieJar)) {\n" " if ($this->cookieJar === '-') {\n" " foreach ((array) $client->set_cookie_headers as $cookie) {\n" " echo $cookie . PHP_EOL;\n" @@ -4213,7 +4714,7 @@ static const char* swoole_library_source_core_curl_handler = " }\n" "\n" " if ($this->writeFunction) {\n" - " if (!is_callable($this->writeFunction)) {\n" + " if (!is_callable($this->writeFunction)) { // @phpstan-ignore booleanNot.alwaysFalse\n" " trigger_error('curl_exec(): Could not call the CURLOPT_WRITEFUNCTION', E_USER_WARNING);\n" " $this->setError(CURLE_WRITE_ERROR, 'Failure writing output to destination');\n" " return false;\n" @@ -4233,18 +4734,18 @@ static const char* swoole_library_source_core_curl_handler = " return true;\n" " }\n" "\n" - " \n" + " /* ====== Redirect helper ====== */\n" "\n" " private static function unparseUrl(array $parsedUrl): string\n" " {\n" - " $scheme = ($parsedUrl['scheme'] ?? 'http') . '://';\n" - " $host = $parsedUrl['host'] ?? '';\n" - " $port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';\n" - " $user = $parsedUrl['user'] ?? '';\n" - " $pass = isset($parsedUrl['pass']) ? ':' . $parsedUrl['pass'] : '';\n" - " $pass = ($user or $pass) ? \"{$pass}@\" : '';\n" - " $path = $parsedUrl['path'] ?? '';\n" - " $query = (isset($parsedUrl['query']) and $parsedUrl['query'] !== '') ? '?' . $parsedUrl['query'] : '';\n" + " $scheme = ($parsedUrl['scheme'] ?? 'http') . '://';\n" + " $host = $parsedUrl['host'] ?? '';\n" + " $port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';\n" + " $user = $parsedUrl['user'] ?? '';\n" + " $pass = isset($parsedUrl['pass']) ? ':' . $parsedUrl['pass'] : '';\n" + " $pass = ($user or $pass) ? \"{$pass}@\" : '';\n" + " $path = $parsedUrl['path'] ?? '';\n" + " $query = (isset($parsedUrl['query']) and $parsedUrl['query'] !== '') ? '?' . $parsedUrl['query'] : '';\n" " $fragment = isset($parsedUrl['fragment']) ? '#' . $parsedUrl['fragment'] : '';\n" " return $scheme . $user . $pass . $host . $port . $path . $query . $fragment;\n" " }\n" @@ -4258,7 +4759,7 @@ static const char* swoole_library_source_core_curl_handler = " if (!isset($location[0])) {\n" " return [];\n" " }\n" - " $redirectUri = $this->urlInfo;\n" + " $redirectUri = $this->urlInfo;\n" " $redirectUri['query'] = '';\n" " if ($location[0] === '/') {\n" " $redirectUri['path'] = $location;\n" @@ -4267,7 +4768,7 @@ static const char* swoole_library_source_core_curl_handler = " if ($path === '.') {\n" " $path = '/';\n" " }\n" - " if (isset($location[1]) and substr($location, 0, 2) === './') {\n" + " if (isset($location[1]) and str_starts_with($location, './')) {\n" " $location = substr($location, 2);\n" " }\n" " $redirectUri['path'] = $path . $location;\n" @@ -4286,28 +4787,47 @@ static const char* swoole_library_source_core_curl_handler = static const char* swoole_library_source_core_fast_cgi = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "\n" + "/**\n" + " * FastCGI constants.\n" + " */\n" "class FastCGI\n" "{\n" - " \n" + " /**\n" + " * Number of bytes in a FCGI_Header. Future versions of the protocol\n" + " * will not reduce this number.\n" + " */\n" " public const HEADER_LEN = 8;\n" "\n" - " \n" + " /**\n" + " * Format of FCGI_HEADER for unpacking in PHP\n" + " */\n" " public const HEADER_FORMAT = 'Cversion/Ctype/nrequestId/ncontentLength/CpaddingLength/Creserved';\n" "\n" - " \n" + " /**\n" + " * Max content length of a record\n" + " */\n" " public const MAX_CONTENT_LENGTH = 65535;\n" "\n" - " \n" + " /**\n" + " * Value for version component of FCGI_Header\n" + " */\n" " public const VERSION_1 = 1;\n" "\n" - " \n" + " /**\n" + " * Values for type component of FCGI_Header\n" + " */\n" " public const BEGIN_REQUEST = 1;\n" "\n" " public const ABORT_REQUEST = 2;\n" @@ -4330,20 +4850,28 @@ static const char* swoole_library_source_core_fast_cgi = "\n" " public const UNKNOWN_TYPE = 11;\n" "\n" - " \n" + " /**\n" + " * Value for requestId component of FCGI_Header\n" + " */\n" " public const DEFAULT_REQUEST_ID = 1;\n" "\n" - " \n" + " /**\n" + " * Mask for flags component of FCGI_BeginRequestBody\n" + " */\n" " public const KEEP_CONN = 1;\n" "\n" - " \n" + " /**\n" + " * Values for role component of FCGI_BeginRequestBody\n" + " */\n" " public const RESPONDER = 1;\n" "\n" " public const AUTHORIZER = 2;\n" "\n" " public const FILTER = 3;\n" "\n" - " \n" + " /**\n" + " * Values for protocolStatus component of FCGI_EndRequestBody\n" + " */\n" " public const REQUEST_COMPLETE = 0;\n" "\n" " public const CANT_MPX_CONN = 1;\n" @@ -4355,7 +4883,13 @@ static const char* swoole_library_source_core_fast_cgi = static const char* swoole_library_source_core_fast_cgi_record = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4363,34 +4897,54 @@ static const char* swoole_library_source_core_fast_cgi_record = "\n" "use Swoole\\FastCGI;\n" "\n" - "\n" - "class Record\n" + "/**\n" + " * FastCGI record.\n" + " */\n" + "class Record implements \\Stringable\n" "{\n" - " \n" - " protected $version = FastCGI::VERSION_1;\n" - "\n" - " \n" - " protected $type = FastCGI::UNKNOWN_TYPE;\n" - "\n" - " \n" - " protected $requestId = FastCGI::DEFAULT_REQUEST_ID;\n" - "\n" - " \n" - " protected $reserved = 0;\n" - "\n" - " \n" - " private $contentLength = 0;\n" - "\n" - " \n" - " private $paddingLength = 0;\n" - "\n" - " \n" - " private $contentData = '';\n" - "\n" - " \n" - " private $paddingData = '';\n" - "\n" - " \n" + " /**\n" + " * Identifies the FastCGI protocol version.\n" + " */\n" + " protected int $version = FastCGI::VERSION_1;\n" + "\n" + " /**\n" + " * Identifies the FastCGI record type, i.e. the general function that the record performs.\n" + " */\n" + " protected int $type = FastCGI::UNKNOWN_TYPE;\n" + "\n" + " /**\n" + " * Identifies the FastCGI request to which the record belongs.\n" + " */\n" + " protected int $requestId = FastCGI::DEFAULT_REQUEST_ID;\n" + "\n" + " /**\n" + " * Reserved byte for future proposes\n" + " */\n" + " protected int $reserved = 0;\n" + "\n" + " /**\n" + " * The number of bytes in the contentData component of the record.\n" + " */\n" + " private int $contentLength = 0;\n" + "\n" + " /**\n" + " * The number of bytes in the paddingData component of the record.\n" + " */\n" + " private int $paddingLength = 0;\n" + "\n" + " /**\n" + " * Binary data, between 0 and 65535 bytes of data, interpreted according to the record type.\n" + " */\n" + " private string $contentData = '';\n" + "\n" + " /**\n" + " * Padding data, between 0 and 255 bytes of data, which are ignored.\n" + " */\n" + " private string $paddingData = '';\n" + "\n" + " /**\n" + " * Returns the binary message representation of record\n" + " */\n" " final public function __toString(): string\n" " {\n" " $headerPacket = pack(\n" @@ -4409,98 +4963,139 @@ static const char* swoole_library_source_core_fast_cgi_record = " return $headerPacket . $payloadPacket . $paddingPacket;\n" " }\n" "\n" - " \n" - " final public static function unpack(string $data): self\n" + " /**\n" + " * Unpacks the message from the binary data buffer\n" + " */\n" + " final public static function unpack(string $binaryData): static\n" " {\n" - " $self = new static();\n" + " /** @var static $self */\n" + " $self = (new \\ReflectionClass(static::class))->newInstanceWithoutConstructor();\n" + "\n" + " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int, reserved: int} */\n" + " $packet = unpack(FastCGI::HEADER_FORMAT, $binaryData);\n" + " if ($packet === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" " [\n" " $self->version,\n" " $self->type,\n" " $self->requestId,\n" " $self->contentLength,\n" " $self->paddingLength,\n" - " $self->reserved\n" - " ] = array_values(unpack(FastCGI::HEADER_FORMAT, $data));\n" + " $self->reserved,\n" + " ] = array_values($packet);\n" "\n" - " $payload = substr($data, FastCGI::HEADER_LEN);\n" + " $payload = substr($binaryData, FastCGI::HEADER_LEN);\n" " self::unpackPayload($self, $payload);\n" - " if (get_called_class() !== __CLASS__ && $self->contentLength > 0) {\n" + " if (static::class !== self::class && $self->contentLength > 0) {\n" " static::unpackPayload($self, $payload);\n" " }\n" "\n" " return $self;\n" " }\n" "\n" - " \n" + " /**\n" + " * Sets the content data and adjusts the length fields\n" + " *\n" + " * @return static\n" + " */\n" " public function setContentData(string $data): self\n" " {\n" " $this->contentLength = strlen($data);\n" " if ($this->contentLength > FastCGI::MAX_CONTENT_LENGTH) {\n" " $this->contentLength = FastCGI::MAX_CONTENT_LENGTH;\n" - " $this->contentData = substr($data, 0, FastCGI::MAX_CONTENT_LENGTH);\n" + " $this->contentData = substr($data, 0, FastCGI::MAX_CONTENT_LENGTH);\n" " } else {\n" " $this->contentData = $data;\n" " }\n" - " $extraLength = $this->contentLength % 8;\n" + " $extraLength = $this->contentLength % 8;\n" " $this->paddingLength = $extraLength ? (8 - $extraLength) : 0;\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the context data from the record\n" + " */\n" " public function getContentData(): string\n" " {\n" " return $this->contentData;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the version of record\n" + " */\n" " public function getVersion(): int\n" " {\n" " return $this->version;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns record type\n" + " */\n" " public function getType(): int\n" " {\n" " return $this->type;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns request ID\n" + " */\n" " public function getRequestId(): int\n" " {\n" " return $this->requestId;\n" " }\n" "\n" - " \n" + " /**\n" + " * Sets request ID\n" + " *\n" + " * There should be only one unique ID for all active requests,\n" + " * use random number or preferably resetting auto-increment.\n" + " *\n" + " * @return static\n" + " */\n" " public function setRequestId(int $requestId): self\n" " {\n" " $this->requestId = $requestId;\n" " return $this;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the size of content length\n" + " */\n" " final public function getContentLength(): int\n" " {\n" " return $this->contentLength;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the size of padding length\n" + " */\n" " final public function getPaddingLength(): int\n" " {\n" " return $this->paddingLength;\n" " }\n" "\n" - " \n" - " protected static function unpackPayload($self, string $data): void\n" + " /**\n" + " * Method to unpack the payload for the record.\n" + " *\n" + " * NB: Default implementation will be always called\n" + " */\n" + " protected static function unpackPayload(self $self, string $binaryData): void\n" " {\n" + " /** @phpstan-var false|array{contentData: string, paddingData: string} */\n" + " $payload = unpack(\"a{$self->contentLength}contentData/a{$self->paddingLength}paddingData\", $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" " [\n" " $self->contentData,\n" - " $self->paddingData\n" - " ] = array_values(\n" - " unpack(\"a{$self->contentLength}contentData/a{$self->paddingLength}paddingData\", $data)\n" - " );\n" + " $self->paddingData,\n" + " ] = array_values($payload);\n" " }\n" "\n" - " \n" + " /**\n" + " * Implementation of packing the payload\n" + " */\n" " protected function packPayload(): string\n" " {\n" " return pack(\"a{$this->contentLength}\", $this->contentData);\n" @@ -4509,7 +5104,13 @@ static const char* swoole_library_source_core_fast_cgi_record = static const char* swoole_library_source_core_fast_cgi_record_params = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4518,76 +5119,116 @@ static const char* swoole_library_source_core_fast_cgi_record_params = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Params request record\n" + " */\n" "class Params extends Record\n" "{\n" - " \n" - " protected $values = [];\n" - "\n" - " \n" - " public function __construct(array $values = [])\n" - " {\n" - " $this->type = FastCGI::PARAMS;\n" + " /**\n" + " * List of params\n" + " *\n" + " * @var string[]\n" + " * @phpstan-var array\n" + " */\n" + " protected array $values = [];\n" + "\n" + " /**\n" + " * Constructs a param request\n" + " *\n" + " * @phpstan-param array $values\n" + " */\n" + " public function __construct(array $values)\n" + " {\n" + " $this->type = FastCGI::PARAMS;\n" " $this->values = $values;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns an associative list of parameters\n" + " *\n" + " * @phpstan-return array\n" + " */\n" " public function getValues(): array\n" " {\n" " return $this->values;\n" " }\n" "\n" - " \n" - " protected static function unpackPayload($self, string $data): void\n" + " /**\n" + " * {@inheritdoc}\n" + " * @param static $self\n" + " */\n" + " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" + " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" " $currentOffset = 0;\n" " do {\n" - " [$nameLengthHigh] = array_values(unpack('CnameLengthHigh', $data));\n" - " $isLongName = ($nameLengthHigh >> 7 == 1);\n" - " $valueOffset = $isLongName ? 4 : 1;\n" - "\n" - " [$valueLengthHigh] = array_values(unpack('CvalueLengthHigh', substr($data, $valueOffset)));\n" - " $isLongValue = ($valueLengthHigh >> 7 == 1);\n" - " $dataOffset = $valueOffset + ($isLongValue ? 4 : 1);\n" + " /** @phpstan-var false|array{nameLengthHigh: int} */\n" + " $payload = unpack('CnameLengthHigh', $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" + " [$nameLengthHigh] = array_values($payload);\n" + " $isLongName = ($nameLengthHigh >> 7 == 1);\n" + " $valueOffset = $isLongName ? 4 : 1;\n" + "\n" + " /** @phpstan-var false|array{valueLengthHigh: int} */\n" + " $payload = unpack('CvalueLengthHigh', substr($binaryData, $valueOffset));\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" + " [$valueLengthHigh] = array_values($payload);\n" + " $isLongValue = ($valueLengthHigh >> 7 == 1);\n" + " $dataOffset = $valueOffset + ($isLongValue ? 4 : 1);\n" "\n" " $formatParts = [\n" " $isLongName ? 'NnameLength' : 'CnameLength',\n" " $isLongValue ? 'NvalueLength' : 'CvalueLength',\n" " ];\n" - " $format = join('/', $formatParts);\n" - " [$nameLength, $valueLength] = array_values(unpack($format, $data));\n" + " $format = join('/', $formatParts);\n" + "\n" + " /** @phpstan-var false|array{nameLength: int, valueLength: int} */\n" + " $payload = unpack($format, $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" + " [$nameLength, $valueLength] = array_values($payload);\n" "\n" - " \n" + " // Clear top bit for long record\n" " $nameLength &= ($isLongName ? 0x7FFFFFFF : 0x7F);\n" " $valueLength &= ($isLongValue ? 0x7FFFFFFF : 0x7F);\n" "\n" - " [$nameData, $valueData] = array_values(\n" - " unpack(\n" - " \"a{$nameLength}nameData/a{$valueLength}valueData\",\n" - " substr($data, $dataOffset)\n" - " )\n" + " /** @phpstan-var false|array{nameData: string, valueData: string} */\n" + " $payload = unpack(\n" + " \"a{$nameLength}nameData/a{$valueLength}valueData\",\n" + " substr($binaryData, $dataOffset)\n" " );\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" + " [$nameData, $valueData] = array_values($payload);\n" "\n" " $self->values[$nameData] = $valueData;\n" "\n" " $keyValueLength = $dataOffset + $nameLength + $valueLength;\n" - " $data = substr($data, $keyValueLength);\n" + " $binaryData = substr($binaryData, $keyValueLength);\n" " $currentOffset += $keyValueLength;\n" " } while ($currentOffset < $self->getContentLength());\n" " }\n" "\n" - " \n" + " /**\n" + " * {@inheritdoc}\n" + " */\n" " protected function packPayload(): string\n" " {\n" " $payload = '';\n" " foreach ($this->values as $nameData => $valueData) {\n" - " if ($valueData === null) {\n" + " if ($valueData === null) { // @phpstan-ignore identical.alwaysFalse\n" " continue;\n" " }\n" - " $nameLength = strlen($nameData);\n" + " $nameLength = strlen($nameData);\n" " $valueLength = strlen((string) $valueData);\n" - " $isLongName = $nameLength > 127;\n" + " $isLongName = $nameLength > 127;\n" " $isLongValue = $valueLength > 127;\n" " $formatParts = [\n" " $isLongName ? 'N' : 'C',\n" @@ -4595,6 +5236,7 @@ static const char* swoole_library_source_core_fast_cgi_record_params = " \"a{$nameLength}\",\n" " \"a{$valueLength}\",\n" " ];\n" + "\n" " $format = join('', $formatParts);\n" "\n" " $payload .= pack(\n" @@ -4612,7 +5254,13 @@ static const char* swoole_library_source_core_fast_cgi_record_params = static const char* swoole_library_source_core_fast_cgi_record_abort_request = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4621,10 +5269,12 @@ static const char* swoole_library_source_core_fast_cgi_record_abort_request = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * The Web server sends a FCGI_ABORT_REQUEST record to abort a request\n" + " */\n" "class AbortRequest extends Record\n" "{\n" - " public function __construct(int $requestId = 0)\n" + " public function __construct(int $requestId)\n" " {\n" " $this->type = FastCGI::ABORT_REQUEST;\n" " $this->setRequestId($requestId);\n" @@ -4633,7 +5283,13 @@ static const char* swoole_library_source_core_fast_cgi_record_abort_request = static const char* swoole_library_source_core_fast_cgi_record_begin_request = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4642,50 +5298,94 @@ static const char* swoole_library_source_core_fast_cgi_record_begin_request = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * The Web server sends a FCGI_BEGIN_REQUEST record to start a request.\n" + " */\n" "class BeginRequest extends Record\n" "{\n" - " \n" - " protected $role = FastCGI::UNKNOWN_ROLE;\n" - "\n" - " \n" - " protected $flags;\n" - "\n" - " \n" - " protected $reserved1;\n" + " /**\n" + " * The role component sets the role the Web server expects the application to play.\n" + " * The currently-defined roles are:\n" + " * FCGI_RESPONDER\n" + " * FCGI_AUTHORIZER\n" + " * FCGI_FILTER\n" + " */\n" + " protected int $role = FastCGI::UNKNOWN_ROLE;\n" + "\n" + " /**\n" + " * The flags component contains a bit that controls connection shutdown.\n" + " *\n" + " * flags & FCGI_KEEP_CONN:\n" + " * If zero, the application closes the connection after responding to this request.\n" + " * If not zero, the application does not close the connection after responding to this request;\n" + " * the Web server retains responsibility for the connection.\n" + " */\n" + " protected int $flags;\n" + "\n" + " /**\n" + " * Reserved data, 5 bytes maximum\n" + " */\n" + " protected string $reserved1;\n" "\n" " public function __construct(int $role = FastCGI::UNKNOWN_ROLE, int $flags = 0, string $reserved = '')\n" " {\n" - " $this->type = FastCGI::BEGIN_REQUEST;\n" - " $this->role = $role;\n" - " $this->flags = $flags;\n" + " $this->type = FastCGI::BEGIN_REQUEST;\n" + " $this->role = $role;\n" + " $this->flags = $flags;\n" " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the role\n" + " *\n" + " * The role component sets the role the Web server expects the application to play.\n" + " * The currently-defined roles are:\n" + " * FCGI_RESPONDER\n" + " * FCGI_AUTHORIZER\n" + " * FCGI_FILTER\n" + " */\n" " public function getRole(): int\n" " {\n" " return $this->role;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the flags\n" + " *\n" + " * The flags component contains a bit that controls connection shutdown.\n" + " *\n" + " * flags & FCGI_KEEP_CONN:\n" + " * If zero, the application closes the connection after responding to this request.\n" + " * If not zero, the application does not close the connection after responding to this request;\n" + " * the Web server retains responsibility for the connection.\n" + " */\n" " public function getFlags(): int\n" " {\n" " return $this->flags;\n" " }\n" "\n" - " \n" - " protected static function unpackPayload($self, string $data): void\n" + " /**\n" + " * {@inheritdoc}\n" + " * @param static $self\n" + " */\n" + " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" + " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" + "\n" + " /** @phpstan-var false|array{role: int, flags: int, reserved: string} */\n" + " $payload = unpack('nrole/Cflags/a5reserved', $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" " [\n" " $self->role,\n" " $self->flags,\n" - " $self->reserved1\n" - " ] = array_values(unpack('nrole/Cflags/a5reserved', $data));\n" + " $self->reserved1,\n" + " ] = array_values($payload);\n" " }\n" "\n" - " \n" + " /** {@inheritdoc} */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" @@ -4699,7 +5399,13 @@ static const char* swoole_library_source_core_fast_cgi_record_begin_request = static const char* swoole_library_source_core_fast_cgi_record_data = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4708,10 +5414,14 @@ static const char* swoole_library_source_core_fast_cgi_record_data = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Data binary stream\n" + " *\n" + " * FCGI_DATA is a second stream record type used to send additional data to the application.\n" + " */\n" "class Data extends Record\n" "{\n" - " public function __construct(string $contentData = '')\n" + " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::DATA;\n" " $this->setContentData($contentData);\n" @@ -4720,7 +5430,13 @@ static const char* swoole_library_source_core_fast_cgi_record_data = static const char* swoole_library_source_core_fast_cgi_record_end_request = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4729,53 +5445,95 @@ static const char* swoole_library_source_core_fast_cgi_record_end_request = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * The application sends a FCGI_END_REQUEST record to terminate a request, either because the application\n" + " * has processed the request or because the application has rejected the request.\n" + " */\n" "class EndRequest extends Record\n" "{\n" - " \n" - " protected $appStatus = 0;\n" - "\n" - " \n" - " protected $protocolStatus = FastCGI::REQUEST_COMPLETE;\n" - "\n" - " \n" - " protected $reserved1;\n" - "\n" - " public function __construct(\n" - " int $protocolStatus = FastCGI::REQUEST_COMPLETE,\n" - " int $appStatus = 0,\n" - " string $reserved = ''\n" - " ) {\n" - " $this->type = FastCGI::END_REQUEST;\n" + " /**\n" + " * The appStatus component is an application-level status code. Each role documents its usage of appStatus.\n" + " */\n" + " protected int $appStatus = 0;\n" + "\n" + " /**\n" + " * The protocolStatus component is a protocol-level status code.\n" + " *\n" + " * The possible protocolStatus values are:\n" + " * FCGI_REQUEST_COMPLETE: normal end of request.\n" + " * FCGI_CANT_MPX_CONN: rejecting a new request.\n" + " * This happens when a Web server sends concurrent requests over one connection to an application that is\n" + " * designed to process one request at a time per connection.\n" + " * FCGI_OVERLOADED: rejecting a new request.\n" + " * This happens when the application runs out of some resource, e.g. database connections.\n" + " * FCGI_UNKNOWN_ROLE: rejecting a new request.\n" + " * This happens when the Web server has specified a role that is unknown to the application.\n" + " */\n" + " protected int $protocolStatus = FastCGI::REQUEST_COMPLETE;\n" + "\n" + " /**\n" + " * Reserved data, 3 bytes maximum\n" + " */\n" + " protected string $reserved1;\n" + "\n" + " public function __construct(int $protocolStatus = FastCGI::REQUEST_COMPLETE, int $appStatus = 0, string $reserved = '')\n" + " {\n" + " $this->type = FastCGI::END_REQUEST;\n" " $this->protocolStatus = $protocolStatus;\n" - " $this->appStatus = $appStatus;\n" - " $this->reserved1 = $reserved;\n" + " $this->appStatus = $appStatus;\n" + " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns app status\n" + " *\n" + " * The appStatus component is an application-level status code. Each role documents its usage of appStatus.\n" + " */\n" " public function getAppStatus(): int\n" " {\n" " return $this->appStatus;\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the protocol status\n" + " *\n" + " * The possible protocolStatus values are:\n" + " * FCGI_REQUEST_COMPLETE: normal end of request.\n" + " * FCGI_CANT_MPX_CONN: rejecting a new request.\n" + " * This happens when a Web server sends concurrent requests over one connection to an application that is\n" + " * designed to process one request at a time per connection.\n" + " * FCGI_OVERLOADED: rejecting a new request.\n" + " * This happens when the application runs out of some resource, e.g. database connections.\n" + " * FCGI_UNKNOWN_ROLE: rejecting a new request.\n" + " * This happens when the Web server has specified a role that is unknown to the application.\n" + " */\n" " public function getProtocolStatus(): int\n" " {\n" " return $this->protocolStatus;\n" " }\n" "\n" - " \n" - " protected static function unpackPayload($self, string $data): void\n" + " /**\n" + " * {@inheritdoc}\n" + " * @param static $self\n" + " */\n" + " protected static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" + " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" + "\n" + " /** @phpstan-var false|array{appStatus: int, protocolStatus: int, reserved: string} */\n" + " $payload = unpack('NappStatus/CprotocolStatus/a3reserved', $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" " [\n" " $self->appStatus,\n" " $self->protocolStatus,\n" - " $self->reserved1\n" - " ] = array_values(unpack('NappStatus/CprotocolStatus/a3reserved', $data));\n" + " $self->reserved1,\n" + " ] = array_values($payload);\n" " }\n" "\n" - " \n" + " /** {@inheritdoc} */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" @@ -4789,7 +5547,13 @@ static const char* swoole_library_source_core_fast_cgi_record_end_request = static const char* swoole_library_source_core_fast_cgi_record_get_values = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4797,11 +5561,36 @@ static const char* swoole_library_source_core_fast_cgi_record_get_values = "\n" "use Swoole\\FastCGI;\n" "\n" - "\n" + "/**\n" + " * GetValues API\n" + " *\n" + " * The Web server can query specific variables within the application.\n" + " * The server will typically perform a query on application startup in order to to automate certain aspects of\n" + " * system configuration.\n" + " *\n" + " * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied.\n" + " * If the application doesn't understand a variable name that was included in the query, it omits that name from\n" + " * the response.\n" + " *\n" + " * FCGI_GET_VALUES is designed to allow an open-ended set of variables.\n" + " *\n" + " * The initial set provides information to help the server perform application and connection management:\n" + " * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept,\n" + " * e.g. \"1\" or \"10\".\n" + " * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. \"1\" or \"50\".\n" + " * FCGI_MPXS_CONNS: \"0\" if this application does not multiplex connections (i.e. handle concurrent requests\n" + " * over each connection), \"1\" otherwise.\n" + " */\n" "class GetValues extends Params\n" "{\n" - " \n" - " public function __construct(array $keys = [])\n" + " /**\n" + " * Constructs a request\n" + " *\n" + " * @param array $keys List of keys to receive\n" + " *\n" + " * @phpstan-param list $keys\n" + " */\n" + " public function __construct(array $keys)\n" " {\n" " parent::__construct(array_fill_keys($keys, ''));\n" " $this->type = FastCGI::GET_VALUES;\n" @@ -4810,7 +5599,13 @@ static const char* swoole_library_source_core_fast_cgi_record_get_values = static const char* swoole_library_source_core_fast_cgi_record_get_values_result = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4818,11 +5613,34 @@ static const char* swoole_library_source_core_fast_cgi_record_get_values_result "\n" "use Swoole\\FastCGI;\n" "\n" - "\n" + "/**\n" + " * GetValues API\n" + " *\n" + " * The Web server can query specific variables within the application.\n" + " * The server will typically perform a query on application startup in order to to automate certain aspects of\n" + " * system configuration.\n" + " *\n" + " * The application responds by sending a record {FCGI_GET_VALUES_RESULT, 0, ...} with the values supplied.\n" + " * If the application doesn't understand a variable name that was included in the query, it omits that name from\n" + " * the response.\n" + " *\n" + " * FCGI_GET_VALUES is designed to allow an open-ended set of variables.\n" + " *\n" + " * The initial set provides information to help the server perform application and connection management:\n" + " * FCGI_MAX_CONNS: The maximum number of concurrent transport connections this application will accept,\n" + " * e.g. \"1\" or \"10\".\n" + " * FCGI_MAX_REQS: The maximum number of concurrent requests this application will accept, e.g. \"1\" or \"50\".\n" + " * FCGI_MPXS_CONNS: \"0\" if this application does not multiplex connections (i.e. handle concurrent requests\n" + " * over each connection), \"1\" otherwise.\n" + " */\n" "class GetValuesResult extends Params\n" "{\n" - " \n" - " public function __construct(array $values = [])\n" + " /**\n" + " * Constructs a param request\n" + " *\n" + " * @phpstan-param array $values\n" + " */\n" + " public function __construct(array $values)\n" " {\n" " parent::__construct($values);\n" " $this->type = FastCGI::GET_VALUES_RESULT;\n" @@ -4831,7 +5649,13 @@ static const char* swoole_library_source_core_fast_cgi_record_get_values_result static const char* swoole_library_source_core_fast_cgi_record_stdin = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4840,10 +5664,14 @@ static const char* swoole_library_source_core_fast_cgi_record_stdin = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Stdin binary stream\n" + " *\n" + " * FCGI_STDIN is a stream record type used in sending arbitrary data from the Web server to the application\n" + " */\n" "class Stdin extends Record\n" "{\n" - " public function __construct(string $contentData = '')\n" + " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDIN;\n" " $this->setContentData($contentData);\n" @@ -4852,7 +5680,13 @@ static const char* swoole_library_source_core_fast_cgi_record_stdin = static const char* swoole_library_source_core_fast_cgi_record_stdout = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4861,10 +5695,14 @@ static const char* swoole_library_source_core_fast_cgi_record_stdout = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Stdout binary stream\n" + " *\n" + " * FCGI_STDOUT is a stream record for sending arbitrary data from the application to the Web server\n" + " */\n" "class Stdout extends Record\n" "{\n" - " public function __construct(string $contentData = '')\n" + " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDOUT;\n" " $this->setContentData($contentData);\n" @@ -4873,7 +5711,13 @@ static const char* swoole_library_source_core_fast_cgi_record_stdout = static const char* swoole_library_source_core_fast_cgi_record_stderr = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4882,10 +5726,14 @@ static const char* swoole_library_source_core_fast_cgi_record_stderr = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Stderr binary stream\n" + " *\n" + " * FCGI_STDERR is a stream record for sending arbitrary data from the application to the Web server\n" + " */\n" "class Stderr extends Record\n" "{\n" - " public function __construct(string $contentData = '')\n" + " public function __construct(string $contentData)\n" " {\n" " $this->type = FastCGI::STDERR;\n" " $this->setContentData($contentData);\n" @@ -4894,7 +5742,13 @@ static const char* swoole_library_source_core_fast_cgi_record_stderr = static const char* swoole_library_source_core_fast_cgi_record_unknown_type = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -4903,36 +5757,61 @@ static const char* swoole_library_source_core_fast_cgi_record_unknown_type = "use Swoole\\FastCGI;\n" "use Swoole\\FastCGI\\Record;\n" "\n" - "\n" + "/**\n" + " * Record for unknown queries\n" + " *\n" + " * The set of management record types is likely to grow in future versions of this protocol.\n" + " * To provide for this evolution, the protocol includes the FCGI_UNKNOWN_TYPE management record.\n" + " * When an application receives a management record whose type T it does not understand, the application responds\n" + " * with {FCGI_UNKNOWN_TYPE, 0, {T}}.\n" + " */\n" "class UnknownType extends Record\n" "{\n" - " \n" - " protected $type1;\n" + " /**\n" + " * Type of the unrecognized management record.\n" + " */\n" + " protected int $type1;\n" "\n" - " \n" - " protected $reserved1;\n" + " /**\n" + " * Reserved data, 7 bytes maximum\n" + " */\n" + " protected string $reserved1;\n" "\n" - " public function __construct(int $type = 0, string $reserved = '')\n" + " public function __construct(int $type, string $reserved = '')\n" " {\n" - " $this->type = FastCGI::UNKNOWN_TYPE;\n" - " $this->type1 = $type;\n" + " $this->type = FastCGI::UNKNOWN_TYPE;\n" + " $this->type1 = $type;\n" " $this->reserved1 = $reserved;\n" " $this->setContentData($this->packPayload());\n" " }\n" "\n" - " \n" + " /**\n" + " * Returns the unrecognized type\n" + " */\n" " public function getUnrecognizedType(): int\n" " {\n" " return $this->type1;\n" " }\n" "\n" - " \n" - " public static function unpackPayload($self, string $data): void\n" + " /**\n" + " * {@inheritdoc}\n" + " * @param static $self\n" + " */\n" + " public static function unpackPayload(Record $self, string $binaryData): void\n" " {\n" - " [$self->type1, $self->reserved1] = array_values(unpack('Ctype/a7reserved', $data));\n" + " assert($self instanceof self); // @phpstan-ignore function.alreadyNarrowedType,instanceof.alwaysTrue\n" + "\n" + " /** @phpstan-var false|array{type: int, reserved: string} */\n" + " $payload = unpack('Ctype/a7reserved', $binaryData);\n" + " if ($payload === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" + " [$self->type1, $self->reserved1] = array_values($payload);\n" " }\n" "\n" - " \n" + " /**\n" + " * {@inheritdoc}\n" + " */\n" " protected function packPayload(): string\n" " {\n" " return pack(\n" @@ -4945,43 +5824,70 @@ static const char* swoole_library_source_core_fast_cgi_record_unknown_type = static const char* swoole_library_source_core_fast_cgi_frame_parser = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" - "use DomainException;\n" - "use RuntimeException;\n" "use Swoole\\FastCGI;\n" + "use Swoole\\FastCGI\\Record\\AbortRequest;\n" + "use Swoole\\FastCGI\\Record\\BeginRequest;\n" + "use Swoole\\FastCGI\\Record\\Data;\n" + "use Swoole\\FastCGI\\Record\\EndRequest;\n" + "use Swoole\\FastCGI\\Record\\GetValues;\n" + "use Swoole\\FastCGI\\Record\\GetValuesResult;\n" + "use Swoole\\FastCGI\\Record\\Params;\n" + "use Swoole\\FastCGI\\Record\\Stderr;\n" + "use Swoole\\FastCGI\\Record\\Stdin;\n" + "use Swoole\\FastCGI\\Record\\Stdout;\n" + "use Swoole\\FastCGI\\Record\\UnknownType;\n" "\n" - "\n" + "/**\n" + " * Utility class to simplify parsing of FastCGI protocol data.\n" + " */\n" "class FrameParser\n" "{\n" - " \n" - " protected static $classMapping = [\n" - " FastCGI::BEGIN_REQUEST => FastCGI\\Record\\BeginRequest::class,\n" - " FastCGI::ABORT_REQUEST => FastCGI\\Record\\AbortRequest::class,\n" - " FastCGI::END_REQUEST => FastCGI\\Record\\EndRequest::class,\n" - " FastCGI::PARAMS => FastCGI\\Record\\Params::class,\n" - " FastCGI::STDIN => FastCGI\\Record\\Stdin::class,\n" - " FastCGI::STDOUT => FastCGI\\Record\\Stdout::class,\n" - " FastCGI::STDERR => FastCGI\\Record\\Stderr::class,\n" - " FastCGI::DATA => FastCGI\\Record\\Data::class,\n" - " FastCGI::GET_VALUES => FastCGI\\Record\\GetValues::class,\n" - " FastCGI::GET_VALUES_RESULT => FastCGI\\Record\\GetValuesResult::class,\n" - " FastCGI::UNKNOWN_TYPE => FastCGI\\Record\\UnknownType::class,\n" + " /**\n" + " * Mapping of constants to the classes\n" + " *\n" + " * @phpstan-var array\n" + " */\n" + " protected static array $classMapping = [\n" + " FastCGI::BEGIN_REQUEST => BeginRequest::class,\n" + " FastCGI::ABORT_REQUEST => AbortRequest::class,\n" + " FastCGI::END_REQUEST => EndRequest::class,\n" + " FastCGI::PARAMS => Params::class,\n" + " FastCGI::STDIN => Stdin::class,\n" + " FastCGI::STDOUT => Stdout::class,\n" + " FastCGI::STDERR => Stderr::class,\n" + " FastCGI::DATA => Data::class,\n" + " FastCGI::GET_VALUES => GetValues::class,\n" + " FastCGI::GET_VALUES_RESULT => GetValuesResult::class,\n" + " FastCGI::UNKNOWN_TYPE => UnknownType::class,\n" " ];\n" "\n" - " \n" - " public static function hasFrame(string $buffer): bool\n" + " /**\n" + " * Checks if the buffer contains a valid frame to parse\n" + " */\n" + " public static function hasFrame(string $binaryBuffer): bool\n" " {\n" - " $bufferLength = strlen($buffer);\n" + " $bufferLength = strlen($binaryBuffer);\n" " if ($bufferLength < FastCGI::HEADER_LEN) {\n" " return false;\n" " }\n" "\n" - " $fastInfo = unpack(FastCGI::HEADER_FORMAT, $buffer);\n" + " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */\n" + " $fastInfo = unpack(FastCGI::HEADER_FORMAT, $binaryBuffer);\n" + " if ($fastInfo === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" + " }\n" " if ($bufferLength < FastCGI::HEADER_LEN + $fastInfo['contentLength'] + $fastInfo['paddingLength']) {\n" " return false;\n" " }\n" @@ -4989,25 +5895,33 @@ static const char* swoole_library_source_core_fast_cgi_frame_parser = " return true;\n" " }\n" "\n" - " \n" - " public static function parseFrame(string &$buffer): Record\n" + " /**\n" + " * Parses a frame from the binary buffer\n" + " *\n" + " * @return Record One of the corresponding FastCGI record\n" + " */\n" + " public static function parseFrame(string &$binaryBuffer): Record\n" " {\n" - " $bufferLength = strlen($buffer);\n" + " $bufferLength = strlen($binaryBuffer);\n" " if ($bufferLength < FastCGI::HEADER_LEN) {\n" - " throw new RuntimeException('Not enough data in the buffer to parse');\n" + " throw new \\RuntimeException('Not enough data in the buffer to parse');\n" + " }\n" + " /** @phpstan-var false|array{version: int, type: int, requestId: int, contentLength: int, paddingLength: int} */\n" + " $recordHeader = unpack(FastCGI::HEADER_FORMAT, $binaryBuffer);\n" + " if ($recordHeader === false) {\n" + " throw new \\RuntimeException('Can not unpack data from the binary buffer');\n" " }\n" - " $recordHeader = unpack(FastCGI::HEADER_FORMAT, $buffer);\n" " $recordType = $recordHeader['type'];\n" " if (!isset(self::$classMapping[$recordType])) {\n" - " throw new DomainException(\"Invalid FastCGI record type {$recordType} received\");\n" + " throw new \\DomainException(\"Invalid FastCGI record type {$recordType} received\");\n" " }\n" "\n" - " \n" + " /** @var Record $className */\n" " $className = self::$classMapping[$recordType];\n" - " $record = $className::unpack($buffer);\n" + " $record = $className::unpack($binaryBuffer);\n" "\n" - " $offset = FastCGI::HEADER_LEN + $record->getContentLength() + $record->getPaddingLength();\n" - " $buffer = substr($buffer, $offset);\n" + " $offset = FastCGI::HEADER_LEN + $record->getContentLength() + $record->getPaddingLength();\n" + " $binaryBuffer = substr($binaryBuffer, $offset);\n" "\n" " return $record;\n" " }\n" @@ -5015,7 +5929,13 @@ static const char* swoole_library_source_core_fast_cgi_frame_parser = static const char* swoole_library_source_core_fast_cgi_message = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -5023,27 +5943,24 @@ static const char* swoole_library_source_core_fast_cgi_message = "\n" "class Message\n" "{\n" - " \n" - " protected $params = [];\n" + " protected array $params = [];\n" "\n" - " \n" - " protected $body = '';\n" + " protected string $body = '';\n" "\n" - " \n" - " protected $error = '';\n" + " protected string $error = '';\n" "\n" " public function getParam(string $name): ?string\n" " {\n" " return $this->params[$name] ?? null;\n" " }\n" "\n" - " public function withParam(string $name, string $value): self\n" + " public function withParam(string $name, string $value): static\n" " {\n" " $this->params[$name] = $value;\n" " return $this;\n" " }\n" "\n" - " public function withoutParam(string $name): self\n" + " public function withoutParam(string $name): static\n" " {\n" " unset($this->params[$name]);\n" " return $this;\n" @@ -5054,13 +5971,13 @@ static const char* swoole_library_source_core_fast_cgi_message = " return $this->params;\n" " }\n" "\n" - " public function withParams(array $params): self\n" + " public function withParams(array $params): static\n" " {\n" " $this->params = $params;\n" " return $this;\n" " }\n" "\n" - " public function withAddedParams(array $params): self\n" + " public function withAddedParams(array $params): static\n" " {\n" " $this->params = $params + $this->params;\n" " return $this;\n" @@ -5071,7 +5988,7 @@ static const char* swoole_library_source_core_fast_cgi_message = " return $this->body;\n" " }\n" "\n" - " public function withBody($body): self\n" + " public function withBody(string|\\Stringable $body): self\n" " {\n" " $this->body = (string) $body;\n" " return $this;\n" @@ -5082,7 +5999,7 @@ static const char* swoole_library_source_core_fast_cgi_message = " return $this->error;\n" " }\n" "\n" - " public function withError(string $error): self\n" + " public function withError(string $error): static\n" " {\n" " $this->error = $error;\n" " return $this;\n" @@ -5091,7 +6008,13 @@ static const char* swoole_library_source_core_fast_cgi_message = static const char* swoole_library_source_core_fast_cgi_request = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -5102,18 +6025,18 @@ static const char* swoole_library_source_core_fast_cgi_request = "use Swoole\\FastCGI\\Record\\Params;\n" "use Swoole\\FastCGI\\Record\\Stdin;\n" "\n" - "class Request extends Message\n" + "class Request extends Message implements \\Stringable\n" "{\n" - " protected $keepConn = false;\n" + " protected bool $keepConn = false;\n" "\n" " public function __toString(): string\n" " {\n" - " $body = $this->getBody();\n" - " $beginRequestFrame = new BeginRequest(FastCGI::RESPONDER, ($this->keepConn ? FastCGI::KEEP_CONN : 0));\n" - " $paramsFrame = new Params($this->getParams());\n" - " $paramsEofFrame = new Params();\n" + " $body = $this->getBody();\n" + " $beginRequestFrame = new BeginRequest(FastCGI::RESPONDER, $this->keepConn ? FastCGI::KEEP_CONN : 0);\n" + " $paramsFrame = new Params($this->getParams());\n" + " $paramsEofFrame = new Params([]);\n" " if (empty($body)) {\n" - " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}}\";\n" + " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}\";\n" " } else {\n" " $stdinList = [];\n" " while (true) {\n" @@ -5124,9 +6047,9 @@ static const char* swoole_library_source_core_fast_cgi_request = " }\n" " $body = substr($body, $stdinLength);\n" " }\n" - " $stdinList[] = new Stdin();\n" - " $stdin = implode($stdinList);\n" - " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}{$stdin}}\";\n" + " $stdinList[] = new Stdin('');\n" + " $stdin = implode('', $stdinList);\n" + " $message = \"{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}{$stdin}\";\n" " }\n" " return $message;\n" " }\n" @@ -5145,26 +6068,34 @@ static const char* swoole_library_source_core_fast_cgi_request = static const char* swoole_library_source_core_fast_cgi_response = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" - "use InvalidArgumentException;\n" "use Swoole\\FastCGI\\Record\\EndRequest;\n" "use Swoole\\FastCGI\\Record\\Stderr;\n" "use Swoole\\FastCGI\\Record\\Stdout;\n" "\n" "class Response extends Message\n" "{\n" - " public function __construct(array $records = [])\n" + " /**\n" + " * @param array $records\n" + " */\n" + " public function __construct(array $records)\n" " {\n" " if (!static::verify($records)) {\n" - " throw new InvalidArgumentException('Bad records');\n" + " throw new \\InvalidArgumentException('Bad records');\n" " }\n" - " $body = '';\n" - " $error = '';\n" + "\n" + " $body = $error = '';\n" " foreach ($records as $record) {\n" " if ($record instanceof Stdout) {\n" " if ($record->getContentLength() > 0) {\n" @@ -5179,44 +6110,51 @@ static const char* swoole_library_source_core_fast_cgi_response = " $this->withBody($body)->withError($error);\n" " }\n" "\n" - " public static function verify(array $records): bool\n" + " /**\n" + " * @param array $records\n" + " */\n" + " protected static function verify(array $records): bool\n" " {\n" - " return !empty($records) && $records[count($records) - 1] instanceof EndRequest;\n" + " return !empty($records) && $records[array_key_last($records)] instanceof EndRequest;\n" " }\n" "}\n"; static const char* swoole_library_source_core_fast_cgi_http_request = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" - "use InvalidArgumentException;\n" - "\n" "class HttpRequest extends Request\n" "{\n" - " protected $params = [\n" - " 'REQUEST_SCHEME' => 'http',\n" - " 'REQUEST_METHOD' => 'GET',\n" - " 'DOCUMENT_ROOT' => '',\n" - " 'SCRIPT_FILENAME' => '',\n" - " 'SCRIPT_NAME' => '',\n" - " 'DOCUMENT_URI' => '/',\n" - " 'REQUEST_URI' => '/',\n" - " 'QUERY_STRING' => '',\n" - " 'CONTENT_TYPE' => 'text/plain',\n" - " 'CONTENT_LENGTH' => '0',\n" + " protected array $params = [\n" + " 'REQUEST_SCHEME' => 'http',\n" + " 'REQUEST_METHOD' => 'GET',\n" + " 'DOCUMENT_ROOT' => '',\n" + " 'SCRIPT_FILENAME' => '',\n" + " 'SCRIPT_NAME' => '',\n" + " 'DOCUMENT_URI' => '/',\n" + " 'REQUEST_URI' => '/',\n" + " 'QUERY_STRING' => '',\n" + " 'CONTENT_TYPE' => 'text/plain',\n" + " 'CONTENT_LENGTH' => '0',\n" " 'GATEWAY_INTERFACE' => 'CGI/1.1',\n" - " 'SERVER_PROTOCOL' => 'HTTP/1.1',\n" - " 'SERVER_SOFTWARE' => 'swoole/' . SWOOLE_VERSION,\n" - " 'REMOTE_ADDR' => 'unknown',\n" - " 'REMOTE_PORT' => '0',\n" - " 'SERVER_ADDR' => 'unknown',\n" - " 'SERVER_PORT' => '0',\n" - " 'SERVER_NAME' => 'Swoole',\n" - " 'REDIRECT_STATUS' => '200',\n" + " 'SERVER_PROTOCOL' => 'HTTP/1.1',\n" + " 'SERVER_SOFTWARE' => 'swoole/' . SWOOLE_VERSION,\n" + " 'REMOTE_ADDR' => 'unknown',\n" + " 'REMOTE_PORT' => '0',\n" + " 'SERVER_ADDR' => 'unknown',\n" + " 'SERVER_PORT' => '0',\n" + " 'SERVER_NAME' => 'Swoole',\n" + " 'REDIRECT_STATUS' => '200',\n" " ];\n" "\n" " public function getScheme(): ?string\n" @@ -5304,7 +6242,8 @@ static const char* swoole_library_source_core_fast_cgi_http_request = " $info = parse_url($uri);\n" " return $this->withRequestUri($uri)\n" " ->withDocumentUri($info['path'] ?? '')\n" - " ->withQueryString($info['query'] ?? '');\n" + " ->withQueryString($info['query'] ?? '')\n" + " ;\n" " }\n" "\n" " public function getDocumentUri(): ?string\n" @@ -5430,7 +6369,7 @@ static const char* swoole_library_source_core_fast_cgi_http_request = " public function withProtocolVersion(string $protocolVersion): self\n" " {\n" " if (!is_numeric($protocolVersion)) {\n" - " throw new InvalidArgumentException('Protocol version must be numeric');\n" + " throw new \\InvalidArgumentException('Protocol version must be numeric');\n" " }\n" " $this->params['SERVER_PROTOCOL'] = \"HTTP/{$protocolVersion}\";\n" " return $this;\n" @@ -5568,7 +6507,7 @@ static const char* swoole_library_source_core_fast_cgi_http_request = " {\n" " $headers = [];\n" " foreach ($this->params as $name => $value) {\n" - " if (strpos($name, 'HTTP_') === 0) {\n" + " if (str_starts_with($name, 'HTTP_')) {\n" " $headers[static::convertParamNameToHeaderName($name)] = $value;\n" " }\n" " }\n" @@ -5583,14 +6522,14 @@ static const char* swoole_library_source_core_fast_cgi_http_request = " return $this;\n" " }\n" "\n" - " \n" - " public function withBody($body): Message\n" + " public function withBody(array|string|\\Stringable $body): self\n" " {\n" " if (is_array($body)) {\n" " $body = http_build_query($body);\n" " $this->withContentType('application/x-www-form-urlencoded');\n" " }\n" " parent::withBody($body);\n" + "\n" " return $this->withContentLength(strlen($body));\n" " }\n" "\n" @@ -5607,55 +6546,73 @@ static const char* swoole_library_source_core_fast_cgi_http_request = static const char* swoole_library_source_core_fast_cgi_http_response = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\FastCGI;\n" "\n" + "use Swoole\\FastCGI\\Record\\EndRequest;\n" + "use Swoole\\FastCGI\\Record\\Stderr;\n" + "use Swoole\\FastCGI\\Record\\Stdout;\n" "use Swoole\\Http\\Status;\n" "\n" "class HttpResponse extends Response\n" "{\n" - " \n" + " /** @var int */\n" " protected $statusCode;\n" "\n" - " \n" + " /** @var string */\n" " protected $reasonPhrase;\n" "\n" - " \n" - " protected $headers = [];\n" + " /**\n" + " * @var array\n" + " */\n" + " protected array $headers = [];\n" "\n" - " \n" - " protected $headersMap = [];\n" + " /**\n" + " * @var array\n" + " */\n" + " protected array $headersMap = [];\n" "\n" - " \n" - " protected $setCookieHeaderLines = [];\n" + " /**\n" + " * @var array\n" + " */\n" + " protected array $setCookieHeaderLines = [];\n" "\n" + " /**\n" + " * @param array $records\n" + " */\n" " public function __construct(array $records = [])\n" " {\n" " parent::__construct($records);\n" - " $body = (string) $this->getBody();\n" + " $body = $this->getBody();\n" " if (strlen($body) === 0) {\n" " return;\n" " }\n" - " $array = explode(\"\\r\\n\\r\\n\", $body, 2); \n" + " $array = explode(\"\\r\\n\\r\\n\", $body, 2); // An array that contains the HTTP headers and the body.\n" " if (count($array) != 2) {\n" " $this->withStatusCode(Status::BAD_GATEWAY)->withReasonPhrase('Invalid FastCGI Response')->withError($body);\n" " return;\n" " }\n" " $headers = explode(\"\\r\\n\", $array[0]);\n" - " $body = $array[1];\n" + " $body = $array[1];\n" " foreach ($headers as $header) {\n" - " $array = explode(':', $header, 2); \n" + " $array = explode(':', $header, 2); // An array that contains the name and the value of an HTTP header.\n" " if (count($array) != 2) {\n" - " continue; \n" + " continue; // Invalid HTTP header? Ignore it!\n" " }\n" - " $name = trim($array[0]);\n" + " $name = trim($array[0]);\n" " $value = trim($array[1]);\n" " if (strcasecmp($name, 'Status') === 0) {\n" - " $array = explode(' ', $value, 2); \n" - " $statusCode = $array[0];\n" + " $array = explode(' ', $value, 2); // An array that contains the status code (and the reason phrase).\n" + " $statusCode = $array[0];\n" " $reasonPhrase = $array[1] ?? null;\n" " } elseif (strcasecmp($name, 'Set-Cookie') === 0) {\n" " $this->withSetCookieHeaderLine($value);\n" @@ -5663,8 +6620,8 @@ static const char* swoole_library_source_core_fast_cgi_http_response = " $this->withHeader($name, $value);\n" " }\n" " }\n" - " $statusCode = (int) ($statusCode ?? Status::OK);\n" - " $reasonPhrase = (string) ($reasonPhrase ?? Status::getReasonPhrase($statusCode));\n" + " $statusCode = (int) ($statusCode ?? Status::OK);\n" + " $reasonPhrase = $reasonPhrase ?? Status::getReasonPhrase($statusCode);\n" " $this->withStatusCode($statusCode)->withReasonPhrase($reasonPhrase);\n" " $this->withBody($body);\n" " }\n" @@ -5697,6 +6654,9 @@ static const char* swoole_library_source_core_fast_cgi_http_response = " return $name ? $this->headers[$name] : null;\n" " }\n" "\n" + " /**\n" + " * @return array\n" + " */\n" " public function getHeaders(): array\n" " {\n" " return $this->headers;\n" @@ -5704,11 +6664,14 @@ static const char* swoole_library_source_core_fast_cgi_http_response = "\n" " public function withHeader(string $name, string $value): self\n" " {\n" - " $this->headers[$name] = $value;\n" + " $this->headers[$name] = $value;\n" " $this->headersMap[strtolower($name)] = $name;\n" " return $this;\n" " }\n" "\n" + " /**\n" + " * @param array $headers\n" + " */\n" " public function withHeaders(array $headers): self\n" " {\n" " foreach ($headers as $name => $value) {\n" @@ -5717,6 +6680,9 @@ static const char* swoole_library_source_core_fast_cgi_http_response = " return $this;\n" " }\n" "\n" + " /**\n" + " * @return array\n" + " */\n" " public function getSetCookieHeaderLines(): array\n" " {\n" " return $this->setCookieHeaderLines;\n" @@ -5731,13 +6697,19 @@ static const char* swoole_library_source_core_fast_cgi_http_response = static const char* swoole_library_source_core_coroutine_fast_cgi_client = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\FastCGI;\n" "\n" - "use InvalidArgumentException;\n" + "use Swoole\\Constant;\n" "use Swoole\\Coroutine\\FastCGI\\Client\\Exception;\n" "use Swoole\\Coroutine\\Socket;\n" "use Swoole\\FastCGI\\FrameParser;\n" @@ -5749,45 +6721,43 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = "\n" "class Client\n" "{\n" - " \n" - " protected $af;\n" + " protected int $af;\n" "\n" - " \n" - " protected $host;\n" + " protected string $host;\n" "\n" - " \n" - " protected $port;\n" + " protected int $port;\n" "\n" - " \n" - " protected $ssl;\n" + " protected bool $ssl;\n" "\n" - " \n" - " protected $socket;\n" + " protected ?Socket $socket;\n" "\n" " public function __construct(string $host, int $port = 0, bool $ssl = false)\n" " {\n" " if (stripos($host, 'unix:/') === 0) {\n" " $this->af = AF_UNIX;\n" - " $host = '/' . ltrim(substr($host, strlen('unix:/')), '/');\n" - " $port = 0;\n" - " } elseif (strpos($host, ':') !== false) {\n" + " $host = '/' . ltrim(substr($host, strlen('unix:/')), '/');\n" + " $port = 0;\n" + " } elseif (str_contains($host, ':')) {\n" " $this->af = AF_INET6;\n" " } else {\n" " $this->af = AF_INET;\n" " }\n" " $this->host = $host;\n" " $this->port = $port;\n" - " $this->ssl = $ssl;\n" + " $this->ssl = $ssl;\n" " }\n" "\n" - " \n" + " /**\n" + " * @return ($request is HttpRequest ? HttpResponse : Response)\n" + " * @throws Exception\n" + " */\n" " public function execute(Request $request, float $timeout = -1): Response\n" " {\n" - " if (!$this->socket) {\n" + " if (!isset($this->socket)) {\n" " $this->socket = $socket = new Socket($this->af, SOCK_STREAM, IPPROTO_IP);\n" " $socket->setProtocol([\n" - " 'open_ssl' => $this->ssl,\n" - " 'open_fastcgi_protocol' => true,\n" + " Constant::OPTION_OPEN_SSL => $this->ssl,\n" + " Constant::OPTION_OPEN_FASTCGI_PROTOCOL => true,\n" " ]);\n" " if (!$socket->connect($this->host, $this->port, $timeout)) {\n" " $this->ioException();\n" @@ -5801,33 +6771,17 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = " }\n" " $records = [];\n" " while (true) {\n" - " if (SWOOLE_VERSION_ID < 40500) {\n" - " $recvData = '';\n" - " while (true) {\n" - " $tmp = $socket->recv(8192, $timeout);\n" - " if (!$tmp) {\n" - " if ($tmp === '') {\n" - " $this->ioException(SOCKET_ECONNRESET);\n" - " }\n" - " $this->ioException();\n" - " }\n" - " $recvData .= $tmp;\n" - " if (FrameParser::hasFrame($recvData)) {\n" - " break;\n" - " }\n" - " }\n" - " } else {\n" - " $recvData = $socket->recvPacket($timeout);\n" - " if (!$recvData) {\n" - " if ($recvData === '') {\n" - " $this->ioException(SOCKET_ECONNRESET);\n" - " }\n" - " $this->ioException();\n" - " }\n" - " if (!FrameParser::hasFrame($recvData)) {\n" - " $this->ioException(SOCKET_EPROTO);\n" + " $recvData = $socket->recvPacket($timeout);\n" + " if (!$recvData) {\n" + " if ($recvData === '') {\n" + " $this->ioException(SOCKET_ECONNRESET);\n" " }\n" + " $this->ioException();\n" " }\n" + " if (!FrameParser::hasFrame($recvData)) {\n" + " $this->ioException(SOCKET_EPROTO);\n" + " }\n" + "\n" " do {\n" " $records[] = $record = FrameParser::parseFrame($recvData);\n" " } while (strlen($recvData) !== 0);\n" @@ -5836,27 +6790,24 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = " $this->socket->close();\n" " $this->socket = null;\n" " }\n" - " switch (true) {\n" - " case $request instanceof HttpRequest:\n" - " return new HttpResponse($records);\n" - " default:\n" - " return new Response($records);\n" - " }\n" + " // @phpstan-ignore argument.type,argument.type\n" + " return ($request instanceof HttpRequest) ? new HttpResponse($records) : new Response($records);\n" " }\n" " }\n" - " \n" - " exit(1);\n" + "\n" + " // Code execution should never reach here. However, we still put an exit() statement here for safe purpose.\n" + " exit(1); // @phpstan-ignore deadCode.unreachable\n" " }\n" "\n" " public static function parseUrl(string $url): array\n" " {\n" - " $url = parse_url($url);\n" + " $url = parse_url($url);\n" " $host = $url['host'] ?? '';\n" " $port = $url['port'] ?? 0;\n" " if (empty($host)) {\n" " $host = $url['path'] ?? '';\n" " if (empty($host)) {\n" - " throw new InvalidArgumentException('Invalid url');\n" + " throw new \\InvalidArgumentException('Invalid url');\n" " }\n" " $host = \"unix:/{$host}\";\n" " }\n" @@ -5865,15 +6816,15 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = "\n" " public static function call(string $url, string $path, $data = '', float $timeout = -1): string\n" " {\n" - " $client = new Client(...static::parseUrl($url));\n" - " $pathInfo = parse_url($path);\n" - " $path = $pathInfo['path'] ?? '';\n" - " $root = dirname($path);\n" - " $scriptName = '/' . basename($path);\n" + " $client = new Client(...static::parseUrl($url));\n" + " $pathInfo = parse_url($path);\n" + " $path = $pathInfo['path'] ?? '';\n" + " $root = dirname($path);\n" + " $scriptName = '/' . basename($path);\n" " $documentUri = $scriptName;\n" - " $query = $pathInfo['query'] ?? '';\n" - " $requestUri = $query ? \"{$documentUri}?{$query}\" : $documentUri;\n" - " $request = new HttpRequest();\n" + " $query = $pathInfo['query'] ?? '';\n" + " $requestUri = $query ? \"{$documentUri}?{$query}\" : $documentUri;\n" + " $request = new HttpRequest();\n" " $request->withDocumentRoot($root)\n" " ->withScriptFilename($path)\n" " ->withScriptName($documentUri)\n" @@ -5881,7 +6832,8 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = " ->withRequestUri($requestUri)\n" " ->withQueryString($query)\n" " ->withBody($data)\n" - " ->withMethod($request->getContentLength() === 0 ? 'GET' : 'POST');\n" + " ->withMethod($request->getContentLength() === 0 ? 'GET' : 'POST')\n" + " ;\n" " $response = $client->execute($request, $timeout);\n" " return $response->getBody();\n" " }\n" @@ -5891,7 +6843,7 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = " $socket = $this->socket;\n" " if ($errno !== null) {\n" " $socket->errCode = $errno;\n" - " $socket->errMsg = swoole_strerror($errno);\n" + " $socket->errMsg = swoole_strerror($errno);\n" " }\n" " $socket->close();\n" " $this->socket = null;\n" @@ -5901,7 +6853,13 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client = static const char* swoole_library_source_core_coroutine_fast_cgi_client_exception = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -5913,48 +6871,55 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_client_exceptio static const char* swoole_library_source_core_coroutine_fast_cgi_proxy = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Coroutine\\FastCGI;\n" "\n" - "use InvalidArgumentException;\n" "use Swoole\\FastCGI\\HttpRequest;\n" "use Swoole\\FastCGI\\HttpResponse;\n" "use Swoole\\Http;\n" + "use Swoole\\Http\\Request as SwooleHttpRequest;\n" + "use Swoole\\Http\\Response as SwooleHttpResponse;\n" "\n" "class Proxy\n" "{\n" - " \n" + " /* @var string */\n" " protected $host;\n" "\n" - " \n" + " /* @var int */\n" " protected $port;\n" "\n" - " \n" + " /* @var float */\n" " protected $timeout = -1;\n" "\n" - " \n" + " /* @var string */\n" " protected $documentRoot;\n" "\n" - " \n" + " /* @var bool */\n" " protected $https = false;\n" "\n" - " \n" + " /* @var string */\n" " protected $index = 'index.php';\n" "\n" - " \n" + " /* @var array */\n" " protected $params = [];\n" "\n" - " \n" + " /* @var null|callable */\n" " protected $staticFileFilter;\n" "\n" " public function __construct(string $url, string $documentRoot = '/')\n" " {\n" " [$this->host, $this->port] = Client::parseUrl($url);\n" - " $this->documentRoot = $documentRoot;\n" - " $this->staticFileFilter = [$this, 'staticFileFiltrate'];\n" + " $this->documentRoot = $documentRoot;\n" + " $this->staticFileFilter = [$this, 'staticFileFiltrate'];\n" " }\n" "\n" " public function withTimeout(float $timeout): self\n" @@ -6015,67 +6980,60 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_proxy = " return $this;\n" " }\n" "\n" - " public function translateRequest($userRequest): HttpRequest\n" + " public function translateRequest(SwooleHttpRequest $userRequest): HttpRequest\n" " {\n" - " $request = new HttpRequest();\n" - " if ($userRequest instanceof \\Swoole\\Http\\Request) {\n" - " $server = $userRequest->server;\n" - " $headers = $userRequest->header;\n" - " $pathInfo = $userRequest->server['path_info'];\n" - " $pathInfo = '/' . (ltrim($pathInfo, '/'));\n" - " if (strlen($this->index) !== 0) {\n" - " $extension = pathinfo($pathInfo, PATHINFO_EXTENSION);\n" - " if (empty($extension)) {\n" - " $pathInfo = rtrim($pathInfo, '/') . '/' . $this->index;\n" - " }\n" - " }\n" - " $requestUri = $scriptName = $documentUri = $server['request_uri'];\n" - " $queryString = $server['query_string'] ?? '';\n" - " if (strlen($queryString) !== 0) {\n" - " $requestUri .= \"?{$server['query_string']}\";\n" + " $server = $userRequest->server;\n" + " $headers = $userRequest->header;\n" + " $pathInfo = $userRequest->server['path_info'];\n" + " $pathInfo = '/' . ltrim($pathInfo, '/');\n" + " if (strlen($this->index) !== 0) {\n" + " $extension = pathinfo($pathInfo, PATHINFO_EXTENSION);\n" + " if (empty($extension)) {\n" + " $pathInfo = rtrim($pathInfo, '/') . '/' . $this->index;\n" " }\n" - " $request\n" - " ->withDocumentRoot($this->documentRoot)\n" - " ->withScriptFilename($this->documentRoot . $pathInfo)\n" - " ->withScriptName($scriptName)\n" - " ->withDocumentUri($documentUri)\n" - " ->withServerProtocol($server['server_protocol'])\n" - " ->withServerAddr('127.0.0.1')\n" - " ->withServerPort($server['server_port'])\n" - " ->withRemoteAddr($server['remote_addr'])\n" - " ->withRemotePort($server['remote_port'])\n" - " ->withMethod($server['request_method'])\n" - " ->withRequestUri($requestUri)\n" - " ->withQueryString($queryString)\n" - " ->withContentType($headers['content-type'] ?? '')\n" - " ->withContentLength((int) ($headers['content-length'] ?? 0))\n" - " ->withHeaders($headers)\n" - " ->withBody($userRequest->rawContent())\n" - " ->withAddedParams($this->params);\n" - " if ($this->https) {\n" - " $request->withParam('HTTPS', '1');\n" - " }\n" - " } else {\n" - " throw new InvalidArgumentException('Not supported on ' . get_class($userRequest));\n" " }\n" + " $requestUri = $scriptName = $documentUri = $server['request_uri'];\n" + " $queryString = $server['query_string'] ?? '';\n" + " if (strlen($queryString) !== 0) {\n" + " $requestUri .= \"?{$server['query_string']}\";\n" + " }\n" + " $request = (new HttpRequest())\n" + " ->withDocumentRoot($this->documentRoot)\n" + " ->withScriptFilename($this->documentRoot . $pathInfo)\n" + " ->withScriptName($scriptName)\n" + " ->withDocumentUri($documentUri)\n" + " ->withServerProtocol($server['server_protocol'])\n" + " ->withServerAddr('127.0.0.1')\n" + " ->withServerPort($server['server_port'])\n" + " ->withRemoteAddr($server['remote_addr'])\n" + " ->withRemotePort($server['remote_port'])\n" + " ->withMethod($server['request_method'])\n" + " ->withRequestUri($requestUri)\n" + " ->withQueryString($queryString)\n" + " ->withContentType($headers['content-type'] ?? '')\n" + " ->withContentLength((int) ($headers['content-length'] ?? 0))\n" + " ->withHeaders($headers)\n" + " ->withBody($userRequest->rawContent())\n" + " ->withAddedParams($this->params)\n" + " ;\n" + " if ($this->https) {\n" + " $request->withParam('HTTPS', '1');\n" + " }\n" + "\n" " return $request;\n" " }\n" "\n" - " public function translateResponse(HttpResponse $response, $userResponse): void\n" + " public function translateResponse(HttpResponse $response, SwooleHttpResponse $userResponse): void\n" " {\n" - " if ($userResponse instanceof \\Swoole\\Http\\Response) {\n" - " $userResponse->status($response->getStatusCode(), $response->getReasonPhrase());\n" - " $userResponse->header = $response->getHeaders();\n" - " $userResponse->cookie = $response->getSetCookieHeaderLines();\n" - " $userResponse->end($response->getBody());\n" - " } else {\n" - " throw new InvalidArgumentException('Not supported on ' . get_class($userResponse));\n" - " }\n" + " $userResponse->status($response->getStatusCode(), $response->getReasonPhrase());\n" + " $userResponse->header = $response->getHeaders();\n" + " $userResponse->cookie = $response->getSetCookieHeaderLines();\n" + " $userResponse->end($response->getBody());\n" " }\n" "\n" - " public function pass($userRequest, $userResponse): void\n" + " public function pass(SwooleHttpRequest|HttpRequest $userRequest, SwooleHttpResponse $userResponse): void\n" " {\n" - " if (!($userRequest instanceof HttpRequest)) {\n" + " if (!$userRequest instanceof HttpRequest) {\n" " $request = $this->translateRequest($userRequest);\n" " } else {\n" " $request = $userRequest;\n" @@ -6087,54 +7045,70 @@ static const char* swoole_library_source_core_coroutine_fast_cgi_proxy = " return;\n" " }\n" " }\n" - " $client = new Client($this->host, $this->port);\n" - " $response = $client->execute($request, $this->timeout);\n" + " $response = (new Client($this->host, $this->port))->execute($request, $this->timeout);\n" " $this->translateResponse($response, $userResponse);\n" " }\n" "\n" - " \n" - " public function staticFileFiltrate(HttpRequest $request, $userResponse): bool\n" + " /**\n" + " * Send content of a static file to the client, if the file is accessible and is not a PHP file.\n" + " *\n" + " * @return bool True if the file doesn't have an extension of 'php', false otherwise. Note that the file may not be\n" + " * accessible even the return value is true.\n" + " */\n" + " public function staticFileFiltrate(HttpRequest $request, SwooleHttpResponse $userResponse): bool\n" " {\n" - " if ($userResponse instanceof \\Swoole\\Http\\Response) {\n" - " $extension = pathinfo($request->getScriptFilename(), PATHINFO_EXTENSION);\n" - " if ($extension !== 'php') {\n" - " $realPath = realpath($request->getScriptFilename());\n" - " if (!$realPath || strpos($realPath, $this->documentRoot) !== 0 || !is_file($realPath)) {\n" - " $userResponse->status(Http\\Status::NOT_FOUND);\n" - " } else {\n" - " $userResponse->sendfile($realPath);\n" - " }\n" - " return true;\n" + " $extension = pathinfo($request->getScriptFilename(), PATHINFO_EXTENSION);\n" + " if ($extension !== 'php') {\n" + " $realPath = realpath($request->getScriptFilename());\n" + " if (!$realPath || !str_starts_with($realPath, $this->documentRoot) || !is_file($realPath)) {\n" + " $userResponse->status(Http\\Status::NOT_FOUND);\n" + " } else {\n" + " $userResponse->sendfile($realPath);\n" " }\n" - " return false;\n" + " return true;\n" " }\n" - " throw new InvalidArgumentException('Not supported on ' . get_class($userResponse));\n" + " return false;\n" " }\n" "}\n"; static const char* swoole_library_source_core_process_manager = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Process;\n" "\n" "use Swoole\\Constant;\n" + "\n" "use function Swoole\\Coroutine\\run;\n" "\n" "class Manager\n" "{\n" - " \n" + " /**\n" + " * @var Pool\n" + " */\n" " protected $pool;\n" "\n" - " \n" + " /**\n" + " * @var int\n" + " */\n" " protected $ipcType = SWOOLE_IPC_NONE;\n" "\n" - " \n" + " /**\n" + " * @var int\n" + " */\n" " protected $msgQueueKey = 0;\n" "\n" - " \n" + " /**\n" + " * @var array\n" + " */\n" " protected $startFuncMap = [];\n" "\n" " public function __construct(int $ipcType = SWOOLE_IPC_NONE, int $msgQueueKey = 0)\n" @@ -6195,112 +7169,981 @@ static const char* swoole_library_source_core_process_manager = " }\n" "}\n"; -static const char* swoole_library_source_core_server_admin = - "\n" +static const char* swoole_library_source_core_remote_object = "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" - "namespace Swoole\\Server;\n" + "namespace Swoole;\n" "\n" - "use Reflection;\n" - "use ReflectionClass;\n" - "use ReflectionExtension;\n" - "use ReflectionFunction;\n" - "use ReflectionMethod;\n" - "use Swoole\\Coroutine;\n" - "use Swoole\\Http\\Request;\n" - "use Swoole\\Http\\Response;\n" - "use Swoole\\Server;\n" - "use Swoole\\StringObject;\n" - "use Swoole\\Timer;\n" + "use Swoole\\RemoteObject\\Client;\n" + "use Swoole\\RemoteObject\\Exception;\n" "\n" - "class Admin\n" + "class RemoteObject implements \\ArrayAccess, \\Stringable, \\Iterator, \\Countable\n" "{\n" - " \n" - " public const SIZE_OF_ZVAL = 16;\n" + " private int $objectId = 0;\n" "\n" - " public const SIZE_OF_ZEND_STRING = 32;\n" + " private int $coroutineId;\n" "\n" - " public const SIZE_OF_ZEND_OBJECT = 56;\n" + " private string $clientId;\n" "\n" - " public const SIZE_OF_ZEND_ARRAY = 56;\n" + " private ?Client $client = null;\n" "\n" - " private static $map = [\n" - " 'reactor' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" - " 'reactor_thread' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" - " 'worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" - " 'event_worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" - " 'task' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" - " 'task_worker' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" - " ];\n" + " public function __construct($coroutineId, $clientId)\n" + " {\n" + " $this->coroutineId = $coroutineId;\n" + " $this->clientId = $clientId;\n" + " }\n" "\n" - " private static $allList = [\n" - " 'all',\n" - " 'all_reactor',\n" - " 'all_reactor_thread',\n" - " 'all_worker',\n" - " 'all_event_worker',\n" - " 'all_task',\n" - " 'all_task_worker',\n" - " 'specific',\n" - " ];\n" + " public function __destruct()\n" + " {\n" + " // On the server side, this object will also be constructed,\n" + " // but it is only used for data storage and serialization.\n" + " // No remote calls are executed during destruction.\n" + " // If the objectId is 0, it indicates that the object may have been a temporary object created by a function call\n" + " // and does not need to be destructed.\n" + " if ($this->client and $this->objectId > 0) {\n" + " try {\n" + " $this->execute('/destroy', [\n" + " 'object' => $this->objectId,\n" + " ]);\n" + " } catch (Exception $e) {\n" + " error_log($e->getMessage());\n" + " debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n" + " }\n" + " }\n" + " }\n" "\n" - " private static $postMethodList = [\n" - " 'server_reload',\n" - " 'server_shutdown',\n" - " 'close_session',\n" - " ];\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function __call(string $method, array $args)\n" + " {\n" + " $rs = $this->execute('/call_method', [\n" + " 'object' => $this->objectId,\n" + " 'method' => $method,\n" + " 'args' => serialize($args),\n" + " ]);\n" + " return $rs['result'];\n" + " }\n" "\n" - " private static $accessToken = '';\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function __get(string $property)\n" + " {\n" + " $rs = $this->execute('/read_property', [\n" + " 'object' => $this->objectId,\n" + " 'property' => $property,\n" + " ]);\n" + " return $rs['property'];\n" + " }\n" "\n" - " public static function init(Server $server)\n" + " public function __set(string $property, mixed $value)\n" " {\n" - " $accepted_process_types = SWOOLE_SERVER_COMMAND_MASTER |\n" - " SWOOLE_SERVER_COMMAND_MANAGER |\n" - " SWOOLE_SERVER_COMMAND_EVENT_WORKER |\n" - " SWOOLE_SERVER_COMMAND_TASK_WORKER;\n" + " $this->execute('/write_property', [\n" + " 'object' => $this->objectId,\n" + " 'property' => $property,\n" + " 'value' => serialize($value),\n" + " ]);\n" + " }\n" "\n" - " $server->addCommand(\n" - " 'server_reload',\n" - " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " $server->reload();\n" - " return self::json('Operation succeeded');\n" - " }\n" - " );\n" + " public function __unserialize(array $data): void\n" + " {\n" + " $this->objectId = $data['objectId'];\n" + " $this->coroutineId = $data['coroutineId'];\n" + " $this->clientId = $data['clientId'];\n" + " $this->client = Client::getInstance($this->clientId);\n" + " }\n" "\n" - " $server->addCommand(\n" - " 'server_shutdown',\n" - " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " $server->shutdown();\n" - " }\n" - " );\n" + " public function __serialize(): array\n" + " {\n" + " return [\n" + " 'objectId' => $this->objectId,\n" + " 'coroutineId' => $this->coroutineId,\n" + " 'clientId' => $this->clientId,\n" + " ];\n" + " }\n" "\n" - " $server->addCommand(\n" - " 'coroutine_stats',\n" - " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(Coroutine::stats());\n" - " }\n" - " );\n" + " public function __toString(): string\n" + " {\n" + " $rs = $this->execute('/to_string', [\n" + " 'object' => $this->objectId,\n" + " ]);\n" + " return $rs['value'];\n" + " }\n" "\n" - " $server->addCommand(\n" - " 'coroutine_list',\n" - " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(iterator_to_array(Coroutine::list()));\n" - " }\n" + " public function __invoke(...$args)\n" + " {\n" + " $rs = $this->execute('/call_method', [\n" + " 'object' => $this->objectId,\n" + " 'method' => '__invoke',\n" + " 'args' => serialize($args),\n" + " ]);\n" + " return $rs['result'];\n" + " }\n" + "\n" + " public static function call(Client $client, string $fn, array $args)\n" + " {\n" + " $object = new self(Coroutine::getCid(), $client->getId());\n" + " $object->client = $client;\n" + " $rs = $object->execute('/call_function', [\n" + " 'function' => $fn,\n" + " 'args' => serialize($args),\n" + " ]);\n" + " return $rs['result'];\n" + " }\n" + "\n" + " public function getObjectId(): int\n" + " {\n" + " return $this->objectId;\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public static function create(Client $client, string $class, array $args): RemoteObject\n" + " {\n" + " $object = new self(Coroutine::getCid(), $client->getId());\n" + " $object->client = $client;\n" + " $rs = $object->execute('/new', [\n" + " 'class' => $class,\n" + " 'args' => serialize($args),\n" + " ]);\n" + " $object->objectId = intval($rs['object']);\n" + " return $object;\n" + " }\n" + "\n" + " /**\n" + " * This method is only used on the server side.\n" + " */\n" + " public static function marshal(int $objectId, int $ownerCoroutineId, string $clientId): RemoteObject\n" + " {\n" + " $object = new self($ownerCoroutineId, $clientId);\n" + " $object->objectId = $objectId;\n" + " return $object;\n" + " }\n" + "\n" + " public function offsetGet(mixed $offset): mixed\n" + " {\n" + " $rs = $this->execute('/offset_get', [\n" + " 'object' => $this->objectId,\n" + " 'offset' => $offset,\n" + " ]);\n" + " return $rs['value'];\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function offsetSet(mixed $offset, mixed $value): void\n" + " {\n" + " $this->execute('/offset_set', [\n" + " 'object' => $this->objectId,\n" + " 'offset' => $offset,\n" + " 'value' => serialize($value),\n" + " ]);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function offsetUnset(mixed $offset): void\n" + " {\n" + " $this->execute('/offset_unset', [\n" + " 'object' => $this->objectId,\n" + " 'offset' => $offset,\n" + " ]);\n" + " }\n" + "\n" + " public function offsetExists(mixed $offset): bool\n" + " {\n" + " $rs = $this->execute('/offset_exists', [\n" + " 'object' => $this->objectId,\n" + " 'offset' => $offset,\n" + " ]);\n" + " return $rs['exists'];\n" + " }\n" + "\n" + " public function current(): mixed\n" + " {\n" + " return $this->__call('current', []);\n" + " }\n" + "\n" + " public function next(): void\n" + " {\n" + " $this->__call('next', []);\n" + " }\n" + "\n" + " public function key(): mixed\n" + " {\n" + " return $this->__call('key', []);\n" + " }\n" + "\n" + " public function valid(): bool\n" + " {\n" + " return $this->__call('valid', []);\n" + " }\n" + "\n" + " public function rewind(): void\n" + " {\n" + " $this->__call('rewind', []);\n" + " }\n" + "\n" + " public function count(): int\n" + " {\n" + " return $this->__call('count', []);\n" + " }\n" + "\n" + " private function execute(string $path, array $params = []): array\n" + " {\n" + " if (!$this->client) {\n" + " throw new Exception('This remote object is not bound to a client, and cannot initiate remote calls');\n" + " }\n" + " return $this->client->execute($path, $params);\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_remote_object_server = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\RemoteObject;\n" + "\n" + "use Swoole\\Atomic\\Long;\n" + "use Swoole\\Http\\Request;\n" + "use Swoole\\Http\\Response;\n" + "use Swoole\\Http\\Server as HttpServer;\n" + "use Swoole\\RemoteObject;\n" + "\n" + "class Server\n" + "{\n" + " public const DEFAULT_PORT = 9567;\n" + "\n" + " private HttpServer $server;\n" + "\n" + " private array $objects = [];\n" + "\n" + " private array $allowedClasses = [];\n" + "\n" + " private array $allowedFunctions = [];\n" + "\n" + " private Long $nextObjectId;\n" + "\n" + " private string $apiKey = '';\n" + "\n" + " public function __construct(string $host = '127.0.0.1', int $port = self::DEFAULT_PORT, array $options = [])\n" + " {\n" + " // By default, thread mode is used, and when viewed with ps, only one process will be displayed.\n" + " $server_mode = $options['server_mode'] ?? SWOOLE_THREAD;\n" + " $socket_type = $options['socket_type'] ?? SWOOLE_SOCK_TCP;\n" + " $server = new HttpServer($host, $port, $server_mode, $socket_type);\n" + " unset($options['server_mode'], $options['socket_type']);\n" + "\n" + " if (isset($options['allowed_classes'])) {\n" + " if (!is_array($options['allowed_classes'])) {\n" + " throw new Exception('allowed_classes must be an array');\n" + " }\n" + " $this->allowedClasses = array_flip($options['allowed_classes']);\n" + " unset($options['allowed_classes']);\n" + " }\n" + "\n" + " if (isset($options['allowed_functions'])) {\n" + " if (!is_array($options['allowed_functions'])) {\n" + " throw new Exception('allowed_functions must be an array');\n" + " }\n" + " $this->allowedFunctions = array_flip($options['allowed_functions']);\n" + " unset($options['allowed_functions']);\n" + " }\n" + "\n" + " if (isset($options['api_key'])) {\n" + " $this->apiKey = $options['api_key'];\n" + " unset($options['api_key']);\n" + " }\n" + "\n" + " if ($options) {\n" + " $server->set($options);\n" + " }\n" + " $server->on('request', [$this, 'onRequest']);\n" + " $server->on('start', [$this, 'onStart']);\n" + " $this->server = $server;\n" + " $this->nextObjectId = new Long(1);\n" + " }\n" + "\n" + " public function start(): bool\n" + " {\n" + " return $this->server->start();\n" + " }\n" + "\n" + " public function onStart(): void\n" + " {\n" + " echo \"The remote-object server is started at http://{$this->server->host}:{$this->server->port}\\n\";\n" + " }\n" + "\n" + " public function onRequest(Request $request, Response $response): void\n" + " {\n" + " $ctx = new Context($request, $response);\n" + " if ($this->apiKey and $this->apiKey !== $request->header['x-api-key']) {\n" + " $response->status(403);\n" + " $ctx->end(['code' => -3, 'msg' => 'invalid api key']);\n" + " return;\n" + " }\n" + " try {\n" + " $method = $ctx->getHandler();\n" + " if (method_exists($this, $method)) {\n" + " $this->{$method}($ctx);\n" + " } else {\n" + " $ctx->end(['code' => -1, 'msg' => 'invalid request']);\n" + " }\n" + " } catch (\\Throwable $e) {\n" + " $ctx->end(['code' => -2, 'exception' => [\n" + " 'message' => $e->getMessage(),\n" + " 'code' => $e->getCode(),\n" + " 'class' => get_class($e),\n" + " ]]);\n" + " }\n" + " }\n" + "\n" + " private function addObject($object): int\n" + " {\n" + " // The spl_object_id/spl_object_hash cannot be used,\n" + " // as the IDs they generate will be reused after the objects are destroyed.\n" + " $object_id = $this->nextObjectId->add();\n" + " $this->objects[$object_id] = $object;\n" + " return $object_id;\n" + " }\n" + "\n" + " private function marshal(Context $ctx, mixed $data): mixed\n" + " {\n" + " if (is_object($data) or is_resource($data)) {\n" + " $object_id = $this->addObject($data);\n" + " return RemoteObject::marshal($object_id, $ctx->getCoroutineId(), $ctx->getClientId());\n" + " }\n" + " if (is_array($data)) {\n" + " foreach ($data as $key => $value) {\n" + " $data[$key] = $this->marshal($ctx, $value);\n" + " }\n" + " }\n" + " return $data;\n" + " }\n" + "\n" + " private function unmarshal($data): mixed\n" + " {\n" + " if (is_object($data) and $data instanceof RemoteObject) {\n" + " return $this->objects[$data->getObjectId()];\n" + " }\n" + " if (is_array($data)) {\n" + " foreach ($data as $key => $value) {\n" + " $data[$key] = $this->unmarshal($value);\n" + " }\n" + " return $data;\n" + " }\n" + " return $data;\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function _new(Context $ctx): void\n" + " {\n" + " $class = trim($ctx->getParam('class'), '\\ ');\n" + " if (count($this->allowedClasses) > 0 and !isset($this->allowedClasses[$class])) {\n" + " throw new Exception(\"class[{$class}] not allowed\");\n" + " }\n" + " $class = '\\\\' . $class;\n" + " $args = $ctx->getDataParam('args');\n" + " foreach ($args as $key => $value) {\n" + " $args[$key] = $this->unmarshal($value);\n" + " }\n" + " $obj = new $class(...$args);\n" + " $object_id = $this->addObject($obj);\n" + " $ctx->end(['code' => 0, 'object' => $object_id]);\n" + " }\n" + "\n" + " private function _call_function(Context $ctx): void\n" + " {\n" + " $fn = trim($ctx->getParam('function'), '\\ ');\n" + " if (count($this->allowedFunctions) > 0 and !isset($this->allowedFunctions[$fn])) {\n" + " throw new Exception(\"function[{$fn}] not allowed\");\n" + " }\n" + " $args = $ctx->getDataParam('args');\n" + " foreach ($args as $key => $value) {\n" + " $args[$key] = $this->unmarshal($value);\n" + " }\n" + " $fn = '\\\\' . $fn;\n" + " if (!function_exists($fn)) {\n" + " throw new Exception(\"function[{$fn}] not found\");\n" + " }\n" + " $result = $fn(...$args);\n" + " $ctx->end(['code' => 0, 'result' => $this->marshal($ctx, $result)]);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function _call_method(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $method = $ctx->getParam('method');\n" + " $args = $ctx->getDataParam('args');\n" + " foreach ($args as $key => $value) {\n" + " $args[$key] = $this->unmarshal($value);\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " if (!method_exists($obj, $method)) {\n" + " $class = get_class($obj);\n" + " throw new Exception(\"method[{$class}::{$method}] not found\");\n" + " }\n" + " $result = $obj->{$method}(...$args);\n" + " $ctx->end(['code' => 0, 'result' => $this->marshal($ctx, $result)]);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function _read_property(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $property = $ctx->getParam('property');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $result = $obj->{$property};\n" + " $ctx->end(['code' => 0, 'property' => $this->marshal($ctx, $result)]);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function _write_property(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $property = $ctx->getParam('property');\n" + " $value = $ctx->getDataParam('value');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $obj->{$property} = $this->unmarshal($value);\n" + " $ctx->end(['code' => 0]);\n" + " }\n" + "\n" + " private function _ping(Context $ctx): void\n" + " {\n" + " $ctx->end(['code' => 0]);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " private function _destroy(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " unset($this->objects[$object_id]);\n" + " $ctx->end(['code' => 0]);\n" + " }\n" + "\n" + " private function _to_string(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $ctx->end(['code' => 0, 'value' => (string) $obj]);\n" + " }\n" + "\n" + " private function _offset_get(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $offset = $ctx->getParam('offset');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $result = $obj->{$offset};\n" + " $ctx->end(['code' => 0, 'value' => $this->marshal($ctx, $result)]);\n" + " }\n" + "\n" + " private function _offset_set(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $offset = $ctx->getParam('offset');\n" + " $value = $ctx->getDataParam('value');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $obj->{$offset} = $this->unmarshal($value);\n" + " $ctx->end(['code' => 0]);\n" + " }\n" + "\n" + " private function _offset_unset(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $offset = $ctx->getParam('offset');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " unset($obj->{$offset});\n" + " $ctx->end(['code' => 0]);\n" + " }\n" + "\n" + " private function _offset_exists(Context $ctx): void\n" + " {\n" + " $object_id = $ctx->getParam('object');\n" + " $offset = $ctx->getParam('offset');\n" + " if (!isset($this->objects[$object_id])) {\n" + " throw new Exception(\"object[#{$object_id}] not found\");\n" + " }\n" + " $obj = $this->objects[$object_id];\n" + " $result = isset($obj->{$offset});\n" + " $ctx->end(['code' => 0, 'value' => $this->marshal($ctx, $result)]);\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_remote_object_context = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\RemoteObject;\n" + "\n" + "use Swoole\\Http\\Request;\n" + "use Swoole\\Http\\Response;\n" + "\n" + "class Context\n" + "{\n" + " public string $clientId;\n" + "\n" + " public int $coroutineId;\n" + "\n" + " public Request $request;\n" + "\n" + " public Response $response;\n" + "\n" + " public function __construct(Request $request, Response $response)\n" + " {\n" + " $this->clientId = $request->header['client-id'] ?? '';\n" + " $this->coroutineId = intval($request->header['coroutine-id'] ?? 0);\n" + " $this->request = $request;\n" + " $this->response = $response;\n" + " }\n" + "\n" + " public function end(array $data): void\n" + " {\n" + " $this->response->header('Content-Type', 'application/octet-stream');\n" + " $this->response->end(serialize($data));\n" + " }\n" + "\n" + " public function getHandler(): string\n" + " {\n" + " $path = $this->request->server['request_uri'];\n" + " return str_replace('/', '_', $path);\n" + " }\n" + "\n" + " public function getParam(string $name): string\n" + " {\n" + " if (!isset($this->request->post[$name])) {\n" + " throw new Exception(\"param[{$name}] is empty\");\n" + " }\n" + " return $this->request->post[$name];\n" + " }\n" + "\n" + " public function getDataParam(string $name): mixed\n" + " {\n" + " return unserialize($this->getParam($name));\n" + " }\n" + "\n" + " public function getCoroutineId(): int\n" + " {\n" + " $coroutine_id = $this->request->header['coroutine-id'] ?? '';\n" + " if (!$coroutine_id) {\n" + " throw new Exception('coroutine-id is empty');\n" + " }\n" + " return intval($coroutine_id);\n" + " }\n" + "\n" + " public function getClientId(): string\n" + " {\n" + " $client_id = $this->request->header['client-id'] ?? '';\n" + " if (!$client_id) {\n" + " throw new Exception('client-id is empty');\n" + " }\n" + " return $client_id;\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_remote_object_client = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\RemoteObject;\n" + "\n" + "use Swoole\\Coroutine;\n" + "use Swoole\\Coroutine\\Http\\Client as HttpClient;\n" + "use Swoole\\RemoteObject;\n" + "\n" + "class Client\n" + "{\n" + " private static array $clients = [];\n" + "\n" + " private HttpClient $client;\n" + "\n" + " private string $id;\n" + "\n" + " private int $ownerCoroutineId;\n" + "\n" + " public function __construct(string $host = '127.0.0.1', int $port = Server::DEFAULT_PORT, array $options = [])\n" + " {\n" + " $this->id = $this->genUuid();\n" + " $this->client = new HttpClient($host, $port);\n" + " $this->ownerCoroutineId = Coroutine::getCid();\n" + "\n" + " $headers = [\n" + " 'client-id' => $this->id,\n" + " 'coroutine-id' => $this->ownerCoroutineId,\n" + " ];\n" + " if (isset($options['api_key'])) {\n" + " $headers['x-api-key'] = $options['api_key'];\n" + " }\n" + " $this->client->setHeaders($headers);\n" + " self::$clients[$this->id] = $this;\n" + " }\n" + "\n" + " public function create(string $class, mixed ...$args): RemoteObject\n" + " {\n" + " return RemoteObject::create($this, $class, $args);\n" + " }\n" + "\n" + " public function call(string $fn, mixed ...$args): mixed\n" + " {\n" + " return RemoteObject::call($this, $fn, $args);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public static function getInstance(string $clientId): ?static\n" + " {\n" + " if (empty($clientId)) {\n" + " throw new Exception('RemoteObject is not bound to a client');\n" + " }\n" + " if (!isset(self::$clients[$clientId])) {\n" + " return null;\n" + " }\n" + " return self::$clients[$clientId];\n" + " }\n" + "\n" + " public function getId(): string\n" + " {\n" + " return $this->id;\n" + " }\n" + "\n" + " public function execute(string $path, array $array)\n" + " {\n" + " $rs = $this->client->post($path, $array);\n" + " if (!$rs) {\n" + " throw new Exception($this->client->errMsg);\n" + " }\n" + " $result = unserialize($this->client->body);\n" + " if ($result['code'] != 0) {\n" + " $ex = $result['exception'];\n" + " throw new Exception('Server Error: ' . $ex['message'], $ex['code']);\n" + " }\n" + " return $result;\n" + " }\n" + "\n" + " public function ping(): bool\n" + " {\n" + " try {\n" + " $this->execute('/ping', []);\n" + " return true;\n" + " } catch (\\Throwable $e) {\n" + " return false;\n" + " }\n" + " }\n" + "\n" + " private function genUuid(): string\n" + " {\n" + " $data = random_bytes(16);\n" + " $data[6] = chr(ord($data[6]) & 0x0F | 0x40);\n" + " $data[8] = chr(ord($data[8]) & 0x3F | 0x80);\n" + " return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_remote_object_exception = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\RemoteObject;\n" + "\n" + "class Exception extends \\RuntimeException\n" + "{\n" + "}\n"; + +static const char* swoole_library_source_core_remote_object_proxy_trait = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\RemoteObject;\n" + "\n" + "use Swoole\\RemoteObject;\n" + "\n" + "trait ProxyTrait\n" + "{\n" + " public function __call(string $method, array $args)\n" + " {\n" + " return $this->getObject()->{$method}(...$args);\n" + " }\n" + "\n" + " public function __get(string $property)\n" + " {\n" + " return $this->getObject()->{$property};\n" + " }\n" + "\n" + " public function __set(string $property, mixed $value)\n" + " {\n" + " $this->getObject()->{$property} = $value;\n" + " }\n" + "\n" + " public function __toString(): string\n" + " {\n" + " return $this->getObject()->__toString();\n" + " }\n" + "\n" + " public function __invoke(...$args)\n" + " {\n" + " return $this->getObject()->__invoke(...$args);\n" + " }\n" + "\n" + " public function offsetGet(mixed $offset): mixed\n" + " {\n" + " return $this->getObject()->offsetGet($offset);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function offsetSet(mixed $offset, mixed $value): void\n" + " {\n" + " $this->getObject()->offsetSet($offset, $value);\n" + " }\n" + "\n" + " /**\n" + " * @throws Exception\n" + " */\n" + " public function offsetUnset(mixed $offset): void\n" + " {\n" + " $this->getObject()->offsetUnset($offset);\n" + " }\n" + "\n" + " public function offsetExists(mixed $offset): bool\n" + " {\n" + " return $this->getObject()->offsetExists($offset);\n" + " }\n" + "\n" + " public function current(): mixed\n" + " {\n" + " return $this->getObject()->current();\n" + " }\n" + "\n" + " public function next(): void\n" + " {\n" + " $this->getObject()->next();\n" + " }\n" + "\n" + " public function key(): mixed\n" + " {\n" + " return $this->getObject()->key();\n" + " }\n" + "\n" + " public function valid(): bool\n" + " {\n" + " return $this->getObject()->valid();\n" + " }\n" + "\n" + " public function rewind(): void\n" + " {\n" + " $this->getObject()->rewind();\n" + " }\n" + "\n" + " public function count(): int\n" + " {\n" + " return $this->getObject()->count();\n" + " }\n" + "\n" + " abstract protected function getObject(): RemoteObject;\n" + "}\n"; + +static const char* swoole_library_source_core_server_admin = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\Server;\n" + "\n" + "use Swoole\\Coroutine;\n" + "use Swoole\\Http\\Request;\n" + "use Swoole\\Http\\Response;\n" + "use Swoole\\Server;\n" + "use Swoole\\StringObject;\n" + "use Swoole\\Timer;\n" + "\n" + "class Admin\n" + "{\n" + " /**\n" + " * gdb php\n" + " * (gdb) p sizeof(zval)\n" + " * $2 = 16\n" + " * (gdb) p sizeof(zend_array)\n" + " * $1 = 56\n" + " * (gdb) p sizeof(zend_string)\n" + " * $3 = 32\n" + " * (gdb) p sizeof(zend_object)\n" + " * $4 = 56\n" + " */\n" + " public const SIZE_OF_ZVAL = 16;\n" + "\n" + " public const SIZE_OF_ZEND_STRING = 32;\n" + "\n" + " public const SIZE_OF_ZEND_OBJECT = 56;\n" + "\n" + " public const SIZE_OF_ZEND_ARRAY = 56;\n" + "\n" + " private static array $map = [\n" + " 'reactor' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" + " 'reactor_thread' => SWOOLE_SERVER_COMMAND_REACTOR_THREAD,\n" + " 'worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" + " 'event_worker' => SWOOLE_SERVER_COMMAND_EVENT_WORKER,\n" + " 'task' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" + " 'task_worker' => SWOOLE_SERVER_COMMAND_TASK_WORKER,\n" + " ];\n" + "\n" + " private static array $allList = [\n" + " 'all',\n" + " 'all_reactor',\n" + " 'all_reactor_thread',\n" + " 'all_worker',\n" + " 'all_event_worker',\n" + " 'all_task',\n" + " 'all_task_worker',\n" + " 'specific',\n" + " ];\n" + "\n" + " private static array $postMethodList = [\n" + " 'server_reload',\n" + " 'server_shutdown',\n" + " 'close_session',\n" + " ];\n" + "\n" + " private static string $accessToken = '';\n" + "\n" + " public static function init(Server $server): void\n" + " {\n" + " $accepted_process_types = SWOOLE_SERVER_COMMAND_MASTER |\n" + " SWOOLE_SERVER_COMMAND_MANAGER |\n" + " SWOOLE_SERVER_COMMAND_EVENT_WORKER |\n" + " SWOOLE_SERVER_COMMAND_TASK_WORKER;\n" + "\n" + " $server->addCommand(\n" + " 'server_reload',\n" + " $accepted_process_types,\n" + " function (Server $server, string $msg) {\n" + " $server->reload();\n" + " return self::json('Operation succeeded');\n" + " }\n" + " );\n" + "\n" + " $server->addCommand(\n" + " 'server_shutdown',\n" + " $accepted_process_types,\n" + " function (Server $server, string $msg): void {\n" + " $server->shutdown();\n" + " }\n" + " );\n" + "\n" + " $server->addCommand(\n" + " 'coroutine_stats',\n" + " $accepted_process_types,\n" + " fn (Server $server, string $msg) => self::json(Coroutine::stats())\n" + " );\n" + "\n" + " $server->addCommand(\n" + " 'coroutine_list',\n" + " $accepted_process_types,\n" + " fn (Server $server, string $msg) => self::json(iterator_to_array(Coroutine::list()))\n" " );\n" "\n" " $server->addCommand(\n" " 'coroutine_bt',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " $json = json_decode($msg);\n" - " $cid = empty($json->cid) ? 0 : intval($json->cid);\n" - " $bt = Coroutine::getBackTrace($cid);\n" + " function (Server $server, string $msg) {\n" + " $json = json_decode($msg, null, 512, JSON_THROW_ON_ERROR);\n" + " $cid = empty($json->cid) ? 0 : intval($json->cid);\n" + " $bt = Coroutine::getBackTrace($cid);\n" " if ($bt === false) {\n" " return self::json(\"Coroutine#{$cid} not exists\", 4004);\n" " }\n" @@ -6311,20 +8154,18 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'server_stats',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json($server->stats());\n" - " }\n" + " fn (Server $server, string $msg) => self::json($server->stats())\n" " );\n" "\n" " $server->addCommand(\n" " 'server_setting',\n" " $accepted_process_types,\n" - " function (Server $server, $msg) {\n" - " $setting = $server->setting;\n" - " $setting['mode'] = $server->mode;\n" - " $setting['host'] = $server->host;\n" - " $setting['port'] = $server->port;\n" - " $setting['master_pid'] = $server->master_pid;\n" + " function (Server $server, string $msg) {\n" + " $setting = $server->setting;\n" + " $setting['mode'] = $server->mode;\n" + " $setting['host'] = $server->host;\n" + " $setting['port'] = $server->port;\n" + " $setting['master_pid'] = $server->master_pid;\n" " $setting['manager_pid'] = $server->manager_pid;\n" " return self::json($setting);\n" " }\n" @@ -6333,8 +8174,8 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_client_info',\n" " $accepted_process_types,\n" - " function (Server $server, $msg) {\n" - " $json = json_decode($msg, true);\n" + " function (Server $server, string $msg) {\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['session_id'])) {\n" " return self::json('require session_id', 4003);\n" " }\n" @@ -6342,39 +8183,38 @@ static const char* swoole_library_source_core_server_admin = " }\n" " );\n" "\n" - " $server->addCommand('close_session', $accepted_process_types, [__CLASS__, 'handlerCloseSession']);\n" - " $server->addCommand('get_version_info', $accepted_process_types, [__CLASS__, 'handlerGetVersionInfo']);\n" - " $server->addCommand('get_worker_info', $accepted_process_types, [__CLASS__, 'handlerGetWorkerInfo']);\n" - " $server->addCommand('get_timer_list', $accepted_process_types, [__CLASS__, 'handlerGetTimerList']);\n" - " $server->addCommand('get_coroutine_list', $accepted_process_types, [__CLASS__, 'handlerGetCoroutineList']);\n" - " $server->addCommand('get_objects', $accepted_process_types, [__CLASS__, 'handlerGetObjects']);\n" - " $server->addCommand('get_class_info', $accepted_process_types, [__CLASS__, 'handlerGetClassInfo']);\n" - " $server->addCommand('get_function_info', $accepted_process_types, [__CLASS__, 'handlerGetFunctionInfo']);\n" - " $server->addCommand('get_object_by_handle', $accepted_process_types, [__CLASS__, 'handlerGetObjectByHandle']);\n" - " $server->addCommand('get_server_cpu_usage', $accepted_process_types, [__CLASS__, 'handlerGetServerCpuUsage']);\n" + " $server->addCommand('close_session', $accepted_process_types, [self::class, 'handlerCloseSession']);\n" + " $server->addCommand('get_version_info', $accepted_process_types, [self::class, 'handlerGetVersionInfo']);\n" + " $server->addCommand('get_worker_info', $accepted_process_types, [self::class, 'handlerGetWorkerInfo']);\n" + " $server->addCommand('get_timer_list', $accepted_process_types, [self::class, 'handlerGetTimerList']);\n" + " $server->addCommand('get_coroutine_list', $accepted_process_types, [self::class, 'handlerGetCoroutineList']);\n" + " $server->addCommand('get_objects', $accepted_process_types, [self::class, 'handlerGetObjects']);\n" + " $server->addCommand('get_class_info', $accepted_process_types, [self::class, 'handlerGetClassInfo']);\n" + " $server->addCommand('get_function_info', $accepted_process_types, [self::class, 'handlerGetFunctionInfo']);\n" + " $server->addCommand('get_object_by_handle', $accepted_process_types, [self::class, 'handlerGetObjectByHandle']);\n" + " $server->addCommand('get_server_cpu_usage', $accepted_process_types, [self::class, 'handlerGetServerCpuUsage']);\n" " $server->addCommand(\n" " 'get_server_memory_usage',\n" " $accepted_process_types,\n" - " [__CLASS__, 'handlerGetServerMemoryUsage']\n" + " [self::class, 'handlerGetServerMemoryUsage']\n" " );\n" " $server->addCommand(\n" " 'get_static_property_value',\n" " $accepted_process_types,\n" - " [__CLASS__, 'handlerGetStaticPropertyValue']\n" + " [self::class, 'handlerGetStaticPropertyValue']\n" " );\n" " $server->addCommand(\n" " 'get_defined_functions',\n" " $accepted_process_types,\n" - " [__CLASS__, 'handlerGetDefinedFunctions']\n" + " [self::class, 'handlerGetDefinedFunctions']\n" " );\n" - " $server->addCommand('get_declared_classes', $accepted_process_types, [__CLASS__, 'handlerGetDeclaredClasses']);\n" + " $server->addCommand('get_declared_classes', $accepted_process_types, [self::class, 'handlerGetDeclaredClasses']);\n" "\n" " $server->addCommand(\n" " 'gc_status',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " $status = function_exists('gc_status') ? gc_status() : [];\n" - " return self::json($status);\n" + " function (Server $server, string $msg) {\n" + " return self::json(gc_status());\n" " }\n" " );\n" "\n" @@ -6382,45 +8222,37 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'opcache_status',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(opcache_get_status(true));\n" - " }\n" + " fn (Server $server, string $msg) => self::json(opcache_get_status(true))\n" " );\n" " }\n" "\n" " $server->addCommand(\n" " 'getpid',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(['pid' => posix_getpid()]);\n" - " }\n" + " fn (Server $server, string $msg) => self::json(['pid' => posix_getpid()])\n" " );\n" "\n" " $server->addCommand(\n" " 'memory_usage',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json([\n" - " 'usage' => memory_get_usage(),\n" - " 'real_usage' => memory_get_usage(true),\n" - " ]);\n" - " }\n" + " fn (Server $server, string $msg) => self::json([\n" + " 'usage' => memory_get_usage(),\n" + " 'real_usage' => memory_get_usage(true),\n" + " ])\n" " );\n" "\n" " $server->addCommand(\n" " 'get_included_files',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(['files' => get_included_files()]);\n" - " }\n" + " fn (Server $server, string $msg) => self::json(['files' => get_included_files()])\n" " );\n" "\n" - " $server->addCommand('get_resources', $accepted_process_types, [__CLASS__, 'handlerGetResources']);\n" + " $server->addCommand('get_resources', $accepted_process_types, [self::class, 'handlerGetResources']);\n" "\n" " $server->addCommand(\n" " 'get_defined_constants',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" + " function (Server $server, string $msg) {\n" " $constants = get_defined_constants();\n" " foreach ($constants as $k => $c) {\n" " if (is_resource($c)) {\n" @@ -6435,15 +8267,15 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_loaded_extensions',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" + " function (Server $server, string $msg) {\n" " $extensions = get_loaded_extensions();\n" - " $list = [];\n" + " $list = [];\n" " foreach ($extensions as $key => $extension) {\n" - " $ext = new ReflectionExtension($extension);\n" + " $ext = new \\ReflectionExtension($extension);\n" " $list[$key] = [\n" - " 'id' => ++$key,\n" - " 'name' => $extension,\n" - " 'version' => $ext->getVersion() ?? '',\n" + " 'id' => ++$key,\n" + " 'name' => $extension,\n" + " 'version' => (string) $ext->getVersion(),\n" " ];\n" " }\n" " return self::json($list);\n" @@ -6453,24 +8285,20 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_declared_interfaces',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(get_declared_interfaces());\n" - " }\n" + " fn (Server $server, string $msg) => self::json(get_declared_interfaces())\n" " );\n" "\n" " $server->addCommand(\n" " 'get_declared_traits',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" - " return self::json(get_declared_traits());\n" - " }\n" + " fn (Server $server, string $msg) => self::json(get_declared_traits())\n" " );\n" "\n" " $server->addCommand(\n" " 'get_included_file_contents',\n" " $accepted_process_types,\n" - " function (Server $server, $msg) {\n" - " $json = json_decode($msg, true);\n" + " function (Server $server, string $msg) {\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['filename'])) {\n" " return self::json('require filename', 4003);\n" " }\n" @@ -6490,18 +8318,18 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_globals',\n" " $accepted_process_types,\n" - " function ($server, $msg) {\n" + " function (Server $server, string $msg) {\n" " $globals = [];\n" " foreach ($GLOBALS as $key => $item) {\n" " if ($key === 'GLOBALS') {\n" " continue;\n" " }\n" - " $type = gettype($item);\n" + " $type = gettype($item);\n" " $other = [];\n" " if ($type === 'object') {\n" " $other = [\n" - " 'class_name' => get_class($item),\n" - " 'object_id' => spl_object_id($item),\n" + " 'class_name' => $item::class,\n" + " 'object_id' => spl_object_id($item),\n" " 'object_hash' => spl_object_hash($item),\n" " ];\n" " }\n" @@ -6509,9 +8337,9 @@ static const char* swoole_library_source_core_server_admin = " $item = '';\n" " }\n" " $globals[] = [\n" - " 'key' => $key,\n" + " 'key' => $key,\n" " 'value' => $item,\n" - " 'type' => $type,\n" + " 'type' => $type,\n" " 'other' => $other,\n" " ];\n" " }\n" @@ -6522,14 +8350,14 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_extension_info',\n" " $accepted_process_types,\n" - " function (Server $server, $msg) {\n" - " $json = json_decode($msg, true);\n" + " function (Server $server, string $msg) {\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" "\n" " if (empty($json['extension_name']) || !extension_loaded($json['extension_name'])) {\n" " return self::json('require extension_name', 4004);\n" " }\n" "\n" - " $ext = new ReflectionExtension($json['extension_name']);\n" + " $ext = new \\ReflectionExtension($json['extension_name']);\n" "\n" " ob_start();\n" " $ext->info();\n" @@ -6545,13 +8373,13 @@ static const char* swoole_library_source_core_server_admin = " unset($constants['NULL'], $constants['NAN'], $constants['INF']);\n" "\n" " return self::json([\n" - " 'classes' => $ext->getClassNames(),\n" - " 'version' => $ext->getVersion(),\n" - " 'constants' => $constants,\n" - " 'ini_entries' => $ext->getINIEntries(),\n" + " 'classes' => $ext->getClassNames(),\n" + " 'version' => $ext->getVersion(),\n" + " 'constants' => $constants,\n" + " 'ini_entries' => $ext->getINIEntries(),\n" " 'dependencies' => $ext->getDependencies(),\n" - " 'functions' => array_keys($ext->getFunctions()),\n" - " 'info' => trim($info),\n" + " 'functions' => array_keys($ext->getFunctions()),\n" + " 'info' => trim($info),\n" " ]);\n" " }\n" " );\n" @@ -6559,7 +8387,7 @@ static const char* swoole_library_source_core_server_admin = " $server->addCommand(\n" " 'get_composer_packages',\n" " $accepted_process_types,\n" - " function (Server $server, $msg) {\n" + " function (Server $server, string $msg) {\n" " if (!class_exists(\\Composer\\InstalledVersions::class)) {\n" " return self::json('require composer 2.0', 4003);\n" " }\n" @@ -6582,7 +8410,7 @@ static const char* swoole_library_source_core_server_admin = " $key_name = \"__root__{$key}\";\n" " }\n" " $package['root']['install_path'] = !empty($package['root']['install_path']) ? realpath($package['root']['install_path']) : '';\n" - " $list[$key_name] = $package;\n" + " $list[$key_name] = $package;\n" " }\n" " break;\n" " }\n" @@ -6596,17 +8424,18 @@ static const char* swoole_library_source_core_server_admin = " return self::$accessToken;\n" " }\n" "\n" - " public static function start(Server $server)\n" + " public static function start(Server $server): void\n" " {\n" " $admin_server_uri = swoole_string($server->setting['admin_server']);\n" " if ($admin_server_uri->startsWith('unix:/')) {\n" - " return swoole_error_log(SWOOLE_LOG_ERROR, \"admin_server[{$server->setting['admin_server']}] is not supported\");\n" + " swoole_error_log(SWOOLE_LOG_ERROR, \"admin_server[{$server->setting['admin_server']}] is not supported\");\n" + " return;\n" " }\n" "\n" " if ($admin_server_uri->contains('@')) {\n" " [$access_name, $access_secret] = $admin_server_uri->split('@', 2)->get(0)->split(':', 2)->toArray();\n" - " self::$accessToken = sha1($access_name . $access_secret);\n" - " [$host, $port] = $admin_server_uri->split('@', 2)->get(1)->split(':', 2)->toArray();\n" + " self::$accessToken = sha1($access_name . $access_secret);\n" + " [$host, $port] = $admin_server_uri->split('@', 2)->get(1)->split(':', 2)->toArray();\n" " } else {\n" " [$host, $port] = $admin_server_uri->split(':', 2)->toArray();\n" " }\n" @@ -6629,7 +8458,8 @@ static const char* swoole_library_source_core_server_admin = " $method = $req->getMethod();\n" "\n" " if ($method === 'OPTIONS') {\n" - " return $resp->end();\n" + " $resp->end();\n" + " return;\n" " }\n" "\n" " $token = self::getAccessToken();\n" @@ -6657,7 +8487,7 @@ static const char* swoole_library_source_core_server_admin = " }\n" "\n" " if ($cmd === 'multi') {\n" - " $body = json_decode($req->getContent(), true);\n" + " $body = json_decode($req->getContent(), true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($body) || !is_array($body) || $method != 'POST') {\n" " goto _bad_process;\n" " }\n" @@ -6675,10 +8505,10 @@ static const char* swoole_library_source_core_server_admin = "\n" " if ($process->startsWith('master')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MASTER;\n" - " $process_id = 0;\n" + " $process_id = 0;\n" " } elseif ($process->startsWith('manager')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MANAGER;\n" - " $process_id = 0;\n" + " $process_id = 0;\n" " } elseif ($process->startsWith('all') || $process->equals('specific')) {\n" " if (!in_array($process->toString(), self::$allList)) {\n" " goto _bad_process;\n" @@ -6702,7 +8532,7 @@ static const char* swoole_library_source_core_server_admin = " }\n" "\n" " $process_type = self::$map[$array->get(0)->toString()];\n" - " $process_id = intval($array->get(1)->toString());\n" + " $process_id = intval($array->get(1)->toString());\n" " }\n" "\n" " $result = $server->command($cmd, $process_id, intval($process_type), $data, false);\n" @@ -6710,26 +8540,28 @@ static const char* swoole_library_source_core_server_admin = " $resp->end(json_encode([\n" " 'code' => swoole_last_error(),\n" " 'data' => swoole_strerror(swoole_last_error()),\n" - " ]));\n" + " ], JSON_THROW_ON_ERROR));\n" " } else {\n" " $resp->end($result);\n" " }\n" " });\n" - " $admin_server->handle('/', function (Request $req, Response $resp) use ($server) {\n" + " $admin_server->handle('/', function (Request $req, Response $resp): void {\n" " $resp->status(404);\n" " });\n" " $server->admin_server = $admin_server;\n" " $admin_server->start();\n" " }\n" "\n" - " \n" - " public static function handlerGetResources($server, $msg)\n" + " /**\n" + " * @return false|string\n" + " */\n" + " public static function handlerGetResources(Server $server, string $msg)\n" " {\n" " $resources = get_resources();\n" - " $list = [];\n" + " $list = [];\n" " foreach ($resources as $r) {\n" " $info = [\n" - " 'id' => function_exists('get_resource_id') ? get_resource_id($r) : intval($r),\n" + " 'id' => get_resource_id($r),\n" " 'type' => get_resource_type($r),\n" " ];\n" " if ($info['type'] == 'stream') {\n" @@ -6740,29 +8572,31 @@ static const char* swoole_library_source_core_server_admin = " return self::json($list);\n" " }\n" "\n" - " \n" - " public static function handlerGetWorkerInfo($server, $msg)\n" + " /**\n" + " * @return false|string\n" + " */\n" + " public static function handlerGetWorkerInfo(Server $server, string $msg)\n" " {\n" " $info = [\n" - " 'id' => $server->getWorkerId(),\n" - " 'pid' => $server->getWorkerPid(),\n" - " 'gc_status' => function_exists('gc_status') ? gc_status() : [],\n" - " 'memory_usage' => memory_get_usage(),\n" + " 'id' => $server->getWorkerId(),\n" + " 'pid' => $server->getWorkerPid(),\n" + " 'gc_status' => gc_status(),\n" + " 'memory_usage' => memory_get_usage(),\n" " 'memory_real_usage' => memory_get_usage(true),\n" - " 'process_status' => self::getProcessStatus(),\n" - " 'coroutine_stats' => Coroutine::stats(),\n" - " 'timer_stats' => Timer::stats(),\n" + " 'process_status' => self::getProcessStatus(),\n" + " 'coroutine_stats' => Coroutine::stats(),\n" + " 'timer_stats' => Timer::stats(),\n" + " 'vm_status' => swoole_get_vm_status(),\n" " ];\n" - " if (function_exists('swoole_get_vm_status')) {\n" - " $info['vm_status'] = swoole_get_vm_status();\n" - " }\n" " return self::json($info);\n" " }\n" "\n" - " \n" - " public static function handlerCloseSession($server, $msg)\n" + " /**\n" + " * @return false|string\n" + " */\n" + " public static function handlerCloseSession(Server $server, string $msg)\n" " {\n" - " $json = json_decode($msg, true);\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['session_id'])) {\n" " return self::json('require session_id', 4003);\n" " }\n" @@ -6772,13 +8606,15 @@ static const char* swoole_library_source_core_server_admin = " return self::json(['error' => swoole_last_error()], 4004);\n" " }\n" "\n" - " \n" - " public static function handlerGetTimerList($server, $msg)\n" + " /**\n" + " * @return false|string\n" + " */\n" + " public static function handlerGetTimerList(Server $server, string $msg)\n" " {\n" " $list = [];\n" " foreach (Timer::list() as $timer_id) {\n" " $list[] = [\n" - " 'id' => $timer_id,\n" + " 'id' => $timer_id,\n" " 'info' => Timer::info($timer_id),\n" " ];\n" " }\n" @@ -6786,40 +8622,39 @@ static const char* swoole_library_source_core_server_admin = " return self::json($list);\n" " }\n" "\n" - " \n" - " public static function handlerGetCoroutineList($server, $msg)\n" + " /**\n" + " * @return false|string\n" + " */\n" + " public static function handlerGetCoroutineList(Server $server, string $msg)\n" " {\n" " $list = [];\n" " foreach (Coroutine::list() as $cid) {\n" " $list[] = [\n" - " 'id' => $cid,\n" - " 'elapsed' => Coroutine::getElapsed($cid),\n" + " 'id' => $cid,\n" + " 'elapsed' => Coroutine::getElapsed($cid),\n" " 'stack_usage' => Coroutine::getStackUsage($cid),\n" - " 'backTrace' => Coroutine::getBackTrace($cid, DEBUG_BACKTRACE_IGNORE_ARGS, 1),\n" + " 'backTrace' => Coroutine::getBackTrace($cid, DEBUG_BACKTRACE_IGNORE_ARGS, 1),\n" " ];\n" " }\n" "\n" " return self::json($list);\n" " }\n" "\n" - " public static function handlerGetObjects($server, $msg)\n" + " public static function handlerGetObjects(Server $server, string $msg)\n" " {\n" - " if (!function_exists('swoole_get_objects')) {\n" - " return self::json(['require ext-swoole_plus'], 5000);\n" - " }\n" - " $list = [];\n" + " $list = [];\n" " $objects = swoole_get_objects();\n" " foreach ($objects as $o) {\n" - " $class_name = get_class($o);\n" - " $class = new ReflectionClass($class_name);\n" - " $filename = $class->getFileName();\n" - " $line = $class->getStartLine();\n" - " $list[] = [\n" - " 'id' => spl_object_id($o),\n" - " 'hash' => spl_object_hash($o),\n" - " 'class' => $class_name,\n" - " 'filename' => $filename ?: '',\n" - " 'line' => $line ?: '',\n" + " $class_name = $o::class;\n" + " $class = new \\ReflectionClass($class_name);\n" + " $filename = $class->getFileName();\n" + " $line = $class->getStartLine();\n" + " $list[] = [\n" + " 'id' => spl_object_id($o),\n" + " 'hash' => spl_object_hash($o),\n" + " 'class' => $class_name,\n" + " 'filename' => $filename ?: '',\n" + " 'line' => $line ?: '',\n" " 'memory_size' => self::getObjectMemorySize($o),\n" " ];\n" " }\n" @@ -6827,28 +8662,24 @@ static const char* swoole_library_source_core_server_admin = " return self::json($list);\n" " }\n" "\n" - " public static function handlerGetClassInfo($server, $msg)\n" + " public static function handlerGetClassInfo(Server $server, string $msg)\n" " {\n" - " $json = json_decode($msg, true);\n" - " if (empty($json['class_name']) && empty($json['interface_name'])) {\n" - " return self::json(['error' => 'require class_name or interface_name'], 4004);\n" - " }\n" - "\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (!empty($json['class_name'])) {\n" " if (!class_exists($json['class_name'], false) && !interface_exists($json['class_name'], false)) {\n" " return self::json(\"{$json['class_name']} not exists\", 4003);\n" " }\n" " $name = $json['class_name'];\n" - " }\n" - "\n" - " if (!empty($json['interface_name'])) {\n" + " } elseif (!empty($json['interface_name'])) {\n" " if (!interface_exists($json['interface_name'], false)) {\n" " return self::json(\"{$json['interface_name']} not exists\", 4003);\n" " }\n" " $name = $json['interface_name'];\n" + " } else {\n" + " return self::json(['error' => 'require class_name or interface_name'], 4004);\n" " }\n" "\n" - " $class = new ReflectionClass($name);\n" + " $class = new \\ReflectionClass($name);\n" "\n" " $filename = $class->getFileName();\n" "\n" @@ -6856,38 +8687,38 @@ static const char* swoole_library_source_core_server_admin = " $tmp = [];\n" " foreach ($data as $k => $v) {\n" " $tmp[] = [\n" - " 'name' => $k,\n" + " 'name' => $k,\n" " 'value' => is_array($v) ? var_export($v, true) : $v,\n" - " 'type' => is_array($v) ? 'detail' : 'default',\n" + " 'type' => is_array($v) ? 'detail' : 'default',\n" " ];\n" " }\n" " return $tmp;\n" " };\n" "\n" " $tmpConstants = $class->getConstants();\n" - " $constants = $tmpConstants ? $getTmpConstants($tmpConstants) : [];\n" + " $constants = $tmpConstants ? $getTmpConstants($tmpConstants) : [];\n" "\n" " $staticProperties = [];\n" - " $properties = [];\n" - " $tmpProperties = $class->getProperties();\n" + " $properties = [];\n" + " $tmpProperties = $class->getProperties();\n" "\n" " $getTmpProperties = function ($class, $data) {\n" - " $static = [];\n" - " $noStatic = [];\n" + " $static = [];\n" + " $noStatic = [];\n" " $defaultProperties = $class->getDefaultProperties();\n" " foreach ($data as $k => $v) {\n" - " $name = $v->getName();\n" - " $modifiers = Reflection::getModifierNames($v->getModifiers());\n" + " $name = $v->getName();\n" + " $modifiers = \\Reflection::getModifierNames($v->getModifiers());\n" " if ($v->isStatic()) {\n" " $static[] = [\n" - " 'name' => $name,\n" - " 'value' => $defaultProperties[$name],\n" + " 'name' => $name,\n" + " 'value' => $defaultProperties[$name],\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " } else {\n" " $noStatic[] = [\n" - " 'name' => $name,\n" - " 'value' => $defaultProperties[$name],\n" + " 'name' => $name,\n" + " 'value' => $defaultProperties[$name],\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " }\n" @@ -6896,32 +8727,32 @@ static const char* swoole_library_source_core_server_admin = " };\n" "\n" " if ($tmpProperties) {\n" - " $tmpProperties = $getTmpProperties($class, $tmpProperties);\n" + " $tmpProperties = $getTmpProperties($class, $tmpProperties);\n" " $staticProperties = $tmpProperties['static'];\n" - " $properties = $tmpProperties['no_static'];\n" + " $properties = $tmpProperties['no_static'];\n" " }\n" "\n" - " $staticMethods = [];\n" - " $methods = [];\n" + " $staticMethods = [];\n" + " $methods = [];\n" " $tmpStaticMethods = $class->getMethods();\n" "\n" " $getTmpMethods = function ($data) {\n" - " $static = [];\n" + " $static = [];\n" " $noStatic = [];\n" " foreach ($data as $k => $v) {\n" - " $name = $v->getName();\n" - " $line = $v->getStartLine();\n" - " $modifiers = Reflection::getModifierNames($v->getModifiers());\n" + " $name = $v->getName();\n" + " $line = $v->getStartLine();\n" + " $modifiers = \\Reflection::getModifierNames($v->getModifiers());\n" " if ($v->isStatic()) {\n" " $static[] = [\n" - " 'name' => $name,\n" - " 'line' => $line ?: '',\n" + " 'name' => $name,\n" + " 'line' => $line ?: '',\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " } else {\n" " $noStatic[] = [\n" - " 'name' => $name,\n" - " 'line' => $line ?: '',\n" + " 'name' => $name,\n" + " 'line' => $line ?: '',\n" " 'modifiers' => implode(' ', $modifiers),\n" " ];\n" " }\n" @@ -6931,34 +8762,31 @@ static const char* swoole_library_source_core_server_admin = "\n" " if ($tmpStaticMethods) {\n" " $tmpStaticMethods = $getTmpMethods($tmpStaticMethods);\n" - " $staticMethods = $tmpStaticMethods['static'];\n" - " $methods = $tmpStaticMethods['no_static'];\n" + " $staticMethods = $tmpStaticMethods['static'];\n" + " $methods = $tmpStaticMethods['no_static'];\n" " }\n" "\n" " $tmpParentClass = $class->getParentClass();\n" - " $parentClass = $tmpParentClass ? $tmpParentClass->getName() : '';\n" - "\n" - " $tmpInterface = $class->getInterfaceNames();\n" - " $interface = $tmpInterface ?? [];\n" + " $parentClass = $tmpParentClass ? $tmpParentClass->getName() : '';\n" "\n" " $data = [\n" - " 'filename' => $filename,\n" - " 'constants' => $constants,\n" + " 'filename' => $filename,\n" + " 'constants' => $constants,\n" " 'staticProperties' => $staticProperties,\n" - " 'properties' => $properties,\n" - " 'staticMethods' => $staticMethods,\n" - " 'methods' => $methods,\n" - " 'parentClass' => $parentClass,\n" - " 'interface' => $interface,\n" + " 'properties' => $properties,\n" + " 'staticMethods' => $staticMethods,\n" + " 'methods' => $methods,\n" + " 'parentClass' => $parentClass,\n" + " 'interface' => $class->getInterfaceNames(),\n" " ];\n" " return self::json($data);\n" " }\n" "\n" - " public static function handlerGetFunctionInfo($server, $msg)\n" + " public static function handlerGetFunctionInfo(Server $server, string $msg)\n" " {\n" - " $json = json_decode($msg, true);\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" "\n" - " $className = $json['class_name'] ?? '';\n" + " $className = $json['class_name'] ?? '';\n" " $functionName = $json['function_name'] ?? '';\n" "\n" " if (empty($json) || empty($functionName)) {\n" @@ -6973,22 +8801,22 @@ static const char* swoole_library_source_core_server_admin = " if (!method_exists($className, $functionName)) {\n" " return self::json(\"{$className}->{$functionName} not exists\", 4004);\n" " }\n" - " $ref = new ReflectionMethod($className, $functionName);\n" + " $ref = new \\ReflectionMethod($className, $functionName);\n" " $isStatic = $ref->isStatic();\n" " } else {\n" " if (!function_exists($functionName)) {\n" " return self::json(\"{$functionName} not exists\", 4004);\n" " }\n" - " $ref = new ReflectionFunction($functionName);\n" + " $ref = new \\ReflectionFunction($functionName);\n" " }\n" "\n" " $result = [\n" - " 'filename' => $ref->getFileName(),\n" - " 'line' => $ref->getStartLine() ?? '',\n" - " 'num' => $ref->getNumberOfParameters(),\n" + " 'filename' => $ref->getFileName(),\n" + " 'line' => $ref->getStartLine() ?: '',\n" + " 'num' => $ref->getNumberOfParameters(),\n" " 'user_defined' => $ref->isUserDefined(),\n" - " 'extension' => $ref->getExtensionName(),\n" - " 'is_static' => $isStatic,\n" + " 'extension' => $ref->getExtensionName(),\n" + " 'is_static' => $isStatic,\n" " ];\n" "\n" " $params = $ref->getParameters();\n" @@ -7000,7 +8828,7 @@ static const char* swoole_library_source_core_server_admin = " $paramName = $param->getName();\n" "\n" " if ($param->hasType()) {\n" - " \n" + " /** @var \\ReflectionNamedType|\\ReflectionUnionType $reflection */\n" " $reflection = $param->getType();\n" " if ($reflection instanceof \\ReflectionUnionType) {\n" " $unionType = [];\n" @@ -7014,9 +8842,6 @@ static const char* swoole_library_source_core_server_admin = " }\n" "\n" " if ($param->isOptional() && !$param->isVariadic()) {\n" - " if (!$result['user_defined'] && PHP_VERSION_ID < 80000) {\n" - " continue;\n" - " }\n" " $optional = '?';\n" " if ($param->isDefaultValueAvailable()) {\n" " $value = $param->getDefaultValue();\n" @@ -7039,19 +8864,19 @@ static const char* swoole_library_source_core_server_admin = " }\n" "\n" " $isPassedByReference = $param->isPassedByReference() ? '&' : '';\n" - " $isVariadic = $param->isVariadic() ? '...' : '';\n" + " $isVariadic = $param->isVariadic() ? '...' : '';\n" "\n" " $option = \"{$optional}{$type} {$isPassedByReference}{$isVariadic}\";\n" - " $param = \"\\${$paramName}{$default}\";\n" + " $param = \"\\${$paramName}{$default}\";\n" "\n" " $list[] = [\n" - " 'optional' => $optional,\n" - " 'type' => $type,\n" + " 'optional' => $optional,\n" + " 'type' => $type,\n" " 'is_passed_by_reference' => $isPassedByReference,\n" - " 'is_variadic' => $isVariadic,\n" - " 'name' => $paramName,\n" - " 'default' => $default,\n" - " 'full' => $option !== ' ' ? \"{$option}{$param}\" : $param,\n" + " 'is_variadic' => $isVariadic,\n" + " 'name' => $paramName,\n" + " 'default' => $default,\n" + " 'full' => $option !== ' ' ? \"{$option}{$param}\" : $param,\n" " ];\n" " }\n" " $result['params'] = $list;\n" @@ -7059,13 +8884,9 @@ static const char* swoole_library_source_core_server_admin = " return self::json($result);\n" " }\n" "\n" - " public static function handlerGetObjectByHandle($server, $msg)\n" + " public static function handlerGetObjectByHandle(Server $server, string $msg)\n" " {\n" - " if (!function_exists('swoole_get_object_by_handle')) {\n" - " return self::json(['require ext-swoole_plus'], 5000);\n" - " }\n" - "\n" - " $json = json_decode($msg, true);\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json) || empty($json['object_id']) || empty($json['object_hash'])) {\n" " return self::json(['error' => 'Params Error!'], 4004);\n" " }\n" @@ -7075,7 +8896,7 @@ static const char* swoole_library_source_core_server_admin = " return self::json(['error' => 'Object destroyed!'], 4004);\n" " }\n" "\n" - " $object_hash = spl_object_hash($object);\n" + " $object_hash = spl_object_hash($object); // @phpstan-ignore argument.type\n" " if ($object_hash != $json['object_hash']) {\n" " return self::json(['error' => 'Object destroyed!'], 4004);\n" " }\n" @@ -7083,11 +8904,11 @@ static const char* swoole_library_source_core_server_admin = " return self::json(var_export($object, true));\n" " }\n" "\n" - " public static function handlerGetVersionInfo($server, $msg)\n" + " public static function handlerGetVersionInfo(Server $server, string $msg)\n" " {\n" " $ip_arr = swoole_get_local_ip();\n" - " $host = [];\n" - " $local = [];\n" + " $host = [];\n" + " $local = [];\n" " foreach ($ip_arr as $k => $ip) {\n" " if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n" " $host[] = $ip;\n" @@ -7096,55 +8917,55 @@ static const char* swoole_library_source_core_server_admin = " }\n" " }\n" " $data = [\n" - " 'os' => php_uname('s') . '-' . php_uname('r'),\n" + " 'os' => php_uname('s') . '-' . php_uname('r'),\n" " 'swoole' => swoole_version(),\n" - " 'php' => phpversion(),\n" - " 'ip' => $host ? $host[0] : $local[0],\n" + " 'php' => phpversion(),\n" + " 'ip' => $host ? $host[0] : $local[0],\n" " ];\n" " return self::json($data);\n" " }\n" "\n" - " public static function handlerGetDefinedFunctions($server, $msg)\n" + " public static function handlerGetDefinedFunctions(Server $server, string $msg)\n" " {\n" " $functions = get_defined_functions();\n" - " $arr = [];\n" - " if ($functions) {\n" - " $arr['internal'] = $functions['internal'];\n" - "\n" + " $arr = [\n" + " 'internal' => $functions['internal'],\n" + " ];\n" + " if (!empty($functions['user'])) {\n" " foreach ($functions['user'] as $function_name) {\n" - " $function = new ReflectionFunction($function_name);\n" - " $filename = $function->getFileName();\n" - " $line = $function->getStartLine();\n" + " $function = new \\ReflectionFunction($function_name);\n" + " $filename = $function->getFileName();\n" + " $line = $function->getStartLine();\n" " $arr['user'][] = [\n" " 'function' => $function_name,\n" " 'filename' => $filename,\n" - " 'line' => $line,\n" + " 'line' => $line,\n" " ];\n" " }\n" " }\n" " return self::json($arr);\n" " }\n" "\n" - " public static function handlerGetDeclaredClasses($server, $msg)\n" + " public static function handlerGetDeclaredClasses(Server $server, string $msg)\n" " {\n" " $classes = get_declared_classes();\n" - " $arr = [];\n" + " $arr = [];\n" " if ($classes) {\n" " foreach ($classes as $classes_name) {\n" - " $function = new ReflectionClass($classes_name);\n" + " $function = new \\ReflectionClass($classes_name);\n" " $filename = $function->getFileName();\n" - " $line = $function->getStartLine();\n" - " $arr[] = [\n" - " 'class' => $classes_name,\n" + " $line = $function->getStartLine();\n" + " $arr[] = [\n" + " 'class' => $classes_name,\n" " 'filename' => $filename ?: '',\n" - " 'line' => $line ?: '',\n" + " 'line' => $line ?: '',\n" " ];\n" " }\n" " }\n" " return self::json($arr);\n" " }\n" "\n" - " public static function handlerGetServerMemoryUsage($server, $msg)\n" + " public static function handlerGetServerMemoryUsage(Server $server, string $msg)\n" " {\n" " $total = 0;\n" "\n" @@ -7160,8 +8981,9 @@ static const char* swoole_library_source_core_server_admin = " $total += $result['manager'];\n" "\n" " $n = $server->setting['worker_num'] + $server->setting['task_worker_num'];\n" + " /** @var int $n */\n" " for ($i = 0; $i < $n; $i++) {\n" - " $key = 'worker-' . $i;\n" + " $key = 'worker-' . $i;\n" " $result[$key] = self::getProcessMemoryRealUsage($server->getWorkerPid($i));\n" " $total += $result[$key];\n" " }\n" @@ -7169,16 +8991,16 @@ static const char* swoole_library_source_core_server_admin = " $result['total'] = $total;\n" "\n" " $result['memory_size'] = 0;\n" - " \n" + " // TODO: Support other OS\n" " if (PHP_OS_FAMILY === 'Linux') {\n" " preg_match('#MemTotal:\\s+(\\d+) kB#i', file_get_contents('/proc/meminfo'), $match);\n" - " $result['memory_size'] = $match[1] * 1024;\n" + " $result['memory_size'] = intval($match[1]) * 1024;\n" " }\n" "\n" " return self::json($result);\n" " }\n" "\n" - " public static function handlerGetServerCpuUsage($server, $msg)\n" + " public static function handlerGetServerCpuUsage(Server $server, string $msg)\n" " {\n" " $total = 0;\n" "\n" @@ -7196,21 +9018,22 @@ static const char* swoole_library_source_core_server_admin = " $total += $result['manager'][1] ?? 0;\n" "\n" " $n = $server->setting['worker_num'] + $server->setting['task_worker_num'];\n" + " /** @var int $n */\n" " for ($i = 0; $i < $n; $i++) {\n" - " $key = 'worker-' . $i;\n" + " $key = 'worker-' . $i;\n" " $result[$key] = self::getProcessCpuUsage($server->getWorkerPid($i))[1] ?? 0;\n" " $total += $result[$key];\n" " }\n" "\n" - " $result['total'] = $total;\n" + " $result['total'] = $total;\n" " $result['cpu_num'] = swoole_cpu_num();\n" "\n" " return self::json($result);\n" " }\n" "\n" - " public static function handlerGetStaticPropertyValue($server, $msg)\n" + " public static function handlerGetStaticPropertyValue(Server $server, string $msg)\n" " {\n" - " $json = json_decode($msg, true);\n" + " $json = json_decode($msg, true, 512, JSON_THROW_ON_ERROR);\n" " if (empty($json['class_name'])) {\n" " return self::json(['error' => 'require class_name!'], 4004);\n" " }\n" @@ -7218,15 +9041,15 @@ static const char* swoole_library_source_core_server_admin = " return self::json(['error' => 'require property_name!'], 4004);\n" " }\n" "\n" - " $className = $json['class_name'];\n" + " $className = $json['class_name'];\n" " $propertyName = $json['property_name'];\n" "\n" " if (!class_exists($className)) {\n" " return self::json(\"class[{$className}] not exists\", 4004);\n" " }\n" "\n" - " $reflection = new ReflectionClass($className);\n" - " $value = $reflection->getStaticPropertyValue($propertyName, []);\n" + " $reflection = new \\ReflectionClass($className);\n" + " $value = $reflection->getStaticPropertyValue($propertyName, []);\n" "\n" " $result = [\n" " 'value' => var_export($value, true),\n" @@ -7239,7 +9062,7 @@ static const char* swoole_library_source_core_server_admin = " $return_list = [];\n" " foreach ($list as $key => $content) {\n" " $path_array = swoole_string($content['path'])->trim('/')->split('/');\n" - " $cmd = $path_array->get(1)->toString();\n" + " $cmd = $path_array->get(1)->toString();\n" "\n" " if ($path_array->count() == 2) {\n" " $process = swoole_string('master');\n" @@ -7247,7 +9070,7 @@ static const char* swoole_library_source_core_server_admin = " $process = $path_array->get(2);\n" " }\n" "\n" - " $data = [];\n" + " $data = [];\n" " $url_query = parse_url($process->toString(), PHP_URL_QUERY) ?? [];\n" " if (!empty($url_query)) {\n" " parse_str($url_query, $data);\n" @@ -7256,10 +9079,10 @@ static const char* swoole_library_source_core_server_admin = "\n" " if ($process->startsWith('master')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MASTER;\n" - " $process_id = 0;\n" + " $process_id = 0;\n" " } elseif ($process->startsWith('manager')) {\n" " $process_type = SWOOLE_SERVER_COMMAND_MANAGER;\n" - " $process_id = 0;\n" + " $process_id = 0;\n" " } elseif ($process->startsWith('all') || $process->startsWith('specific')) {\n" " if (!in_array($process->toString(), self::$allList) && !$process->startsWith('specific')) {\n" " $return_list[$key] = json_decode('{}');\n" @@ -7279,7 +9102,7 @@ static const char* swoole_library_source_core_server_admin = " }\n" "\n" " $process_type = self::$map[$array->get(0)->toString()];\n" - " $process_id = intval($array->get(1)->toString());\n" + " $process_id = intval($array->get(1)->toString());\n" " }\n" "\n" " $return_list[$key] = $server->command($cmd, $process_id, intval($process_type), $data, true);\n" @@ -7302,7 +9125,7 @@ static const char* swoole_library_source_core_server_admin = " } elseif ($process->startsWith('all_task')) {\n" " $result = self::handlerGetAllTaskWorker($cmd, $data, $server, $json_decode);\n" " } else {\n" - " \n" + " // specific\n" " $result = [];\n" " if (!empty($data['workers']) && is_array($data['workers'])) {\n" " foreach ($data['workers'] as $name) {\n" @@ -7316,8 +9139,8 @@ static const char* swoole_library_source_core_server_admin = " if ($array->count() != 2 || !isset(self::$map[$array->get(0)->toString()])) {\n" " $result[$name] = $json_decode ? json_decode('{}') : $json_decode;\n" " } else {\n" - " $process_type = self::$map[$array->get(0)->toString()];\n" - " $process_id = intval($array->get(1)->toString());\n" + " $process_type = self::$map[$array->get(0)->toString()];\n" + " $process_id = intval($array->get(1)->toString());\n" " $result[$name] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" " }\n" @@ -7361,8 +9184,8 @@ static const char* swoole_library_source_core_server_admin = " private static function handlerGetAllWorker($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $process_type = SWOOLE_SERVER_COMMAND_EVENT_WORKER;\n" - " $worker_num = $server->setting['worker_num'];\n" - " $list = [];\n" + " $worker_num = $server->setting['worker_num'];\n" + " $list = [];\n" " for ($process_id = 0; $process_id < $worker_num; $process_id++) {\n" " $list[\"worker-{$process_id}\"] = $server->command($cmd, $process_id, $process_type, $data, $json_decode);\n" " }\n" @@ -7372,7 +9195,7 @@ static const char* swoole_library_source_core_server_admin = " private static function handlerGetAllTaskWorker($cmd, $data, Server $server, bool $json_decode = false)\n" " {\n" " $process_type = SWOOLE_SERVER_COMMAND_TASK_WORKER;\n" - " $list = [];\n" + " $list = [];\n" " if (empty($server->setting['task_worker_num'])) {\n" " return $list;\n" " }\n" @@ -7385,12 +9208,12 @@ static const char* swoole_library_source_core_server_admin = "\n" " private static function getProcessCpuUsage($pid)\n" " {\n" - " \n" + " // TODO: Support other OS\n" " if (PHP_OS_FAMILY !== 'Linux' || !file_exists(\"/proc/{$pid}/stat\")) {\n" " return [0];\n" " }\n" "\n" - " $statAll = file_get_contents('/proc/stat');\n" + " $statAll = file_get_contents('/proc/stat');\n" " $statProc = file_get_contents(\"/proc/{$pid}/stat\");\n" "\n" " $dataAll = preg_split(\"/[ \\t]+/\", $statAll, 6);\n" @@ -7418,17 +9241,17 @@ static const char* swoole_library_source_core_server_admin = " private static function getProcessStatus($pid = 'self')\n" " {\n" " $array = [];\n" - " \n" + " // TODO: Support other OS\n" " if (PHP_OS_FAMILY !== 'Linux' || !file_exists(\"/proc/{$pid}/status\")) {\n" " return $array;\n" " }\n" " $status = swoole_string(trim(file_get_contents(\"/proc/{$pid}/status\")));\n" - " $lines = $status->split(\"\\n\");\n" + " $lines = $status->split(\"\\n\");\n" " foreach ($lines as $l) {\n" " if (empty($l)) {\n" " continue;\n" " }\n" - " [$k, $v] = swoole_string($l)->split(':');\n" + " [$k, $v] = swoole_string($l)->split(':');\n" " $array[$k] = trim($v);\n" " }\n" " return $array;\n" @@ -7510,234 +9333,254 @@ static const char* swoole_library_source_core_server_admin = static const char* swoole_library_source_core_server_helper = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole\\Server;\n" "\n" + "use Swoole\\Constant;\n" + "use Swoole\\Coroutine;\n" "use Swoole\\Server;\n" "use Swoole\\Timer;\n" - "use function Swoole\\Coroutine\\go;\n" "\n" "class Helper\n" "{\n" " public const STATS_TIMER_INTERVAL_TIME = 1000;\n" "\n" " public const GLOBAL_OPTIONS = [\n" - " 'debug_mode' => true,\n" - " 'trace_flags' => true,\n" - " 'log_file' => true,\n" - " 'log_level' => true,\n" - " 'log_date_format' => true,\n" - " 'log_date_with_microseconds' => true,\n" - " 'log_rotation' => true,\n" - " 'display_errors' => true,\n" - " 'dns_server' => true,\n" - " 'socket_dns_timeout' => true,\n" - " 'socket_connect_timeout' => true,\n" - " 'socket_write_timeout' => true,\n" - " 'socket_send_timeout' => true,\n" - " 'socket_read_timeout' => true,\n" - " 'socket_recv_timeout' => true,\n" - " 'socket_buffer_size' => true,\n" - " 'socket_timeout' => true,\n" + " 'debug_mode' => true,\n" + " 'trace_flags' => true,\n" + " 'log_file' => true,\n" + " 'log_level' => true,\n" + " 'log_date_format' => true,\n" + " 'log_date_with_microseconds' => true,\n" + " 'log_rotation' => true,\n" + " 'display_errors' => true,\n" + " 'dns_server' => true,\n" + " 'socket_dns_timeout' => true,\n" + " 'socket_connect_timeout' => true,\n" + " 'socket_write_timeout' => true,\n" + " 'socket_send_timeout' => true,\n" + " 'socket_read_timeout' => true,\n" + " 'socket_recv_timeout' => true,\n" + " 'socket_buffer_size' => true,\n" + " 'socket_timeout' => true,\n" + " 'http2_header_table_size' => true,\n" + " 'http2_enable_push' => true,\n" + " 'http2_max_concurrent_streams' => true,\n" + " 'http2_init_window_size' => true,\n" + " 'http2_max_frame_size' => true,\n" + " 'http2_max_header_list_size' => true,\n" " ];\n" "\n" " public const SERVER_OPTIONS = [\n" - " 'chroot' => true,\n" - " 'user' => true,\n" - " 'group' => true,\n" - " 'daemonize' => true,\n" - " 'pid_file' => true,\n" - " 'reactor_num' => true,\n" - " 'single_thread' => true,\n" - " 'worker_num' => true,\n" - " 'max_wait_time' => true,\n" - " 'max_queued_bytes' => true,\n" - " 'max_concurrency' => true,\n" - " 'worker_max_concurrency' => true,\n" - " 'enable_coroutine' => true,\n" - " 'send_timeout' => true,\n" - " 'dispatch_mode' => true,\n" - " 'send_yield' => true,\n" - " 'dispatch_func' => true,\n" - " 'discard_timeout_request' => true,\n" - " 'enable_unsafe_event' => true,\n" - " 'enable_delay_receive' => true,\n" - " 'enable_reuse_port' => true,\n" - " 'task_use_object' => true,\n" - " 'task_object' => true,\n" - " 'event_object' => true,\n" - " 'task_enable_coroutine' => true,\n" - " 'task_worker_num' => true,\n" - " 'task_ipc_mode' => true,\n" - " 'task_tmpdir' => true,\n" - " 'task_max_request' => true,\n" - " 'task_max_request_grace' => true,\n" - " 'max_connection' => true,\n" - " 'max_conn' => true,\n" - " 'start_session_id' => true,\n" - " 'heartbeat_check_interval' => true,\n" - " 'heartbeat_idle_time' => true,\n" - " 'max_request' => true,\n" - " 'max_request_grace' => true,\n" - " 'reload_async' => true,\n" - " 'open_cpu_affinity' => true,\n" - " 'cpu_affinity_ignore' => true,\n" - " 'http_parse_cookie' => true,\n" - " 'http_parse_post' => true,\n" - " 'http_parse_files' => true,\n" - " 'http_compression' => true,\n" - " 'http_compression_level' => true,\n" - " 'compression_level' => true,\n" - " 'http_gzip_level' => true,\n" + " 'chroot' => true,\n" + " 'user' => true,\n" + " 'group' => true,\n" + " 'daemonize' => true,\n" + " 'pid_file' => true,\n" + " 'reactor_num' => true,\n" + " 'single_thread' => true,\n" + " 'worker_num' => true,\n" + " 'max_wait_time' => true,\n" + " 'max_queued_bytes' => true,\n" + " 'max_concurrency' => true,\n" + " 'worker_max_concurrency' => true,\n" + " 'enable_coroutine' => true,\n" + " 'send_timeout' => true,\n" + " 'dispatch_mode' => true,\n" + " 'send_yield' => true,\n" + " 'dispatch_func' => true,\n" + " 'discard_timeout_request' => true,\n" + " 'enable_unsafe_event' => true,\n" + " 'enable_delay_receive' => true,\n" + " 'enable_reuse_port' => true,\n" + " 'task_use_object' => true,\n" + " 'task_object' => true,\n" + " 'event_object' => true,\n" + " 'task_enable_coroutine' => true,\n" + " 'task_worker_num' => true,\n" + " 'task_ipc_mode' => true,\n" + " 'task_tmpdir' => true,\n" + " 'task_max_request' => true,\n" + " 'task_max_request_grace' => true,\n" + " 'max_connection' => true,\n" + " 'max_conn' => true,\n" + " 'start_session_id' => true,\n" + " 'heartbeat_check_interval' => true,\n" + " 'heartbeat_idle_time' => true,\n" + " 'max_request' => true,\n" + " 'max_request_grace' => true,\n" + " 'reload_async' => true,\n" + " 'open_cpu_affinity' => true,\n" + " 'cpu_affinity_ignore' => true,\n" + " 'http_parse_cookie' => true,\n" + " 'http_parse_post' => true,\n" + " 'http_parse_files' => true,\n" + " 'http_compression' => true,\n" + " 'http_compression_level' => true,\n" + " 'compression_level' => true,\n" + " 'http_gzip_level' => true,\n" " 'http_compression_min_length' => true,\n" - " 'compression_min_length' => true,\n" - " 'websocket_compression' => true,\n" - " 'upload_tmp_dir' => true,\n" - " 'upload_max_filesize' => true,\n" - " 'enable_static_handler' => true,\n" - " 'document_root' => true,\n" - " 'http_autoindex' => true,\n" - " 'http_index_files' => true,\n" - " 'http_compression_types' => true,\n" - " 'compression_types' => true,\n" - " 'static_handler_locations' => true,\n" - " 'input_buffer_size' => true,\n" - " 'buffer_input_size' => true,\n" - " 'output_buffer_size' => true,\n" - " 'buffer_output_size' => true,\n" - " 'message_queue_key' => true,\n" + " 'compression_min_length' => true,\n" + " 'websocket_compression' => true,\n" + " 'upload_tmp_dir' => true,\n" + " 'upload_max_filesize' => true,\n" + " 'enable_static_handler' => true,\n" + " 'document_root' => true,\n" + " 'http_autoindex' => true,\n" + " 'http_index_files' => true,\n" + " 'http_compression_types' => true,\n" + " 'compression_types' => true,\n" + " 'static_handler_locations' => true,\n" + " 'input_buffer_size' => true,\n" + " 'buffer_input_size' => true,\n" + " 'output_buffer_size' => true,\n" + " 'buffer_output_size' => true,\n" + " 'message_queue_key' => true,\n" + " 'bootstrap' => true,\n" + " 'init_arguments' => true,\n" + " 'url_rewrite_rules' => true,\n" " ];\n" "\n" " public const PORT_OPTIONS = [\n" - " 'ssl_cert_file' => true,\n" - " 'ssl_key_file' => true,\n" - " 'backlog' => true,\n" - " 'socket_buffer_size' => true,\n" + " 'ssl_cert_file' => true,\n" + " 'ssl_key_file' => true,\n" + " 'backlog' => true,\n" + " 'socket_buffer_size' => true,\n" " 'kernel_socket_recv_buffer_size' => true,\n" " 'kernel_socket_send_buffer_size' => true,\n" - " 'heartbeat_idle_time' => true,\n" - " 'buffer_high_watermark' => true,\n" - " 'buffer_low_watermark' => true,\n" - " 'open_tcp_nodelay' => true,\n" - " 'tcp_defer_accept' => true,\n" - " 'open_tcp_keepalive' => true,\n" - " 'open_eof_check' => true,\n" - " 'open_eof_split' => true,\n" - " 'package_eof' => true,\n" - " 'open_http_protocol' => true,\n" - " 'open_websocket_protocol' => true,\n" - " 'websocket_subprotocol' => true,\n" - " 'open_websocket_close_frame' => true,\n" - " 'open_websocket_ping_frame' => true,\n" - " 'open_websocket_pong_frame' => true,\n" - " 'open_http2_protocol' => true,\n" - " 'open_mqtt_protocol' => true,\n" - " 'open_redis_protocol' => true,\n" - " 'max_idle_time' => true,\n" - " 'tcp_keepidle' => true,\n" - " 'tcp_keepinterval' => true,\n" - " 'tcp_keepcount' => true,\n" - " 'tcp_user_timeout' => true,\n" - " 'tcp_fastopen' => true,\n" - " 'open_length_check' => true,\n" - " 'package_length_type' => true,\n" - " 'package_length_offset' => true,\n" - " 'package_body_offset' => true,\n" - " 'package_body_start' => true,\n" - " 'package_length_func' => true,\n" - " 'package_max_length' => true,\n" - " 'ssl_compress' => true,\n" - " 'ssl_protocols' => true,\n" - " 'ssl_verify_peer' => true,\n" - " 'ssl_allow_self_signed' => true,\n" - " 'ssl_client_cert_file' => true,\n" - " 'ssl_verify_depth' => true,\n" - " 'ssl_prefer_server_ciphers' => true,\n" - " 'ssl_ciphers' => true,\n" - " 'ssl_ecdh_curve' => true,\n" - " 'ssl_dhparam' => true,\n" - " 'ssl_sni_certs' => true,\n" + " 'heartbeat_idle_time' => true,\n" + " 'buffer_high_watermark' => true,\n" + " 'buffer_low_watermark' => true,\n" + " 'open_tcp_nodelay' => true,\n" + " 'tcp_defer_accept' => true,\n" + " 'open_tcp_keepalive' => true,\n" + " 'open_eof_check' => true,\n" + " 'open_eof_split' => true,\n" + " 'package_eof' => true,\n" + " 'open_http_protocol' => true,\n" + " 'open_websocket_protocol' => true,\n" + " 'websocket_subprotocol' => true,\n" + " 'open_websocket_close_frame' => true,\n" + " 'open_websocket_ping_frame' => true,\n" + " 'open_websocket_pong_frame' => true,\n" + " 'open_http2_protocol' => true,\n" + " 'open_mqtt_protocol' => true,\n" + " 'open_redis_protocol' => true,\n" + " 'max_idle_time' => true,\n" + " 'tcp_keepidle' => true,\n" + " 'tcp_keepinterval' => true,\n" + " 'tcp_keepcount' => true,\n" + " 'tcp_user_timeout' => true,\n" + " 'tcp_fastopen' => true,\n" + " 'open_length_check' => true,\n" + " 'package_length_type' => true,\n" + " 'package_length_offset' => true,\n" + " 'package_body_offset' => true,\n" + " 'package_body_start' => true,\n" + " 'package_length_func' => true,\n" + " 'package_max_length' => true,\n" + " 'ssl_compress' => true,\n" + " 'ssl_protocols' => true,\n" + " 'ssl_verify_peer' => true,\n" + " 'ssl_allow_self_signed' => true,\n" + " 'ssl_client_cert_file' => true,\n" + " 'ssl_cafile' => true,\n" + " 'ssl_capath' => true,\n" + " 'ssl_verify_depth' => true,\n" + " 'ssl_prefer_server_ciphers' => true,\n" + " 'ssl_ciphers' => true,\n" + " 'ssl_ecdh_curve' => true,\n" + " 'ssl_dhparam' => true,\n" + " 'ssl_sni_certs' => true,\n" " ];\n" "\n" " public const AIO_OPTIONS = [\n" - " 'aio_core_worker_num' => true,\n" - " 'aio_worker_num' => true,\n" - " 'aio_max_wait_time' => true,\n" - " 'aio_max_idle_time' => true,\n" - " 'enable_signalfd' => true,\n" - " 'wait_signal' => true,\n" + " 'aio_core_worker_num' => true,\n" + " 'aio_worker_num' => true,\n" + " 'aio_max_wait_time' => true,\n" + " 'aio_max_idle_time' => true,\n" + " 'iouring_entries' => true,\n" + " 'iouring_workers' => true,\n" + " 'iouring_flag' => true,\n" + " 'enable_signalfd' => true,\n" + " 'wait_signal' => true,\n" " 'dns_cache_refresh_time' => true,\n" - " 'thread_num' => true,\n" - " 'min_thread_num' => true,\n" - " 'max_thread_num' => true,\n" - " 'socket_dontwait' => true,\n" - " 'dns_lookup_random' => true,\n" - " 'use_async_resolver' => true,\n" - " 'enable_coroutine' => true,\n" + " 'thread_num' => true,\n" + " 'min_thread_num' => true,\n" + " 'max_thread_num' => true,\n" + " 'socket_dontwait' => true,\n" + " 'dns_lookup_random' => true,\n" + " 'use_async_resolver' => true,\n" + " 'enable_coroutine' => true,\n" " ];\n" "\n" " public const COROUTINE_OPTIONS = [\n" - " 'max_coro_num' => true,\n" - " 'max_coroutine' => true,\n" - " 'enable_deadlock_check' => true,\n" - " 'hook_flags' => true,\n" + " 'max_coro_num' => true,\n" + " 'max_coroutine' => true,\n" + " 'enable_deadlock_check' => true,\n" + " 'hook_flags' => true,\n" " 'enable_preemptive_scheduler' => true,\n" - " 'c_stack_size' => true,\n" - " 'stack_size' => true,\n" - " 'name_resolver' => true,\n" - " 'dns_cache_expire' => true,\n" - " 'dns_cache_capacity' => true,\n" - " 'max_concurrency' => true,\n" + " 'c_stack_size' => true,\n" + " 'stack_size' => true,\n" + " 'name_resolver' => true,\n" + " 'dns_cache_expire' => true,\n" + " 'dns_cache_capacity' => true,\n" " ];\n" "\n" " public const HELPER_OPTIONS = [\n" - " 'stats_file' => true,\n" + " 'stats_file' => true,\n" " 'stats_timer_interval' => true,\n" - " 'admin_server' => true,\n" + " 'admin_server' => true,\n" " ];\n" "\n" - " public static function checkOptions(array $input_options)\n" + " public static function checkOptions(array $input_options): void\n" " {\n" " $const_options = self::GLOBAL_OPTIONS + self::SERVER_OPTIONS + self::PORT_OPTIONS\n" " + self::AIO_OPTIONS + self::COROUTINE_OPTIONS + self::HELPER_OPTIONS;\n" "\n" " foreach ($input_options as $k => $v) {\n" " if (!array_key_exists(strtolower($k), $const_options)) {\n" - " \n" + " // TODO throw exception\n" " trigger_error(\"unsupported option [{$k}]\", E_USER_WARNING);\n" " debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);\n" " }\n" " }\n" " }\n" "\n" - " public static function onBeforeStart(Server $server)\n" + " public static function onBeforeStart(Server $server): void\n" " {\n" " if (!empty($server->setting['admin_server'])) {\n" " Admin::init($server);\n" " }\n" " }\n" "\n" - " public static function onBeforeShutdown(Server $server)\n" + " public static function onBeforeShutdown(Server $server): void\n" " {\n" - " if ($server->admin_server) {\n" + " if (isset($server->admin_server)) { // @phpstan-ignore isset.property\n" " $server->admin_server->shutdown();\n" - " $server->admin_server = null;\n" + " $server->admin_server = null; // @phpstan-ignore assign.propertyType\n" " }\n" " }\n" "\n" - " public static function onWorkerStart(Server $server, int $workerId)\n" + " public static function onWorkerStart(Server $server, int $workerId): void\n" " {\n" " if (!empty($server->setting['stats_file']) and $workerId == 0) {\n" " $interval_ms = empty($server->setting['stats_timer_interval']) ? self::STATS_TIMER_INTERVAL_TIME : intval($server->setting['stats_timer_interval']);\n" "\n" " $server->stats_timer = Timer::tick($interval_ms, function () use ($server) {\n" - " $stats = $server->stats();\n" + " $stats = $server->stats();\n" " $stats_file = swoole_string($server->setting['stats_file']);\n" " if ($stats_file->endsWith('.json')) {\n" - " $out = json_encode($stats);\n" + " $out = json_encode($stats, JSON_THROW_ON_ERROR);\n" " } elseif ($stats_file->endsWith('.php')) {\n" " $out = \"stats_timer) {\n" " Timer::clear($server->stats_timer);\n" @@ -7764,10 +9607,10 @@ static const char* swoole_library_source_core_server_helper = " {\n" " }\n" "\n" - " public static function onStart(Server $server)\n" + " public static function onStart(Server $server): void\n" " {\n" - " if (!empty($server->setting['admin_server'])) {\n" - " go(function () use ($server) {\n" + " if (!empty($server->setting[Constant::OPTION_ADMIN_SERVER])) {\n" + " Coroutine::create(function () use ($server): void {\n" " Admin::start($server);\n" " });\n" " }\n" @@ -7800,14 +9643,20 @@ static const char* swoole_library_source_core_server_helper = static const char* swoole_library_source_core_name_resolver = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" "namespace Swoole;\n" "\n" - "use RuntimeException;\n" "use Swoole\\Coroutine\\Http\\ClientProxy;\n" + "use Swoole\\Http\\Status;\n" "use Swoole\\NameResolver\\Cluster;\n" "use Swoole\\NameResolver\\Exception;\n" "\n" @@ -7815,16 +9664,13 @@ static const char* swoole_library_source_core_name_resolver = "{\n" " protected $baseUrl;\n" "\n" - " protected $prefix;\n" - "\n" " protected $info;\n" "\n" " private $filter_fn;\n" "\n" - " public function __construct($url, $prefix = 'swoole_service_')\n" + " public function __construct($url, protected $prefix = 'swoole_service_')\n" " {\n" " $this->checkServerUrl($url);\n" - " $this->prefix = $prefix;\n" " }\n" "\n" " abstract public function join(string $name, string $ip, int $port, array $options = []): bool;\n" @@ -7849,35 +9695,43 @@ static const char* swoole_library_source_core_name_resolver = " return !empty($this->filter_fn);\n" " }\n" "\n" - " \n" + " /**\n" + " * return string: final result, non-empty string must be a valid IP address,\n" + " * and an empty string indicates name lookup failed, and lookup operation will not continue.\n" + " * return Cluster: has multiple nodes and failover is possible\n" + " * return false or null: try another name resolver\n" + " * @return Cluster|false|string|null\n" + " */\n" " public function lookup(string $name)\n" " {\n" " if ($this->hasFilter() and ($this->getFilter())($name) !== true) {\n" " return null;\n" " }\n" " $cluster = $this->getCluster($name);\n" - " \n" + " // lookup failed, terminate execution\n" " if ($cluster == null) {\n" " return '';\n" " }\n" - " \n" + " // only one node, cannot retry\n" " if ($cluster->count() == 1) {\n" " return $cluster->pop();\n" " }\n" " return $cluster;\n" " }\n" "\n" - " \n" - " protected function checkServerUrl($url)\n" + " /**\n" + " * !!! The host MUST BE IP ADDRESS\n" + " */\n" + " protected function checkServerUrl(string $url)\n" " {\n" " $info = parse_url($url);\n" " if (empty($info['scheme']) or empty($info['host'])) {\n" - " throw new RuntimeException(\"invalid url parameter '{$url}'\");\n" + " throw new \\RuntimeException(\"invalid url parameter '{$url}'\");\n" " }\n" " if (!filter_var($info['host'], FILTER_VALIDATE_IP)) {\n" " $info['ip'] = gethostbyname($info['host']);\n" " if (!filter_var($info['ip'], FILTER_VALIDATE_IP)) {\n" - " throw new RuntimeException(\"Failed to resolve host '{$info['host']}'\");\n" + " throw new \\RuntimeException(\"Failed to resolve host '{$info['host']}'\");\n" " }\n" " } else {\n" " $info['ip'] = $info['host'];\n" @@ -7890,37 +9744,38 @@ static const char* swoole_library_source_core_name_resolver = " $baseUrl .= rtrim($info['path'], '/');\n" " }\n" " $this->baseUrl = $baseUrl;\n" - " $this->info = $info;\n" + " $this->info = $info;\n" " }\n" "\n" - " \n" - " protected function checkResponse($r, $url)\n" + " protected function checkResponse(ClientProxy $response): bool\n" " {\n" - " if (empty($r)) {\n" - " throw new Exception(\"failed to request URL({$url})\");\n" - " }\n" - " if ($r->getStatusCode() !== 200) {\n" - " $msg = '';\n" - " if (!empty($r->errMsg)) {\n" - " $msg .= 'errMsg: ' . $r->errMsg;\n" - " }\n" - " $body = $r->getBody();\n" - " if (empty($r->errMsg)) {\n" - " $msg .= 'Http Body: ' . $body;\n" - " }\n" - " throw new Exception($msg, $r->errCode ?: $r->getStatusCode());\n" + " if ($response->getStatusCode() === Status::OK) {\n" + " return true;\n" " }\n" - " return true;\n" + "\n" + " throw new Exception('Http Body: ' . $response->getBody(), $response->getStatusCode());\n" " }\n" "}\n"; static const char* swoole_library_source_core_name_resolver_exception = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @see https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" @@ -7930,7 +9785,13 @@ static const char* swoole_library_source_core_name_resolver_exception = static const char* swoole_library_source_core_name_resolver_cluster = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -7940,10 +9801,11 @@ static const char* swoole_library_source_core_name_resolver_cluster = "\n" "class Cluster\n" "{\n" - " \n" - " private $nodes = [];\n" + " private array $nodes = [];\n" "\n" - " \n" + " /**\n" + " * @throws Exception\n" + " */\n" " public function add(string $host, int $port, int $weight = 100): void\n" " {\n" " if (!filter_var($host, FILTER_VALIDATE_IP)) {\n" @@ -7958,14 +9820,16 @@ static const char* swoole_library_source_core_name_resolver_cluster = " $this->nodes[] = ['host' => $host, 'port' => $port, 'weight' => $weight];\n" " }\n" "\n" - " \n" + " /**\n" + " * @return false|string\n" + " */\n" " public function pop()\n" " {\n" " if (empty($this->nodes)) {\n" " return false;\n" " }\n" " $index = array_rand($this->nodes, 1);\n" - " $node = $this->nodes[$index];\n" + " $node = $this->nodes[$index];\n" " unset($this->nodes[$index]);\n" " return $node;\n" " }\n" @@ -7978,10 +9842,22 @@ static const char* swoole_library_source_core_name_resolver_cluster = static const char* swoole_library_source_core_name_resolver_redis = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @see https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" @@ -8051,10 +9927,22 @@ static const char* swoole_library_source_core_name_resolver_redis = static const char* swoole_library_source_core_name_resolver_nacos = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @see https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" @@ -8063,45 +9951,51 @@ static const char* swoole_library_source_core_name_resolver_nacos = "\n" "class Nacos extends NameResolver\n" "{\n" - " \n" + " /**\n" + " * @throws Coroutine\\Http\\Client\\Exception|Exception\n" + " */\n" " public function join(string $name, string $ip, int $port, array $options = []): bool\n" " {\n" - " $params['port'] = $port;\n" - " $params['ip'] = $ip;\n" - " $params['healthy'] = 'true';\n" - " $params['weight'] = $options['weight'] ?? 100;\n" - " $params['encoding'] = $options['encoding'] ?? 'utf-8';\n" + " $params['port'] = $port;\n" + " $params['ip'] = $ip;\n" + " $params['healthy'] = 'true';\n" + " $params['weight'] = $options['weight'] ?? 100;\n" + " $params['encoding'] = $options['encoding'] ?? 'utf-8';\n" " $params['namespaceId'] = $options['namespaceId'] ?? 'public';\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params);\n" - " $r = Coroutine\\Http\\post($url, []);\n" - " return $this->checkResponse($r, $url);\n" + " $r = Coroutine\\Http\\post($url, []);\n" + " return $this->checkResponse($r);\n" " }\n" "\n" - " \n" + " /**\n" + " * @throws Coroutine\\Http\\Client\\Exception|Exception\n" + " */\n" " public function leave(string $name, string $ip, int $port): bool\n" " {\n" - " $params['port'] = $port;\n" - " $params['ip'] = $ip;\n" + " $params['port'] = $port;\n" + " $params['ip'] = $ip;\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params);\n" - " $r = Coroutine\\Http\\request($this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params), 'DELETE');\n" - " return $this->checkResponse($r, $url);\n" + " $r = Coroutine\\Http\\request($this->baseUrl . '/nacos/v1/ns/instance?' . http_build_query($params), 'DELETE');\n" + " return $this->checkResponse($r);\n" " }\n" "\n" - " \n" + " /**\n" + " * @throws Coroutine\\Http\\Client\\Exception|Exception|\\Swoole\\Exception\n" + " */\n" " public function getCluster(string $name): ?Cluster\n" " {\n" " $params['serviceName'] = $this->prefix . $name;\n" "\n" " $url = $this->baseUrl . '/nacos/v1/ns/instance/list?' . http_build_query($params);\n" - " $r = Coroutine\\Http\\get($url);\n" - " if (!$this->checkResponse($r, $url)) {\n" + " $r = Coroutine\\Http\\get($url);\n" + " if (!$this->checkResponse($r)) {\n" " return null;\n" " }\n" - " $result = json_decode($r->getBody());\n" + " $result = json_decode($r->getBody(), null, 512, JSON_THROW_ON_ERROR);\n" " if (empty($result)) {\n" " return null;\n" " }\n" @@ -8115,14 +10009,27 @@ static const char* swoole_library_source_core_name_resolver_nacos = static const char* swoole_library_source_core_name_resolver_consul = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @see https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "namespace Swoole\\NameResolver;\n" "\n" "use Swoole\\NameResolver;\n" + "\n" "use function Swoole\\Coroutine\\Http\\get;\n" "use function Swoole\\Coroutine\\Http\\request;\n" "\n" @@ -8131,20 +10038,20 @@ static const char* swoole_library_source_core_name_resolver_consul = " public function join(string $name, string $ip, int $port, array $options = []): bool\n" " {\n" " $weight = $options['weight'] ?? 100;\n" - " $data = [\n" - " 'ID' => $this->getServiceId($name, $ip, $port),\n" - " 'Name' => $this->prefix . $name,\n" - " 'Address' => $ip,\n" - " 'Port' => $port,\n" + " $data = [\n" + " 'ID' => $this->getServiceId($name, $ip, $port),\n" + " 'Name' => $this->prefix . $name,\n" + " 'Address' => $ip,\n" + " 'Port' => $port,\n" " 'EnableTagOverride' => false,\n" - " 'Weights' => [\n" + " 'Weights' => [\n" " 'Passing' => $weight,\n" " 'Warning' => 1,\n" " ],\n" " ];\n" " $url = $this->baseUrl . '/v1/agent/service/register';\n" - " $r = request($url, 'PUT', json_encode($data));\n" - " return $this->checkResponse($r, $url);\n" + " $r = request($url, 'PUT', json_encode($data, JSON_THROW_ON_ERROR));\n" + " return $this->checkResponse($r);\n" " }\n" "\n" " public function leave(string $name, string $ip, int $port): bool\n" @@ -8155,7 +10062,7 @@ static const char* swoole_library_source_core_name_resolver_consul = " $port\n" " );\n" " $r = request($url, 'PUT');\n" - " return $this->checkResponse($r, $url);\n" + " return $this->checkResponse($r);\n" " }\n" "\n" " public function enableMaintenanceMode(string $name, string $ip, int $port): bool\n" @@ -8166,17 +10073,17 @@ static const char* swoole_library_source_core_name_resolver_consul = " $port\n" " );\n" " $r = request($url, 'PUT');\n" - " return $this->checkResponse($r, $url);\n" + " return $this->checkResponse($r);\n" " }\n" "\n" " public function getCluster(string $name): ?Cluster\n" " {\n" " $url = $this->baseUrl . '/v1/catalog/service/' . $this->prefix . $name;\n" - " $r = get($url);\n" - " if (!$this->checkResponse($r, $url)) {\n" + " $r = get($url);\n" + " if (!$this->checkResponse($r)) {\n" " return null;\n" " }\n" - " $list = json_decode($r->getBody());\n" + " $list = json_decode($r->getBody(), null, 512, JSON_THROW_ON_ERROR);\n" " if (empty($list)) {\n" " return null;\n" " }\n" @@ -8193,9 +10100,265 @@ static const char* swoole_library_source_core_name_resolver_consul = " }\n" "}\n"; -static const char* swoole_library_source_core_coroutine_functions = +static const char* swoole_library_source_core_thread_pool = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\Thread;\n" + "\n" + "use PhpParser\\Error;\n" + "use PhpParser\\ParserFactory;\n" + "use Swoole\\Thread;\n" + "\n" + "/**\n" + " * @since 6.0.0-beta\n" + " */\n" + "class Pool\n" + "{\n" + " private array $threads = [];\n" + "\n" + " private string $autoloader = '';\n" + "\n" + " private string $classDefinitionFile = '';\n" + "\n" + " private string $runnableClass = '';\n" + "\n" + " private int $threadNum = 0;\n" + "\n" + " private string $proxyFile;\n" + "\n" + " private array $arguments = [];\n" + "\n" + " private object $running;\n" + "\n" + " private object $queue;\n" + "\n" + " private array $indexes = [];\n" + "\n" + " public function __construct(string $runnableClass, int $threadNum)\n" + " {\n" + " if ($threadNum <= 0) {\n" + " throw new \\Exception('threadNum must be greater than 0');\n" + " }\n" + " $this->runnableClass = $runnableClass;\n" + " $this->threadNum = $threadNum;\n" + " }\n" + "\n" + " public function withArguments(...$arguments): static\n" + " {\n" + " $this->arguments = $arguments;\n" + " return $this;\n" + " }\n" + "\n" + " public function withAutoloader(string $autoloader): static\n" + " {\n" + " $this->autoloader = $autoloader;\n" + " return $this;\n" + " }\n" + "\n" + " public function withClassDefinitionFile(string $classDefinitionFile): static\n" + " {\n" + " $this->classDefinitionFile = $classDefinitionFile;\n" + " return $this;\n" + " }\n" + "\n" + " /**\n" + " * @throws \\ReflectionException\n" + " */\n" + " public function start(): void\n" + " {\n" + " if (empty($this->classDefinitionFile) and class_exists($this->runnableClass, false)) {\n" + " $file = (new \\ReflectionClass($this->runnableClass))->getFileName();\n" + " if (!$this->isValidPhpFile($file)) {\n" + " throw new \\Exception('class definition file must not contain any expressions.');\n" + " }\n" + " $this->classDefinitionFile = $file;\n" + " } elseif ($this->classDefinitionFile) {\n" + " require_once $this->classDefinitionFile;\n" + " }\n" + "\n" + " if (!class_exists($this->runnableClass)) {\n" + " throw new \\Exception(\"class `{$this->runnableClass}` not found\");\n" + " }\n" + "\n" + " if (!is_subclass_of($this->runnableClass, Runnable::class)) {\n" + " throw new \\Exception(\"class `{$this->runnableClass}` must implements Thread\\\\Runnable\");\n" + " }\n" + "\n" + " if (empty($this->autoloader)) {\n" + " $include_files = get_included_files();\n" + " foreach ($include_files as $file) {\n" + " if (str_ends_with($file, 'vendor/autoload.php')) {\n" + " $this->autoloader = $file;\n" + " break;\n" + " }\n" + " }\n" + " }\n" + "\n" + " if ($this->autoloader) {\n" + " $this->proxyFile = dirname($this->autoloader) . '/thread_runner.php';\n" + " } else {\n" + " $this->proxyFile = dirname($this->classDefinitionFile) . '/thread_runner.php';\n" + " }\n" + "\n" + " if (!is_file($this->proxyFile)) {\n" + " $script = 'run($threadArguments); }' . PHP_EOL;\n" + " $script .= 'finally { $queue->push($threadId, Swoole\\Thread\\Queue::NOTIFY_ONE); }' . PHP_EOL;\n" + " $script .= PHP_EOL;\n" + " file_put_contents($this->proxyFile, $script);\n" + " }\n" + "\n" + " $this->queue = new Queue();\n" + " $this->running = new Atomic(1);\n" + "\n" + " for ($index = 0; $index < $this->threadNum; $index++) {\n" + " $this->createThread($index);\n" + " }\n" + "\n" + " while ($this->running->get()) {\n" + " $threadId = $this->queue->pop(-1);\n" + " $thread = $this->threads[$threadId];\n" + " $index = $this->indexes[$threadId];\n" + " $thread->join();\n" + " unset($this->threads[$threadId], $this->indexes[$threadId]);\n" + "\n" + " $this->createThread($index);\n" + " }\n" + "\n" + " foreach ($this->threads as $thread) {\n" + " $thread->join();\n" + " }\n" + " }\n" + "\n" + " public function shutdown(): void\n" + " {\n" + " $this->running->set(0);\n" + " }\n" + "\n" + " protected function isValidPhpFile($filePath): bool\n" + " {\n" + " $allowedNodeTypes = [\n" + " \\PhpParser\\Node\\Stmt\\Class_::class,\n" + " \\PhpParser\\Node\\Stmt\\Const_::class,\n" + " \\PhpParser\\Node\\Stmt\\Use_::class,\n" + " \\PhpParser\\Node\\Stmt\\Namespace_::class,\n" + " \\PhpParser\\Node\\Stmt\\Declare_::class,\n" + " ];\n" + "\n" + " $parser = (new ParserFactory())->createForNewestSupportedVersion();\n" + " try {\n" + " $code = file_get_contents($filePath);\n" + " $stmts = $parser->parse($code);\n" + " $skipLine = -1;\n" + " foreach ($stmts as $stmt) {\n" + " $isAllowed = false;\n" + " foreach ($allowedNodeTypes as $allowedNodeType) {\n" + " if ($stmt instanceof $allowedNodeType) {\n" + " $isAllowed = true;\n" + " break;\n" + " }\n" + " }\n" + " if (!$isAllowed) {\n" + " if ($stmt->getLine() == $skipLine) {\n" + " continue;\n" + " }\n" + " return false;\n" + " }\n" + " }\n" + " } catch (Error $error) {\n" + " return false;\n" + " }\n" + " return true;\n" + " }\n" + "\n" + " protected function createThread($index): void\n" + " {\n" + " $thread = new Thread($this->proxyFile,\n" + " $this->autoloader,\n" + " $this->runnableClass,\n" + " $this->queue,\n" + " $this->classDefinitionFile,\n" + " $this->running,\n" + " $index,\n" + " ...$this->arguments\n" + " );\n" + " $this->indexes[$thread->id] = $index;\n" + " $this->threads[$thread->id] = $thread;\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_thread_runnable = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" "\n" + "namespace Swoole\\Thread;\n" "\n" + "/**\n" + " * @since 6.0.0-beta\n" + " */\n" + "abstract class Runnable\n" + "{\n" + " protected Atomic $running;\n" + "\n" + " protected int $id;\n" + "\n" + " public function __construct($running, $index)\n" + " {\n" + " $this->running = $running;\n" + " $this->id = $index;\n" + " }\n" + "\n" + " abstract public function run(array $args): void;\n" + "\n" + " protected function isRunning(): bool\n" + " {\n" + " return $this->running->get() === 1;\n" + " }\n" + "\n" + " protected function shutdown(): void\n" + " {\n" + " $this->running->set(0);\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_core_coroutine_functions = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -8205,7 +10368,7 @@ static const char* swoole_library_source_core_coroutine_functions = "\n" "function run(callable $fn, ...$args)\n" "{\n" - " $s = new Scheduler();\n" + " $s = new Scheduler();\n" " $options = Coroutine::getOptions();\n" " if (!isset($options['hook_flags'])) {\n" " $s->set(['hook_flags' => SWOOLE_HOOK_ALL]);\n" @@ -8241,7 +10404,7 @@ static const char* swoole_library_source_core_coroutine_functions = "function parallel(int $n, callable $fn): void\n" "{\n" " $count = $n;\n" - " $wg = new WaitGroup($n);\n" + " $wg = new WaitGroup($n);\n" " while ($count--) {\n" " Coroutine::create(function () use ($fn, $wg) {\n" " $fn();\n" @@ -8251,13 +10414,27 @@ static const char* swoole_library_source_core_coroutine_functions = " $wg->wait();\n" "}\n" "\n" + "/**\n" + " * Applies the callback to the elements of the given list.\n" + " *\n" + " * The callback function takes on two parameters. The list parameter's value being the first, and the key/index second.\n" + " * Each callback runs in a new coroutine, allowing the list to be processed in parallel.\n" + " *\n" + " * @param array $list A list of key/value paired input data.\n" + " * @param callable $fn The callback function to apply to each item on the list. The callback takes on two parameters.\n" + " * The list parameter's value being the first, and the key/index second.\n" + " * @param float $timeout > 0 means waiting for the specified number of seconds. other means no waiting.\n" + " * @return array Returns an array containing the results of applying the callback function to the corresponding value\n" + " * and key of the list (used as arguments for the callback). The returned array will preserve the keys of\n" + " * the list.\n" + " */\n" "function map(array $list, callable $fn, float $timeout = -1): array\n" "{\n" " $wg = new WaitGroup(count($list));\n" " foreach ($list as $id => $elem) {\n" " Coroutine::create(function () use ($wg, &$list, $id, $elem, $fn): void {\n" " $list[$id] = null;\n" - " $list[$id] = $fn($elem);\n" + " $list[$id] = $fn($elem, $id);\n" " $wg->done();\n" " });\n" " }\n" @@ -8268,35 +10445,51 @@ static const char* swoole_library_source_core_coroutine_functions = "function deadlock_check()\n" "{\n" " $all_coroutines = Coroutine::listCoroutines();\n" - " $count = Coroutine::stats()['coroutine_num'];\n" - " echo \"\\n===================================================================\",\n" - " \"\\n [FATAL ERROR]: all coroutines (count: {$count}) are asleep - deadlock!\",\n" - " \"\\n===================================================================\\n\";\n" - "\n" + " $count = Coroutine::stats()['coroutine_num'];\n" + "\n" + " // coroutine deadlock detected, header\n" + " $hr_width = 64 + strlen(strval($count));\n" + " $hr1 = str_repeat('=', $hr_width);\n" + " $hr2 = str_repeat('-', $hr_width);\n" + " echo '',\n" + " \"\\n {$hr1}\",\n" + " \"\\n [FATAL ERROR]: all coroutines (count: {$count}) are asleep - deadlock!\",\n" + " \"\\n {$hr1}\",\n" + " \"\\n\";\n" + "\n" + " // print all coroutine backtraces\n" " $options = Coroutine::getOptions();\n" " if (empty($options['deadlock_check_disable_trace'])) {\n" " $index = 0;\n" " $limit = empty($options['deadlock_check_limit']) ? 32 : intval($options['deadlock_check_limit']);\n" " $depth = empty($options['deadlock_check_depth']) ? 32 : intval($options['deadlock_check_depth']);\n" " foreach ($all_coroutines as $cid) {\n" - " echo \"\\n [Coroutine-{$cid}]\";\n" - " echo \"\\n--------------------------------------------------------------------\\n\";\n" + " echo \"\\n [Coroutine-{$cid}]\";\n" + " echo \"\\n {$hr2}\\n\";\n" " echo Coroutine::printBackTrace($cid, DEBUG_BACKTRACE_IGNORE_ARGS, $depth);\n" - " echo \"\\n\";\n" " $index++;\n" - " \n" + " // limit the number of maximum outputs\n" " if ($index >= $limit) {\n" " break;\n" " }\n" " }\n" " }\n" + "\n" + " // footer\n" + " echo \"\\n {$hr1}\\n\";\n" "}\n"; static const char* swoole_library_source_ext_curl = "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" - "\n" - "\n" + "/* @noinspection PhpComposerExtensionStubsInspection */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -8329,46 +10522,32 @@ static const char* swoole_library_source_ext_curl = "{\n" " $info = $obj->getInfo();\n" " if (is_array($info) and $opt) {\n" - " switch ($opt) {\n" - " case CURLINFO_EFFECTIVE_URL:\n" - " return $info['url'];\n" - " case CURLINFO_HTTP_CODE:\n" - " return $info['http_code'];\n" - " case CURLINFO_CONTENT_TYPE:\n" - " return $info['content_type'];\n" - " case CURLINFO_REDIRECT_COUNT:\n" - " return $info['redirect_count'];\n" - " case CURLINFO_REDIRECT_URL:\n" - " return $info['redirect_url'];\n" - " case CURLINFO_TOTAL_TIME:\n" - " return $info['total_time'];\n" - " case CURLINFO_STARTTRANSFER_TIME:\n" - " return $info['starttransfer_time'];\n" - " case CURLINFO_SIZE_DOWNLOAD:\n" - " return $info['size_download'];\n" - " case CURLINFO_SPEED_DOWNLOAD:\n" - " return $info['speed_download'];\n" - " case CURLINFO_REDIRECT_TIME:\n" - " return $info['redirect_time'];\n" - " case CURLINFO_HEADER_SIZE:\n" - " return $info['header_size'];\n" - " case CURLINFO_PRIMARY_IP:\n" - " return $info['primary_ip'];\n" - " case CURLINFO_PRIVATE:\n" - " return $info['private'];\n" - " default:\n" - " return null;\n" - " }\n" + " return match ($opt) {\n" + " CURLINFO_EFFECTIVE_URL => $info['url'],\n" + " CURLINFO_HTTP_CODE => $info['http_code'],\n" + " CURLINFO_CONTENT_TYPE => $info['content_type'],\n" + " CURLINFO_REDIRECT_COUNT => $info['redirect_count'],\n" + " CURLINFO_REDIRECT_URL => $info['redirect_url'],\n" + " CURLINFO_TOTAL_TIME => $info['total_time'],\n" + " CURLINFO_STARTTRANSFER_TIME => $info['starttransfer_time'],\n" + " CURLINFO_SIZE_DOWNLOAD => $info['size_download'],\n" + " CURLINFO_SPEED_DOWNLOAD => $info['speed_download'],\n" + " CURLINFO_REDIRECT_TIME => $info['redirect_time'],\n" + " CURLINFO_HEADER_SIZE => $info['header_size'],\n" + " CURLINFO_PRIMARY_IP => $info['primary_ip'],\n" + " CURLINFO_PRIVATE => $info['private'],\n" + " default => null,\n" + " };\n" " }\n" " return $info;\n" "}\n" "\n" - "function swoole_curl_errno(Swoole\\Curl\\Handler $obj)\n" + "function swoole_curl_errno(Swoole\\Curl\\Handler $obj): int\n" "{\n" " return $obj->errno();\n" "}\n" "\n" - "function swoole_curl_error(Swoole\\Curl\\Handler $obj)\n" + "function swoole_curl_error(Swoole\\Curl\\Handler $obj): string\n" "{\n" " return $obj->error();\n" "}\n" @@ -8378,9 +10557,9 @@ static const char* swoole_library_source_ext_curl = " return $obj->reset();\n" "}\n" "\n" - "function swoole_curl_close(Swoole\\Curl\\Handler $obj)\n" + "function swoole_curl_close(Swoole\\Curl\\Handler $obj): void\n" "{\n" - " return $obj->close();\n" + " $obj->close();\n" "}\n" "\n" "function swoole_curl_multi_getcontent(Swoole\\Curl\\Handler $obj)\n" @@ -8390,7 +10569,13 @@ static const char* swoole_library_source_ext_curl = static const char* swoole_library_source_ext_sockets = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -8575,7 +10760,7 @@ static const char* swoole_library_source_ext_sockets = " $socket->close();\n" "}\n" "\n" - "function swoole_socket_clear_error(Socket $socket = null)\n" + "function swoole_socket_clear_error(?Socket $socket = null)\n" "{\n" " if ($socket) {\n" " $socket->errCode = 0;\n" @@ -8583,7 +10768,7 @@ static const char* swoole_library_source_ext_sockets = " swoole_clear_error();\n" "}\n" "\n" - "function swoole_socket_last_error(Socket $socket = null): int\n" + "function swoole_socket_last_error(?Socket $socket = null): int\n" "{\n" " if ($socket) {\n" " return $socket->errCode;\n" @@ -8597,9 +10782,9 @@ static const char* swoole_library_source_ext_sockets = " return false;\n" " }\n" " if (isset($socket->__ext_sockets_nonblock) and $socket->__ext_sockets_nonblock) {\n" - " $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, $socket->__ext_sockets_timeout);\n" + " $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, $socket->__ext_sockets_timeout); // @phpstan-ignore property.notFound\n" " }\n" - " $socket->__ext_sockets_nonblock = false;\n" + " $socket->__ext_sockets_nonblock = false; // @phpstan-ignore property.notFound\n" " return true;\n" "}\n" "\n" @@ -8611,8 +10796,8 @@ static const char* swoole_library_source_ext_sockets = " if (isset($socket->__ext_sockets_nonblock) and $socket->__ext_sockets_nonblock) {\n" " return true;\n" " }\n" - " $socket->__ext_sockets_nonblock = true;\n" - " $socket->__ext_sockets_timeout = $socket->getOption(SOL_SOCKET, SO_RCVTIMEO);\n" + " $socket->__ext_sockets_nonblock = true; // @phpstan-ignore property.notFound\n" + " $socket->__ext_sockets_timeout = $socket->getOption(SOL_SOCKET, SO_RCVTIMEO); // @phpstan-ignore property.notFound\n" " $socket->setOption(SOL_SOCKET, SO_RCVTIMEO, ['sec' => 0, 'usec' => 1000]);\n" " return true;\n" "}\n" @@ -8621,7 +10806,7 @@ static const char* swoole_library_source_ext_sockets = " int $domain,\n" " int $type,\n" " int $protocol,\n" - " array &$pair\n" + " array &$pair,\n" ") {\n" " $_pair = swoole_coroutine_socketpair($domain, $type, $protocol);\n" " if ($_pair) {\n" @@ -8631,22 +10816,171 @@ static const char* swoole_library_source_ext_sockets = " return false;\n" "}\n" "\n" - "function swoole_socket_import_stream($stream)\n" + "/**\n" + " * @since 5.0.0\n" + " */\n" + "function swoole_socket_import_stream(mixed $stream): Socket|false\n" "{\n" - " return Socket::import($stream);\n" + " return Socket::import($stream); // @phpstan-ignore staticMethod.notFound\n" "}\n"; -static const char* swoole_library_source_functions = +static const char* swoole_library_source_ext_standard = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "/* @noinspection PhpComposerExtensionStubsInspection */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "use Swoole\\Coroutine\\System;\n" + "\n" + "function swoole_gethostbynamel(string $domain)\n" + "{\n" + " return System::getaddrinfo($domain);\n" + "}\n" + "\n" + "function swoole_mail(string $to, string $subject, string $message, array $headers = []): bool\n" + "{\n" + " $client = swoole_get_default_remote_object_client();\n" + " return $client->call('mail', $to, $subject, $message, $headers);\n" + "}\n" + "\n" + "function swoole_checkdnsrr(string $hostname, string $type = 'MX'): bool\n" + "{\n" + " $client = swoole_get_default_remote_object_client();\n" + " return $client->call('checkdnsrr', ...func_get_args());\n" + "}\n" + "\n" + "function swoole_dns_check_record(string $hostname, string $type = 'MX'): bool\n" + "{\n" + " return swoole_checkdnsrr($hostname, $type);\n" + "}\n" + "\n" + "function swoole_real_getmxrr(string $hostname, ?array $hosts = null, ?array $weights = null): array\n" + "{\n" + " if (func_num_args() === 2) {\n" + " $result['result'] = getmxrr($hostname, $hosts);\n" + " $result['host'] = $hosts;\n" + " } else {\n" + " $result['result'] = getmxrr($hostname, $hosts, $weights);\n" + " $result['host'] = $hosts;\n" + " $result['weight'] = $weights;\n" + " }\n" + " return $result;\n" + "}\n" + "\n" + "function swoole_getmxrr(string $hostname, array &$hosts, ?array &$weights = null): bool\n" + "{\n" + " $client = swoole_get_default_remote_object_client();\n" + " $_hosts = $hosts;\n" + " $_weights = $weights === null ? null : $weights;\n" + " $result = $client->call('swoole_real_getmxrr', $hostname, $_hosts, $_weights);\n" + " $hosts = $result['host'];\n" + " $weights = $result['weight'];\n" + " return $result['result'];\n" + "}\n" + "\n" + "function swoole_dns_get_mx(string $hostname, array &$hosts, ?array &$weights = null): bool\n" + "{\n" + " return swoole_getmxrr($hostname, $hosts, $weights);\n" + "}\n" + "\n" + "function swoole_real_dns_get_record(string $hostname, int $type, ?array $authoritative_name_servers = null, ?array $additional_records = null, bool $raw = false): array\n" + "{\n" + " if ($authoritative_name_servers === null && $additional_records === null) {\n" + " $result['result'] = dns_get_record($hostname, $type);\n" + " } elseif ($additional_records === null) {\n" + " $result['result'] = dns_get_record($hostname, $type, $authoritative_name_servers);\n" + " } else {\n" + " $result['result'] = dns_get_record($hostname, $type, $authoritative_name_servers, $additional_records);\n" + " }\n" + " $result['authoritative_name_servers'] = $authoritative_name_servers;\n" + " $result['additional_records'] = $additional_records;\n" + " return $result;\n" + "}\n" + "\n" + "function swoole_dns_get_record(string $hostname,\n" + " int $type = DNS_ANY,\n" + " ?array &$authoritative_name_servers = null,\n" + " ?array &$additional_records = null,\n" + " bool $raw = false): array|false\n" + "{\n" + " $client = swoole_get_default_remote_object_client();\n" + " $result = $client->call('swoole_real_dns_get_record', $hostname, $type, $authoritative_name_servers, $additional_records, $raw);\n" + " $authoritative_name_servers = $result['authoritative_name_servers'];\n" + " $additional_records = $result['additional_records'];\n" + " return $result['result'];\n" + "}\n" + "\n" + "function swoole_gethostbyaddr(string $ip): string\n" + "{\n" + " $client = swoole_get_default_remote_object_client();\n" + " return $client->call('gethostbyaddr', $ip);\n" + "}\n"; + +static const char* swoole_library_source_ext_mongodb = + "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" + "\n" + "declare(strict_types=1);\n" + "\n" + "namespace Swoole\\MongoDB;\n" + "\n" + "use Swoole\\RemoteObject;\n" + "use Swoole\\RemoteObject\\ProxyTrait;\n" + "\n" + "class Client\n" + "{\n" + " use ProxyTrait;\n" + "\n" + " public const DEFAULT_URI = 'mongodb://127.0.0.1/';\n" + "\n" + " protected RemoteObject $client;\n" + "\n" + " public function __construct(?string $uri = self::DEFAULT_URI, array $uriOptions = [], array $driverOptions = [])\n" + " {\n" + " $remoteObjectClient = swoole_library_get_option('mongodb_remote_object_client');\n" + " if ($remoteObjectClient === null) {\n" + " $remoteObjectClient = swoole_get_default_remote_object_client();\n" + " }\n" + " $this->client = $remoteObjectClient->create(\\MongoDB\\Client::class, $uri, $uriOptions, $driverOptions);\n" + " }\n" "\n" + " protected function getObject(): RemoteObject\n" + " {\n" + " return $this->client;\n" + " }\n" + "}\n"; + +static const char* swoole_library_source_functions = "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" - "if (PHP_VERSION_ID < 70200) {\n" - " throw new RuntimeException('require PHP version 7.2 or later');\n" + "if (PHP_VERSION_ID < 80100) { // @phpstan-ignore smaller.alwaysFalse\n" + " throw new RuntimeException('require PHP version 8.1 or later');\n" "}\n" "\n" - "if (SWOOLE_USE_SHORTNAME) {\n" + "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " function _string(string $string = ''): Swoole\\StringObject\n" " {\n" " return new Swoole\\StringObject($string);\n" @@ -8665,25 +10999,35 @@ static const char* swoole_library_source_functions = "\n" "class SwooleLibrary\n" "{\n" - " public static $options = [];\n" + " /**\n" + " * @var array\n" + " */\n" + " public static array $options = [];\n" + "\n" + " public static bool $remote_object_server_initiated = false;\n" + "\n" + " public static string $remote_object_server_socket_file = '';\n" "}\n" "\n" - "function swoole_library_set_options(array $options)\n" + "/**\n" + " * @param array $options\n" + " */\n" + "function swoole_library_set_options(array $options): void\n" "{\n" " SwooleLibrary::$options = $options;\n" "}\n" "\n" - "function swoole_library_get_options()\n" + "function swoole_library_get_options(): array\n" "{\n" " return SwooleLibrary::$options;\n" "}\n" "\n" - "function swoole_library_set_option(string $key, $value)\n" + "function swoole_library_set_option(string $key, mixed $value): void\n" "{\n" " SwooleLibrary::$options[$key] = $value;\n" "}\n" "\n" - "function swoole_library_get_option(string $key)\n" + "function swoole_library_get_option(string $key): mixed\n" "{\n" " return SwooleLibrary::$options[$key] ?? null;\n" "}\n" @@ -8710,33 +11054,32 @@ static const char* swoole_library_source_functions = " $table = new Swoole\\Table($size, 0.25);\n" "\n" " foreach ($_fields as $f) {\n" - " $_f = swoole_string($f)->trim()->split(':');\n" + " $_f = swoole_string($f)->trim()->split(':');\n" " $name = $_f->get(0)->trim()->toString();\n" " $type = $_f->get(1)->trim();\n" "\n" " switch ($type) {\n" - " case 'i':\n" - " case 'int':\n" - " $table->column($name, Swoole\\Table::TYPE_INT);\n" - " break;\n" - " case 'f':\n" - " case 'float':\n" - " $table->column($name, Swoole\\Table::TYPE_FLOAT);\n" - " break;\n" - " case 's':\n" - " case 'string':\n" - " if ($_f->count() < 3) {\n" - " throw new RuntimeException('need to give string length');\n" - " }\n" - " $length = intval($_f->get(2)->trim()->toString());\n" - " if ($length <= 0) {\n" - " throw new RuntimeException(\"invalid string length[{$length}]\");\n" - " }\n" - " $table->column($name, Swoole\\Table::TYPE_STRING, $length);\n" - " break;\n" - " default:\n" - " throw new RuntimeException(\"unknown field type[{$type}]\");\n" - " break;\n" + " case 'i':\n" + " case 'int':\n" + " $table->column($name, Swoole\\Table::TYPE_INT);\n" + " break;\n" + " case 'f':\n" + " case 'float':\n" + " $table->column($name, Swoole\\Table::TYPE_FLOAT);\n" + " break;\n" + " case 's':\n" + " case 'string':\n" + " if ($_f->count() < 3) {\n" + " throw new RuntimeException('need to give string length');\n" + " }\n" + " $length = (int) $_f->get(2)->trim()->toString();\n" + " if ($length <= 0) {\n" + " throw new RuntimeException(\"invalid string length[{$length}]\");\n" + " }\n" + " $table->column($name, Swoole\\Table::TYPE_STRING, $length);\n" + " break;\n" + " default:\n" + " throw new RuntimeException(\"unknown field type[{$type}]\");\n" " }\n" " }\n" "\n" @@ -8757,33 +11100,178 @@ static const char* swoole_library_source_functions = " return array_key_exists($key, $array) ? $array[$key] : $default_value;\n" "}\n" "\n" - "if (!function_exists('array_key_last')) {\n" - " function array_key_last(array $array)\n" - " {\n" - " if (!empty($array)) {\n" - " return key(array_slice($array, -1, 1, true));\n" + "function swoole_is_in_container(): bool\n" + "{\n" + " $mountinfo = file_get_contents('/proc/self/mountinfo');\n" + " return strpos($mountinfo, 'kubepods') > 0 || strpos($mountinfo, 'docker') > 0;\n" + "}\n" + "\n" + "function swoole_container_cpu_num(): int\n" + "{\n" + " $swoole_cpu_num = intval(getenv('SWOOLE_CPU_NUM'));\n" + " if ($swoole_cpu_num > 0) {\n" + " return $swoole_cpu_num;\n" + " }\n" + " if (!swoole_is_in_container()) {\n" + " return swoole_cpu_num();\n" + " }\n" + " // cgroup v2\n" + " $cpu_max = '/sys/fs/cgroup/cpu.max';\n" + " if (file_exists($cpu_max)) {\n" + " $cpu_max = file_get_contents($cpu_max);\n" + " $fields = explode($cpu_max, ' ');\n" + " $quota_us = $fields[0];\n" + " if ($quota_us === 'max') { // @phpstan-ignore identical.alwaysFalse\n" + " return swoole_cpu_num();\n" + " }\n" + " $period_us = $fields[1] ?? 100000;\n" + " } else {\n" + " $quota_us = file_get_contents('/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us');\n" + " $period_us = file_get_contents('/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us');\n" + " }\n" + " $cpu_num = floatval($quota_us) / floatval($period_us);\n" + " if ($cpu_num < 1) {\n" + " return swoole_cpu_num();\n" + " }\n" + " return intval(floor($cpu_num));\n" + "}\n" + "\n" + "function swoole_init_default_remote_object_server(): void\n" + "{\n" + " $dir = swoole_library_get_option('default_remote_object_server_dir');\n" + " if (empty($dir)) {\n" + " $home = getenv('HOME') ?: sys_get_temp_dir();\n" + " $dir = $home . '/.swoole';\n" + " swoole_library_set_option('default_remote_object_server_dir', $dir);\n" + " }\n" + "\n" + " $pid_file = $dir . '/remote-object-server.pid';\n" + "\n" + " if (!is_dir($dir)) {\n" + " mkdir($dir, 0755, true);\n" + " } else {\n" + " if (is_file($pid_file)\n" + " and posix_kill(intval(file_get_contents($pid_file)), 0)) {\n" + " return;\n" " }\n" - " return null;\n" " }\n" + "\n" + " $options = swoole_library_get_option('default_remote_object_server_options');\n" + " if (!$options) {\n" + " $worker_num = swoole_library_get_option('default_remote_object_server_worker_num') ?: 128;\n" + " $options = [\n" + " 'worker_num' => $worker_num,\n" + " 'server_mode' => defined('SWOOLE_THREAD') ? SWOOLE_THREAD : SWOOLE_BASE,\n" + " ];\n" + " }\n" + "\n" + " $php_file = $dir . '/remote-object-server.php';\n" + " $socket_file = $dir . '/remote-object-server.sock';\n" + " $log_file = $dir . '/remote-object-server.log';\n" + " $lock_file = $dir . '/remote-object-server.lock';\n" + "\n" + " $wait_ready_fn = function () use ($socket_file) {\n" + " // wait for remote object server ready\n" + " while (true) {\n" + " if (posix_access($socket_file, POSIX_R_OK)) {\n" + " break;\n" + " }\n" + " usleep(500000);\n" + " }\n" + " };\n" + "\n" + " $lock_handle = fopen($lock_file, 'c');\n" + " if (!$lock_handle) {\n" + " throw new RuntimeException(\"failed to open lock file[{$lock_file}]\");\n" + " }\n" + " // If the lock was not acquired, it indicates that another process is trying to start the remote object server.\n" + " // In this case, the service should be skipped from starting and proceed to the ready wait detection branch.\n" + " if (!flock($lock_handle, LOCK_EX | LOCK_NB)) {\n" + " fclose($lock_handle);\n" + " $wait_ready_fn();\n" + " return;\n" + " }\n" + "\n" + " $options['enable_coroutine'] = false;\n" + " $options['bootstrap'] = $php_file;\n" + " $options['pid_file'] = $pid_file;\n" + " $options['log_file'] = $log_file;\n" + " $options['daemonize'] = true;\n" + " $options['socket_type'] = SWOOLE_SOCK_UNIX_STREAM;\n" + "\n" + " $rv = file_put_contents($php_file, 'start();\\n\");\n" + " if (!$rv) {\n" + " throw new RuntimeException(\"failed to write php file[{$php_file}]\");\n" + " }\n" + "\n" + " $php_bin = PHP_BINARY;\n" + " if (posix_access($socket_file, POSIX_R_OK)) {\n" + " unlink($socket_file);\n" + " }\n" + "\n" + " $hook_flags = Swoole\\Runtime::getHookFlags();\n" + " // Having enabled the MongoDB hook, you need to install the MongoDB PHP library through Composer.\n" + " if (defined('SWOOLE_HOOK_MONGODB') and $hook_flags & SWOOLE_HOOK_MONGODB and !is_dir($dir . '/vendor/mongodb/mongodb')) {\n" + " system(\"cd {$dir} && composer require mongodb/mongodb\");\n" + " }\n" + "\n" + " // start server\n" + " $proc = proc_open(\"{$php_bin} {$php_file}\", [\n" + " 0 => ['pipe', 'r'],\n" + " 1 => ['pipe', 'w'],\n" + " 2 => ['pipe', 'w'],\n" + " ], $pipes);\n" + " if ($proc === false) {\n" + " throw new RuntimeException('failed to start remote object server');\n" + " }\n" + " $rc = proc_close($proc);\n" + " if ($rc !== 0) {\n" + " $output = stream_get_contents($pipes[1]) . stream_get_contents($pipes[2]);\n" + " throw new RuntimeException(\"failed to start remote object server: exit code {$rc}, output: \" . $output);\n" + " }\n" + "\n" + " $wait_ready_fn();\n" + " flock($lock_handle, LOCK_UN);\n" + " fclose($lock_handle);\n" "}\n" "\n" - "if (!function_exists('array_key_first')) {\n" - " function array_key_first(array $array)\n" - " {\n" - " foreach ($array as $key => $unused) {\n" - " return $key;\n" + "function swoole_get_default_remote_object_client(): Swoole\\RemoteObject\\Client\n" + "{\n" + " if (!SwooleLibrary::$remote_object_server_initiated) {\n" + " SwooleLibrary::$remote_object_server_initiated = true;\n" + " swoole_init_default_remote_object_server();\n" + " }\n" + " if (!SwooleLibrary::$remote_object_server_socket_file) {\n" + " $dir = swoole_library_get_option('default_remote_object_server_dir');\n" + " if (empty($dir)) {\n" + " $home = getenv('HOME') ?: sys_get_temp_dir();\n" + " $dir = $home . '/.swoole';\n" " }\n" - " return null;\n" + " SwooleLibrary::$remote_object_server_socket_file = 'unix://' . $dir . '/remote-object-server.sock';\n" " }\n" + " return new Swoole\\RemoteObject\\Client(SwooleLibrary::$remote_object_server_socket_file);\n" "}\n"; static const char* swoole_library_source_alias = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" - "if (SWOOLE_USE_SHORTNAME) {\n" + "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " class_alias(Swoole\\Coroutine\\WaitGroup::class, Co\\WaitGroup::class, true);\n" " class_alias(Swoole\\Coroutine\\Server::class, Co\\Server::class, true);\n" " class_alias(Swoole\\Coroutine\\Server\\Connection::class, Co\\Server\\Connection::class, true);\n" @@ -8796,7 +11284,13 @@ static const char* swoole_library_source_alias = static const char* swoole_library_source_alias_ns = "\n" - "\n" + "/**\n" + " * This file is part of Swoole.\n" + " *\n" + " * @link https://www.swoole.com\n" + " * @contact team@swoole.com\n" + " * @license https://github.com/swoole/library/blob/master/LICENSE\n" + " */\n" "\n" "declare(strict_types=1);\n" "\n" @@ -8804,7 +11298,7 @@ static const char* swoole_library_source_alias_ns = "\n" "use Swoole\\Coroutine;\n" "\n" - "if (SWOOLE_USE_SHORTNAME) {\n" + "if (SWOOLE_USE_SHORTNAME) { // @phpstan-ignore if.alwaysTrue\n" " function run(callable $fn, ...$args)\n" " {\n" " return \\Swoole\\Coroutine\\run($fn, ...$args);\n" @@ -8823,71 +11317,82 @@ static const char* swoole_library_source_alias_ns = void php_swoole_load_library() { - zend::eval(swoole_library_source_constants, "@swoole-src/library/constants.php"); - zend::eval(swoole_library_source_std_exec, "@swoole-src/library/std/exec.php"); - zend::eval(swoole_library_source_core_constant, "@swoole-src/library/core/Constant.php"); - zend::eval(swoole_library_source_core_string_object, "@swoole-src/library/core/StringObject.php"); - zend::eval(swoole_library_source_core_multibyte_string_object, "@swoole-src/library/core/MultibyteStringObject.php"); - zend::eval(swoole_library_source_core_exception_array_key_not_exists, "@swoole-src/library/core/Exception/ArrayKeyNotExists.php"); - zend::eval(swoole_library_source_core_array_object, "@swoole-src/library/core/ArrayObject.php"); - zend::eval(swoole_library_source_core_object_proxy, "@swoole-src/library/core/ObjectProxy.php"); - zend::eval(swoole_library_source_core_coroutine_wait_group, "@swoole-src/library/core/Coroutine/WaitGroup.php"); - zend::eval(swoole_library_source_core_coroutine_server, "@swoole-src/library/core/Coroutine/Server.php"); - zend::eval(swoole_library_source_core_coroutine_server_connection, "@swoole-src/library/core/Coroutine/Server/Connection.php"); - zend::eval(swoole_library_source_core_coroutine_barrier, "@swoole-src/library/core/Coroutine/Barrier.php"); - zend::eval(swoole_library_source_core_coroutine_http_client_proxy, "@swoole-src/library/core/Coroutine/Http/ClientProxy.php"); - zend::eval(swoole_library_source_core_coroutine_http_functions, "@swoole-src/library/core/Coroutine/Http/functions.php"); - zend::eval(swoole_library_source_core_connection_pool, "@swoole-src/library/core/ConnectionPool.php"); - zend::eval(swoole_library_source_core_database_object_proxy, "@swoole-src/library/core/Database/ObjectProxy.php"); - zend::eval(swoole_library_source_core_database_mysqli_config, "@swoole-src/library/core/Database/MysqliConfig.php"); - zend::eval(swoole_library_source_core_database_mysqli_exception, "@swoole-src/library/core/Database/MysqliException.php"); - zend::eval(swoole_library_source_core_database_mysqli_pool, "@swoole-src/library/core/Database/MysqliPool.php"); - zend::eval(swoole_library_source_core_database_mysqli_proxy, "@swoole-src/library/core/Database/MysqliProxy.php"); - zend::eval(swoole_library_source_core_database_mysqli_statement_proxy, "@swoole-src/library/core/Database/MysqliStatementProxy.php"); - zend::eval(swoole_library_source_core_database_pdo_config, "@swoole-src/library/core/Database/PDOConfig.php"); - zend::eval(swoole_library_source_core_database_pdo_pool, "@swoole-src/library/core/Database/PDOPool.php"); - zend::eval(swoole_library_source_core_database_pdo_proxy, "@swoole-src/library/core/Database/PDOProxy.php"); - zend::eval(swoole_library_source_core_database_pdo_statement_proxy, "@swoole-src/library/core/Database/PDOStatementProxy.php"); - zend::eval(swoole_library_source_core_database_redis_config, "@swoole-src/library/core/Database/RedisConfig.php"); - zend::eval(swoole_library_source_core_database_redis_pool, "@swoole-src/library/core/Database/RedisPool.php"); - zend::eval(swoole_library_source_core_http_status, "@swoole-src/library/core/Http/Status.php"); - zend::eval(swoole_library_source_core_curl_exception, "@swoole-src/library/core/Curl/Exception.php"); - zend::eval(swoole_library_source_core_curl_handler, "@swoole-src/library/core/Curl/Handler.php"); - zend::eval(swoole_library_source_core_fast_cgi, "@swoole-src/library/core/FastCGI.php"); - zend::eval(swoole_library_source_core_fast_cgi_record, "@swoole-src/library/core/FastCGI/Record.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_params, "@swoole-src/library/core/FastCGI/Record/Params.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_abort_request, "@swoole-src/library/core/FastCGI/Record/AbortRequest.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_begin_request, "@swoole-src/library/core/FastCGI/Record/BeginRequest.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_data, "@swoole-src/library/core/FastCGI/Record/Data.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_end_request, "@swoole-src/library/core/FastCGI/Record/EndRequest.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_get_values, "@swoole-src/library/core/FastCGI/Record/GetValues.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_get_values_result, "@swoole-src/library/core/FastCGI/Record/GetValuesResult.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_stdin, "@swoole-src/library/core/FastCGI/Record/Stdin.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_stdout, "@swoole-src/library/core/FastCGI/Record/Stdout.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_stderr, "@swoole-src/library/core/FastCGI/Record/Stderr.php"); - zend::eval(swoole_library_source_core_fast_cgi_record_unknown_type, "@swoole-src/library/core/FastCGI/Record/UnknownType.php"); - zend::eval(swoole_library_source_core_fast_cgi_frame_parser, "@swoole-src/library/core/FastCGI/FrameParser.php"); - zend::eval(swoole_library_source_core_fast_cgi_message, "@swoole-src/library/core/FastCGI/Message.php"); - zend::eval(swoole_library_source_core_fast_cgi_request, "@swoole-src/library/core/FastCGI/Request.php"); - zend::eval(swoole_library_source_core_fast_cgi_response, "@swoole-src/library/core/FastCGI/Response.php"); - zend::eval(swoole_library_source_core_fast_cgi_http_request, "@swoole-src/library/core/FastCGI/HttpRequest.php"); - zend::eval(swoole_library_source_core_fast_cgi_http_response, "@swoole-src/library/core/FastCGI/HttpResponse.php"); - zend::eval(swoole_library_source_core_coroutine_fast_cgi_client, "@swoole-src/library/core/Coroutine/FastCGI/Client.php"); - zend::eval(swoole_library_source_core_coroutine_fast_cgi_client_exception, "@swoole-src/library/core/Coroutine/FastCGI/Client/Exception.php"); - zend::eval(swoole_library_source_core_coroutine_fast_cgi_proxy, "@swoole-src/library/core/Coroutine/FastCGI/Proxy.php"); - zend::eval(swoole_library_source_core_process_manager, "@swoole-src/library/core/Process/Manager.php"); - zend::eval(swoole_library_source_core_server_admin, "@swoole-src/library/core/Server/Admin.php"); - zend::eval(swoole_library_source_core_server_helper, "@swoole-src/library/core/Server/Helper.php"); - zend::eval(swoole_library_source_core_name_resolver, "@swoole-src/library/core/NameResolver.php"); - zend::eval(swoole_library_source_core_name_resolver_exception, "@swoole-src/library/core/NameResolver/Exception.php"); - zend::eval(swoole_library_source_core_name_resolver_cluster, "@swoole-src/library/core/NameResolver/Cluster.php"); - zend::eval(swoole_library_source_core_name_resolver_redis, "@swoole-src/library/core/NameResolver/Redis.php"); - zend::eval(swoole_library_source_core_name_resolver_nacos, "@swoole-src/library/core/NameResolver/Nacos.php"); - zend::eval(swoole_library_source_core_name_resolver_consul, "@swoole-src/library/core/NameResolver/Consul.php"); - zend::eval(swoole_library_source_core_coroutine_functions, "@swoole-src/library/core/Coroutine/functions.php"); - zend::eval(swoole_library_source_ext_curl, "@swoole-src/library/ext/curl.php"); - zend::eval(swoole_library_source_ext_sockets, "@swoole-src/library/ext/sockets.php"); - zend::eval(swoole_library_source_functions, "@swoole-src/library/functions.php"); - zend::eval(swoole_library_source_alias, "@swoole-src/library/alias.php"); - zend::eval(swoole_library_source_alias_ns, "@swoole-src/library/alias_ns.php"); + _eval(swoole_library_source_constants, "@swoole/library/constants.php"); + _eval(swoole_library_source_std_exec, "@swoole/library/std/exec.php"); + _eval(swoole_library_source_core_constant, "@swoole/library/core/Constant.php"); + _eval(swoole_library_source_core_string_object, "@swoole/library/core/StringObject.php"); + _eval(swoole_library_source_core_multibyte_string_object, "@swoole/library/core/MultibyteStringObject.php"); + _eval(swoole_library_source_core_exception_array_key_not_exists, "@swoole/library/core/Exception/ArrayKeyNotExists.php"); + _eval(swoole_library_source_core_array_object, "@swoole/library/core/ArrayObject.php"); + _eval(swoole_library_source_core_object_proxy, "@swoole/library/core/ObjectProxy.php"); + _eval(swoole_library_source_core_coroutine_wait_group, "@swoole/library/core/Coroutine/WaitGroup.php"); + _eval(swoole_library_source_core_coroutine_server, "@swoole/library/core/Coroutine/Server.php"); + _eval(swoole_library_source_core_coroutine_server_connection, "@swoole/library/core/Coroutine/Server/Connection.php"); + _eval(swoole_library_source_core_coroutine_barrier, "@swoole/library/core/Coroutine/Barrier.php"); + _eval(swoole_library_source_core_coroutine_http_client_proxy, "@swoole/library/core/Coroutine/Http/ClientProxy.php"); + _eval(swoole_library_source_core_coroutine_http_functions, "@swoole/library/core/Coroutine/Http/functions.php"); + _eval(swoole_library_source_core_connection_pool, "@swoole/library/core/ConnectionPool.php"); + _eval(swoole_library_source_core_database_object_proxy, "@swoole/library/core/Database/ObjectProxy.php"); + _eval(swoole_library_source_core_database_mysqli_config, "@swoole/library/core/Database/MysqliConfig.php"); + _eval(swoole_library_source_core_database_mysqli_exception, "@swoole/library/core/Database/MysqliException.php"); + _eval(swoole_library_source_core_database_mysqli_pool, "@swoole/library/core/Database/MysqliPool.php"); + _eval(swoole_library_source_core_database_mysqli_proxy, "@swoole/library/core/Database/MysqliProxy.php"); + _eval(swoole_library_source_core_database_mysqli_statement_proxy, "@swoole/library/core/Database/MysqliStatementProxy.php"); + _eval(swoole_library_source_core_database_detects_lost_connections, "@swoole/library/core/Database/DetectsLostConnections.php"); + _eval(swoole_library_source_core_database_pdo_config, "@swoole/library/core/Database/PDOConfig.php"); + _eval(swoole_library_source_core_database_pdo_pool, "@swoole/library/core/Database/PDOPool.php"); + _eval(swoole_library_source_core_database_pdo_proxy, "@swoole/library/core/Database/PDOProxy.php"); + _eval(swoole_library_source_core_database_pdo_statement_proxy, "@swoole/library/core/Database/PDOStatementProxy.php"); + _eval(swoole_library_source_core_database_redis_config, "@swoole/library/core/Database/RedisConfig.php"); + _eval(swoole_library_source_core_database_redis_pool, "@swoole/library/core/Database/RedisPool.php"); + _eval(swoole_library_source_core_http_status, "@swoole/library/core/Http/Status.php"); + _eval(swoole_library_source_core_curl_exception, "@swoole/library/core/Curl/Exception.php"); + _eval(swoole_library_source_core_curl_handler, "@swoole/library/core/Curl/Handler.php"); + _eval(swoole_library_source_core_fast_cgi, "@swoole/library/core/FastCGI.php"); + _eval(swoole_library_source_core_fast_cgi_record, "@swoole/library/core/FastCGI/Record.php"); + _eval(swoole_library_source_core_fast_cgi_record_params, "@swoole/library/core/FastCGI/Record/Params.php"); + _eval(swoole_library_source_core_fast_cgi_record_abort_request, "@swoole/library/core/FastCGI/Record/AbortRequest.php"); + _eval(swoole_library_source_core_fast_cgi_record_begin_request, "@swoole/library/core/FastCGI/Record/BeginRequest.php"); + _eval(swoole_library_source_core_fast_cgi_record_data, "@swoole/library/core/FastCGI/Record/Data.php"); + _eval(swoole_library_source_core_fast_cgi_record_end_request, "@swoole/library/core/FastCGI/Record/EndRequest.php"); + _eval(swoole_library_source_core_fast_cgi_record_get_values, "@swoole/library/core/FastCGI/Record/GetValues.php"); + _eval(swoole_library_source_core_fast_cgi_record_get_values_result, "@swoole/library/core/FastCGI/Record/GetValuesResult.php"); + _eval(swoole_library_source_core_fast_cgi_record_stdin, "@swoole/library/core/FastCGI/Record/Stdin.php"); + _eval(swoole_library_source_core_fast_cgi_record_stdout, "@swoole/library/core/FastCGI/Record/Stdout.php"); + _eval(swoole_library_source_core_fast_cgi_record_stderr, "@swoole/library/core/FastCGI/Record/Stderr.php"); + _eval(swoole_library_source_core_fast_cgi_record_unknown_type, "@swoole/library/core/FastCGI/Record/UnknownType.php"); + _eval(swoole_library_source_core_fast_cgi_frame_parser, "@swoole/library/core/FastCGI/FrameParser.php"); + _eval(swoole_library_source_core_fast_cgi_message, "@swoole/library/core/FastCGI/Message.php"); + _eval(swoole_library_source_core_fast_cgi_request, "@swoole/library/core/FastCGI/Request.php"); + _eval(swoole_library_source_core_fast_cgi_response, "@swoole/library/core/FastCGI/Response.php"); + _eval(swoole_library_source_core_fast_cgi_http_request, "@swoole/library/core/FastCGI/HttpRequest.php"); + _eval(swoole_library_source_core_fast_cgi_http_response, "@swoole/library/core/FastCGI/HttpResponse.php"); + _eval(swoole_library_source_core_coroutine_fast_cgi_client, "@swoole/library/core/Coroutine/FastCGI/Client.php"); + _eval(swoole_library_source_core_coroutine_fast_cgi_client_exception, "@swoole/library/core/Coroutine/FastCGI/Client/Exception.php"); + _eval(swoole_library_source_core_coroutine_fast_cgi_proxy, "@swoole/library/core/Coroutine/FastCGI/Proxy.php"); + _eval(swoole_library_source_core_process_manager, "@swoole/library/core/Process/Manager.php"); + _eval(swoole_library_source_core_remote_object, "@swoole/library/core/RemoteObject.php"); + _eval(swoole_library_source_core_remote_object_server, "@swoole/library/core/RemoteObject/Server.php"); + _eval(swoole_library_source_core_remote_object_context, "@swoole/library/core/RemoteObject/Context.php"); + _eval(swoole_library_source_core_remote_object_client, "@swoole/library/core/RemoteObject/Client.php"); + _eval(swoole_library_source_core_remote_object_exception, "@swoole/library/core/RemoteObject/Exception.php"); + _eval(swoole_library_source_core_remote_object_proxy_trait, "@swoole/library/core/RemoteObject/ProxyTrait.php"); + _eval(swoole_library_source_core_server_admin, "@swoole/library/core/Server/Admin.php"); + _eval(swoole_library_source_core_server_helper, "@swoole/library/core/Server/Helper.php"); + _eval(swoole_library_source_core_name_resolver, "@swoole/library/core/NameResolver.php"); + _eval(swoole_library_source_core_name_resolver_exception, "@swoole/library/core/NameResolver/Exception.php"); + _eval(swoole_library_source_core_name_resolver_cluster, "@swoole/library/core/NameResolver/Cluster.php"); + _eval(swoole_library_source_core_name_resolver_redis, "@swoole/library/core/NameResolver/Redis.php"); + _eval(swoole_library_source_core_name_resolver_nacos, "@swoole/library/core/NameResolver/Nacos.php"); + _eval(swoole_library_source_core_name_resolver_consul, "@swoole/library/core/NameResolver/Consul.php"); + _eval(swoole_library_source_core_thread_pool, "@swoole/library/core/Thread/Pool.php"); + _eval(swoole_library_source_core_thread_runnable, "@swoole/library/core/Thread/Runnable.php"); + _eval(swoole_library_source_core_coroutine_functions, "@swoole/library/core/Coroutine/functions.php"); + _eval(swoole_library_source_ext_curl, "@swoole/library/ext/curl.php"); + _eval(swoole_library_source_ext_sockets, "@swoole/library/ext/sockets.php"); + _eval(swoole_library_source_ext_standard, "@swoole/library/ext/standard.php"); + _eval(swoole_library_source_ext_mongodb, "@swoole/library/ext/mongodb.php"); + _eval(swoole_library_source_functions, "@swoole/library/functions.php"); + _eval(swoole_library_source_alias, "@swoole/library/alias.php"); + _eval(swoole_library_source_alias_ns, "@swoole/library/alias_ns.php"); } diff --git a/ext-src/php_swoole_mysql_proto.h b/ext-src/php_swoole_mysql_proto.h deleted file mode 100644 index 62b7b19a6e..0000000000 --- a/ext-src/php_swoole_mysql_proto.h +++ /dev/null @@ -1,1017 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Author: Twosee | - | Author: Tianfeng Han | - +----------------------------------------------------------------------+ -*/ - -#pragma once - -#include "php_swoole_cxx.h" -#include "swoole_util.h" - -#ifdef SW_USE_OPENSSL -#ifndef OPENSSL_NO_RSA -#define SW_MYSQL_RSA_SUPPORT -#include -#include -#include -#endif -#endif - -enum sw_mysql_command -{ - SW_MYSQL_COM_NULL = -1, - SW_MYSQL_COM_SLEEP = 0, - SW_MYSQL_COM_QUIT, - SW_MYSQL_COM_INIT_DB, - SW_MYSQL_COM_QUERY = 3, - SW_MYSQL_COM_FIELD_LIST, - SW_MYSQL_COM_CREATE_DB, - SW_MYSQL_COM_DROP_DB, - SW_MYSQL_COM_REFRESH, - SW_MYSQL_COM_SHUTDOWN, - SW_MYSQL_COM_STATISTICS, - SW_MYSQL_COM_PROCESS_INFO, - SW_MYSQL_COM_CONNECT, - SW_MYSQL_COM_PROCESS_KILL, - SW_MYSQL_COM_DEBUG, - SW_MYSQL_COM_PING, - SW_MYSQL_COM_TIME, - SW_MYSQL_COM_DELAYED_INSERT, - SW_MYSQL_COM_CHANGE_USER, - SW_MYSQL_COM_BINLOG_DUMP, - SW_MYSQL_COM_TABLE_DUMP, - SW_MYSQL_COM_CONNECT_OUT, - SW_MYSQL_COM_REGISTER_SLAVE, - SW_MYSQL_COM_STMT_PREPARE, - SW_MYSQL_COM_STMT_EXECUTE, - SW_MYSQL_COM_STMT_SEND_LONG_DATA, - SW_MYSQL_COM_STMT_CLOSE, - SW_MYSQL_COM_STMT_RESET, - SW_MYSQL_COM_SET_OPTION, - SW_MYSQL_COM_STMT_FETCH, - SW_MYSQL_COM_DAEMON, - SW_MYSQL_COM_END -}; - -enum sw_mysql_handshake_state -{ - SW_MYSQL_HANDSHAKE_WAIT_REQUEST, - SW_MYSQL_HANDSHAKE_WAIT_SWITCH, - SW_MYSQL_HANDSHAKE_WAIT_SIGNATURE, - SW_MYSQL_HANDSHAKE_WAIT_RSA, - SW_MYSQL_HANDSHAKE_WAIT_RESULT, - SW_MYSQL_HANDSHAKE_COMPLETED, -}; - -#define SW_MYSQL_AUTH_SIGNATRUE_PACKET_LENGTH 2 - -enum sw_mysql_auth_signature -{ - SW_MYSQL_AUTH_SIGNATURE_ERROR = 0x00, // get signature failed - SW_MYSQL_AUTH_SIGNATURE = 0x01, - SW_MYSQL_AUTH_SIGNATURE_RSA_PREPARED = 0x02, - SW_MYSQL_AUTH_SIGNATURE_SUCCESS = 0x03, - SW_MYSQL_AUTH_SIGNATURE_FULL_AUTH_REQUIRED = 0x04, // rsa required -}; - -enum sw_mysql_command_flag -{ - SW_MYSQL_COMMAND_FLAG_QUERY = 1 << 4, - SW_MYSQL_COMMAND_FLAG_EXECUTE = 1 << 5, -}; - -enum sw_mysql_state -{ - SW_MYSQL_STATE_CLOSED = 0, - SW_MYSQL_STATE_IDLE = 1, - SW_MYSQL_STATE_QUERY = 2 | SW_MYSQL_COMMAND_FLAG_QUERY, - SW_MYSQL_STATE_QUERY_FETCH = 3 | SW_MYSQL_COMMAND_FLAG_QUERY, - SW_MYSQL_STATE_QUERY_MORE_RESULTS = 4 | SW_MYSQL_COMMAND_FLAG_QUERY, - SW_MYSQL_STATE_PREPARE = 5 | SW_MYSQL_COMMAND_FLAG_QUERY, - SW_MYSQL_STATE_EXECUTE = 6 | SW_MYSQL_COMMAND_FLAG_EXECUTE, - SW_MYSQL_STATE_EXECUTE_FETCH = 7 | SW_MYSQL_COMMAND_FLAG_EXECUTE, - SW_MYSQL_STATE_EXECUTE_MORE_RESULTS = 8 | SW_MYSQL_COMMAND_FLAG_EXECUTE, -}; - -enum sw_mysql_packet_types -{ - SW_MYSQL_PACKET_OK = 0x0, - SW_MYSQL_PACKET_AUTH_SIGNATURE_REQUEST = 0x01, - - /* not defined in protocol */ - SW_MYSQL_PACKET_RAW_DATA, - SW_MYSQL_PACKET_GREETING, - SW_MYSQL_PACKET_LOGIN, - SW_MYSQL_PACKET_AUTH_SWITCH_RESPONSE, - SW_MYSQL_PACKET_AUTH_SIGNATURE_RESPONSE, - SW_MYSQL_PACKET_LCB, // length coded binary - SW_MYSQL_PACKET_FIELD, - SW_MYSQL_PACKET_ROW_DATA, - SW_MYSQL_PACKET_PREPARE_STATEMENT, - /* ======================= */ - - SW_MYSQL_PACKET_NULL = 0xfb, - SW_MYSQL_PACKET_EOF = 0xfe, - SW_MYSQL_PACKET_AUTH_SWITCH_REQUEST = 0xfe, - SW_MYSQL_PACKET_ERR = 0xff -}; - -enum sw_mysql_field_types -{ - SW_MYSQL_TYPE_DECIMAL, - SW_MYSQL_TYPE_TINY, - SW_MYSQL_TYPE_SHORT, - SW_MYSQL_TYPE_LONG, - SW_MYSQL_TYPE_FLOAT, - SW_MYSQL_TYPE_DOUBLE, - SW_MYSQL_TYPE_NULL, - SW_MYSQL_TYPE_TIMESTAMP, - SW_MYSQL_TYPE_LONGLONG, - SW_MYSQL_TYPE_INT24, - SW_MYSQL_TYPE_DATE, - SW_MYSQL_TYPE_TIME, - SW_MYSQL_TYPE_DATETIME, - SW_MYSQL_TYPE_YEAR, - SW_MYSQL_TYPE_NEWDATE, - SW_MYSQL_TYPE_VARCHAR, - SW_MYSQL_TYPE_BIT, - SW_MYSQL_TYPE_JSON = 245, - SW_MYSQL_TYPE_NEWDECIMAL, - SW_MYSQL_TYPE_ENUM, - SW_MYSQL_TYPE_SET, - SW_MYSQL_TYPE_TINY_BLOB, - SW_MYSQL_TYPE_MEDIUM_BLOB, - SW_MYSQL_TYPE_LONG_BLOB, - SW_MYSQL_TYPE_BLOB, - SW_MYSQL_TYPE_VAR_STRING, - SW_MYSQL_TYPE_STRING, - SW_MYSQL_TYPE_GEOMETRY -}; - -// ref: https://dev.mysql.com/doc/dev/mysql-server/8.0.0/group__group__cs__capabilities__flags.html -// use regex: "\#define[ ]+(CLIENT_[A-Z_\d]+)[ ]+(\(?[\dA-Z <]+\)?)\n[ ]+?[ ]+([\s\S ]+?\.) More\.\.\.\n?" -// to "SW_MYSQL_$1 = $2, /* $3 */" -enum sw_mysql_client_capability_flags -{ - SW_MYSQL_CLIENT_LONG_PASSWORD = 1, /* Use the improved version of Old Password Authentication. */ - SW_MYSQL_CLIENT_FOUND_ROWS = 2, /* Send found rows instead of affected rows in EOF_Packet. */ - SW_MYSQL_CLIENT_LONG_FLAG = 4, /* Get all column flags. */ - SW_MYSQL_CLIENT_CONNECT_WITH_DB = 8, /* Database (schema) name can be specified on connect in Handshake Response Packet. */ - SW_MYSQL_CLIENT_NO_SCHEMA = 16, /* Don't allow database.table.column. */ - SW_MYSQL_CLIENT_COMPRESS = 32, /* Compression protocol supported. */ - SW_MYSQL_CLIENT_ODBC = 64, /* Special handling of ODBC behavior. */ - SW_MYSQL_CLIENT_LOCAL_FILES = 128, /* Can use LOAD DATA LOCAL. */ - SW_MYSQL_CLIENT_IGNORE_SPACE = 256, /* Ignore spaces before '('. */ - SW_MYSQL_CLIENT_PROTOCOL_41 = 512, /* New 4.1 protocol. */ - SW_MYSQL_CLIENT_INTERACTIVE = 1024, /* This is an interactive client. */ - SW_MYSQL_CLIENT_SSL = 2048, /* Use SSL encryption for the session. */ - SW_MYSQL_CLIENT_IGNORE_SIGPIPE = 4096, /* Client only flag. */ - SW_MYSQL_CLIENT_TRANSACTIONS = 8192, /* Client knows about transactions. */ - SW_MYSQL_CLIENT_RESERVED = 16384, /* flag for 4.1 protocol. */ - SW_MYSQL_CLIENT_SECURE_CONNECTION = 32768, /* swoole custom name for RESERVED2. */ - SW_MYSQL_CLIENT_RESERVED2 = 32768, /* flag for 4.1 authentication. */ - SW_MYSQL_CLIENT_MULTI_STATEMENTS = (1UL << 16), /* Enable/disable multi-stmt support. */ - SW_MYSQL_CLIENT_MULTI_RESULTS = (1UL << 17), /* Enable/disable multi-results. */ - SW_MYSQL_CLIENT_PS_MULTI_RESULTS = (1UL << 18), /* Multi-results and OUT parameters in PS-protocol. */ - SW_MYSQL_CLIENT_PLUGIN_AUTH = (1UL << 19), /* Client supports plugin authentication. */ - SW_MYSQL_CLIENT_CONNECT_ATTRS = (1UL << 20), /* Client supports connection attributes. */ - SW_MYSQL_CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1UL << 21), /* Enable authentication response packet to be larger than 255 bytes. */ - SW_MYSQL_CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1UL << 22), /* Don't close the connection for a user account with expired password. */ - SW_MYSQL_CLIENT_SESSION_TRACK = (1UL << 23), /* Capable of handling server state change information. */ - SW_MYSQL_CLIENT_DEPRECATE_EOF = (1UL << 24), /* Client no longer needs EOF_Packet and will use OK_Packet instead. */ - SW_MYSQL_CLIENT_SSL_VERIFY_SERVER_CERT = (1UL << 30), /* Verify server certificate. */ - SW_MYSQL_CLIENT_REMEMBER_OPTIONS = (1UL << 31) /* Don't reset the options after an unsuccessful connect. */ -}; - -// ref: https://dev.mysql.com/doc/internals/en/status-flags.html -enum sw_mysql_server_status_flags -{ - SW_MYSQL_SERVER_STATUS_IN_TRANS = 0x0001, // a transaction is active - SW_MYSQL_SERVER_STATUS_AUTOCOMMIT = 0x0002, //auto-commit is enabled - SW_MYSQL_SERVER_MORE_RESULTS_EXISTS = 0x0008, - SW_MYSQL_SERVER_STATUS_NO_GOOD_INDEX_USED = 0x0010, - SW_MYSQL_SERVER_STATUS_NO_INDEX_USED = 0x0020, - SW_MYSQL_SERVER_STATUS_CURSOR_EXISTS = 0x0040, // Used by Binary Protocol Resultset to signal that COM_STMT_FETCH must be used to fetch the row-data. - SW_MYSQL_SERVER_STATUS_LAST_ROW_SENT = 0x0080, - SW_MYSQL_SERVER_STATUS_DB_DROPPED = 0x0100, - SW_MYSQL_SERVER_STATUS_NO_BACKSLASH_ESCAPES = 0x0200, - SW_MYSQL_SERVER_STATUS_METADATA_CHANGED = 0x0400, - SW_MYSQL_SERVER_QUERY_WAS_SLOW = 0x0800, - SW_MYSQL_SERVER_PS_OUT_PARAMS = 0x1000, - SW_MYSQL_SERVER_STATUS_IN_TRANS_READONLY = 0x2000, // in a read-only transaction - SW_MYSQL_SERVER_SESSION_STATE_CHANGED = 0x4000 // connection state information has changed -}; - -#define SW_MYSQL_NO_RSA_ERROR "MySQL8 caching_sha2_password authentication plugin need enable OpenSSL support" - -#define SW_MYSQL_NOT_NULL_FLAG 1 -#define SW_MYSQL_PRI_KEY_FLAG 2 -#define SW_MYSQL_UNIQUE_KEY_FLAG 4 -#define SW_MYSQL_MULTIPLE_KEY_FLAG 8 -#define SW_MYSQL_BLOB_FLAG 16 -#define SW_MYSQL_UNSIGNED_FLAG 32 -#define SW_MYSQL_ZEROFILL_FLAG 64 -#define SW_MYSQL_BINARY_FLAG 128 -#define SW_MYSQL_ENUM_FLAG 256 -#define SW_MYSQL_AUTO_INCREMENT_FLAG 512 -#define SW_MYSQL_TIMESTAMP_FLAG 1024 -#define SW_MYSQL_SET_FLAG 2048 -#define SW_MYSQL_NO_DEFAULT_VALUE_FLAG 4096 -#define SW_MYSQL_ON_UPDATE_NOW_FLAG 8192 -#define SW_MYSQL_PART_KEY_FLAG 16384 -#define SW_MYSQL_GROUP_FLAG 32768 -#define SW_MYSQL_NUM_FLAG 32768 - -/* int<3> payload_length + int<1> sequence_id */ -#define SW_MYSQL_PACKET_HEADER_SIZE 4 -#define SW_MYSQL_PACKET_TYPE_OFFSET 5 -#define SW_MYSQL_PACKET_EOF_MAX_SIZE 9 -#define SW_MYSQL_PACKET_PREPARED_OK_SIZE 12 -#define SW_MYSQL_MAX_PACKET_BODY_SIZE 0x00ffffff -#define SW_MYSQL_MAX_PACKET_SIZE (SW_MYSQL_PACKET_HEADER_SIZE + SW_MYSQL_MAX_PACKET_BODY_SIZE) - -// nonce: a number or bit string used only once, in security engineering -// other names on doc: challenge/scramble/salt -#define SW_MYSQL_NONCE_LENGTH 20 - -// clang-format off -#define sw_mysql_uint2korr2korr(A) (uint16_t) (((uint16_t) ((uchar) (A)[0])) +\ - ((uint16_t) ((uchar) (A)[1]) << 8)) -#define sw_mysql_uint2korr3korr(A) (uint32_t) (((uint32_t) ((uchar) (A)[0])) +\ - (((uint32_t) ((uchar) (A)[1])) << 8) +\ - (((uint32_t) ((uchar) (A)[2])) << 16)) -#define sw_mysql_uint2korr4korr(A) (uint32_t) (((uint32_t) ((uchar) (A)[0])) +\ - (((uint32_t) ((uchar) (A)[1])) << 8) +\ - (((uint32_t) ((uchar) (A)[2])) << 16) +\ - (((uint32_t) ((uchar) (A)[3])) << 24)) -#define sw_mysql_uint2korr8korr(A) ((uint64_t)(((uint32_t) ((uchar) (A)[0])) +\ - (((uint32_t) ((uchar) (A)[1])) << 8) +\ - (((uint32_t) ((uchar) (A)[2])) << 16) +\ - (((uint32_t) ((uchar) (A)[3])) << 24)) +\ - (((uint64_t) (((uint32_t) ((uchar) (A)[4])) +\ - (((uint32_t) ((uchar) (A)[5])) << 8) +\ - (((uint32_t) ((uchar) (A)[6])) << 16) +\ - (((uint32_t) ((uchar) (A)[7])) << 24))) << 32)) - -#define sw_mysql_int1store(T,A) do { *((int8_t*) (T)) = (int8_t)(A); } while(0) -#define sw_mysql_int2store(T,A) do { uint32_t def_temp= (uint32_t) (A) ;\ - *((uchar*) (T)) = (uchar)(def_temp); \ - *((uchar*) (T+1)) = (uchar)((def_temp >> 8)); } while (0) -#define sw_mysql_int3store(T,A) do { /*lint -save -e734 */\ - *(((char *)(T))) = (char) ((A));\ - *(((char *)(T))+1) = (char) (((A) >> 8));\ - *(((char *)(T))+2) = (char) (((A) >> 16)); \ - /*lint -restore */} while (0) -#define sw_mysql_int4store(T,A) do { \ - *(((char *)(T))) = (char) ((A));\ - *(((char *)(T))+1) = (char) (((A) >> 8));\ - *(((char *)(T))+2) = (char) (((A) >> 16));\ - *(((char *)(T))+3) = (char) (((A) >> 24)); } while (0) -#define sw_mysql_int5store(T,A) do { \ - *(((char *)(T))) = (char)((A));\ - *(((char *)(T))+1) = (char)(((A) >> 8));\ - *(((char *)(T))+2) = (char)(((A) >> 16));\ - *(((char *)(T))+3) = (char)(((A) >> 24)); \ - *(((char *)(T))+4) = (char)(((A) >> 32)); } while (0) -/* Based on int5store() from Andrey Hristov */ -#define sw_mysql_int6store(T,A) do { \ - *(((char *)(T))) = (char)((A));\ - *(((char *)(T))+1) = (char)(((A) >> 8));\ - *(((char *)(T))+2) = (char)(((A) >> 16));\ - *(((char *)(T))+3) = (char)(((A) >> 24)); \ - *(((char *)(T))+4) = (char)(((A) >> 32)); \ - *(((char *)(T))+5) = (char)(((A) >> 40)); } while (0) - -// clang-format on - -#define sw_mysql_int8store(T,A) do { \ - uint32_t def_temp= (uint32_t) (A), def_temp2= (uint32_t) ((A) >> 32); \ - sw_mysql_int4store((T),def_temp); \ - sw_mysql_int4store((T+4),def_temp2); } while (0) - -#define sw_mysql_doublestore(T,A) do { \ - double def_temp = (double) A; \ - memcpy(T, &def_temp, sizeof(double)); \ - } while (0) - -#if defined(SW_DEBUG) && defined(SW_LOG_TRACE_OPEN) -#define swMysqlPacketDump(length, number, data, title) \ - if (SW_LOG_TRACE >= sw_logger()->get_level() && (SW_TRACE_MYSQL_CLIENT & SwooleG.trace_flags)) \ - { \ - swoole_debug("+----------+------------+-------------------------------------------------------+"); \ - swoole_debug("| P#%-6u | L%-9u | %-10u %42s |", number, SW_MYSQL_PACKET_HEADER_SIZE + length, length, title); \ - swoole_hex_dump(data, length); \ - } -#else -#define swMysqlPacketDump(length, number, data, title) -#endif - -namespace swoole { namespace mysql { -//-----------------------------------namespace begin-------------------------------------------- -char get_charset(const char *name); -uint8_t get_static_type_size(uint8_t type); - -inline uint8_t read_lcb_size(const char *p) -{ - switch ((uchar) p[0]) - { - case 251: - return 1; - case 252: - return 3; - case 253: - return 4; - case 254: - return 9; - default: - return 1; - } -} - -inline uint8_t read_lcb(const char *p, uint64_t *length, bool *nul) -{ - switch ((uchar) p[0]) - { - case 251: /* fb : 1 octet */ - *length = 0; - *nul = true; - return 1; - case 252: /* fc : 2 octets */ - *length = sw_mysql_uint2korr2korr(p + 1); - *nul = false; - return 3; - case 253: /* fd : 3 octets */ - *length = sw_mysql_uint2korr3korr(p + 1); - *nul = false; - return 4; - case 254: /* fe : 8 octets */ - *length = sw_mysql_uint2korr8korr(p + 1); - *nul = false; - return 9; - default: - *length = (uchar) p[0]; - *nul = false; - return 1; - } -} - -inline uint8_t read_lcb(const char *p, uint32_t *length, bool *nul) -{ - uint64_t _r; - uint8_t ret = read_lcb(p, &_r, nul); - *length = _r; - return ret; -} - -inline uint8_t write_lcb(char *p, uint64_t length, bool nul = false) -{ - if (nul) - { - sw_mysql_int1store(p++, 251); - return 1; - } - if (length <= 250) - { - sw_mysql_int1store(p, length); - return 1; - } - else if (length <= 0xffff) - { - sw_mysql_int1store(p++, 252); - sw_mysql_int2store(p, length); - return 3; - } - else if (length <= 0xffffff) - { - sw_mysql_int1store(p++, 253); - sw_mysql_int3store(p, length); - return 4; - } - else - { - sw_mysql_int1store(p++, 254); - sw_mysql_int8store(p, length); - return 9; - } -} - -class packet -{ -public: - static inline uint32_t get_length(const char *data) - { - return sw_mysql_uint2korr3korr(data); - } - static inline uint32_t get_number(const char *data) - { - return (uint8_t) data[3]; - } - static inline void set_length(char *buffer, uint32_t length) - { - buffer[0] = length; - buffer[1] = length >> 8; - buffer[2] = length >> 16; - } - static inline void set_number(char *buffer, uint8_t number) - { - buffer[3] = number; - } - static inline void set_header(char *buffer, uint32_t length, uint8_t number) - { - set_length(buffer, length); - set_number(buffer, number); - } -}; - -class server_packet : public packet -{ -public: - struct header { - uint32_t length :24; - uint32_t number :8; - header() : length(0), number(0) { } - } header; - server_packet() { } - server_packet(const char *data) - { - parse(data); - } - inline void parse(const char *data) - { - header.length = packet::get_length(data); - header.number = packet::get_number(data); - } - static inline uint8_t parse_type(const char *data) - { - if (sw_unlikely(!data)) - { - return SW_MYSQL_PACKET_NULL; - } - return (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE]; - } - static inline bool is_eof(const char *data) - { - return (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE] == SW_MYSQL_PACKET_EOF; - } - static inline bool is_ok(const char *data) - { - return (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE] == SW_MYSQL_PACKET_OK; - } - static inline bool is_err(const char *data) - { - return (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE] == SW_MYSQL_PACKET_ERR; - } -}; - -class server_status -{ -public: - int16_t status = 0; - void operator =(uint16_t status) - { - this->status = status; - } - inline bool more_results_exists() - { - bool b = !!(status & SW_MYSQL_SERVER_MORE_RESULTS_EXISTS); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "More results exist = %u", b); - return b; - } -}; - -class client_packet : public packet -{ -public: - client_packet(size_t body_size = 1024 - SW_MYSQL_PACKET_HEADER_SIZE) - { - SW_ASSERT(body_size > 0); - if (body_size <= 4) - { - data.header = stack_buffer; - } - else - { - data.header = new char[SW_MEM_ALIGNED_SIZE(SW_MYSQL_PACKET_HEADER_SIZE + body_size)](); - } - data.body = data.header + SW_MYSQL_PACKET_HEADER_SIZE; - } - inline const char* get_data() - { - return data.header; - } - inline uint32_t get_data_length() - { - return SW_MYSQL_PACKET_HEADER_SIZE + get_length(); - } - inline uint32_t get_length() - { - return sw_mysql_uint2korr3korr(data.header); - } - inline uint8_t get_number() - { - return (uint8_t) data.header[3]; - } - inline const char* get_body() - { - return data.body; - } - inline void set_header(uint32_t length, uint8_t number) - { - packet::set_header(data.header, length, number); - } - ~client_packet() - { - if (data.header != stack_buffer) - { - delete[] data.header; - } - } -protected: - struct { - char *header = nullptr; - char *body = nullptr; - } data; - char stack_buffer[SW_MYSQL_PACKET_HEADER_SIZE + 4] = {}; -}; - -class command_packet : public client_packet -{ -public: - command_packet(enum sw_mysql_command command, const char *sql = nullptr, size_t length = 0) : client_packet(1 + length) - { - set_command(command); - set_header(1 + length, 0); - if (length > 0) - { - memcpy(data.body + 1, sql, length); - } - }; - inline void set_command(enum sw_mysql_command command) - { - data.body[0] = (char) command; - } -}; - -class err_packet : public server_packet -{ -public: - uint16_t code; - std::string msg; - char sql_state[5 + 1]; - err_packet(const char *data); -}; - -class ok_packet : public server_packet -{ -public: - uint64_t affected_rows = 0; - uint64_t last_insert_id = 0; - mysql::server_status server_status; - unsigned int warning_count = 0; - ok_packet() { } - ok_packet(const char *data); -}; - -class eof_packet : public server_packet -{ -public: - uint16_t warning_count; - mysql::server_status server_status; - eof_packet(const char *data); -}; - -class raw_data_packet : public server_packet -{ -public: - const char *body; - raw_data_packet(const char *data) : server_packet(data), body(data + SW_MYSQL_PACKET_HEADER_SIZE) - { - swMysqlPacketDump(header.length, header.number, data, "Protocol::RawData"); - } -}; - -class greeting_packet : public server_packet -{ -public: - uint8_t protocol_version = 0; - std::string server_version = ""; - int connection_id = 0; - char auth_plugin_data[SW_MYSQL_NONCE_LENGTH + 1] = {}; // nonce + '\0' - uint8_t auth_plugin_data_length = 0; - char filler = 0; - int capability_flags = 0; - char charset = SW_MYSQL_DEFAULT_CHARSET; - mysql::server_status status_flags; - char reserved[10] = {}; - std::string auth_plugin_name = ""; - greeting_packet(const char *data); -}; - -class login_packet : public client_packet -{ -public: - login_packet( - greeting_packet *greeting_packet, - const std::string &user, - const std::string &password, - std::string database, - char charset - ); -}; - -class auth_switch_request_packet : public server_packet -{ -public: - std::string auth_method_name = "mysql_native_password"; - char auth_method_data[SW_MYSQL_NONCE_LENGTH + 1] = {}; - auth_switch_request_packet(const char *data); -}; - -class auth_switch_response_packet : public client_packet -{ -public: - auth_switch_response_packet(auth_switch_request_packet *req, const std::string &password); -}; - -class auth_signature_request_packet : public server_packet -{ -public: - char data[2] = {}; - auth_signature_request_packet(const char *data) :server_packet(data) - { - swMysqlPacketDump(header.length, header.number, data, "Protocol::AuthSignatureRequest"); - memcpy(&this->data, data + SW_MYSQL_PACKET_HEADER_SIZE, 2); - } - inline bool is_full_auth_required() - { - return data[1] == SW_MYSQL_AUTH_SIGNATURE_FULL_AUTH_REQUIRED; - } - inline bool is_vaild() - { - return data[0] == SW_MYSQL_AUTH_SIGNATURE && (data[1] == SW_MYSQL_AUTH_SIGNATURE_SUCCESS || data[1] == SW_MYSQL_AUTH_SIGNATURE_FULL_AUTH_REQUIRED); - } -}; - -class auth_signature_prepared_packet : public client_packet -{ -public: - auth_signature_prepared_packet(uint8_t number) : client_packet(1) - { - set_header(1, number); - data.body[0] = SW_MYSQL_AUTH_SIGNATURE_RSA_PREPARED; - } -}; - -class auth_signature_response_packet : public client_packet -{ -public: - auth_signature_response_packet(raw_data_packet *raw_data_pakcet, const std::string &password, const char *auth_plugin_data); -}; - -class lcb_packet : public server_packet -{ -public: - uint32_t length = 0; - bool nul = 0; - lcb_packet(const char *data) : server_packet(data) - { - swMysqlPacketDump(header.length, header.number, data, "Protocol::LengthCodedBinary"); - bytes_length = read_lcb(data + SW_MYSQL_PACKET_HEADER_SIZE, &length, &nul); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "binary_length=%u, nul=%u", header.length, nul); - } - bool is_vaild() - { - return header.length == bytes_length; - } -private: - uint8_t bytes_length; -}; - -class field_packet : public server_packet -{ -public: - char *catalog = nullptr; /* Catalog for table */ - uint32_t catalog_length = 0; - char *database = nullptr; /* Database for table */ - uint32_t database_length = 0; - char *table = nullptr; /* Table of column if column was a field */ - uint32_t table_length = 0; - char *org_table = nullptr; /* Org table name, if table was an alias */ - uint32_t org_table_length = 0; - char *name = nullptr; /* Name of column */ - uint32_t name_length = 0; - char *org_name = nullptr; /* Original column name, if an alias */ - uint32_t org_name_length = 0; - char charset = 0; - uint64_t length = 0; /* Width of column (create length) */ - uint8_t type = 0; /* Type of field. See mysql_com.h for types */ - uint32_t flags = 0; /* Div flags */ - uint32_t decimals = 0; /* Number of decimals in field */ - char *def = nullptr; /* Default value (set by mysql_list_fields) */ - uint32_t def_length = 0; - void *extension = nullptr; - field_packet() { } - field_packet(const char *data) { - parse(data); - } - void parse(const char *data); - ~field_packet() - { - if (body) - { - delete[] body; - } - } -protected: - char *body = nullptr; -}; - -typedef field_packet param_packet; - -class row_data -{ -public: - char stack_buffer[32]; - struct { - uint64_t length; // binary code length - bool nul; // is nul? - } text; - row_data(const char *data) - { - next_packet(data); - } - inline void next_packet(const char *data) - { - read_ptr = packet_body = data + SW_MYSQL_PACKET_HEADER_SIZE; - packet_eof = packet_body + packet::get_length(data); - } - inline bool eof() - { - return read_ptr == packet_eof; - } - inline const char* read(size_t length) - { - if (sw_likely(read_ptr + length <= packet_eof)) - { - const char *p = read_ptr; - read_ptr += length; - return p; - } - return nullptr; - } - inline uint32_t recv(char *buf, size_t size) - { - uint32_t readable_length = packet_eof - read_ptr; - uint32_t read_bytes = SW_MIN(readable_length, size); - if (sw_likely(read_bytes > 0)) - { - memcpy(buf, read_ptr, read_bytes); - read_ptr += read_bytes; - } - return read_bytes; - } -protected: - const char *packet_body; - const char *packet_eof; - const char *read_ptr; -}; - -class row_data_text -{ -public: - uint64_t length = 0; - bool nul = false; - const char *body = nullptr; - row_data_text(const char **pp) - { - body = *pp + read_lcb(*pp, &length, &nul); - *pp = body + length; - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, - "text[%" PRIu64 "]: %.*s%s", - length, (int) SW_MIN(64, length), body, - nul ? "null" : ((length > 64 /*|| length > readable_length*/) ? "..." : "") - ); - } -}; - -inline std::string datetime(const char *p, uint8_t length, uint32_t decimals) -{ - uint16_t y = 0; - uint8_t m = 0, d = 0, h = 0, i = 0, s = 0; - uint32_t sp = 0; - if (length != 0) - { - y = sw_mysql_uint2korr2korr(p); - m = *(uint8_t *) (p + 2); - d = *(uint8_t *) (p + 3); - if (length > 4) - { - h = *(uint8_t *) (p + 4); - i = *(uint8_t *) (p + 5); - s = *(uint8_t *) (p + 6); - } - if (length > 7) - { - sp = sw_mysql_uint2korr4korr(p + 7); - } - } - if (decimals > 0 && decimals < 7) { - return swoole::std_string::format( - "%04u-%02u-%02u %02u:%02u:%02u.%0*u", - y, m, d, h, i, s, decimals, (uint32_t) (sp / ::pow(10, (double) (6 - decimals))) - ); - } else { - return swoole::std_string::format( - "%04u-%02u-%02u %02u:%02u:%02u", - y, m, d, h, i, s - ); - } -} - -inline std::string time(const char *p, uint8_t length, uint32_t decimals) -{ - bool neg = false; - uint32_t d = 0, sp = 0; - uint8_t h = 0, m = 0, s = 0; - if (length != 0) - { - neg = (bool) *((uint8_t *) p); - d = sw_mysql_uint2korr4korr(p + 1); - h = *(uint8_t *) (p + 5); - m = *(uint8_t *) (p + 6); - s = *(uint8_t *) (p + 7); - if (length > 8) - { - sp = sw_mysql_uint2korr4korr(p + 8); - } - if (d != 0) { - /* Convert days to hours at once */ - h += d * 24; - } - } - if (decimals > 0 && decimals < 7) { - return swoole::std_string::format( - "%s%02u:%02u:%02u.%0*u", - (neg ? "-" : ""), h, m, s, decimals, (uint32_t) (sp / ::pow(10, (double) (6 - decimals))) - ); - } else { - return swoole::std_string::format( - "%s%02u:%02u:%02u", - (neg ? "-" : ""), h, m, s - ); - } -} - -inline std::string date(const char *p, uint8_t length) -{ - uint16_t y = 0; - uint8_t m = 0, d = 0; - if (length != 0) - { - y = sw_mysql_uint2korr2korr(p); - m = *(uint8_t *) (p + 2); - d = *(uint8_t *) (p + 3); - } - return swoole::std_string::format("%04u-%02u-%02u", y, m, d); -} - -inline std::string year(const char *p, uint8_t length) -{ - uint16_t y = 0; - if (length != 0) - { - y = sw_mysql_uint2korr2korr(p); - } - return swoole::std_string::format("%04u", y); -} - -class result_info -{ -public: - ok_packet ok; - - inline void alloc_fields(uint32_t length) - { - clear_fields(); - if (sw_likely(length != 0)) - { - fields.info = new field_packet[length]; - fields.length = length; - } - else - { - fields.length = 0; - fields.info = nullptr; - } - } - inline uint32_t get_fields_length() - { - return fields.length; - } - inline field_packet* get_fields(uint32_t index) - { - return fields.info; - } - inline field_packet* get_field(uint32_t index) - { - return &fields.info[index]; - } - inline void set_field(uint32_t index, const char *data) - { - fields.info[index].parse(data); - } - inline void clear_fields() - { - if (fields.length > 0) - { - delete[] fields.info; - } - } - ~result_info() - { - clear_fields(); - } -protected: - struct { - uint32_t length = 0; - field_packet *info = nullptr; - } fields; -}; - -class statement : public server_packet -{ -public: - uint32_t id = 0; - uint16_t field_count = 0; - uint16_t param_count = 0; - uint16_t warning_count = 0; - statement() { } - statement(const char* data) : server_packet(data) - { - swMysqlPacketDump(header.length, header.number, data, "COM_STMT_PREPARE_OK_Packet"); - // skip the packet header - data += SW_MYSQL_PACKET_HEADER_SIZE; - // status (1) -- [00] OK - SW_ASSERT(data[0] == SW_MYSQL_PACKET_OK); - data += 1; - // statement_id (4) -- statement-id - id = sw_mysql_uint2korr4korr(data); - data += 4; - // num_columns (2) -- number of columns - field_count = sw_mysql_uint2korr2korr(data); - data += 2; - // num_params (2) -- number of params - param_count = sw_mysql_uint2korr2korr(data); - data += 2; - // reserved_1 (1) -- [00] filler - data += 1; - // warning_count (2) -- number of warnings - warning_count = sw_mysql_uint2korr2korr(data); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "statement_id=%u, field_count=%u, param_count=%u, warning_count=%u", - id, field_count, param_count, warning_count - ); - } -}; - -class null_bitmap -{ -public: - static uint32_t get_size(uint32_t field_length) - { - return ((field_length + 9) / 8) + 1; - } - null_bitmap(const char *p, uint32_t size) : - size(size) - { - map = new char[size]; - memcpy(map, p, size); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "null_count=%u", size); - } - inline bool is_null(size_t i) - { - return ((map + 1)[((i + 2) / 8)] & (0x01 << ((i + 2) % 8))) != 0; - } - ~null_bitmap() - { - delete[] map; - } -protected: - uint32_t size; - char *map; -}; -//-----------------------------------namespace end-------------------------------------------- -}} diff --git a/ext-src/php_swoole_odbc.h b/ext-src/php_swoole_odbc.h new file mode 100644 index 0000000000..3e09f9e340 --- /dev/null +++ b/ext-src/php_swoole_odbc.h @@ -0,0 +1,127 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_SWOOLE_ODBC_H +#define PHP_SWOOLE_ODBC_H + +#include "php_swoole.h" + +#ifdef SW_USE_ODBC +BEGIN_EXTERN_C() + +#include "ext/pdo/php_pdo_driver.h" + +#if PHP_VERSION_ID < 80300 +#include "thirdparty/php82/pdo_odbc/php_pdo_odbc_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h" +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 +#include "thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h" +#else +#include "thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h" +#endif + +extern const pdo_driver_t swoole_pdo_odbc_driver; + +#include "php_version.h" +#define PHP_PDO_ODBC_VERSION PHP_VERSION + +RETCODE swoole_odbc_SQLConnect(SQLHDBC ConnectionHandle, + SQLCHAR *ServerName, + SQLSMALLINT NameLength1, + SQLCHAR *UserName, + SQLSMALLINT NameLength2, + SQLCHAR *Authentication, + SQLSMALLINT NameLength3); + +SQLRETURN SQL_API swoole_odbc_SQLDriverConnect(SQLHDBC hdbc, + SQLHWND hwnd, + SQLCHAR *szConnStrIn, + SQLSMALLINT cbConnStrIn, + SQLCHAR *szConnStrOut, + SQLSMALLINT cbConnStrOutMax, + SQLSMALLINT *pcbConnStrOut, + SQLUSMALLINT fDriverCompletion); + +SQLRETURN SQL_API swoole_odbc_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength); + +SQLRETURN SQL_API swoole_odbc_SQLGetInfo(SQLHDBC ConnectionHandle, + SQLUSMALLINT InfoType, SQLPOINTER InfoValue, + SQLSMALLINT BufferLength, SQLSMALLINT *StringLength); + +SQLRETURN SQL_API swoole_odbc_SQLGetDiagRec(SQLSMALLINT HandleType, + SQLHANDLE Handle, + SQLSMALLINT RecNumber, + SQLCHAR *Sqlstate, + SQLINTEGER *NativeError, + SQLCHAR *MessageText, + SQLSMALLINT BufferLength, + SQLSMALLINT *TextLength); + +SQLRETURN SQL_API swoole_odbc_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength); + +SQLRETURN SQL_API swoole_odbc_SQLExecute(SQLHSTMT StatementHandle); + +SQLRETURN SQL_API swoole_odbc_SQLCloseCursor(SQLHSTMT StatementHandle); + +SQLRETURN SQL_API swoole_odbc_SQLPutData(SQLHSTMT StatementHandle, SQLPOINTER Data, SQLLEN StrLen_or_Ind); + +SQLRETURN SQL_API swoole_odbc_SQLGetData(SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, + SQLPOINTER TargetValue, SQLLEN BufferLength, + SQLLEN *StrLen_or_Ind); + +SQLRETURN SQL_API swoole_odbc_SQLRowCount(SQLHSTMT StatementHandle, SQLLEN *RowCount); + +SQLRETURN SQL_API swoole_odbc_SQLDescribeCol(SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, SQLCHAR *ColumnName, + SQLSMALLINT BufferLength, SQLSMALLINT *NameLength, + SQLSMALLINT *DataType, SQLULEN *ColumnSize, + SQLSMALLINT *DecimalDigits, SQLSMALLINT *Nullable); + +SQLRETURN SQL_API swoole_odbc_SQLMoreResults( + SQLHSTMT hstmt); + +SQLRETURN SQL_API swoole_odbc_SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT CompletionType); + +SQLRETURN SQL_API swoole_odbc_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); + +SQLRETURN SQL_API swoole_odbc_SQLDisconnect(SQLHDBC ConnectionHandle); + +void swoole_odbc_set_blocking(bool blocking); + +#ifdef SW_USE_ODBC_HOOK + +#define SQLConnect swoole_odbc_SQLConnect +#define SQLDriverConnect swoole_odbc_SQLDriverConnect +#define SQLExecDirect swoole_odbc_SQLExecDirect +#define SQLGetInfo swoole_odbc_SQLGetInfo +#define SQLGetDiagRec swoole_odbc_SQLGetDiagRec +#define SQLPrepare swoole_odbc_SQLPrepare +#define SQLExecute swoole_odbc_SQLExecute +#define SQLCloseCursor swoole_odbc_SQLCloseCursor +#define SQLGetData swoole_odbc_SQLGetData +#define SQLPutData swoole_odbc_SQLPutData +#define SQLRowCount swoole_odbc_SQLRowCount +#define SQLDescribeCol swoole_odbc_SQLDescribeCol +#define SQLEndTran swoole_odbc_SQLEndTran +#define SQLFreeHandle swoole_odbc_SQLFreeHandle +#define SQLDisconnect swoole_odbc_SQLDisconnect + +#endif +END_EXTERN_C() +#endif +#endif diff --git a/ext-src/php_swoole_oracle.h b/ext-src/php_swoole_oracle.h new file mode 100644 index 0000000000..6e013bc669 --- /dev/null +++ b/ext-src/php_swoole_oracle.h @@ -0,0 +1,67 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_SWOOLE_ORACLE_H +#define PHP_SWOOLE_ORACLE_H + +#include "php_swoole.h" + +#ifdef SW_USE_ORACLE + +BEGIN_EXTERN_C() + +#include "ext/pdo/php_pdo_driver.h" + +#include "thirdparty/pdo_oci/php_pdo_oci_int.h" + +extern const pdo_driver_t swoole_pdo_oci_driver; + +void swoole_oracle_set_blocking(bool blocking); +sword swoole_oci_session_begin(OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 credt, ub4 mode); +sword swoole_oci_server_detach(OCIServer *srvhp, OCIError *errhp, ub4 mode); +sword swoole_oci_stmt_prepare( + OCIStmt *stmtp, OCIError *errhp, const OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode); +sword swoole_oci_stmt_execute(OCISvcCtx *svchp, + OCIStmt *stmtp, + OCIError *errhp, + ub4 iters, + ub4 rowoff, + const OCISnapshot *snap_in, + OCISnapshot *snap_out, + ub4 mode); +sword swoole_oci_stmt_fetch(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, ub4 mode); +sword swoole_oci_stmt_fetch2(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, sb4 scrollOffset, ub4 mode); +sword swoole_oci_trans_commit(OCISvcCtx *svchp, OCIError *errhp, ub4 flags); +sword swoole_oci_trans_rollback(OCISvcCtx *svchp, OCIError *errhp, ub4 flags); +sword swoole_oci_ping(OCISvcCtx *svchp, OCIError *errhp, ub4 mode); + +#ifdef SW_USE_ORACLE_HOOK +#define OCISessionBegin swoole_oci_session_begin +#define OCIServerDetach swoole_oci_server_detach +#define OCIStmtPrepare swoole_oci_stmt_prepare +#define OCIStmtExecute swoole_oci_stmt_execute +#define OCIStmtFetch swoole_oci_stmt_fetch +#define OCIStmtFetch2 swoole_oci_stmt_fetch2 +#define OCITransCommit swoole_oci_trans_commit +#define OCITransRollback swoole_oci_trans_rollback +#define OCIPing swoole_oci_ping +#endif + +END_EXTERN_C() +#endif +#endif diff --git a/ext-src/php_swoole_pgsql.h b/ext-src/php_swoole_pgsql.h new file mode 100644 index 0000000000..a5fa8e8fca --- /dev/null +++ b/ext-src/php_swoole_pgsql.h @@ -0,0 +1,65 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_SWOOLE_PGSQL_H +#define PHP_SWOOLE_PGSQL_H + +#include "php_swoole.h" + +#ifdef SW_USE_PGSQL + +BEGIN_EXTERN_C() + +#include "ext/pdo/php_pdo_driver.h" + +#if PHP_VERSION_ID < 80300 +#include "thirdparty/php82/pdo_pgsql/php_pdo_pgsql_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h" +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 +#include "thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h" +#else +#include "thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h" +#endif + + +extern const pdo_driver_t swoole_pdo_pgsql_driver; + +#include +#include + +void swoole_pgsql_set_blocking(bool blocking); + +PGconn *swoole_pgsql_connectdb(const char *conninfo); +PGresult *swoole_pgsql_prepare(PGconn *conn, const char *stmt_name, const char *query, int n_params, const Oid *param_types); +PGresult *swoole_pgsql_exec_prepared(PGconn *conn, const char *stmt_name, int n_params, + const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format); +PGresult *swoole_pgsql_exec(PGconn *conn, const char *query); +PGresult *swoole_pgsql_exec_params(PGconn *conn, const char *command, int n_params, + const Oid *param_types, const char *const *param_values, const int *param_lengths, const int *param_formats, int result_format); + +#ifdef SW_USE_PGSQL_HOOK +#define PQconnectdb swoole_pgsql_connectdb +#define PQprepare swoole_pgsql_prepare +#define PQexecPrepared swoole_pgsql_exec_prepared +#define PQexec swoole_pgsql_exec +#define PQexecParams swoole_pgsql_exec_params +#endif + +END_EXTERN_C() + +#endif +#endif diff --git a/ext-src/php_swoole_private.h b/ext-src/php_swoole_private.h index 2bbbed7893..5b5f1d23c2 100644 --- a/ext-src/php_swoole_private.h +++ b/ext-src/php_swoole_private.h @@ -17,18 +17,11 @@ #ifndef PHP_SWOOLE_PRIVATE_H #define PHP_SWOOLE_PRIVATE_H -// C++ build format macros must defined earlier -#ifdef __cplusplus -#define __STDC_FORMAT_MACROS -#endif - #include "php_swoole.h" - -#define SW_HAVE_COUNTABLE 1 - -#include "swoole_c_api.h" +#include "php_swoole_api.h" +#include "swoole.h" #include "swoole_api.h" -#include "swoole_async.h" +#include "swoole_coroutine_api.h" #ifdef SW_HAVE_ZLIB #include @@ -44,9 +37,6 @@ BEGIN_EXTERN_C() #include #define PHP_SWOOLE_VERSION SWOOLE_VERSION -#define PHP_SWOOLE_CLIENT_USE_POLL - -extern PHPAPI int php_array_merge(zend_array *dest, zend_array *src); #ifdef PHP_WIN32 #define PHP_SWOOLE_API __declspec(dllexport) @@ -62,6 +52,7 @@ extern PHPAPI int php_array_merge(zend_array *dest, zend_array *src); } else { \ RETURN_TRUE; \ } + #define SW_LOCK_CHECK_RETURN(s) \ zend_long ___tmp_return_value = s; \ if (___tmp_return_value == 0) { \ @@ -71,14 +62,43 @@ extern PHPAPI int php_array_merge(zend_array *dest, zend_array *src); RETURN_FALSE; \ } +#ifdef SW_THREAD +#define SW_MUST_BE_MAIN_THREAD_EX(op) \ + if (!tsrm_is_main_thread()) { \ + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); \ + op; \ + } +#define SW_MUST_BE_MAIN_THREAD() SW_MUST_BE_MAIN_THREAD_EX(RETURN_TRUE) +#else +#define SW_MUST_BE_MAIN_THREAD_EX(op) +#define SW_MUST_BE_MAIN_THREAD() +#endif + #define php_swoole_fatal_error(level, fmt_str, ...) \ + swoole_set_last_error(SW_ERROR_PHP_FATAL_ERROR); \ php_error_docref(NULL, level, (const char *) (fmt_str), ##__VA_ARGS__) +/** + * The error occurred at the PHP layer and no error code was set + */ #define php_swoole_error(level, fmt_str, ...) \ - if (SWOOLE_G(display_errors) || level == E_ERROR) php_swoole_fatal_error(level, fmt_str, ##__VA_ARGS__) + swoole_set_last_error(SW_ERROR_PHP_RUNTIME_NOTICE); \ + if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) + +/** + * The error occurred in the core must have error code + */ +#define php_swoole_core_error(level, fmt_str, ...) \ + if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) + +#define php_swoole_error_ex(level, err_code, fmt_str, ...) \ + swoole_set_last_error(err_code); \ + if (SWOOLE_G(display_errors) || level == E_ERROR) php_error_docref(NULL, level, fmt_str, ##__VA_ARGS__) #define php_swoole_sys_error(level, fmt_str, ...) \ - php_swoole_error(level, fmt_str ", Error: %s[%d]", ##__VA_ARGS__, strerror(errno), errno) + swoole_set_last_error(errno); \ + if (SWOOLE_G(display_errors) || level == E_ERROR) \ + php_error_docref(NULL, level, fmt_str ", Error: %s[%d]", ##__VA_ARGS__, strerror(errno), errno) #ifdef SW_USE_CARES #ifndef HAVE_CARES @@ -86,38 +106,37 @@ extern PHPAPI int php_array_merge(zend_array *dest, zend_array *src); #endif #endif +#if defined(SW_HAVE_ZLIB) || defined(SW_HAVE_BROTLI) || defined(SW_HAVE_ZSTD) +#define SW_HAVE_COMPRESSION +#endif + #ifdef SW_SOCKETS #include "ext/sockets/php_sockets.h" #define SWOOLE_SOCKETS_SUPPORT #endif -#if PHP_VERSION_ID < 80000 -#error "require PHP version 8.0 or later" +#if PHP_VERSION_ID < 80200 +#error "require PHP version 8.2 or later" +#elif PHP_VERSION_ID >= 80600 +#error "require PHP version 8.5 or earlier" #endif #if defined(ZTS) && defined(SW_USE_THREAD_CONTEXT) #error "thread context cannot be used with ZTS" #endif +#if defined(SW_USE_IOURING) && !defined(__linux__) +#error "only linux support iouring" +#endif + +#if defined(SW_THREAD) && !defined(ZTS) +#error "swoole thread must be used with ZTS" +#endif + //-------------------------------------------------------- #define SW_MAX_FIND_COUNT 100 // for swoole_server::connection_list #define SW_PHP_CLIENT_BUFFER_SIZE 65535 -//-------------------------------------------------------- -enum php_swoole_client_callback_type { - SW_CLIENT_CB_onConnect = 1, - SW_CLIENT_CB_onReceive, - SW_CLIENT_CB_onClose, - SW_CLIENT_CB_onError, - SW_CLIENT_CB_onBufferFull, - SW_CLIENT_CB_onBufferEmpty, -#ifdef SW_USE_OPENSSL - SW_CLIENT_CB_onSSLReady, -#endif -}; -//--------------------------------------------------------- -#define SW_FLAG_KEEP (1u << 12) -#define SW_FLAG_ASYNC (1u << 10) -#define SW_FLAG_SYNC (1u << 11) +#define SW_ASYNC_FILE_PROTOCOL "async.file" //--------------------------------------------------------- enum php_swoole_fd_type { PHP_SWOOLE_FD_STREAM_CLIENT = SW_FD_STREAM_CLIENT, @@ -149,14 +168,11 @@ enum php_swoole_hook_type { }; //--------------------------------------------------------- -static sw_inline enum swSocketType php_swoole_socktype(long type) { - return (enum swSocketType)(type & (~SW_FLAG_SYNC) & (~SW_FLAG_ASYNC) & (~SW_FLAG_KEEP) & (~SW_SOCK_SSL)); -} - extern zend_class_entry *swoole_event_ce; extern zend_class_entry *swoole_timer_ce; extern zend_class_entry *swoole_socket_coro_ce; extern zend_class_entry *swoole_client_ce; +extern zend_object_handlers swoole_client_handlers; extern zend_class_entry *swoole_server_ce; extern zend_object_handlers swoole_server_handlers; extern zend_class_entry *swoole_redis_server_ce; @@ -196,6 +212,12 @@ PHP_FUNCTION(swoole_client_select); PHP_FUNCTION(swoole_async_set); PHP_FUNCTION(swoole_async_dns_lookup_coro); //--------------------------------------------------------- +// tracer +//--------------------------------------------------------- +PHP_FUNCTION(swoole_tracer_leak_detect); +PHP_FUNCTION(swoole_tracer_prof_begin); +PHP_FUNCTION(swoole_tracer_prof_end); +//--------------------------------------------------------- // error //--------------------------------------------------------- #define SW_STRERROR_SYSTEM 0 @@ -220,29 +242,55 @@ void php_swoole_timer_minit(int module_number); void php_swoole_coroutine_minit(int module_number); void php_swoole_coroutine_system_minit(int module_number); void php_swoole_coroutine_scheduler_minit(int module_number); +void php_swoole_coroutine_lock_minit(int module_number); void php_swoole_channel_coro_minit(int module_number); void php_swoole_runtime_minit(int module_number); // client void php_swoole_socket_coro_minit(int module_number); void php_swoole_client_minit(int module_number); +void php_swoole_client_async_minit(int module_number); void php_swoole_client_coro_minit(int module_number); void php_swoole_http_client_coro_minit(int module_number); void php_swoole_http2_client_coro_minit(int module_number); -void php_swoole_mysql_coro_minit(int module_number); -void php_swoole_redis_coro_minit(int module_number); #ifdef SW_USE_PGSQL -void php_swoole_postgresql_coro_minit(int module_number); +void php_swoole_pgsql_minit(int module_number); +#endif +#ifdef SW_USE_ODBC +int php_swoole_odbc_minit(int module_id); +#endif +#ifdef SW_USE_ORACLE +void php_swoole_oracle_minit(int module_number); +#endif +#ifdef SW_USE_SQLITE +void php_swoole_sqlite_minit(int module_number); +#endif +#ifdef SW_USE_FIREBIRD +void php_swoole_firebird_minit(int module_number); #endif // server void php_swoole_server_minit(int module_number); void php_swoole_server_port_minit(int module_number); void php_swoole_http_request_minit(int module_number); void php_swoole_http_response_minit(int module_number); +void php_swoole_http_cookie_minit(int module_number); void php_swoole_http_server_minit(int module_number); void php_swoole_http_server_coro_minit(int module_number); void php_swoole_websocket_server_minit(int module_number); void php_swoole_redis_server_minit(int module_number); void php_swoole_name_resolver_minit(int module_number); +#ifdef SW_THREAD +void php_swoole_thread_minit(int module_number); +void php_swoole_thread_atomic_minit(int module_number); +void php_swoole_thread_lock_minit(int module_number); +void php_swoole_thread_barrier_minit(int module_number); +void php_swoole_thread_queue_minit(int module_number); +void php_swoole_thread_map_minit(int module_number); +void php_swoole_thread_arraylist_minit(int module_number); +#endif +#ifdef SW_STDEXT +void php_swoole_stdext_minit(int module_number); +#endif +void php_swoole_tracer_minit(int module_number); /** * RINIT @@ -251,22 +299,35 @@ void php_swoole_name_resolver_minit(int module_number); void php_swoole_http_server_rinit(); void php_swoole_coroutine_rinit(); void php_swoole_runtime_rinit(); +#ifdef SW_USE_ORACLE +void php_swoole_oracle_rinit(); +#endif +void php_swoole_thread_rinit(); +void php_swoole_tracer_rinit(); /** * RSHUTDOWN * ============================================================== */ +void php_swoole_http_server_rshutdown(); +void php_swoole_http_response_rshutdown(); void php_swoole_async_coro_rshutdown(); void php_swoole_redis_server_rshutdown(); void php_swoole_coroutine_rshutdown(); +void php_swoole_process_rshutdown(); void php_swoole_coroutine_scheduler_rshutdown(); void php_swoole_runtime_rshutdown(); +void php_swoole_timer_rshutdown(); void php_swoole_server_rshutdown(); +#ifdef SW_THREAD +void php_swoole_thread_rshutdown(); +#endif +void php_swoole_tracer_rshutdown(); int php_swoole_reactor_init(); void php_swoole_set_global_option(zend_array *vht); void php_swoole_set_coroutine_option(zend_array *vht); -void php_swoole_set_aio_option(zend_array *vht); +void php_swoole_set_aio_option(const zend_array *vht); // shutdown void php_swoole_register_shutdown_function(const char *function); @@ -282,17 +343,18 @@ void php_swoole_event_exit(); * ============================================================== */ void php_swoole_runtime_mshutdown(); -void php_swoole_websocket_server_mshutdown(); - -static sw_inline zend_bool php_swoole_websocket_frame_is_object(zval *zdata) { - return Z_TYPE_P(zdata) == IS_OBJECT && instanceof_function(Z_OBJCE_P(zdata), swoole_websocket_frame_ce); -} - -static sw_inline size_t php_swoole_get_send_data(zval *zdata, char **str) { - convert_to_string(zdata); - *str = Z_STRVAL_P(zdata); - return Z_STRLEN_P(zdata); -} +#ifdef SW_USE_PGSQL +void php_swoole_pgsql_mshutdown(); +#endif +#ifdef SW_USE_ORACLE +void php_swoole_oracle_mshutdown(); +#endif +#ifdef SW_USE_SQLITE +void php_swoole_sqlite_mshutdown(); +#endif +#ifdef SW_USE_FIREBIRD +void php_swoole_firebird_mshutdown(); +#endif int php_swoole_convert_to_fd(zval *zsocket); int php_swoole_convert_to_fd_ex(zval *zsocket, int *async); @@ -301,27 +363,28 @@ int php_swoole_convert_to_fd_ex(zval *zsocket, int *async); php_socket *php_swoole_convert_to_socket(int sock); #endif -zend_bool php_swoole_signal_isset_handler(int signo); - -#if PHP_VERSION_ID < 80200 -# define zend_atomic_bool zend_bool -# define zend_atomic_bool_store(atomic, desired) (*atomic = desired) +#ifdef HAVE_CPU_AFFINITY +bool php_swoole_array_to_cpu_set(const zval *array, cpu_set_t *cpu_set); +/** + * Converts a cpu_set_t structure to a PHP array. + * + * Note: On Cygwin platform, CPU_ISSET is a function that takes a non-const pointer as its second parameter, + * which is why the cpu_set parameter cannot be declared as const. + * + * @param array The PHP array to store the CPU set information + * @param cpu_set The CPU set structure to convert + */ +void php_swoole_cpu_set_to_array(zval *array, cpu_set_t *cpu_set); #endif +zend_bool php_swoole_signal_isset_handler(int signo); + #define sw_zend7_object zend_object -#define SW_Z7_OBJ_P(object) object #define SW_Z8_OBJ_P(zobj) Z_OBJ_P(zobj) typedef ssize_t php_stream_size_t; - -#define ZEND_ERROR_CB_LAST_ARG_D zend_string *message -#define ZEND_ERROR_CB_LAST_ARG_RELAY message - -#if PHP_VERSION_ID < 80100 -typedef const char error_filename_t; -#else typedef zend_string error_filename_t; -#endif + //----------------------------------Zval API------------------------------------ // Deprecated: do not use it anymore @@ -335,56 +398,52 @@ typedef zend_string error_filename_t; #define SW_ZVAL_SOCKET(return_value, result) ZVAL_OBJ(return_value, &result->std) #define SW_Z_SOCKET_P(zsocket) Z_SOCKET_P(zsocket) -#ifndef ZVAL_IS_BOOL -static sw_inline zend_bool ZVAL_IS_BOOL(zval *v) { - return Z_TYPE_P(v) == IS_TRUE || Z_TYPE_P(v) == IS_FALSE; -} -#endif - -#ifndef ZVAL_IS_TRUE -static sw_inline zend_bool ZVAL_IS_TRUE(zval *v) { +static sw_inline zend_bool ZVAL_IS_TRUE(const zval *v) { return Z_TYPE_P(v) == IS_TRUE; } -#endif -#ifndef ZVAL_IS_FALSE -static sw_inline zend_bool ZVAL_IS_FALSE(zval *v) { +static sw_inline zend_bool ZVAL_IS_FALSE(const zval *v) { return Z_TYPE_P(v) == IS_FALSE; } -#endif -#ifndef ZVAL_IS_LONG -static sw_inline zend_bool ZVAL_IS_LONG(zval *v) { +static sw_inline zend_bool ZVAL_IS_BOOL(const zval *v) { + return ZVAL_IS_TRUE(v) || ZVAL_IS_FALSE(v); +} + +static sw_inline zend_bool ZVAL_IS_UNDEF(const zval *v) { + return Z_TYPE_P(v) == IS_UNDEF; +} + +static sw_inline zend_bool ZVAL_IS_LONG(const zval *v) { return Z_TYPE_P(v) == IS_LONG; } -#endif -#ifndef ZVAL_IS_STRING -static sw_inline zend_bool ZVAL_IS_STRING(zval *v) { +static sw_inline zend_bool ZVAL_IS_STRING(const zval *v) { return Z_TYPE_P(v) == IS_STRING; } -#endif -#ifndef Z_BVAL_P -static sw_inline zend_bool Z_BVAL_P(zval *v) { +static sw_inline zend_bool ZVAL_IS_EMPTY_STRING(const zval *v) { + return Z_TYPE_P(v) == IS_STRING && Z_STRLEN_P(v) == 0; +} + +static sw_inline zend_bool Z_BVAL_P(const zval *v) { return Z_TYPE_P(v) == IS_TRUE; } -#endif -#ifndef ZVAL_IS_ARRAY -static sw_inline zend_bool ZVAL_IS_ARRAY(zval *v) { +static sw_inline zend_bool ZVAL_IS_ARRAY(const zval *v) { return Z_TYPE_P(v) == IS_ARRAY; } -#endif -#ifndef ZVAL_IS_OBJECT -static sw_inline zend_bool ZVAL_IS_OBJECT(zval *v) { +static sw_inline zend_bool ZVAL_IS_REF(const zval *v) { + return Z_TYPE_P(v) == IS_REFERENCE; +} + +static sw_inline zend_bool ZVAL_IS_OBJECT(const zval *v) { return Z_TYPE_P(v) == IS_OBJECT; } -#endif static sw_inline zval *sw_malloc_zval() { - return (zval *) emalloc(sizeof(zval)); + return static_cast(emalloc(sizeof(zval))); } static sw_inline zval *sw_zval_dup(zval *val) { @@ -398,6 +457,53 @@ static sw_inline void sw_zval_free(zval *val) { efree(val); } +#ifdef SWOOLE_SOCKETS_SUPPORT +static inline bool sw_zval_is_php_socket(zval *val) { + return instanceof_function(Z_OBJCE_P(val), socket_ce); +} +#endif + +static inline bool sw_zval_is_co_socket(zval *val) { + return instanceof_function(Z_OBJCE_P(val), swoole_socket_coro_ce); +} + +static inline bool sw_zval_is_client(zval *val) { + return instanceof_function(Z_OBJCE_P(val), swoole_client_ce); +} + +static inline bool sw_zval_is_process(zval *val) { + return instanceof_function(Z_OBJCE_P(val), swoole_process_ce); +} + +bool sw_zval_is_serializable(const zval *struc); + +static inline bool sw_is_main_thread() { +#ifdef SW_THREAD + return tsrm_is_main_thread(); +#else + return true; +#endif +} + +int sw_module_number(); + +#ifdef SW_THREAD +size_t sw_active_thread_count(void); +#else +static inline size_t sw_active_thread_count() { + return 1; +} +#endif + +zend_refcounted *sw_get_refcount_ptr(zval *value); + +void sw_php_exit(int status); +void sw_php_print_backtrace(zend_long cid = 0, + zend_long options = 0, + zend_long limit = 0, + zval *return_value = nullptr); +void sw_php_print_backtrace_impl(int skip_last = 1, int options = 0, int limit = 0, bool include_main = false); + //----------------------------------Constant API------------------------------------ #define SW_REGISTER_NULL_CONSTANT(name) REGISTER_NULL_CONSTANT(name, CONST_CS | CONST_PERSISTENT) @@ -429,7 +535,7 @@ static sw_inline zend_string *sw_zend_string_recycle(zend_string *s, size_t allo SW_ASSERT(!ZSTR_IS_INTERNED(s)); if (UNEXPECTED(alloc_len != real_len)) { if (alloc_len > swoole_pagesize() && alloc_len > real_len * 2) { - s = zend_string_realloc(s, real_len, 0); + s = zend_string_realloc(s, real_len, false); } else { ZSTR_LEN(s) = real_len; } @@ -537,13 +643,7 @@ static sw_inline void add_assoc_ulong_safe(zval *arg, const char *key, zend_ulon } \ } while (0) -#if PHP_VERSION_ID < 80100 -#define SW_SET_CLASS_NOT_SERIALIZABLE(module) \ - module##_ce->serialize = zend_class_serialize_deny; \ - module##_ce->unserialize = zend_class_unserialize_deny; -#else #define SW_SET_CLASS_NOT_SERIALIZABLE(module) module##_ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; -#endif #define sw_zend_class_clone_deny NULL #define SW_SET_CLASS_CLONEABLE(module, _clone_obj) module##_handlers.clone_obj = _clone_obj @@ -571,18 +671,20 @@ static sw_inline void add_assoc_ulong_safe(zval *arg, const char *key, zend_ulon } \ } while (0) -#define SW_FUNCTION_ALIAS(origin_function_table, origin, alias_function_table, alias) \ - sw_zend_register_function_alias(origin_function_table, ZEND_STRL(origin), alias_function_table, ZEND_STRL(alias)) +#define SW_FUNCTION_ALIAS(origin_function_table, origin, alias_function_table, alias, arg_info) \ + sw_zend_register_function_alias( \ + origin_function_table, ZEND_STRL(origin), alias_function_table, ZEND_STRL(alias), arg_info) static sw_inline int sw_zend_register_function_alias(zend_array *origin_function_table, const char *origin, size_t origin_length, zend_array *alias_function_table, const char *alias, - size_t alias_length) { - zend_string *lowercase_origin = zend_string_alloc(origin_length, 0); + size_t alias_length, + const zend_internal_arg_info *arg_info) { + zend_string *lowercase_origin = zend_string_alloc(origin_length, false); zend_str_tolower_copy(ZSTR_VAL(lowercase_origin), origin, origin_length); - zend_function *origin_function = (zend_function *) zend_hash_find_ptr(origin_function_table, lowercase_origin); + auto *origin_function = static_cast(zend_hash_find_ptr(origin_function_table, lowercase_origin)); zend_string_release(lowercase_origin); if (UNEXPECTED(!origin_function)) { return FAILURE; @@ -590,12 +692,10 @@ static sw_inline int sw_zend_register_function_alias(zend_array *origin_function SW_ASSERT(origin_function->common.type == ZEND_INTERNAL_FUNCTION); char *_alias = (char *) emalloc(alias_length + 1); ((char *) memcpy(_alias, alias, alias_length))[alias_length] = '\0'; - zend_function_entry zfe[] = {{_alias, - origin_function->internal_function.handler, - ((zend_internal_arg_info *) origin_function->common.arg_info) - 1, - origin_function->common.num_args, - 0}, - PHP_FE_END}; + + zend_function_entry zfe[] = { + {_alias, origin_function->internal_function.handler, arg_info, origin_function->common.num_args, 0}, + PHP_FE_END}; int ret = zend_register_functions(nullptr, zfe, alias_function_table, origin_function->common.type); efree(_alias); return ret; @@ -604,20 +704,20 @@ static sw_inline int sw_zend_register_function_alias(zend_array *origin_function static sw_inline int sw_zend_register_class_alias(const char *name, size_t name_len, zend_class_entry *ce) { zend_string *_name; if (name[0] == '\\') { - _name = zend_string_init(name, name_len, 1); + _name = zend_string_init(name, name_len, true); zend_str_tolower_copy(ZSTR_VAL(_name), name + 1, name_len - 1); } else { - _name = zend_string_init(name, name_len, 1); + _name = zend_string_init(name, name_len, true); zend_str_tolower_copy(ZSTR_VAL(_name), name, name_len); } zend_string *_interned_name = zend_new_interned_string(_name); - return zend_register_class_alias_ex(ZSTR_VAL(_interned_name), ZSTR_LEN(_interned_name), ce, 1); + return zend_register_class_alias_ex(ZSTR_VAL(_interned_name), ZSTR_LEN(_interned_name), ce, true); } static sw_inline zend_object *sw_zend_create_object(zend_class_entry *ce, zend_object_handlers *handlers) { - zend_object *object = (zend_object *) zend_object_alloc(sizeof(zend_object), ce); + auto *object = static_cast(zend_object_alloc(sizeof(zend_object), ce)); zend_object_std_init(object, ce); object_properties_init(object, ce); object->handlers = handlers; @@ -625,8 +725,7 @@ static sw_inline zend_object *sw_zend_create_object(zend_class_entry *ce, zend_o } static sw_inline zend_object *sw_zend_create_object_deny(zend_class_entry *ce) { - zend_object *object; - object = zend_objects_new(ce); + zend_object *object = zend_objects_new(ce); /* Initialize default properties */ if (EXPECTED(ce->default_properties_count != 0)) { zval *p = object->properties_table; @@ -636,7 +735,7 @@ static sw_inline zend_object *sw_zend_create_object_deny(zend_class_entry *ce) { p++; } while (p != end); } - zend_throw_error(NULL, "The object of %s can not be created for security reasons", ZSTR_VAL(ce->name)); + zend_throw_error(nullptr, "The object of %s can not be created for security reasons", ZSTR_VAL(ce->name)); return object; } @@ -647,13 +746,14 @@ static sw_inline void sw_zend_class_unset_property_deny(zend_object *object, zen } SW_ASSERT(ce->type == ZEND_INTERNAL_CLASS); if (EXPECTED(zend_hash_find(&ce->properties_info, member))) { - zend_throw_error(NULL, "Property %s of class %s cannot be unset", ZSTR_VAL(member), ZSTR_VAL(object->ce->name)); + zend_throw_error( + nullptr, "Property %s of class %s cannot be unset", ZSTR_VAL(member), ZSTR_VAL(object->ce->name)); return; } std_object_handlers.unset_property(object, member, cache_slot); } -static sw_inline zval *sw_zend_read_property(zend_class_entry *ce, zval *obj, const char *s, int len, int silent) { +static sw_inline zval *sw_zend_read_property(zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); if (UNEXPECTED(property == &EG(uninitialized_zval))) { zend_update_property_null(ce, SW_Z8_OBJ_P(obj), s, len); @@ -669,38 +769,42 @@ 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); + auto *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; } static sw_inline zval *sw_zend_read_property_not_null( - zend_class_entry *ce, zval *obj, const char *s, int len, int silent) { + zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); zend_uchar type = Z_TYPE_P(property); - return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? NULL : property; + return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? nullptr : property; } static sw_inline zval *sw_zend_read_property_not_null_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); zend_uchar type = Z_TYPE_P(property); - return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? NULL : property; + return (type == IS_NULL || UNEXPECTED(type == IS_UNDEF)) ? nullptr : property; } -static sw_inline zval *sw_zend_update_and_read_property_array(zend_class_entry *ce, zval *obj, const char *s, int len) { +static sw_inline zval *sw_zend_update_and_read_property_array(zend_class_entry *ce, + zval *obj, + const char *s, + size_t len) { zval ztmp; array_init(&ztmp); zend_update_property(ce, SW_Z8_OBJ_P(obj), s, len, &ztmp); zval_ptr_dtor(&ztmp); - return zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, 1, &ztmp); + return zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, true, &ztmp); } static sw_inline zval *sw_zend_read_and_convert_property_array( - zend_class_entry *ce, zval *obj, const char *s, int len, int silent) { + zend_class_entry *ce, zval *obj, const char *s, size_t len, int silent) { zval rv, *property = zend_read_property(ce, SW_Z8_OBJ_P(obj), s, len, silent, &rv); if (Z_TYPE_P(property) != IS_ARRAY) { // NOTICE: if user unset the property, zend_read_property will return uninitialized_zval instead of NULL pointer @@ -762,9 +866,9 @@ static sw_inline zend_bool sw_zend_is_callable_at_frame(zval *zcallable, size_t *callable_name_len, zend_fcall_info_cache *fci_cache, char **error) { - zend_bool ret = - zend_is_callable_at_frame(zcallable, zobject ? Z_OBJ_P(zobject) : NULL, frame, check_flags, fci_cache, error); - zend_string *name = zend_get_callable_name_ex(zcallable, zobject ? Z_OBJ_P(zobject) : NULL); + zend_bool ret = zend_is_callable_at_frame( + zcallable, zobject ? Z_OBJ_P(zobject) : nullptr, frame, check_flags, fci_cache, error); + zend_string *name = zend_get_callable_name_ex(zcallable, zobject ? Z_OBJ_P(zobject) : nullptr); if (callable_name) { *callable_name = estrndup(ZSTR_VAL(name), ZSTR_LEN(name)); } @@ -783,7 +887,7 @@ static sw_inline zend_bool sw_zend_is_callable_ex(zval *zcallable, zend_fcall_info_cache *fci_cache, char **error) { return sw_zend_is_callable_at_frame( - zcallable, zobject, NULL, check_flags, callable_name, callable_name_len, fci_cache, error); + zcallable, zobject, nullptr, check_flags, callable_name, callable_name_len, fci_cache, error); } /* this API can work well when retval is NULL */ @@ -791,10 +895,9 @@ static sw_inline int sw_zend_call_function_ex( zval *function_name, zend_fcall_info_cache *fci_cache, uint32_t param_count, zval *params, zval *retval) { zend_fcall_info fci; zval _retval; - int ret; fci.size = sizeof(fci); - fci.object = NULL; + fci.object = nullptr; if (!fci_cache || !fci_cache->function_handler) { if (!function_name) { php_swoole_fatal_error(E_WARNING, "Bad function"); @@ -807,10 +910,9 @@ static sw_inline int sw_zend_call_function_ex( fci.retval = retval ? retval : &_retval; fci.param_count = param_count; fci.params = params; - fci.named_params = NULL; - - ret = zend_call_function(&fci, fci_cache); + fci.named_params = nullptr; + int ret = zend_call_function(&fci, fci_cache); if (!retval) { zval_ptr_dtor(&_retval); } @@ -829,10 +931,6 @@ static sw_inline int sw_zend_call_function_ex2( static sw_inline int sw_zend_call_function_anyway(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) { zval retval; - zend_object *exception = EG(exception); - if (exception) { - EG(exception) = NULL; - } if (!fci->retval) { fci->retval = &retval; } @@ -840,17 +938,13 @@ static sw_inline int sw_zend_call_function_anyway(zend_fcall_info *fci, zend_fca if (fci->retval == &retval) { zval_ptr_dtor(&retval); } - if (exception) { - EG(exception) = exception; - } return ret; } static sw_inline void sw_zend_fci_params_persist(zend_fcall_info *fci) { if (fci->param_count > 0) { - uint32_t i; zval *params = (zval *) ecalloc(fci->param_count, sizeof(zval)); - for (i = 0; i < fci->param_count; i++) { + for (uint32_t i = 0; i < fci->param_count; i++) { ZVAL_COPY(¶ms[i], &fci->params[i]); } fci->params = params; @@ -859,8 +953,7 @@ static sw_inline void sw_zend_fci_params_persist(zend_fcall_info *fci) { static sw_inline void sw_zend_fci_params_discard(zend_fcall_info *fci) { if (fci->param_count > 0) { - uint32_t i; - for (i = 0; i < fci->param_count; i++) { + for (uint32_t i = 0; i < fci->param_count; i++) { zval_ptr_dtor(&fci->params[i]); } efree(fci->params); @@ -885,17 +978,7 @@ static sw_inline void sw_zend_fci_cache_discard(zend_fcall_info_cache *fci_cache } } -/* use void* to match some C callback function pointers */ -static sw_inline void sw_zend_fci_cache_free(void *fci_cache) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) fci_cache); - efree((zend_fcall_info_cache *) fci_cache); -} - -#if PHP_VERSION_ID >= 80100 #define sw_php_spl_object_hash(o) php_spl_object_hash(Z_OBJ_P(o)) -#else -#define sw_php_spl_object_hash(o) php_spl_object_hash(o) -#endif //----------------------------------Misc API------------------------------------ @@ -910,14 +993,7 @@ 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 char *php_swoole_url_encode(const char *value, size_t value_len, int *exten) { +static sw_inline char *php_swoole_url_encode(const char *value, size_t value_len, size_t *exten) { zend_string *str = php_url_encode(value, value_len); *exten = ZSTR_LEN(str); char *return_str = estrndup(ZSTR_VAL(str), ZSTR_LEN(str)); @@ -927,15 +1003,20 @@ static sw_inline char *php_swoole_url_encode(const char *value, size_t value_len static sw_inline char *php_swoole_http_build_query(zval *zdata, size_t *length, smart_str *formstr) { if (HASH_OF(zdata)) { - php_url_encode_hash_ex(HASH_OF(zdata), formstr, NULL, 0, NULL, 0, NULL, 0, NULL, NULL, (int) PHP_QUERY_RFC1738); +#if PHP_VERSION_ID < 80300 + php_url_encode_hash_ex( + HASH_OF(zdata), formstr, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, nullptr, (int) PHP_QUERY_RFC1738); +#else + php_url_encode_hash_ex(HASH_OF(zdata), formstr, NULL, 0, NULL, NULL, NULL, (int) PHP_QUERY_RFC1738); +#endif } else { if (formstr->s) { smart_str_free(formstr); } - return NULL; + return nullptr; } if (!formstr->s) { - return NULL; + return nullptr; } smart_str_0(formstr); *length = formstr->s->len; @@ -947,11 +1028,7 @@ static inline const char *php_swoole_get_last_error_message() { } static inline const char *php_swoole_get_last_error_file() { -#if PHP_VERSION_ID >= 80100 return PG(last_error_file) ? PG(last_error_file)->val : "-"; -#else - return PG(last_error_file) ? PG(last_error_file) : "-"; -#endif } END_EXTERN_C() diff --git a/ext-src/php_swoole_process.h b/ext-src/php_swoole_process.h index 0dd30a4129..65005e0bbc 100644 --- a/ext-src/php_swoole_process.h +++ b/ext-src/php_swoole_process.h @@ -20,7 +20,15 @@ #include "php_swoole_cxx.h" #include "swoole_process_pool.h" +enum PipeType { + PIPE_TYPE_NONE = 0, + PIPE_TYPE_STREAM = 1, + PIPE_TYPE_DGRAM = 2, +}; + void php_swoole_process_clean(); int php_swoole_process_start(swoole::Worker *process, zval *zobject); -swoole::Worker *php_swoole_process_get_worker(zval *zobject); -void php_swoole_process_set_worker(zval *zobject, swoole::Worker *worker); +swoole::Worker *php_swoole_process_get_worker(const zval *zobject); +void php_swoole_process_set_worker(const zval *zobject, swoole::Worker *worker, bool enable_coroutine, int pipe_type); + +swoole::ProcessPool *sw_process_pool(); diff --git a/ext-src/php_swoole_server.h b/ext-src/php_swoole_server.h index d75fbbd144..e47cab9ffd 100644 --- a/ext-src/php_swoole_server.h +++ b/ext-src/php_swoole_server.h @@ -49,8 +49,8 @@ enum php_swoole_server_port_callback_type { SW_SERVER_CB_onClose, // stream, worker(event) SW_SERVER_CB_onPacket, // dgram, worker(event) SW_SERVER_CB_onRequest, // http, worker(event) - SW_SERVER_CB_onHandShake, // websocket, worker(event) - SW_SERVER_CB_onBeforeHandShakeResponse, // websocket, worker(event) + SW_SERVER_CB_onHandshake, // websocket, worker(event) + SW_SERVER_CB_onBeforeHandshakeResponse, // websocket, worker(event) SW_SERVER_CB_onOpen, // websocket, worker(event) SW_SERVER_CB_onMessage, // websocket, worker(event) SW_SERVER_CB_onDisconnect, // websocket (non websocket connection), worker(event) @@ -62,12 +62,18 @@ enum php_swoole_server_port_callback_type { #define PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM (SW_SERVER_CB_onBufferEmpty + 1) namespace swoole { +struct ServerPortProperty; struct TaskCo; +}; // namespace swoole + +zval *php_swoole_server_zval_ptr(swoole::Server *serv); +swoole::ServerPortProperty *php_swoole_server_get_port_property(swoole::ListenPort *port); +void php_swoole_server_set_port_property(swoole::ListenPort *port, swoole::ServerPortProperty *property); + +namespace swoole { struct ServerPortProperty { - zval *callbacks[PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM]; - zend_fcall_info_cache *caches[PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM]; - zval _callbacks[PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM]; + zend::Callable *callbacks[PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM]; Server *serv; ListenPort *port; zval *zsetting; @@ -76,51 +82,56 @@ struct ServerPortProperty { struct ServerProperty { std::vector ports; std::vector user_processes; - ServerPortProperty *primary_port; - zend_fcall_info_cache *callbacks[PHP_SWOOLE_SERVER_CALLBACK_NUM]; - std::unordered_map task_callbacks; + zend::Callable *callbacks[PHP_SWOOLE_SERVER_CALLBACK_NUM]; + std::unordered_map task_callbacks; std::unordered_map task_coroutine_map; std::unordered_map *> send_coroutine_map; - std::vector command_callbacks; + std::vector command_callbacks; }; struct ServerObject { Server *serv; ServerProperty *property; + zval init_arguments; zend_object std; - zend_class_entry *get_ce() { - return Z_OBJCE_P(get_object()); + zend_class_entry *get_ce() const { + return Z_OBJCE_P(php_swoole_server_zval_ptr(serv)); + } + + bool isset_callback(ListenPort *port, int event_type) const { + return (php_swoole_server_get_port_property(port)->callbacks[event_type] || + php_swoole_server_get_port_property(serv->get_primary_port())->callbacks[event_type]); } - zval *get_object() { - return (zval *) serv->private_data_2; + bool isset_callback(int event_type) const { + return property->callbacks[event_type] != nullptr; } - bool isset_callback(ListenPort *port, int event_type) { - ServerPortProperty *port_property = (ServerPortProperty *) port->ptr; - return (port_property->callbacks[event_type] || property->primary_port->callbacks[event_type]); + zend::Callable *get_callback(int event_type) const { + return property->callbacks[event_type]; } - zend_bool is_websocket_server() { + zend_bool is_websocket_server() const { return instanceof_function(get_ce(), swoole_websocket_server_ce); } - zend_bool is_http_server() { + zend_bool is_http_server() const { return instanceof_function(get_ce(), swoole_http_server_ce); } - zend_bool is_redis_server() { + zend_bool is_redis_server() const { return instanceof_function(get_ce(), swoole_redis_server_ce); } - void register_callback(); + void register_callback() const; void on_before_start(); + void copy_setting(zval *zsetting) const; }; struct TaskCo { Coroutine *co; - int *list; + TaskId *list; uint32_t count; zval *result; }; @@ -128,22 +139,24 @@ void register_admin_server_commands(Server *serv); } // namespace swoole void php_swoole_server_register_callbacks(swServer *serv); -zend_fcall_info_cache *php_swoole_server_get_fci_cache(swServer *serv, int server_fd, int event_type); +zend::Callable *php_swoole_server_get_callback(swServer *serv, int server_fd, int event_type); int php_swoole_create_dir(const char *path, size_t length); void php_swoole_server_before_start(swServer *serv, zval *zobject); -bool php_swoole_server_isset_callback(swServer *serv, swListenPort *port, int event_type); -void php_swoole_server_send_yield(swServer *serv, swoole::SessionId sesion_id, zval *zdata, zval *return_value); -void php_swoole_get_recv_data(swServer *serv, zval *zdata, swRecvData *req); -void php_swoole_server_onConnect(swServer *, swDataHead *); -int php_swoole_server_onReceive(swServer *, swRecvData *); -int php_swoole_http_server_onReceive(swServer *, swRecvData *); -int php_swoole_redis_server_onReceive(swServer *serv, swRecvData *req); -int php_swoole_server_onPacket(swServer *, swRecvData *); -void php_swoole_server_onClose(swServer *, swDataHead *); -void php_swoole_server_onBufferFull(swServer *, swDataHead *); -void php_swoole_server_onBufferEmpty(swServer *, swDataHead *); +bool php_swoole_server_isset_callback(swServer *serv, swoole::ListenPort *port, int event_type); +bool php_swoole_server_send_yield(swServer *serv, swoole::SessionId session_id, zend_string *sdata); +void php_swoole_get_recv_data(swServer *serv, zval *zdata, swoole::RecvData *req); +void php_swoole_server_onConnect(swServer *, swoole::DataHead *); +int php_swoole_server_onReceive(swServer *, swoole::RecvData *); +int php_swoole_http_server_onReceive(swServer *, swoole::RecvData *); +void php_swoole_http_server_onClose(swServer *serv, swoole::DataHead *info); +void php_swoole_http2_server_onClose(swServer *serv, swoole::SessionId session_id); +int php_swoole_redis_server_onReceive(swServer *serv, swoole::RecvData *req); +int php_swoole_server_onPacket(swServer *, swoole::RecvData *); +void php_swoole_server_onClose(swServer *, swoole::DataHead *); +void php_swoole_server_onBufferFull(swServer *, swoole::DataHead *); +void php_swoole_server_onBufferEmpty(swServer *, swoole::DataHead *); swServer *php_swoole_server_get_and_check_server(zval *zobject); void php_swoole_server_port_deref(zend_object *object); +void php_swoole_server_set_websocket_option(swoole::ListenPort *port, zend_array *vht); swoole::ServerObject *php_swoole_server_get_zend_object(swoole::Server *serv); -zval *php_swoole_server_get_zval_object(swoole::Server *serv); diff --git a/ext-src/php_swoole_sqlite.h b/ext-src/php_swoole_sqlite.h new file mode 100644 index 0000000000..e0f62addfb --- /dev/null +++ b/ext-src/php_swoole_sqlite.h @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ +#ifndef SWOOLE_SRC_PHP_SWOOLE_SQLITE_H +#define SWOOLE_SRC_PHP_SWOOLE_SQLITE_H +#include "php_swoole.h" + +#ifdef SW_USE_SQLITE + +BEGIN_EXTERN_C() + +#include "ext/pdo/php_pdo_driver.h" + +#include "thirdparty/pdo_sqlite/php_pdo_sqlite_int.h" + +extern const pdo_driver_t swoole_pdo_sqlite_driver; +void swoole_sqlite_set_blocking(bool blocking); + +int swoole_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs); +int swoole_sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); +int swoole_sqlite3_exec( + sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg); +int swoole_sqlite3_close(sqlite3 *db); +int swoole_sqlite3_close_v2(sqlite3 *db); +int swoole_sqlite3_step(sqlite3_stmt *stmt); + +#ifdef SW_USE_SQLITE_HOOK +#define sqlite3_open_v2 swoole_sqlite3_open_v2 +#define sqlite3_prepare_v2 swoole_sqlite3_prepare_v2 +#define sqlite3_exec swoole_sqlite3_exec +#define sqlite3_close swoole_sqlite3_close +#define sqlite3_close_v2 swoole_sqlite3_close_v2 +#define sqlite3_step swoole_sqlite3_step +#endif + +END_EXTERN_C() +#endif +#endif diff --git a/ext-src/php_swoole_ssh2.h b/ext-src/php_swoole_ssh2.h new file mode 100644 index 0000000000..efe2a0fcd0 --- /dev/null +++ b/ext-src/php_swoole_ssh2.h @@ -0,0 +1,93 @@ +#pragma once + +#include "php_swoole_cxx.h" +#include "php_swoole_ssh2_def.h" + +#include +#include +#include + +typedef struct _php_ssh2_session_data { + /* Userspace callback functions */ + zval *ignore_cb; + zval *debug_cb; + zval *macerror_cb; + zval *disconnect_cb; + + SocketImpl *socket; +} php_ssh2_session_data; + +static inline swoole::EventType ssh2_get_event_type(LIBSSH2_SESSION *session) { + int dir = libssh2_session_block_directions(session); + if (dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) { + return SW_EVENT_WRITE; + } else { + return SW_EVENT_READ; + } +} + +static inline SocketImpl *ssh2_get_socket(LIBSSH2_SESSION *session) { + auto session_data = (php_ssh2_session_data **) libssh2_session_abstract(session); + return (*session_data)->socket; +} + +static inline void ssh2_set_socket_timeout(LIBSSH2_SESSION *session, int timeout_ms) { + auto sock = ssh2_get_socket(session); + sock->set_timeout(timeout_ms / 1000, SW_TIMEOUT_ALL); +} + +class ResourceGuard { + zval zres_; + + public: + ResourceGuard(zval *zres) { + zval_addref_p(zres); + zres_ = *zres; + } + ~ResourceGuard() { + zval_ptr_dtor(&zres_); + } +}; + +static inline int ssh2_async_call(LIBSSH2_SESSION *session, const std::function &fn) { + auto event = ssh2_get_event_type(session); + auto socket = ssh2_get_socket(session); + + socket->check_bound_co(SW_EVENT_READ); + socket->check_bound_co(SW_EVENT_WRITE); + + int rc = 0; + while (1) { + rc = fn(); + if (rc == LIBSSH2_ERROR_EAGAIN) { + if (!socket->poll(event)) { + return LIBSSH2_ERROR_SOCKET_NONE; + } + continue; + } + break; + } + return rc; +} + +template +static inline T *ssh2_async_call_ex(LIBSSH2_SESSION *session, const std::function &fn) { + auto event = ssh2_get_event_type(session); + auto socket = ssh2_get_socket(session); + + socket->check_bound_co(SW_EVENT_READ); + socket->check_bound_co(SW_EVENT_WRITE); + + T *handle; + while (1) { + handle = fn(); + if (handle) { + return handle; + } + if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN && socket->poll(event)) { + continue; + } + break; + } + return nullptr; +} diff --git a/ext-src/php_swoole_ssh2_def.h b/ext-src/php_swoole_ssh2_def.h new file mode 100644 index 0000000000..6e5ec05601 --- /dev/null +++ b/ext-src/php_swoole_ssh2_def.h @@ -0,0 +1,43 @@ +#include "php.h" + +BEGIN_EXTERN_C() +ZEND_FUNCTION(ssh2_connect); +ZEND_FUNCTION(ssh2_disconnect); +ZEND_FUNCTION(ssh2_methods_negotiated); +ZEND_FUNCTION(ssh2_fingerprint); +ZEND_FUNCTION(ssh2_auth_none); +ZEND_FUNCTION(ssh2_auth_password); +ZEND_FUNCTION(ssh2_auth_pubkey_file); +ZEND_FUNCTION(ssh2_auth_pubkey); +ZEND_FUNCTION(ssh2_auth_hostbased_file); +ZEND_FUNCTION(ssh2_forward_listen); +ZEND_FUNCTION(ssh2_forward_accept); +ZEND_FUNCTION(ssh2_shell); +ZEND_FUNCTION(ssh2_shell_resize); +ZEND_FUNCTION(ssh2_exec); +ZEND_FUNCTION(ssh2_tunnel); +ZEND_FUNCTION(ssh2_scp_recv); +ZEND_FUNCTION(ssh2_scp_send); +ZEND_FUNCTION(ssh2_fetch_stream); +ZEND_FUNCTION(ssh2_send_eof); +ZEND_FUNCTION(ssh2_sftp); +ZEND_FUNCTION(ssh2_sftp_rename); +ZEND_FUNCTION(ssh2_sftp_unlink); +ZEND_FUNCTION(ssh2_sftp_mkdir); +ZEND_FUNCTION(ssh2_sftp_rmdir); +ZEND_FUNCTION(ssh2_sftp_chmod); +ZEND_FUNCTION(ssh2_sftp_stat); +ZEND_FUNCTION(ssh2_sftp_lstat); +ZEND_FUNCTION(ssh2_sftp_symlink); +ZEND_FUNCTION(ssh2_sftp_readlink); +ZEND_FUNCTION(ssh2_sftp_realpath); +ZEND_FUNCTION(ssh2_publickey_init); +ZEND_FUNCTION(ssh2_publickey_add); +ZEND_FUNCTION(ssh2_publickey_remove); +ZEND_FUNCTION(ssh2_publickey_list); +ZEND_FUNCTION(ssh2_auth_agent); +END_EXTERN_C() + +int php_swoole_ssh2_mshutdown(); +int php_swoole_ssh2_minit(int module_number); +void php_swoole_ssh2_minfo(); diff --git a/ext-src/php_swoole_ssh2_hook.h b/ext-src/php_swoole_ssh2_hook.h new file mode 100644 index 0000000000..ebf9b0b75d --- /dev/null +++ b/ext-src/php_swoole_ssh2_hook.h @@ -0,0 +1,290 @@ +#define SSH2_ASYNC_CALL(session, libssh2_func, ...) \ + ssh2_async_call(session, [&](void) { return libssh2_func(__VA_ARGS__); }) + +#define SSH2_ASYNC_CALL_EX(T, session, libssh2_func, ...) \ + ssh2_async_call_ex(session, [&](void) -> T * { return libssh2_func(__VA_ARGS__); }) + +#define libssh2_session_handshake(session, sockfd) SSH2_ASYNC_CALL(session, libssh2_session_handshake, session, sockfd) + +#undef libssh2_session_disconnect +#define libssh2_session_disconnect(session, description) \ + SSH2_ASYNC_CALL(session, libssh2_session_disconnect_ex, (session), SSH_DISCONNECT_BY_APPLICATION, (description), "") + +#undef libssh2_channel_open_session +#define libssh2_channel_open_session(session) \ + SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, \ + session, \ + libssh2_channel_open_ex, \ + (session), \ + "session", \ + sizeof("session") - 1, \ + LIBSSH2_CHANNEL_WINDOW_DEFAULT, \ + LIBSSH2_CHANNEL_PACKET_DEFAULT, \ + NULL, \ + 0) + +#define libssh2_channel_setenv_ex(channel, name, name_len, value, value_len) \ + SSH2_ASYNC_CALL(session, libssh2_channel_setenv_ex, channel, name, name_len, value, value_len) + +#define libssh2_channel_request_pty_ex(channel, term, term_len, modes, modes_len, width, height, width_px, height_px) \ + SSH2_ASYNC_CALL(session, \ + libssh2_channel_request_pty_ex, \ + channel, \ + term, \ + term_len, \ + modes, \ + modes_len, \ + width, \ + height, \ + width_px, \ + height_px) + +#undef libssh2_channel_shell +#define libssh2_channel_shell(channel) \ + SSH2_ASYNC_CALL(session, libssh2_channel_process_startup, channel, "shell", sizeof("shell") - 1, NULL, 0) + +#undef libssh2_channel_exec +#define libssh2_channel_exec(channel, command) \ + SSH2_ASYNC_CALL(session, \ + libssh2_channel_process_startup, \ + channel, \ + "exec", \ + sizeof("exec") - 1, \ + (command), \ + (unsigned int) strlen(command)) + +#define libssh2_channel_flush_ex(channel, streamid) \ + SSH2_ASYNC_CALL(session, libssh2_channel_flush_ex, (channel), (streamid)) + +#define libssh2_channel_read_ex(channel, streamid, buf, len) \ + SSH2_ASYNC_CALL(session, libssh2_channel_read_ex, channel, streamid, buf, len) + +#undef libssh2_channel_write_ex +#define libssh2_channel_write_ex(channel, streamid, buf, len) \ + SSH2_ASYNC_CALL(session, libssh2_channel_write_ex, channel, streamid, buf, len) + +#undef libssh2_channel_read +#define libssh2_channel_read(channel, buf, buflen) libssh2_channel_read_ex((channel), 0, (buf), (buflen)) + +#undef libssh2_channel_write +#define libssh2_channel_write(channel, buf, buflen) libssh2_channel_write_ex((channel), 0, (buf), (buflen)) + +#undef libssh2_channel_eof +#define libssh2_channel_eof(channel) SSH2_ASYNC_CALL(session, libssh2_channel_eof, channel) + +#undef libssh2_channel_close +#define libssh2_channel_close(channel) SSH2_ASYNC_CALL(session, libssh2_channel_close, channel) + +#undef libssh2_channel_send_eof +#define libssh2_channel_send_eof(channel) SSH2_ASYNC_CALL(session, libssh2_channel_send_eof, channel) + +#undef libssh2_channel_get_exit_status +#define libssh2_channel_get_exit_status(channel) SSH2_ASYNC_CALL(session, libssh2_channel_get_exit_status, channel) + +#undef libssh2_channel_request_pty_size_ex +#define libssh2_channel_request_pty_size_ex(channel, width, height, width_px, height_px) \ + SSH2_ASYNC_CALL(session, libssh2_channel_request_pty_size_ex, channel, width, height, width_px, height_px) + +#undef libssh2_channel_forward_listen_ex +#define libssh2_channel_forward_listen_ex(session, host, port, addr, num_connections) \ + SSH2_ASYNC_CALL_EX( \ + LIBSSH2_LISTENER, session, libssh2_channel_forward_listen_ex, session, host, port, addr, num_connections) + +#undef libssh2_channel_forward_accept +#define libssh2_channel_forward_accept(listener) \ + SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_channel_forward_accept, listener) + +#undef libssh2_channel_forward_cancel +#define libssh2_channel_forward_cancel(listener) SSH2_ASYNC_CALL(session, libssh2_channel_forward_cancel, listener) + +#undef libssh2_channel_direct_tcpip +#define libssh2_channel_direct_tcpip(session, host, port) \ + SSH2_ASYNC_CALL_EX( \ + LIBSSH2_CHANNEL, session, libssh2_channel_direct_tcpip_ex, (session), (host), (port), "127.0.0.1", 22) + +#undef libssh2_sftp_fstat +#define libssh2_sftp_fstat(handle, attrs) SSH2_ASYNC_CALL(session, libssh2_sftp_fstat_ex, handle, attrs, 0) + +#define libssh2_sftp_stat_ex(sftp, path, path_len, stat_type, attrs) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_stat_ex, sftp, path, path_len, stat_type, attrs) + +#define libssh2_sftp_symlink_ex(sftp, path, path_len, target, target_len, link_type) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_symlink_ex, sftp, path, path_len, target, target_len, link_type) + +#undef libssh2_sftp_open +#define libssh2_sftp_open(sftp, filename, flags, mode) \ + SSH2_ASYNC_CALL_EX(LIBSSH2_SFTP_HANDLE, \ + session, \ + libssh2_sftp_open_ex, \ + (sftp), \ + (filename), \ + strlen(filename), \ + (flags), \ + (mode), \ + LIBSSH2_SFTP_OPENFILE) + +#undef libssh2_sftp_opendir +#define libssh2_sftp_opendir(sftp, path) \ + SSH2_ASYNC_CALL_EX( \ + LIBSSH2_SFTP_HANDLE, session, libssh2_sftp_open_ex, (sftp), (path), strlen(path), 0, 0, LIBSSH2_SFTP_OPENDIR) + +#undef libssh2_sftp_readdir +#define libssh2_sftp_readdir(handle, buffer, buffer_maxlen, attrs) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_readdir_ex, (handle), (buffer), (buffer_maxlen), NULL, 0, (attrs)) + +#undef libssh2_sftp_tell +#define libssh2_sftp_tell(handle) SSH2_ASYNC_CALL(session, libssh2_sftp_tell, handle) + +#undef libssh2_sftp_read +#define libssh2_sftp_read(handle, buffer, count) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_read, (handle), (buffer), (count)) + +#undef libssh2_sftp_write +#define libssh2_sftp_write(handle, buffer, count) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_write, (handle), (buffer), (count)) + +#define libssh2_sftp_init(session) SSH2_ASYNC_CALL_EX(LIBSSH2_SFTP, session, libssh2_sftp_init, session) + +#undef libssh2_scp_recv +#define libssh2_scp_recv(session, path, stat) \ + SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_scp_recv, session, path, stat) + +#undef libssh2_scp_send_ex +#define libssh2_scp_send_ex(session, path, mode, size, atime, mtime) \ + SSH2_ASYNC_CALL_EX(LIBSSH2_CHANNEL, session, libssh2_scp_send_ex, session, path, mode, size, atime, mtime) + +#undef libssh2_sftp_close +#define libssh2_sftp_close(handle) SSH2_ASYNC_CALL(session, libssh2_sftp_close_handle, handle) + +#define libssh2_sftp_unlink_ex(sftp, filename, filename_len) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_unlink_ex, (sftp), (filename), filename_len) + +#undef libssh2_sftp_unlink +#define libssh2_sftp_unlink(sftp, filename) libssh2_sftp_unlink_ex((sftp), (filename), strlen(filename)) + +#define libssh2_sftp_rename_ex(sftp, source_filename, srouce_filename_len, dest_filename, dest_filename_len, flags) \ + SSH2_ASYNC_CALL(session, \ + libssh2_sftp_rename_ex, \ + sftp, \ + source_filename, \ + srouce_filename_len, \ + dest_filename, \ + dest_filename_len, \ + flags) + +#undef libssh2_sftp_rename +#define libssh2_sftp_rename(sftp, sourcefile, destfile) \ + libssh2_sftp_rename_ex((sftp), \ + (sourcefile), \ + strlen(sourcefile), \ + (destfile), \ + strlen(destfile), \ + LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE) + +#define libssh2_sftp_mkdir_ex(sftp, path, path_len, mode) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_mkdir_ex, (sftp), (path), path_len, (mode)) + +#undef libssh2_sftp_mkdir +#define libssh2_sftp_mkdir(sftp, path, mode) libssh2_sftp_mkdir_ex((sftp), (path), strlen(path), (mode)) + +#define libssh2_sftp_rmdir_ex(sftp, path, path_len) \ + SSH2_ASYNC_CALL(session, libssh2_sftp_rmdir_ex, (sftp), (path), path_len) + +#undef libssh2_sftp_rmdir +#define libssh2_sftp_rmdir(sftp, path) libssh2_sftp_rmdir_ex((sftp), (path), strlen(path)) + +/* Agent related functions */ +#undef libssh2_agent_connect +#define libssh2_agent_connect(agent) SSH2_ASYNC_CALL(session, libssh2_agent_connect, agent) + +#undef libssh2_agent_list_identities +#define libssh2_agent_list_identities(agent) SSH2_ASYNC_CALL(session, libssh2_agent_list_identities, agent) + +#undef libssh2_agent_get_identity +#define libssh2_agent_get_identity(agent, identity, prev_identity) \ + SSH2_ASYNC_CALL(session, libssh2_agent_get_identity, agent, identity, prev_identity) + +#undef libssh2_agent_userauth +#define libssh2_agent_userauth(agent, username, identity) \ + SSH2_ASYNC_CALL(session, libssh2_agent_userauth, agent, username, identity) + +#undef libssh2_agent_disconnect +#define libssh2_agent_disconnect(agent) SSH2_ASYNC_CALL(session, libssh2_agent_disconnect, agent) + +/* libssh2_agent_free is just memory operation, no network IO, so don't async it */ + +/* User authentication functions */ +#undef libssh2_userauth_list +#define libssh2_userauth_list(session, username, username_len) \ + SSH2_ASYNC_CALL_EX(char, session, libssh2_userauth_list, session, username, username_len) + +#undef libssh2_userauth_password_ex +#define libssh2_userauth_password_ex(session, username, username_len, password, password_len, change_cb) \ + SSH2_ASYNC_CALL( \ + session, libssh2_userauth_password_ex, session, username, username_len, password, password_len, change_cb) + +#undef libssh2_userauth_publickey_fromfile_ex +#define libssh2_userauth_publickey_fromfile_ex(session, username, username_len, publickey, privatekey, passphrase) \ + SSH2_ASYNC_CALL(session, \ + libssh2_userauth_publickey_fromfile_ex, \ + (session), \ + (username), \ + (username_len), \ + (publickey), \ + (privatekey), \ + (passphrase)) + +#undef libssh2_userauth_publickey_fromfile +#define libssh2_userauth_publickey_fromfile(session, username, publickey, privatekey, passphrase) \ + libssh2_userauth_publickey_fromfile_ex( \ + (session), (username), (unsigned int) strlen(username), (publickey), (privatekey), (passphrase)) + +#undef libssh2_userauth_publickey_frommemory +#define libssh2_userauth_publickey_frommemory( \ + session, username, username_len, pubkeydata, pubkeydata_len, privkeydata, privkeydata_len, passphrase) \ + SSH2_ASYNC_CALL(session, \ + libssh2_userauth_publickey_frommemory, \ + session, \ + username, \ + username_len, \ + pubkeydata, \ + pubkeydata_len, \ + privkeydata, \ + privkeydata_len, \ + passphrase) + +#undef libssh2_userauth_hostbased_fromfile_ex +#define libssh2_userauth_hostbased_fromfile_ex(session, \ + username, \ + username_len, \ + pubkeyfile, \ + privkeyfile, \ + passphrase, \ + hostname, \ + hostname_len, \ + local_username, \ + local_username_len) \ + SSH2_ASYNC_CALL(session, \ + libssh2_userauth_hostbased_fromfile_ex, \ + session, \ + username, \ + username_len, \ + pubkeyfile, \ + privkeyfile, \ + passphrase, \ + hostname, \ + hostname_len, \ + local_username, \ + local_username_len) + +#undef libssh2_userauth_keyboard_interactive +#define libssh2_userauth_keyboard_interactive(session, username, response_callback) \ + SSH2_ASYNC_CALL(session, \ + libssh2_userauth_keyboard_interactive_ex, \ + (session), \ + (username), \ + (unsigned int) strlen(username), \ + (response_callback)) + +#undef libssh2_userauth_authenticated +#define libssh2_userauth_authenticated(session) SSH2_ASYNC_CALL(session, libssh2_userauth_authenticated, session) \ No newline at end of file diff --git a/ext-src/php_swoole_stdext.h b/ext-src/php_swoole_stdext.h new file mode 100644 index 0000000000..58c93488ab --- /dev/null +++ b/ext-src/php_swoole_stdext.h @@ -0,0 +1,45 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2015 The Swoole Group | + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + */ + +#pragma once + +#include "php_swoole_cxx.h" + +SW_EXTERN_C_BEGIN +PHP_FUNCTION(swoole_call_array_method); +PHP_FUNCTION(swoole_call_string_method); +PHP_FUNCTION(swoole_call_stream_method); +PHP_FUNCTION(swoole_array_search); +PHP_FUNCTION(swoole_array_contains); +PHP_FUNCTION(swoole_array_join); +PHP_FUNCTION(swoole_array_key_exists); +PHP_FUNCTION(swoole_array_map); +PHP_FUNCTION(swoole_array_is_typed); +PHP_FUNCTION(swoole_array_is_empty); +PHP_FUNCTION(swoole_str_split); +PHP_FUNCTION(swoole_str_is_empty); +PHP_FUNCTION(swoole_str_match); +PHP_FUNCTION(swoole_str_match_all); +PHP_FUNCTION(swoole_str_json_decode); +PHP_FUNCTION(swoole_str_json_decode_to_object); +PHP_FUNCTION(swoole_parse_str); +PHP_FUNCTION(swoole_hash); +PHP_FUNCTION(swoole_typed_array); +PHP_FUNCTION(swoole_str_replace); +PHP_FUNCTION(swoole_str_ireplace); +PHP_FUNCTION(swoole_array_replace_str); +PHP_FUNCTION(swoole_array_ireplace_str); +SW_EXTERN_C_END diff --git a/ext-src/php_swoole_thread.h b/ext-src/php_swoole_thread.h new file mode 100644 index 0000000000..4331609aef --- /dev/null +++ b/ext-src/php_swoole_thread.h @@ -0,0 +1,314 @@ +/* + +----------------------------------------------------------------------+ + | 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@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Twosee | + | Author: Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "php_swoole_cxx.h" + +#ifdef SW_THREAD +#include "swoole_thread.h" + +typedef uint32_t ThreadResourceId; +class ThreadResource; +class ZendArray; + +extern zend_class_entry *swoole_thread_ce; +extern zend_class_entry *swoole_thread_error_ce; +extern zend_class_entry *swoole_thread_arraylist_ce; +extern zend_class_entry *swoole_thread_atomic_ce; +extern zend_class_entry *swoole_thread_atomic_long_ce; +extern zend_class_entry *swoole_thread_barrier_ce; +extern zend_class_entry *swoole_thread_lock_ce; +extern zend_class_entry *swoole_thread_map_ce; +extern zend_class_entry *swoole_thread_queue_ce; + +void php_swoole_thread_start(std::shared_ptr thread, zend_string *file, ZendArray *argv); +void php_swoole_thread_bailout(); + +ThreadResource *php_swoole_thread_arraylist_cast(const zval *zobject); +ThreadResource *php_swoole_thread_map_cast(const zval *zobject); +ThreadResource *php_swoole_thread_queue_cast(const zval *zobject); +ThreadResource *php_swoole_thread_lock_cast(const zval *zobject); +ThreadResource *php_swoole_thread_atomic_cast(const zval *zobject); +ThreadResource *php_swoole_thread_atomic_long_cast(const zval *zobject); +ThreadResource *php_swoole_thread_barrier_cast(const zval *zobject); + +void php_swoole_thread_arraylist_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_map_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_queue_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_lock_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_atomic_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_atomic_long_create(zval *return_value, ThreadResource *resource); +void php_swoole_thread_barrier_create(zval *return_value, ThreadResource *resource); + +int php_swoole_thread_stream_cast(zval *zstream); +void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd); + +int php_swoole_thread_co_socket_cast(zval *zstream, swSocketType *type); +void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type); + +#define EMSG_NO_RESOURCE "resource not found" +#define ECODE_NO_RESOURCE (-2) + +enum { + IS_ARRAYLIST = 80, + IS_QUEUE = 81, + IS_LOCK = 82, + IS_MAP = 83, + IS_BARRIER = 84, + IS_ATOMIC = 85, + IS_ATOMIC_LONG = 86, + IS_PHP_SOCKET = 96, + IS_CO_SOCKET = 97, + IS_STREAM_SOCKET = 98, + IS_SERIALIZED_OBJECT = 99, +}; + +class ThreadResource { + sw_atomic_t ref_count; + + public: + ThreadResource() { + ref_count = 1; + } + + void add_ref() { + sw_atomic_add_fetch(&ref_count, 1); + } + + void del_ref() { + if (sw_atomic_sub_fetch(&ref_count, 1) == 0) { + delete this; + } + } + + protected: + virtual ~ThreadResource() = default; +}; + +struct ArrayItem { + uint32_t type = IS_UNDEF; + zend_string *key = nullptr; + union { + zend_string *str; + zend_long lval; + double dval; + struct { + int fd; + swSocketType type; + } socket; + zend_string *serialized_object; + ThreadResource *resource; + } value; + + explicit ArrayItem(zval *zvalue) { + value = {}; + store(zvalue); + } + + void setKey(zend::String &_key) { + key = zend_string_init(_key.val(), _key.len(), true); + } + + void setKey(const zend_string *_key) { + key = zend_string_init(ZSTR_VAL(_key), ZSTR_LEN(_key), true); + } + + void store(zval *zvalue); + void fetch(zval *return_value) const; + void release(); + bool equals(const zval *zvalue) const; + + static int compare(Bucket *a, Bucket *b); + + ~ArrayItem() { + if (value.str) { + release(); + } + if (key) { + zend_string_release(key); + } + } +}; + +class ZendArray : public ThreadResource { + protected: + swoole::RWLock lock_; + zend_array ht; + + static void item_dtor(zval *pDest) { + auto item = static_cast(Z_PTR_P(pDest)); + delete item; + } + + public: + ZendArray() : lock_(false) { + zend_hash_init(&ht, 0, NULL, item_dtor, 1); + } + + ~ZendArray() override { + zend_hash_destroy(&ht); + } + + void clean() { + lock_.lock(); + zend_hash_clean(&ht); + lock_.unlock(); + } + + void append(zval *zvalue); + + void add(const zend_string *skey, zval *zvalue) { + auto item = new ArrayItem(zvalue); + item->setKey(skey); + zend_hash_add_ptr(&ht, item->key, item); + } + + void add(zend::String &skey, zval *zvalue) { + auto item = new ArrayItem(zvalue); + item->setKey(skey); + zend_hash_add_ptr(&ht, item->key, item); + } + + void add(zend_long index, zval *zvalue) { + auto item = new ArrayItem(zvalue); + zend_hash_index_add_ptr(&ht, index, item); + } + + bool index_exists(zend_long index) const { + return index < (zend_long) zend_hash_num_elements(&ht); + } + + bool strkey_exists(zend::String &skey) const { + return zend_hash_find_ptr(&ht, skey.get()) != nullptr; + } + + bool intkey_exists(zend_long index) const { + return zend_hash_index_find_ptr(&ht, index) != nullptr; + } + + void strkey_offsetGet(zval *zkey, zval *return_value) { + zend::String skey(zkey); + lock_.lock_rd(); + auto item = static_cast(zend_hash_find_ptr(&ht, skey.get())); + if (item) { + item->fetch(return_value); + } + lock_.unlock(); + } + + void strkey_offsetExists(zval *zkey, zval *return_value) { + zend::String skey(zkey); + lock_.lock_rd(); + RETVAL_BOOL(strkey_exists(skey)); + lock_.unlock(); + } + + void strkey_offsetUnset(zval *zkey) { + zend::String skey(zkey); + lock_.lock(); + zend_hash_del(&ht, skey.get()); + lock_.unlock(); + } + + void strkey_offsetSet(zval *zkey, zval *zvalue) { + zend::String skey(zkey); + auto item = new ArrayItem(zvalue); + item->setKey(skey); + lock_.lock(); + zend_hash_update_ptr(&ht, item->key, item); + lock_.unlock(); + } + + void strkey_incr(zval *zkey, zval *zvalue, zval *return_value); + void intkey_incr(zend_long index, zval *zvalue, zval *return_value); + void strkey_decr(zval *zkey, zval *zvalue, zval *return_value); + void intkey_decr(zend_long index, zval *zvalue, zval *return_value); + bool index_incr(zval *zkey, zval *zvalue, zval *return_value); + bool index_decr(zval *zkey, zval *zvalue, zval *return_value); + + void strkey_add(zval *zkey, zval *zvalue, zval *return_value); + void intkey_add(zend_long index, zval *zvalue, zval *return_value); + void strkey_update(zval *zkey, zval *zvalue, zval *return_value); + void intkey_update(zend_long index, zval *zvalue, zval *return_value); + + void count(zval *return_value) { + lock_.lock_rd(); + RETVAL_LONG(zend_hash_num_elements(&ht)); + lock_.unlock(); + } + + void keys(zval *return_value); + void values(zval *return_value); + void to_array(zval *return_value); + void find(const zval *search, zval *return_value); + void sort(bool renumber); + + void intkey_offsetGet(zend_long index, zval *return_value) { + lock_.lock_rd(); + auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); + if (item) { + item->fetch(return_value); + } + lock_.unlock(); + } + + void intkey_offsetExists(zend_long index, zval *return_value) { + lock_.lock_rd(); + RETVAL_BOOL(intkey_exists(index)); + lock_.unlock(); + } + + void intkey_offsetUnset(zend_long index) { + lock_.lock(); + zend_hash_index_del(&ht, index); + lock_.unlock(); + } + + void intkey_offsetSet(zend_long index, zval *zvalue) { + auto item = new ArrayItem(zvalue); + lock_.lock(); + zend_hash_index_update_ptr(&ht, index, item); + lock_.unlock(); + } + + bool index_offsetGet(zend_long index, zval *return_value); + bool index_offsetSet(zend_long index, zval *zvalue); + void index_offsetUnset(zend_long index); + void index_offsetExists(zend_long index, zval *return_value); + + static void incr_update(ArrayItem *item, zval *zvalue, zval *return_value); + static ArrayItem *incr_create(zval *zvalue, zval *return_value); + static ZendArray *from(zend_array *ht); +}; + +#define INIT_ARRAY_INCR_PARAMS \ + zval *zkey; \ + zval zvalue_, *zvalue = NULL; \ + \ + ZEND_PARSE_PARAMETERS_START(1, 2) \ + Z_PARAM_ZVAL(zkey) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_ZVAL(zvalue) \ + ZEND_PARSE_PARAMETERS_END(); \ + \ + if (!zvalue) { \ + zvalue = &zvalue_; \ + ZVAL_LONG(zvalue, 1); \ + } + +#endif diff --git a/ext-src/php_swoole_websocket.h b/ext-src/php_swoole_websocket.h new file mode 100644 index 0000000000..6051a57e11 --- /dev/null +++ b/ext-src/php_swoole_websocket.h @@ -0,0 +1,62 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2015 The Swoole Group | + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ + +#pragma once + +#include "php_swoole_cxx.h" + +#include "swoole_websocket.h" + +#define SW_WEBSOCKET_DEFAULT_BUFFER 4096 + +namespace swoole { +namespace websocket { +void apply_setting(WebSocketSettings &settings, zend_array *vht, bool in_server); +void recv_frame(const WebSocketSettings &settings, + std::shared_ptr &frame_buffer, + SocketImpl *sock, + zval *return_value, + double timeout); +ssize_t send_frame(const WebSocketSettings &settings, + SocketImpl *sock, + uchar opcode, + uchar flags, + const char *payload, + size_t payload_length); +void construct_frame(zval *zframe, zend_long opcode, zval *zpayload, uint8_t flags); + +#ifdef SW_HAVE_ZLIB +bool message_compress(String *buffer, const char *data, size_t length, int level); +bool message_uncompress(String *buffer, const char *in, size_t in_len); +#endif + +struct FrameObject { + uint8_t opcode; + uint8_t flags; + uint16_t code; + zval *data; + + explicit FrameObject(zval *data, zend_long _opcode = 0, zend_long _flags = 0, zend_long _code = 0); + size_t get_data_size() const { + return (data && ZVAL_IS_STRING(data)) ? Z_STRLEN_P(data) : 0; + } + bool pack(String *buffer); + static bool uncompress(zval *zpayload, const char *data, size_t length); +}; +} // namespace websocket +} // namespace swoole diff --git a/stubs/php_swoole.stub.php b/ext-src/stubs/php_swoole.stub.php similarity index 81% rename from stubs/php_swoole.stub.php rename to ext-src/stubs/php_swoole.stub.php index e401a558df..c86d32c868 100644 --- a/stubs/php_swoole.stub.php +++ b/ext-src/stubs/php_swoole.stub.php @@ -12,11 +12,11 @@ function swoole_last_error(): int { } -function swoole_async_dns_lookup_coro(mixed $domain_name, float $timeout = 60, int $type = AF_INET): string|false +function swoole_async_dns_lookup_coro(string $domain_name, float $timeout = 60, int $type = AF_INET): string|false { } -function swoole_async_set(array $settings): void +function swoole_async_set(array $settings): bool { } @@ -36,7 +36,7 @@ function swoole_test_kernel_coroutine(int $count = 100, float $sleep_time = 1.0) { } -function swoole_client_select(array &$read_array, array &$write_array, array &$error_array, float $timeout = 0.5): false|int +function swoole_client_select(?array &$read, ?array &$write, ?array &$except, ?float $timeout = 0.5): false|int { } @@ -44,7 +44,7 @@ function swoole_set_process_name(string $process_name): bool { } -function swoole_get_local_ip(): array +function swoole_get_local_ip(int $family = 2): array { } @@ -119,3 +119,9 @@ function swoole_substr_json_decode(string $str, int $offset, int $length = 0, bo function swoole_internal_call_user_shutdown_begin(): bool { } + + +function swoole_implicit_fn(string $fn, mixed $args = null): mixed +{ + +} diff --git a/stubs/php_swoole_arginfo.h b/ext-src/stubs/php_swoole_arginfo.h similarity index 86% rename from stubs/php_swoole_arginfo.h rename to ext-src/stubs/php_swoole_arginfo.h index fd801f08ab..cfc09ba395 100644 --- a/stubs/php_swoole_arginfo.h +++ b/ext-src/stubs/php_swoole_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2dd047382586d8c9918b9806711767a10474b0bc */ + * Stub hash: a27c7f31d40af4404abd6b7f055e7d52ace42cbc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_version, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -10,12 +10,12 @@ ZEND_END_ARG_INFO() #define arginfo_swoole_last_error arginfo_swoole_cpu_num ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_async_dns_lookup_coro, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) - ZEND_ARG_TYPE_INFO(0, domain_name, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, domain_name, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_DOUBLE, 0, "60") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, type, IS_LONG, 0, "AF_INET") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_async_set, 0, 1, IS_VOID, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_async_set, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, settings, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -40,10 +40,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_test_kernel_coroutine, 0, ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_client_select, 0, 3, MAY_BE_FALSE|MAY_BE_LONG) - ZEND_ARG_TYPE_INFO(1, read_array, IS_ARRAY, 0) - ZEND_ARG_TYPE_INFO(1, write_array, IS_ARRAY, 0) - ZEND_ARG_TYPE_INFO(1, error_array, IS_ARRAY, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_DOUBLE, 0, "0.5") + ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) + ZEND_ARG_TYPE_INFO(1, write, IS_ARRAY, 1) + ZEND_ARG_TYPE_INFO(1, except, IS_ARRAY, 1) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_DOUBLE, 1, "0.5") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_set_process_name, 0, 1, _IS_BOOL, 0) @@ -51,9 +51,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_set_process_name, 0, 1, _ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_get_local_ip, 0, 0, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, family, IS_LONG, 0, "2") ZEND_END_ARG_INFO() -#define arginfo_swoole_get_local_mac arginfo_swoole_get_local_ip +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_get_local_mac, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_strerror, 0, 1, IS_STRING, 0) ZEND_ARG_TYPE_INFO(0, errno, IS_LONG, 0) @@ -107,7 +109,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_mime_type_exists, 0, 1, _ ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) ZEND_END_ARG_INFO() -#define arginfo_swoole_mime_type_list arginfo_swoole_get_local_ip +#define arginfo_swoole_mime_type_list arginfo_swoole_get_local_mac #define arginfo_swoole_clear_dns_cache arginfo_swoole_clear_error @@ -129,3 +131,8 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_internal_call_user_shutdown_begin, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_implicit_fn, 0, 1, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, fn, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, args, IS_MIXED, 0, "null") +ZEND_END_ARG_INFO() diff --git a/stubs/php_swoole_atomic.stub.php b/ext-src/stubs/php_swoole_atomic.stub.php similarity index 100% rename from stubs/php_swoole_atomic.stub.php rename to ext-src/stubs/php_swoole_atomic.stub.php diff --git a/stubs/php_swoole_atomic_arginfo.h b/ext-src/stubs/php_swoole_atomic_arginfo.h similarity index 100% rename from stubs/php_swoole_atomic_arginfo.h rename to ext-src/stubs/php_swoole_atomic_arginfo.h diff --git a/stubs/php_swoole_channel_coro.stub.php b/ext-src/stubs/php_swoole_channel_coro.stub.php similarity index 100% rename from stubs/php_swoole_channel_coro.stub.php rename to ext-src/stubs/php_swoole_channel_coro.stub.php diff --git a/stubs/php_swoole_channel_coro_arginfo.h b/ext-src/stubs/php_swoole_channel_coro_arginfo.h similarity index 100% rename from stubs/php_swoole_channel_coro_arginfo.h rename to ext-src/stubs/php_swoole_channel_coro_arginfo.h diff --git a/stubs/php_swoole_client.stub.php b/ext-src/stubs/php_swoole_client.stub.php similarity index 93% rename from stubs/php_swoole_client.stub.php rename to ext-src/stubs/php_swoole_client.stub.php index 95a80e5121..8630912fd2 100644 --- a/stubs/php_swoole_client.stub.php +++ b/ext-src/stubs/php_swoole_client.stub.php @@ -9,11 +9,9 @@ public function recv(int $size = 65536, int $flag = 0): string|false {} public function send(string $data, int $flag = 0): false|int {} public function sendfile(string $filename, int $offset = 0, int $length = 0): bool {} public function sendto(string $ip, int $port, string $data): bool {} - #ifdef SW_USE_OPENSSL - public function enableSSL(): bool {} + public function enableSSL(?callable $onSslReady = null): bool {} public function getPeerCert(): string|bool {} public function verifyPeerCert(): bool {} - #endif public function isConnected(): bool {} public function getsockname(): array|false {} public function getpeername(): array|false {} diff --git a/stubs/php_swoole_client_arginfo.h b/ext-src/stubs/php_swoole_client_arginfo.h similarity index 91% rename from stubs/php_swoole_client_arginfo.h rename to ext-src/stubs/php_swoole_client_arginfo.h index f5f0b2956a..7b3b0bc044 100644 --- a/stubs/php_swoole_client_arginfo.h +++ b/ext-src/stubs/php_swoole_client_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7ccb4bccff50af59ec0fc7df447f0df57ea5121e */ + * Stub hash: 75835dd199229a2d75969892c2fce00f4d75a362 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Swoole_Client___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, type, IS_LONG, 0) @@ -43,23 +43,18 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Swoole_Client_sendto, 0, 3 ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) ZEND_END_ARG_INFO() -#if defined(SW_USE_OPENSSL) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Swoole_Client_enableSSL, 0, 0, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, onSslReady, IS_CALLABLE, 1, "null") ZEND_END_ARG_INFO() -#endif -#if defined(SW_USE_OPENSSL) ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Swoole_Client_getPeerCert, 0, 0, MAY_BE_STRING|MAY_BE_BOOL) ZEND_END_ARG_INFO() -#endif - -#if defined(SW_USE_OPENSSL) -#define arginfo_class_Swoole_Client_verifyPeerCert arginfo_class_Swoole_Client_enableSSL -#endif -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Swoole_Client_isConnected, 0, 0, _IS_BOOL, 0) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Swoole_Client_verifyPeerCert, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() +#define arginfo_class_Swoole_Client_isConnected arginfo_class_Swoole_Client_verifyPeerCert + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Swoole_Client_getsockname, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_END_ARG_INFO() @@ -77,3 +72,4 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_Swoole_Client_getSocket, 0, 0, Socket, MAY_BE_FALSE) ZEND_END_ARG_INFO() #endif + diff --git a/ext-src/stubs/php_swoole_client_async.stub.php b/ext-src/stubs/php_swoole_client_async.stub.php new file mode 100644 index 0000000000..8561b8b361 --- /dev/null +++ b/ext-src/stubs/php_swoole_client_async.stub.php @@ -0,0 +1,14 @@ +|null + * @refcount 1 + */ + function ftp_raw(FTP\Connection $ftp, string $command): ?array {} + function ftp_mkdir(FTP\Connection $ftp, string $directory): string|false {} + function ftp_rmdir(FTP\Connection $ftp, string $directory): bool {} + function ftp_chmod(FTP\Connection $ftp, int $permissions, string $filename): int|false {} + + /** @param string $response */ + function ftp_alloc(FTP\Connection $ftp, int $size, &$response = null): bool {} + + /** + * @return array|false + * @refcount 1 + */ + function ftp_nlist(FTP\Connection $ftp, string $directory): array|false {} + + /** + * @return array|false + * @refcount 1 + */ + function ftp_rawlist(FTP\Connection $ftp, string $directory, bool $recursive = false): array|false {} + + /** + * @return array|false + * @refcount 1 + */ + function ftp_mlsd(FTP\Connection $ftp, string $directory): array|false {} + + function ftp_systype(FTP\Connection $ftp): string|false {} + + /** @param resource $stream */ + function ftp_fget(FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} + + /** @param resource $stream */ + function ftp_nb_fget(FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): int {} + function ftp_pasv(FTP\Connection $ftp, bool $enable): bool {} + function ftp_get(FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} + function ftp_nb_get(FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): int|false {} + function ftp_nb_continue(FTP\Connection $ftp): int {} + + /** @param resource $stream */ + function ftp_fput(FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): bool {} + + /** @param resource $stream */ + function ftp_nb_fput(FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): int {} + function ftp_put(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): bool {} + function ftp_append(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY): bool {} + function ftp_nb_put(FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): int|false {} + function ftp_size(FTP\Connection $ftp, string $filename): int {} + function ftp_mdtm(FTP\Connection $ftp, string $filename): int {} + function ftp_rename(FTP\Connection $ftp, string $from, string $to): bool {} + function ftp_delete(FTP\Connection $ftp, string $filename): bool {} + function ftp_site(FTP\Connection $ftp, string $command): bool {} + function ftp_close(FTP\Connection $ftp): bool {} + + /** @alias ftp_close */ + function ftp_quit(FTP\Connection $ftp): bool {} + + /** @param int|bool $value */ + function ftp_set_option(FTP\Connection $ftp, int $option, $value): bool {} + function ftp_get_option(FTP\Connection $ftp, int $option): int|bool {} +} + +namespace FTP { + /** + * @strict-properties + * @not-serializable + */ + final class Connection + { + } +} diff --git a/ext-src/stubs/php_swoole_ftp_arginfo.h b/ext-src/stubs/php_swoole_ftp_arginfo.h new file mode 100644 index 0000000000..09ab9ed356 --- /dev/null +++ b/ext-src/stubs/php_swoole_ftp_arginfo.h @@ -0,0 +1,195 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 4884776ce0fa7815e35882ffc34cbbccd0ec21ad */ + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ftp_connect, 0, 1, FTP\\Connection, MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "21") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "90") +ZEND_END_ARG_INFO() + +#if defined(SW_HAVE_FTP_SSL) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_ftp_ssl_connect, 0, 1, FTP\\Connection, MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, hostname, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, port, IS_LONG, 0, "21") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "90") +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_login, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, username, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, password, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_pwd, 0, 1, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_cdup, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_chdir, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_exec, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, command, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_raw, 0, 2, IS_ARRAY, 1) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, command, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_mkdir, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_ftp_rmdir arginfo_ftp_chdir + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_chmod, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, permissions, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_alloc, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, size, IS_LONG, 0) + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, response, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nlist, 0, 2, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_rawlist, 0, 2, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, directory, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, recursive, _IS_BOOL, 0, "false") +ZEND_END_ARG_INFO() + +#define arginfo_ftp_mlsd arginfo_ftp_nlist + +#define arginfo_ftp_systype arginfo_ftp_pwd + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_fget, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_fget, 0, 3, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_pasv, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_get, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nb_get, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_continue, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_fput, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_nb_fput, 0, 3, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_put, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_append, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_nb_put, 0, 3, MAY_BE_LONG|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, remote_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, local_filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "FTP_BINARY") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, offset, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_size, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_ftp_mdtm arginfo_ftp_size + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_rename, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, from, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, to, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_delete, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_ftp_site arginfo_ftp_exec + +#define arginfo_ftp_close arginfo_ftp_cdup + +#define arginfo_ftp_quit arginfo_ftp_cdup + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ftp_set_option, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ftp_get_option, 0, 2, MAY_BE_LONG|MAY_BE_BOOL) + ZEND_ARG_OBJ_INFO(0, ftp, FTP\\Connection, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) +ZEND_END_ARG_INFO() + diff --git a/stubs/php_swoole_http2_client_coro.stub.php b/ext-src/stubs/php_swoole_http2_client_coro.stub.php similarity index 100% rename from stubs/php_swoole_http2_client_coro.stub.php rename to ext-src/stubs/php_swoole_http2_client_coro.stub.php diff --git a/stubs/php_swoole_http2_client_coro_arginfo.h b/ext-src/stubs/php_swoole_http2_client_coro_arginfo.h similarity index 100% rename from stubs/php_swoole_http2_client_coro_arginfo.h rename to ext-src/stubs/php_swoole_http2_client_coro_arginfo.h diff --git a/ext-src/stubs/php_swoole_http_client_coro.stub.php b/ext-src/stubs/php_swoole_http_client_coro.stub.php new file mode 100644 index 0000000000..d0533eb394 --- /dev/null +++ b/ext-src/stubs/php_swoole_http_client_coro.stub.php @@ -0,0 +1,73 @@ +gs->event_workers.workers; + workers = _server->get_event_worker_pool()->workers; worker_num = _server->worker_num; } else { - workers = _server->gs->task_workers.workers; + workers = _server->get_task_worker_pool()->workers; worker_num = _server->task_worker_num; } @@ -66,7 +66,7 @@ static std::string handle_get_all_unix_sockets(Server *_server, const std::strin {"events", master_socket->events}, {"total_recv_bytes", master_socket->total_recv_bytes}, {"total_send_bytes", master_socket->total_send_bytes}, - {"out_buffer_size", master_socket->out_buffer ? master_socket->out_buffer->length() : 0}, + {"out_buffer_size", master_socket->get_out_buffer_length()}, }); sockets.push_back(master_socket_info); @@ -76,7 +76,7 @@ static std::string handle_get_all_unix_sockets(Server *_server, const std::strin {"events", worker_socket->events}, {"total_recv_bytes", worker_socket->total_recv_bytes}, {"total_send_bytes", worker_socket->total_send_bytes}, - {"out_buffer_size", worker_socket->out_buffer ? worker_socket->out_buffer->length() : 0}, + {"out_buffer_size", worker_socket->get_out_buffer_length()}, }); sockets.push_back(worker_socket_info); } @@ -99,7 +99,6 @@ static std::string handle_get_all_sockets(Server *, const std::string &msg) { json sockets = json::array(); sw_reactor()->foreach_socket([&sockets](int fd, network::Socket *socket) { - network::Address addr{}; if (socket->socket_type > SW_SOCK_UNIX_DGRAM || socket->socket_type < SW_SOCK_TCP) { #ifdef SO_DOMAIN struct stat fdstat; @@ -112,29 +111,26 @@ static std::string handle_get_all_sockets(Server *, const std::string &msg) { if (socket->get_option(SOL_SOCKET, SO_DOMAIN, &domain) < 0) { return; } - int type; - if (socket->get_option(SOL_SOCKET, SO_TYPE, &type) < 0) { + int _type; + if (socket->get_option(SOL_SOCKET, SO_TYPE, &_type) < 0) { return; } - addr.type = network::Socket::convert_to_type(domain, type); - socket->get_name(&addr); + socket->get_name(); } #else return; #endif - } else { - addr = socket->info; } json info = json::object({ - {"fd", socket->fd}, - {"address", addr.get_ip()}, - {"port", addr.get_port()}, + {"fd", socket->get_fd()}, + {"address", socket->get_addr()}, + {"port", socket->get_port()}, {"events", socket->events}, {"socket_type", socket->socket_type}, {"fd_type", socket->fd_type}, {"total_recv_bytes", socket->total_recv_bytes}, {"total_send_bytes", socket->total_send_bytes}, - {"out_buffer_size", socket->out_buffer ? socket->out_buffer->length() : 0}, + {"out_buffer_size", socket->get_out_buffer_length()}, }); sockets.push_back(info); }); @@ -148,7 +144,7 @@ static std::string handle_get_all_sockets(Server *, const std::string &msg) { static std::string handle_get_all_commands(Server *serv, const std::string &msg) { json command_list = json::array(); - for (auto kv : serv->commands) { + for (auto &kv : serv->commands) { json info = json::object({ {"id", kv.second.id}, {"name", kv.second.name}, @@ -165,7 +161,7 @@ static std::string handle_get_all_commands(Server *serv, const std::string &msg) #ifdef TCP_INFO static json get_socket_info(int fd) { - struct tcp_info info; + tcp_info info; socklen_t len = sizeof(info); if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len) < 0) { json return_value{ @@ -174,40 +170,12 @@ static json get_socket_info(int fd) { }; return return_value.dump(); } - json jinfo{ - {"state", info.tcpi_state}, - {"ca_state", info.tcpi_ca_state}, - {"retransmits", info.tcpi_retransmits}, - {"probes", info.tcpi_probes}, - {"backoff", info.tcpi_backoff}, - {"options", info.tcpi_options}, - {"snd_wscale", uint8_t(info.tcpi_snd_wscale)}, - {"rcv_wscale", uint8_t(info.tcpi_rcv_wscale)}, - {"rto", info.tcpi_rto}, - {"ato", info.tcpi_ato}, - {"snd_mss", info.tcpi_snd_mss}, - {"rcv_mss", info.tcpi_rcv_mss}, - {"unacked", info.tcpi_unacked}, - {"sacked", info.tcpi_sacked}, - {"lost", info.tcpi_lost}, - {"retrans", info.tcpi_retrans}, - {"fackets", info.tcpi_fackets}, - {"last_data_sent", info.tcpi_last_data_sent}, - {"last_ack_sent", info.tcpi_last_ack_sent}, - {"last_data_recv", info.tcpi_last_data_recv}, - {"last_ack_recv", info.tcpi_last_ack_recv}, - {"pmtu", info.tcpi_pmtu}, - {"rcv_ssthresh", info.tcpi_rcv_ssthresh}, - {"rtt", info.tcpi_rtt}, - {"rttvar", info.tcpi_rttvar}, - {"snd_ssthresh", info.tcpi_snd_ssthresh}, - {"snd_cwnd", info.tcpi_snd_cwnd}, - {"advmss", info.tcpi_advmss}, - {"reordering", info.tcpi_reordering}, - {"rcv_rtt", info.tcpi_rcv_rtt}, - {"rcv_space", info.tcpi_rcv_space}, - {"total_retrans", info.tcpi_total_retrans}, - }; + + auto info_map = sw_socket_parse_tcp_info(&info); + json jinfo; + for (const auto &iter : info_map) { + jinfo[iter.first] = iter.second; + } return jinfo; } #endif @@ -219,8 +187,8 @@ static json get_connection_info(Server *serv, Connection *conn) { {"reactor_id", conn->reactor_id}, {"fd", conn->fd}, {"server_port", - std::string(server_socket->info.get_ip()) + ":" + std::to_string(server_socket->info.get_port())}, - {"address", conn->info.get_ip()}, + std::string(server_socket->info.get_addr()) + ":" + std::to_string(server_socket->info.get_port())}, + {"address", conn->info.get_addr()}, {"port", conn->info.get_port()}, {"overflow", conn->overflow}, {"connect_time", conn->connect_time}, @@ -253,7 +221,7 @@ static std::string handle_get_socket_info(Server *serv, const std::string &msg) }; #else std::string _fd = _result["fd"]; - int fd = std::atoi(_fd.c_str()); + int fd = sw_atoi(_fd.c_str()); json return_value{ {"data", get_socket_info(fd)}, {"code", 0}, @@ -281,7 +249,7 @@ static std::string handle_get_thread_info(Server *serv, const std::string &msg) } static std::string handle_get_manager_info(Server *serv, const std::string &msg) { - ProcessPool *pool = (ProcessPool *) &serv->gs->event_workers; + ProcessPool *pool = serv->get_event_worker_pool(); json jinfo{ {"pid", getpid()}, {"reload_count", pool->reload_count}, @@ -299,10 +267,8 @@ static size_t get_socket_out_buffer_total_size() { return 0; } size_t size = 0; - for (auto s : sw_reactor()->get_sockets()) { - if (s.second->out_buffer) { - size += s.second->out_buffer->length(); - } + for (auto &s : sw_reactor()->get_sockets()) { + size += s.second->get_out_buffer_length(); } return size; } @@ -337,7 +303,7 @@ static std::string handle_get_connections(Server *serv, const std::string &msg) if (serv->is_process_mode() && conn->reactor_id != SwooleTG.id) { return; } - if (serv->is_base_mode() && SwooleWG.worker && conn->reactor_id != SwooleWG.worker->id) { + if (serv->is_base_mode() && sw_worker() && conn->reactor_id != sw_worker()->id) { return; } list.push_back(get_connection_info(serv, conn)); @@ -360,7 +326,7 @@ static std::string handle_get_connection_info(Server *serv, const std::string &m } std::string _session_id = _result["session_id"]; - int session_id = std::atoi(_session_id.c_str()); + int session_id = sw_atoi(_session_id.c_str()); Connection *conn = serv->get_connection_verify(session_id); if (!conn) { json return_value{ @@ -387,7 +353,7 @@ static std::string handle_get_all_ports(Server *serv, const std::string &msg) { {"type", port->type}, {"ssl", port->ssl}, {"protocols", port->get_protocols()}, - {"connection_num", (long) port->gs->connection_num}, + {"connection_num", (long) port->get_connection_num()}, }); _list.push_back(info); }; @@ -441,6 +407,150 @@ static uint32_t object_store_count() { return count; } +#ifdef TCP_INFO +// clang-format off +std::unordered_map sw_socket_parse_tcp_info(tcp_info *info) { +#if defined(__FreeBSD__) || defined(__NetBSD__) + return { + {"state", info->tcpi_state}, + {"ca_state", info->__tcpi_ca_state}, + {"retransmits", info->__tcpi_retransmits}, + {"probes", info->__tcpi_probes}, + {"backoff", info->__tcpi_backoff}, + {"options", info->tcpi_options}, + {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, + {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, + {"rto", info->tcpi_rto}, + {"ato", info->__tcpi_ato}, + {"snd_mss", info->tcpi_snd_mss}, + {"rcv_mss", info->tcpi_rcv_mss}, + {"unacked", info->__tcpi_unacked}, + {"sacked", info->__tcpi_sacked}, + {"lost", info->__tcpi_lost}, + {"retrans", info->__tcpi_retrans}, + {"fackets", info->__tcpi_fackets}, + {"last_data_sent", info->__tcpi_last_data_sent}, + {"last_ack_sent", info->__tcpi_last_ack_sent}, + {"last_data_recv", info->tcpi_last_data_recv}, + {"last_ack_recv", info->__tcpi_last_ack_recv}, + {"pmtu", info->__tcpi_pmtu}, + {"rcv_ssthresh", info->__tcpi_rcv_ssthresh}, + {"rtt", info->tcpi_rtt}, + {"rttvar", info->tcpi_rttvar}, + {"snd_ssthresh", info->tcpi_snd_ssthresh}, + {"snd_cwnd", info->tcpi_snd_cwnd}, + {"advmss", info->__tcpi_advmss}, + {"reordering", info->__tcpi_reordering}, + {"rcv_rtt", info->__tcpi_rcv_rtt}, + {"rcv_space", info->tcpi_rcv_space}, + {"snd_wnd", info->tcpi_snd_wnd}, + {"snd_nxt", info->tcpi_snd_nxt}, + {"rcv_nxt", info->tcpi_rcv_nxt}, + {"toe_tid", info->tcpi_toe_tid}, + {"total_retrans", info->tcpi_snd_rexmitpack}, + {"rcv_ooopack", info->tcpi_rcv_ooopack}, + {"snd_zerowin", info->tcpi_snd_zerowin}, + }; +#elif defined(__OpenBSD__) + return { + {"state", info->tcpi_state}, + {"ca_state", info->__tcpi_ca_state}, + {"retransmits", info->__tcpi_retransmits}, + {"probes", info->__tcpi_probes}, + {"backoff", info->__tcpi_backoff}, + {"options", info->tcpi_options}, + {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, + {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, + {"rto", info->tcpi_rto}, + {"ato", info->__tcpi_ato}, + {"snd_mss", info->tcpi_snd_mss}, + {"rcv_mss", info->tcpi_rcv_mss}, + {"unacked", info->__tcpi_unacked}, + {"sacked", info->__tcpi_sacked}, + {"lost", info->__tcpi_lost}, + {"retrans", info->__tcpi_retrans}, + {"fackets", info->__tcpi_fackets}, + {"last_data_sent", info->tcpi_last_data_sent}, + {"last_ack_sent", info->tcpi_last_ack_sent}, + {"last_data_recv", info->tcpi_last_data_recv}, + {"last_ack_recv", info->tcpi_last_ack_recv}, + {"pmtu", info->__tcpi_pmtu}, + {"rcv_ssthresh", info->__tcpi_rcv_ssthresh}, + {"rtt", info->tcpi_rtt}, + {"rttvar", info->tcpi_rttvar}, + {"snd_ssthresh", info->tcpi_snd_ssthresh}, + {"snd_cwnd", info->tcpi_snd_cwnd}, + {"advmss", info->__tcpi_advmss}, + {"reordering", info->__tcpi_reordering}, + {"rcv_rtt", info->__tcpi_rcv_rtt}, + {"rcv_space", info->tcpi_rcv_space}, + {"snd_wnd", info->tcpi_snd_wnd}, + {"snd_nxt", info->tcpi_snd_nxt}, + {"rcv_nxt", info->tcpi_rcv_nxt}, + {"toe_tid", info->tcpi_toe_tid}, + {"total_retrans", info->tcpi_snd_rexmitpack}, + {"rcv_ooopack", info->tcpi_rcv_ooopack}, + {"snd_zerowin", info->tcpi_snd_zerowin}, + }; +#elif defined(__linux__) + return { + {"state", info->tcpi_state}, + {"ca_state", info->tcpi_ca_state}, + {"retransmits", info->tcpi_retransmits}, + {"probes", info->tcpi_probes}, + {"backoff", info->tcpi_backoff}, + {"options", info->tcpi_options}, + {"snd_wscale", uint8_t(info->tcpi_snd_wscale)}, + {"rcv_wscale", uint8_t(info->tcpi_rcv_wscale)}, + {"rto", info->tcpi_rto}, + {"ato", info->tcpi_ato}, + {"snd_mss", info->tcpi_snd_mss}, + {"rcv_mss", info->tcpi_rcv_mss}, + {"unacked", info->tcpi_unacked}, + {"sacked", info->tcpi_sacked}, + {"lost", info->tcpi_lost}, + {"retrans", info->tcpi_retrans}, + {"fackets", info->tcpi_fackets}, + {"last_data_sent", info->tcpi_last_data_sent}, + {"last_ack_sent", info->tcpi_last_ack_sent}, + {"last_data_recv", info->tcpi_last_data_recv}, + {"last_ack_recv", info->tcpi_last_ack_recv}, + {"pmtu", info->tcpi_pmtu}, + {"rcv_ssthresh", info->tcpi_rcv_ssthresh}, + {"rtt", info->tcpi_rtt}, + {"rttvar", info->tcpi_rttvar}, + {"snd_ssthresh", info->tcpi_snd_ssthresh}, + {"snd_cwnd", info->tcpi_snd_cwnd}, + {"advmss", info->tcpi_advmss}, + {"reordering", info->tcpi_reordering}, + {"rcv_rtt", info->tcpi_rcv_rtt}, + {"rcv_space", info->tcpi_rcv_space}, + {"total_retrans", info->tcpi_total_retrans}, + }; +#elif defined(__APPLE__) + return { + {"state", (uint32_t) info->tcpi_state}, + {"snd_wscale", (uint32_t) info->tcpi_snd_wscale}, + {"rcv_wscale", (uint32_t) info->tcpi_rcv_wscale}, + {"options", (uint32_t) info->tcpi_options}, + {"flags", (uint32_t) info->tcpi_flags}, + {"rto", info->tcpi_rto}, + {"maxseg", info->tcpi_maxseg}, + {"snd_ssthresh", info->tcpi_snd_ssthresh}, + {"snd_cwnd", info->tcpi_snd_cwnd}, + {"snd_wnd", info->tcpi_snd_wnd}, + {"snd_sbbytes", info->tcpi_snd_sbbytes}, + {"rcv_wnd", info->tcpi_rcv_wnd}, + {"srtt", info->tcpi_srtt}, + {"rttvar", info->tcpi_rttvar}, + }; +#else + return {}; +#endif +} +// clang-format on +#endif + ZEND_FUNCTION(swoole_get_vm_status) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("object_num"), object_store_count()); diff --git a/ext-src/swoole_async_coro.cc b/ext-src/swoole_async_coro.cc index af7240ad6b..53216f0077 100644 --- a/ext-src/swoole_async_coro.cc +++ b/ext-src/swoole_async_coro.cc @@ -26,64 +26,74 @@ using std::vector; using swoole::Coroutine; using swoole::PHPCoroutine; using swoole::Timer; -using swoole::coroutine::Socket; struct DNSCacheEntity { char address[INET6_ADDRSTRLEN]; time_t update_time; }; -static std::unordered_map request_cache_map; +static SW_THREAD_LOCAL std::unordered_map request_cache_map; void php_swoole_async_coro_rshutdown() { - for (auto i = request_cache_map.begin(); i != request_cache_map.end(); i++) { + for (auto i = request_cache_map.begin(); i != request_cache_map.end(); ++i) { efree(i->second); } } -void php_swoole_set_aio_option(HashTable *vht) { +void php_swoole_set_aio_option(const HashTable *vht) { zval *ztmp; /* AIO */ - if (php_swoole_array_get_value(vht, "aio_core_worker_num", ztmp)) { - zend_long v = zval_get_long(ztmp); - v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); - SwooleG.aio_core_worker_num = v; - } - if (php_swoole_array_get_value(vht, "aio_worker_num", ztmp)) { - zend_long v = zval_get_long(ztmp); - v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); - SwooleG.aio_worker_num = v; - } - if (php_swoole_array_get_value(vht, "aio_max_wait_time", ztmp)) { - SwooleG.aio_max_wait_time = zval_get_double(ztmp); - } - if (php_swoole_array_get_value(vht, "aio_max_idle_time", ztmp)) { - SwooleG.aio_max_idle_time = zval_get_double(ztmp); - } + if (php_swoole_array_get_value(vht, "aio_core_worker_num", ztmp)) { + zend_long v = zval_get_long(ztmp); + v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); + SwooleG.aio_core_worker_num = v; + } + if (php_swoole_array_get_value(vht, "aio_worker_num", ztmp)) { + zend_long v = zval_get_long(ztmp); + v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); + SwooleG.aio_worker_num = v; + } + if (php_swoole_array_get_value(vht, "aio_max_wait_time", ztmp)) { + SwooleG.aio_max_wait_time = zval_get_double(ztmp); + } + if (php_swoole_array_get_value(vht, "aio_max_idle_time", ztmp)) { + SwooleG.aio_max_idle_time = zval_get_double(ztmp); + } +#ifdef SW_USE_IOURING + if (php_swoole_array_get_value(vht, "iouring_entries", ztmp)) { + zend_long v = zval_get_long(ztmp); + SwooleG.iouring_entries = SW_MAX(0, SW_MIN(v, UINT32_MAX)); + } + if (php_swoole_array_get_value(vht, "iouring_workers", ztmp)) { + zend_long v = zval_get_long(ztmp); + SwooleG.iouring_workers = SW_MAX(0, SW_MIN(v, UINT32_MAX)); + } + if (php_swoole_array_get_value(vht, "iouring_flag", ztmp)) { + SwooleG.iouring_flag = zval_get_long(ztmp); + } +#endif } PHP_FUNCTION(swoole_async_set) { + SW_MUST_BE_MAIN_THREAD(); if (sw_reactor()) { php_swoole_fatal_error(E_ERROR, "eventLoop has already been created. unable to change settings"); + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); RETURN_FALSE; } zval *zset = nullptr; - HashTable *vht; zval *ztmp; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - vht = Z_ARRVAL_P(zset); + HashTable *vht = Z_ARRVAL_P(zset); php_swoole_set_global_option(vht); php_swoole_set_aio_option(vht); - if (php_swoole_array_get_value(vht, "enable_signalfd", ztmp)) { - SwooleG.enable_signalfd = zval_is_true(ztmp); - } if (php_swoole_array_get_value(vht, "wait_signal", ztmp)) { SwooleG.wait_signal = zval_is_true(ztmp); } @@ -101,9 +111,6 @@ PHP_FUNCTION(swoole_async_set) { v = SW_MAX(1, SW_MIN(v, UINT32_MAX)); SwooleG.aio_worker_num = v; } - if (php_swoole_array_get_value(vht, "socket_dontwait", ztmp)) { - SwooleG.socket_dontwait = zval_is_true(ztmp); - } if (php_swoole_array_get_value(vht, "dns_lookup_random", ztmp)) { SwooleG.dns_lookup_random = zval_is_true(ztmp); } @@ -111,19 +118,24 @@ PHP_FUNCTION(swoole_async_set) { SwooleG.use_async_resolver = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { - SWOOLE_G(enable_coroutine) = zval_is_true(ztmp); + SwooleG.enable_coroutine = zval_is_true(ztmp); } + RETURN_TRUE; } PHP_FUNCTION(swoole_async_dns_lookup_coro) { Coroutine::get_current_safe(); zval *domain; - long type = AF_INET; + zend_long type = AF_INET; double timeout = swoole::network::Socket::default_dns_timeout; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|dl", &domain, &timeout, &type) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_ZVAL(domain) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + Z_PARAM_LONG(type) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (Z_TYPE_P(domain) != IS_STRING) { php_swoole_fatal_error(E_WARNING, "invalid domain name"); @@ -155,7 +167,7 @@ PHP_FUNCTION(swoole_async_dns_lookup_coro) { } if (SwooleG.dns_lookup_random) { - RETVAL_STRING(result[rand() % result.size()].c_str()); + RETVAL_STRING(result[swoole_rand() % result.size()].c_str()); } else { RETVAL_STRING(result[0].c_str()); } @@ -169,5 +181,5 @@ PHP_FUNCTION(swoole_async_dns_lookup_coro) { } memcpy(cache->address, Z_STRVAL_P(return_value), Z_STRLEN_P(return_value)); cache->address[Z_STRLEN_P(return_value)] = '\0'; - cache->update_time = Timer::get_absolute_msec() + (int64_t)(SwooleG.dns_cache_refresh_time * 1000); + cache->update_time = Timer::get_absolute_msec() + (int64_t) (SwooleG.dns_cache_refresh_time * 1000); } diff --git a/ext-src/swoole_atomic.cc b/ext-src/swoole_atomic.cc index e702a2b343..03f1b01834 100644 --- a/ext-src/swoole_atomic.cc +++ b/ext-src/swoole_atomic.cc @@ -21,68 +21,6 @@ BEGIN_EXTERN_C() #include "stubs/php_swoole_atomic_arginfo.h" END_EXTERN_C() -#ifdef HAVE_FUTEX -#include -#include - -static sw_inline int swoole_futex_wait(sw_atomic_t *atomic, double timeout) { - if (sw_atomic_cmp_set(atomic, 1, 0)) { - return SW_OK; - } - - int ret; - struct timespec _timeout; - - if (timeout > 0) { - _timeout.tv_sec = (long) timeout; - _timeout.tv_nsec = (timeout - _timeout.tv_sec) * 1000 * 1000 * 1000; - ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, &_timeout, nullptr, 0); - } else { - ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, nullptr, nullptr, 0); - } - if (ret == SW_OK && sw_atomic_cmp_set(atomic, 1, 0)) { - return SW_OK; - } else { - return SW_ERR; - } -} - -static sw_inline int swoole_futex_wakeup(sw_atomic_t *atomic, int n) { - if (sw_atomic_cmp_set(atomic, 0, 1)) { - return syscall(SYS_futex, atomic, FUTEX_WAKE, n, nullptr, nullptr, 0); - } else { - return SW_OK; - } -} - -#else -static sw_inline int swoole_atomic_wait(sw_atomic_t *atomic, double timeout) { - if (sw_atomic_cmp_set(atomic, (sw_atomic_t) 1, (sw_atomic_t) 0)) { - return SW_OK; - } - timeout = timeout <= 0 ? INT_MAX : timeout; - int32_t i = (int32_t) sw_atomic_sub_fetch(atomic, 1); - while (timeout > 0) { - if ((int32_t) *atomic > i) { - return SW_OK; - } else { - usleep(1000); - timeout -= 0.001; - } - } - sw_atomic_fetch_add(atomic, 1); - return SW_ERR; -} - -static sw_inline int swoole_atomic_wakeup(sw_atomic_t *atomic, int n) { - if (1 == (int32_t) *atomic) { - return SW_OK; - } - sw_atomic_fetch_add(atomic, n); - return SW_OK; -} -#endif - zend_class_entry *swoole_atomic_ce; static zend_object_handlers swoole_atomic_handlers; @@ -94,25 +32,21 @@ struct AtomicObject { zend_object std; }; -static sw_inline AtomicObject *php_swoole_atomic_fetch_object(zend_object *obj) { - return (AtomicObject *) ((char *) obj - swoole_atomic_handlers.offset); +static sw_inline AtomicObject *atomic_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_atomic_handlers.offset); } -static sw_atomic_t *php_swoole_atomic_get_ptr(zval *zobject) { - return php_swoole_atomic_fetch_object(Z_OBJ_P(zobject))->ptr; +static sw_atomic_t *atomic_get_ptr(const zval *zobject) { + return atomic_fetch_object(Z_OBJ_P(zobject))->ptr; } -void php_swoole_atomic_set_ptr(zval *zobject, sw_atomic_t *ptr) { - php_swoole_atomic_fetch_object(Z_OBJ_P(zobject))->ptr = ptr; -} - -static void php_swoole_atomic_free_object(zend_object *object) { - sw_mem_pool()->free((void *) php_swoole_atomic_fetch_object(object)->ptr); +static void atomic_free_object(zend_object *object) { + sw_mem_pool()->free((void *) atomic_fetch_object(object)->ptr); zend_object_std_dtor(object); } -static zend_object *php_swoole_atomic_create_object(zend_class_entry *ce) { - AtomicObject *atomic = (AtomicObject *) zend_object_alloc(sizeof(AtomicObject), ce); +static zend_object *atomic_create_object(zend_class_entry *ce) { + auto *atomic = static_cast(zend_object_alloc(sizeof(AtomicObject), ce)); if (atomic == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } @@ -133,25 +67,21 @@ struct AtomicLongObject { zend_object std; }; -static sw_inline AtomicLongObject *php_swoole_atomic_long_fetch_object(zend_object *obj) { - return (AtomicLongObject *) ((char *) obj - swoole_atomic_long_handlers.offset); -} - -static sw_atomic_long_t *php_swoole_atomic_long_get_ptr(zval *zobject) { - return php_swoole_atomic_long_fetch_object(Z_OBJ_P(zobject))->ptr; +static AtomicLongObject *atomic_long_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_atomic_long_handlers.offset); } -void php_swoole_atomic_long_set_ptr(zval *zobject, sw_atomic_long_t *ptr) { - php_swoole_atomic_long_fetch_object(Z_OBJ_P(zobject))->ptr = ptr; +static sw_atomic_long_t *atomic_long_get_ptr(const zval *zobject) { + return atomic_long_fetch_object(Z_OBJ_P(zobject))->ptr; } -static void php_swoole_atomic_long_free_object(zend_object *object) { - sw_mem_pool()->free((void *) php_swoole_atomic_long_fetch_object(object)->ptr); +static void atomic_long_free_object(zend_object *object) { + sw_mem_pool()->free((void *) atomic_long_fetch_object(object)->ptr); zend_object_std_dtor(object); } -static zend_object *php_swoole_atomic_long_create_object(zend_class_entry *ce) { - AtomicLongObject *atomic_long = (AtomicLongObject *) zend_object_alloc(sizeof(AtomicLongObject), ce); +static zend_object *atomic_long_create_object(zend_class_entry *ce) { + auto *atomic_long = static_cast(zend_object_alloc(sizeof(AtomicLongObject), ce)); if (atomic_long == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); } @@ -219,22 +149,18 @@ void php_swoole_atomic_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_atomic); SW_SET_CLASS_CLONEABLE(swoole_atomic, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_atomic, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_atomic, php_swoole_atomic_create_object, php_swoole_atomic_free_object, AtomicObject, std); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_atomic, atomic_create_object, atomic_free_object, AtomicObject, std); SW_INIT_CLASS_ENTRY(swoole_atomic_long, "Swoole\\Atomic\\Long", nullptr, swoole_atomic_long_methods); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_atomic_long); SW_SET_CLASS_CLONEABLE(swoole_atomic_long, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_atomic_long, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_atomic_long, - php_swoole_atomic_long_create_object, - php_swoole_atomic_long_free_object, - AtomicLongObject, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_atomic_long, atomic_long_create_object, atomic_long_free_object, AtomicLongObject, std); } PHP_METHOD(swoole_atomic, __construct) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long value = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) @@ -246,7 +172,7 @@ PHP_METHOD(swoole_atomic, __construct) { } PHP_METHOD(swoole_atomic, add) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -258,7 +184,7 @@ PHP_METHOD(swoole_atomic, add) { } PHP_METHOD(swoole_atomic, sub) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -270,12 +196,12 @@ PHP_METHOD(swoole_atomic, sub) { } PHP_METHOD(swoole_atomic, get) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); RETURN_LONG(*atomic); } PHP_METHOD(swoole_atomic, set) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -286,7 +212,7 @@ PHP_METHOD(swoole_atomic, set) { } PHP_METHOD(swoole_atomic, cmpset) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) @@ -298,7 +224,7 @@ PHP_METHOD(swoole_atomic, cmpset) { } PHP_METHOD(swoole_atomic, wait) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); double timeout = 1.0; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -306,15 +232,11 @@ PHP_METHOD(swoole_atomic, wait) { Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); -#ifdef HAVE_FUTEX - SW_CHECK_RETURN(swoole_futex_wait(atomic, timeout)); -#else - SW_CHECK_RETURN(swoole_atomic_wait(atomic, timeout)); -#endif + SW_CHECK_RETURN(sw_atomic_futex_wait(atomic, timeout)); } PHP_METHOD(swoole_atomic, wakeup) { - sw_atomic_t *atomic = php_swoole_atomic_get_ptr(ZEND_THIS); + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); zend_long n = 1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -322,15 +244,11 @@ PHP_METHOD(swoole_atomic, wakeup) { Z_PARAM_LONG(n) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); -#ifdef HAVE_FUTEX - SW_CHECK_RETURN(swoole_futex_wakeup(atomic, (int) n)); -#else - SW_CHECK_RETURN(swoole_atomic_wakeup(atomic, n)); -#endif + SW_CHECK_RETURN(sw_atomic_futex_wakeup(atomic, (int) n)); } PHP_METHOD(swoole_atomic_long, __construct) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long value = 0; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -343,7 +261,7 @@ PHP_METHOD(swoole_atomic_long, __construct) { } PHP_METHOD(swoole_atomic_long, add) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long add_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -355,7 +273,7 @@ PHP_METHOD(swoole_atomic_long, add) { } PHP_METHOD(swoole_atomic_long, sub) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long sub_value = 1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -367,12 +285,12 @@ PHP_METHOD(swoole_atomic_long, sub) { } PHP_METHOD(swoole_atomic_long, get) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); RETURN_LONG(*atomic_long); } PHP_METHOD(swoole_atomic_long, set) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long set_value; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -383,7 +301,7 @@ PHP_METHOD(swoole_atomic_long, set) { } PHP_METHOD(swoole_atomic_long, cmpset) { - sw_atomic_long_t *atomic_long = php_swoole_atomic_long_get_ptr(ZEND_THIS); + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); zend_long cmp_value, set_value; ZEND_PARSE_PARAMETERS_START(2, 2) diff --git a/ext-src/swoole_channel_coro.cc b/ext-src/swoole_channel_coro.cc index ec20ae7b7c..75bb2eb96d 100644 --- a/ext-src/swoole_channel_coro.cc +++ b/ext-src/swoole_channel_coro.cc @@ -61,22 +61,22 @@ static const zend_function_entry swoole_channel_coro_methods[] = }; // clang-format on -static sw_inline ChannelObject *php_swoole_channel_coro_fetch_object(zend_object *obj) { - return (ChannelObject *) ((char *) obj - swoole_channel_coro_handlers.offset); +static ChannelObject *channel_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_channel_coro_handlers.offset); } -static sw_inline Channel *php_swoole_get_channel(zval *zobject) { - Channel *chan = php_swoole_channel_coro_fetch_object(Z_OBJ_P(zobject))->chan; +static Channel *channel_coro_get_ptr(const zval *zobject) { + Channel *chan = channel_coro_fetch_object(Z_OBJ_P(zobject))->chan; if (UNEXPECTED(!chan)) { - php_swoole_fatal_error(E_ERROR, "you must call Channel constructor first"); + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return chan; } -static void php_swoole_channel_coro_dtor_object(zend_object *object) { +static void channel_coro_dtor_object(zend_object *object) { zend_objects_destroy_object(object); - ChannelObject *chan_object = php_swoole_channel_coro_fetch_object(object); + ChannelObject *chan_object = channel_coro_fetch_object(object); Channel *chan = chan_object->chan; if (chan) { zval *data; @@ -88,17 +88,14 @@ static void php_swoole_channel_coro_dtor_object(zend_object *object) { } } -static void php_swoole_channel_coro_free_object(zend_object *object) { - ChannelObject *chan_object = php_swoole_channel_coro_fetch_object(object); - Channel *chan = chan_object->chan; - if (chan) { - delete chan; - } +static void channel_coro_free_object(zend_object *object) { + auto *chan_object = channel_coro_fetch_object(object); + delete chan_object->chan; zend_object_std_dtor(object); } -static zend_object *php_swoole_channel_coro_create_object(zend_class_entry *ce) { - ChannelObject *chan_object = (ChannelObject *) zend_object_alloc(sizeof(ChannelObject), ce); +static zend_object *channel_coro_create_object(zend_class_entry *ce) { + auto *chan_object = static_cast(zend_object_alloc(sizeof(ChannelObject), ce)); zend_object_std_init(&chan_object->std, ce); object_properties_init(&chan_object->std, ce); chan_object->std.handlers = &swoole_channel_coro_handlers; @@ -110,12 +107,9 @@ void php_swoole_channel_coro_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_channel_coro); SW_SET_CLASS_CLONEABLE(swoole_channel_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_channel_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_channel_coro, - php_swoole_channel_coro_create_object, - php_swoole_channel_coro_free_object, - ChannelObject, - std); - SW_SET_CLASS_DTOR(swoole_channel_coro, php_swoole_channel_coro_dtor_object); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_channel_coro, channel_coro_create_object, channel_coro_free_object, ChannelObject, std); + SW_SET_CLASS_DTOR(swoole_channel_coro, channel_coro_dtor_object); if (SWOOLE_G(use_shortname)) { SW_CLASS_ALIAS("Chan", swoole_channel_coro); } @@ -141,13 +135,13 @@ static PHP_METHOD(swoole_channel_coro, __construct) { capacity = 1; } - ChannelObject *chan_t = php_swoole_channel_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); + ChannelObject *chan_t = channel_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); chan_t->chan = new Channel(capacity); zend_update_property_long(swoole_channel_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("capacity"), capacity); } static PHP_METHOD(swoole_channel_coro, push) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); zval *zdata; double timeout = -1; @@ -173,7 +167,7 @@ static PHP_METHOD(swoole_channel_coro, push) { } static PHP_METHOD(swoole_channel_coro, pop) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); double timeout = -1; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) @@ -195,27 +189,27 @@ static PHP_METHOD(swoole_channel_coro, pop) { } static PHP_METHOD(swoole_channel_coro, close) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->close()); } static PHP_METHOD(swoole_channel_coro, length) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_LONG(chan->length()); } static PHP_METHOD(swoole_channel_coro, isEmpty) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->is_empty()); } static PHP_METHOD(swoole_channel_coro, isFull) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); RETURN_BOOL(chan->is_full()); } static PHP_METHOD(swoole_channel_coro, stats) { - Channel *chan = php_swoole_get_channel(ZEND_THIS); + Channel *chan = channel_coro_get_ptr(ZEND_THIS); array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("consumer_num"), chan->consumer_num()); add_assoc_long_ex(return_value, ZEND_STRL("producer_num"), chan->producer_num()); diff --git a/ext-src/swoole_client.cc b/ext-src/swoole_client.cc index 7a951e2513..f861adb057 100644 --- a/ext-src/swoole_client.cc +++ b/ext-src/swoole_client.cc @@ -29,81 +29,51 @@ END_EXTERN_C() using swoole::HttpProxy; using swoole::PacketLength; using swoole::Protocol; +using swoole::SocketType; using swoole::Socks5Proxy; using swoole::String; +using swoole::network::Address; using swoole::network::Client; using swoole::network::Socket; -struct ClientCallback { - zend_fcall_info_cache cache_onConnect; - zend_fcall_info_cache cache_onReceive; - zend_fcall_info_cache cache_onClose; - zend_fcall_info_cache cache_onError; - zend_fcall_info_cache cache_onBufferFull; - zend_fcall_info_cache cache_onBufferEmpty; -#ifdef SW_USE_OPENSSL - zend_fcall_info_cache cache_onSSLReady; -#endif - zval _object; -}; - static std::unordered_map *> long_connections; zend_class_entry *swoole_client_ce; -static zend_object_handlers swoole_client_handlers; +zend_object_handlers swoole_client_handlers; static zend_class_entry *swoole_client_exception_ce; static zend_object_handlers swoole_client_exception_handlers; -struct ClientObject { - Client *cli; - zval *zsocket; - ClientCallback *cb; - zend_object std; -}; - -static void php_swoole_client_free(zval *zobject, Client *cli); static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, int port); -static sw_inline ClientObject *php_swoole_client_fetch_object(zend_object *obj) { - return (ClientObject *) ((char *) obj - swoole_client_handlers.offset); -} - -static sw_inline Client *php_swoole_client_get_cli(zval *zobject) { - return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cli; -} - -static sw_inline void php_swoole_client_set_cli(zval *zobject, Client *cli) { +static sw_inline void php_swoole_client_set_cli(const zval *zobject, Client *cli) { php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cli = cli; } #ifdef SWOOLE_SOCKETS_SUPPORT -static sw_inline zval *php_swoole_client_get_zsocket(zval *zobject) { +static zval *client_get_zsocket(const zval *zobject) { return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->zsocket; } -static sw_inline void php_swoole_client_set_zsocket(zval *zobject, zval *zsocket) { +static void client_set_zsocket(const zval *zobject, zval *zsocket) { php_swoole_client_fetch_object(Z_OBJ_P(zobject))->zsocket = zsocket; } #endif -static sw_inline ClientCallback *php_swoole_client_get_cb(zval *zobject) { - return php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cb; -} - -static sw_inline void php_swoole_client_set_cb(zval *zobject, ClientCallback *cb) { - php_swoole_client_fetch_object(Z_OBJ_P(zobject))->cb = cb; -} - -static void php_swoole_client_free_object(zend_object *object) { +static void client_free_object(zend_object *object) { + auto client_obj = php_swoole_client_fetch_object(object); + if (client_obj->async) { + php_swoole_client_async_free_object(client_obj); + } zend_object_std_dtor(object); } -static zend_object *php_swoole_client_create_object(zend_class_entry *ce) { - ClientObject *client = (ClientObject *) zend_object_alloc(sizeof(ClientObject), ce); +static zend_object *client_create_object(zend_class_entry *ce) { + auto *client = static_cast(zend_object_alloc(sizeof(ClientObject), ce)); zend_object_std_init(&client->std, ce); object_properties_init(&client->std, ce); client->std.handlers = &swoole_client_handlers; + client->async = nullptr; return &client->std; } @@ -116,11 +86,9 @@ static PHP_METHOD(swoole_client, recv); static PHP_METHOD(swoole_client, send); static PHP_METHOD(swoole_client, sendfile); static PHP_METHOD(swoole_client, sendto); -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_client, enableSSL); static PHP_METHOD(swoole_client, getPeerCert); static PHP_METHOD(swoole_client, verifyPeerCert); -#endif static PHP_METHOD(swoole_client, isConnected); static PHP_METHOD(swoole_client, getsockname); static PHP_METHOD(swoole_client, getpeername); @@ -132,15 +100,10 @@ static PHP_METHOD(swoole_client, getSocket); #endif SW_EXTERN_C_END -#ifdef PHP_SWOOLE_CLIENT_USE_POLL -static uint32_t client_poll_add(zval *sock_array, uint32_t index, struct pollfd *fds, int maxevents, int event); -static int client_poll_wait(zval *sock_array, struct pollfd *fds, int maxevents, int n_event, int revent); -#else -static int client_select_add(zval *sock_array, fd_set *fds, int *max_fd); -static int client_select_wait(zval *sock_array, fd_set *fds); -#endif +static uint32_t client_poll_add(const zval *sock_array, uint32_t index, pollfd *fds, int maxevents, int event); +static int client_poll_wait(zval *sock_array, const pollfd *fds, int maxevents, int n_event, int revent); -static sw_inline Client *client_get_ptr(zval *zobject) { +Client *php_swoole_client_get_cli_safe(const zval *zobject) { Client *cli = php_swoole_client_get_cli(zobject); if (cli && cli->socket) { if (cli->active) { @@ -151,7 +114,7 @@ static sw_inline Client *client_get_ptr(zval *zobject) { int error = -1; if (cli->get_socket()->get_option(SOL_SOCKET, SO_ERROR, &error) == 0) { if (error == 0) { - cli->active = 1; + cli->active = true; return cli; } } @@ -176,11 +139,9 @@ static const zend_function_entry swoole_client_methods[] = PHP_ME(swoole_client, sendfile, arginfo_class_Swoole_Client_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, sendto, arginfo_class_Swoole_Client_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, shutdown, arginfo_class_Swoole_Client_shutdown, ZEND_ACC_PUBLIC) -#ifdef SW_USE_OPENSSL PHP_ME(swoole_client, enableSSL, arginfo_class_Swoole_Client_enableSSL, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getPeerCert, arginfo_class_Swoole_Client_getPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, verifyPeerCert, arginfo_class_Swoole_Client_verifyPeerCert, ZEND_ACC_PUBLIC) -#endif PHP_ME(swoole_client, isConnected, arginfo_class_Swoole_Client_isConnected, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getsockname, arginfo_class_Swoole_Client_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_client, getpeername, arginfo_class_Swoole_Client_getpeername, ZEND_ACC_PUBLIC) @@ -197,8 +158,7 @@ void php_swoole_client_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client); SW_SET_CLASS_CLONEABLE(swoole_client, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_client, php_swoole_client_create_object, php_swoole_client_free_object, ClientObject, std); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_client, client_create_object, client_free_object, ClientObject, std); SW_INIT_CLASS_ENTRY_EX(swoole_client_exception, "Swoole\\Client\\Exception", nullptr, nullptr, swoole_exception); @@ -218,78 +178,82 @@ void php_swoole_client_minit(int module_number) { zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_RDWR"), SHUT_RDWR); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_RD"), SHUT_RD); zend_declare_class_constant_long(swoole_client_ce, ZEND_STRL("SHUT_WR"), SHUT_WR); + + zend::add_constant("SWOOLE_SYNC", SW_FLAG_SYNC); + zend::add_constant("SWOOLE_ASYNC", SW_FLAG_ASYNC); + zend::add_constant("SWOOLE_KEEP", SW_FLAG_KEEP); } -#ifdef SW_USE_OPENSSL -void php_swoole_client_check_ssl_setting(Client *cli, zval *zset) { +void php_swoole_client_check_ssl_setting(const Client *cli, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { - cli->ssl_context->set_protocols(zval_get_long(ztmp)); + cli->set_ssl_protocols(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { - cli->ssl_context->disable_compress = !zval_is_true(ztmp); + cli->set_ssl_disable_compress(!zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { zend::String str_v(ztmp); - if (!cli->ssl_context->set_cert_file(str_v.to_std_string())) { + if (!cli->set_ssl_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl cert file[%s] not found", str_v.val()); return; } } if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { zend::String str_v(ztmp); - if (!cli->ssl_context->set_key_file(str_v.to_std_string())) { + if (!cli->set_ssl_key_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl key file[%s] not found", str_v.val()); return; } } if (php_swoole_array_get_value(vht, "ssl_passphrase", ztmp)) { zend::String str_v(ztmp); - cli->ssl_context->passphrase = str_v.to_std_string(); + cli->set_ssl_passphrase(str_v.to_std_string()); } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME if (php_swoole_array_get_value(vht, "ssl_host_name", ztmp)) { zend::String str_v(ztmp); - cli->ssl_context->tls_host_name = str_v.to_std_string(); + cli->set_tls_host_name(str_v.to_std_string()); } #endif if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { - cli->ssl_context->verify_peer = zval_is_true(ztmp); + cli->set_ssl_verify_peer(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { - cli->ssl_context->allow_self_signed = zval_is_true(ztmp); + cli->set_ssl_allow_self_signed(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { zend::String str_v(ztmp); - cli->ssl_context->cafile = str_v.to_std_string(); + cli->set_ssl_cafile(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { zend::String str_v(ztmp); - cli->ssl_context->capath = str_v.to_std_string(); + cli->set_ssl_capath(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { zend_long v = zval_get_long(ztmp); - cli->ssl_context->verify_depth = SW_MAX(0, SW_MIN(v, UINT8_MAX)); + cli->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { zend::String str_v(ztmp); - cli->ssl_context->ciphers = str_v.to_std_string(); + cli->set_ssl_ciphers(str_v.to_std_string()); } - if (!cli->ssl_context->cert_file.empty() && cli->ssl_context->key_file.empty()) { + if (!cli->get_ssl_cert_file().empty() && cli->get_ssl_key_file().empty()) { php_swoole_fatal_error(E_ERROR, "ssl require key file"); return; } + if (!cli->get_ssl_key_file().empty() && cli->get_ssl_cert_file().empty()) { + php_swoole_fatal_error(E_ERROR, "ssl require cert file"); + return; + } } -#endif -bool php_swoole_client_check_setting(Client *cli, zval *zset) { - HashTable *vht; +bool php_swoole_client_check_setting(Client *cli, const zval *zset) { zval *ztmp; int value = 1; - - vht = Z_ARRVAL_P(zset); + HashTable *vht = Z_ARRVAL_P(zset); // buffer: eof check if (php_swoole_array_get_value(vht, "open_eof_check", ztmp)) { @@ -299,7 +263,7 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { cli->protocol.split_by_eof = zval_is_true(ztmp); if (cli->protocol.split_by_eof) { - cli->open_eof_check = 1; + cli->open_eof_check = true; } } // package eof @@ -335,7 +299,7 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { if (cli->protocol.package_length_size == 0) { php_swoole_fatal_error(E_ERROR, - "Unknown package_length_type name '%c', see pack(). Link: http://php.net/pack", + "Unknown package_length_type name '%c', see pack(). Link: https://php.net/pack", cli->protocol.package_length_type); return false; } @@ -352,32 +316,15 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { - while (1) { - if (Z_TYPE_P(ztmp) == IS_STRING) { - Protocol::LengthFunc func = Protocol::get_function(std::string(Z_STRVAL_P(ztmp), Z_STRLEN_P(ztmp))); - if (func != nullptr) { - cli->protocol.get_package_length = func; - break; - } - } - - char *func_name; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(ztmp, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return false; - } - efree(func_name); - cli->protocol.get_package_length = php_swoole_length_func; - if (cli->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) cli->protocol.private_data); - efree(cli->protocol.private_data); - } - sw_zend_fci_cache_persist(fci_cache); - cli->protocol.private_data = fci_cache; - break; + auto fci_cache = sw_callable_create(ztmp); + if (!fci_cache) { + return false; } - + cli->protocol.get_package_length = php_swoole_length_func; + if (cli->protocol.private_data_1) { + sw_callable_free(cli->protocol.private_data_1); + } + cli->protocol.private_data_1 = fci_cache; cli->protocol.package_length_size = 0; cli->protocol.package_length_type = '\0'; cli->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; @@ -386,7 +333,7 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); cli->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } else { cli->protocol.package_max_length = SW_INPUT_BUFFER_SIZE; @@ -395,18 +342,18 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { * socket send/recv buffer size */ if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(1, SW_MIN(v, INT_MAX)); cli->socket->set_buffer_size(value); cli->socket->buffer_size = value; } if (php_swoole_array_get_value(vht, "buffer_high_watermark", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(0, SW_MIN(v, UINT32_MAX)); cli->buffer_high_watermark = value; } if (php_swoole_array_get_value(vht, "buffer_low_watermark", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); value = SW_MAX(0, SW_MIN(v, UINT32_MAX)); cli->buffer_low_watermark = value; } @@ -425,9 +372,7 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { if (php_swoole_array_get_value(vht, "bind_address", ztmp)) { bind_address = zend::String(ztmp).to_std_string(); } - if (bind_address.length() > 0 && cli->socket->bind(bind_address, &bind_port) < 0) { - php_swoole_error(E_WARNING, "bind address or port error in set method"); - swoole_set_last_error(errno); + if (!bind_address.empty() && cli->bind(bind_address, bind_port) < 0) { return false; } /** @@ -439,10 +384,10 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { } } else { _open_tcp_nodelay: - if (cli->socket->socket_type == SW_SOCK_TCP || cli->socket->socket_type == SW_SOCK_TCP6) { - if (cli->socket->set_tcp_nodelay() < 0) { - swoole_sys_warning("setsockopt(%d, TCP_NODELAY) failed", cli->socket->fd); - } + // The failure to set tcp_nodelay does not affect the normal operation of the client; + // therefore, only an error log is printed without returning false. + if (cli->socket->is_tcp() && !cli->socket->set_tcp_nodelay()) { + php_swoole_sys_error(E_WARNING, "setsockopt(%d, TCP_NODELAY) failed", cli->socket->fd); } } /** @@ -451,31 +396,20 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { if (php_swoole_array_get_value(vht, "socks5_host", ztmp)) { zend::String host(ztmp); if (php_swoole_array_get_value(vht, "socks5_port", ztmp)) { - if (cli->socks5_proxy == nullptr) { - cli->socks5_proxy = new Socks5Proxy(); - } - cli->socks5_proxy->host = host.to_std_string(); - cli->socks5_proxy->port = zval_get_long(ztmp); - cli->socks5_proxy->dns_tunnel = 1; + auto socks5_port = zval_get_long(ztmp); + std::string username, password; if (php_swoole_array_get_value(vht, "socks5_username", ztmp)) { - zend::String username(ztmp); - if (username.len() > 0 && php_swoole_array_get_value(vht, "socks5_password", ztmp)) { - zend::String password(ztmp); - if (password.len() > 0) { - cli->socks5_proxy->method = 0x02; - cli->socks5_proxy->username = username.to_std_string(); - cli->socks5_proxy->password = password.to_std_string(); - } - } else { - php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); - // because we do not set last errcode, return true - return true; - } + zend::String _value(ztmp); + username = _value.to_std_string(); } + if (php_swoole_array_get_value(vht, "socks5_password", ztmp)) { + zend::String _value(ztmp); + password = _value.to_std_string(); + } + cli->set_socks5_proxy(host.to_std_string(), socks5_port, username, password); } else { php_swoole_fatal_error(E_WARNING, "socks5_port should not be null"); - // because we do not set last errcode, return true - return true; + return false; } } /** @@ -484,62 +418,49 @@ bool php_swoole_client_check_setting(Client *cli, zval *zset) { else if (php_swoole_array_get_value(vht, "http_proxy_host", ztmp)) { zend::String host(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_port", ztmp)) { - if (cli->http_proxy == nullptr) { - cli->http_proxy = new HttpProxy(); - } - cli->http_proxy->proxy_host = host.to_std_string(); - cli->http_proxy->proxy_port = zval_get_long(ztmp); + std::string username, password; + auto http_proxy_port = zval_get_long(ztmp); if (php_swoole_array_get_value(vht, "http_proxy_username", ztmp) || php_swoole_array_get_value(vht, "http_proxy_user", ztmp)) { - zend::String username(ztmp); - if (username.len() > 0 && php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { - zend::String password(ztmp); - if (password.len() > 0) { - cli->http_proxy->username = username.to_std_string(); - cli->http_proxy->password = password.to_std_string(); - } - } else { - php_swoole_fatal_error(E_WARNING, "http_proxy_password should not be null"); - // because we do not set last errcode, return true - return true; - } + zend::String _value(ztmp); + username = _value.to_std_string(); } + if (php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { + zend::String _value(ztmp); + password = _value.to_std_string(); + } + cli->set_http_proxy(host.to_std_string(), http_proxy_port, username, password); } else { php_swoole_fatal_error(E_WARNING, "http_proxy_port should not be null"); - // because we do not set last errcode, return true - return true; + return false; } } /** * ssl */ -#ifdef SW_USE_OPENSSL if (cli->open_ssl) { php_swoole_client_check_ssl_setting(cli, zset); } -#endif return true; } -static void php_swoole_client_free(zval *zobject, Client *cli) { +void php_swoole_client_free(const zval *zobject, Client *cli) { if (cli->timer) { swoole_timer_del(cli->timer); cli->timer = nullptr; } - if (cli->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) cli->protocol.private_data); - efree(cli->protocol.private_data); - cli->protocol.private_data = nullptr; + if (cli->protocol.private_data_1) { + sw_callable_free(cli->protocol.private_data_1); + cli->protocol.private_data_1 = nullptr; } - // long tcp connection, delete from php_sw_long_connections + // long tcp connection, delete from connection pool if (cli->keep) { - std::string conn_key = std::string(cli->server_str, cli->server_strlen); - auto i = long_connections.find(conn_key); + auto i = long_connections.find(cli->server_id); if (i != long_connections.end()) { std::queue *q = i->second; if (q->empty()) { delete q; - long_connections.erase(std::string(cli->server_str, cli->server_strlen)); + long_connections.erase(cli->server_id); } } } @@ -547,10 +468,10 @@ static void php_swoole_client_free(zval *zobject, Client *cli) { delete cli; #ifdef SWOOLE_SOCKETS_SUPPORT - zval *zsocket = php_swoole_client_get_zsocket(zobject); + zval *zsocket = client_get_zsocket(zobject); if (zsocket) { sw_zval_free(zsocket); - php_swoole_client_set_zsocket(zobject, nullptr); + client_set_zsocket(zobject, nullptr); } #endif // unset object @@ -558,14 +479,14 @@ static void php_swoole_client_free(zval *zobject, Client *cli) { } ssize_t php_swoole_length_func(const Protocol *protocol, Socket *_socket, PacketLength *pl) { - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) protocol->private_data; + auto *cb = static_cast(protocol->private_data_1); zval zdata; zval retval; ssize_t ret = -1; // TODO: reduce memory copy ZVAL_STRINGL(&zdata, pl->buf, pl->buf_size); - if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, fci_cache, 1, &zdata, &retval) != SUCCESS)) { + if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, cb->ptr(), 1, &zdata, &retval) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "length function handler error"); } else { ret = zval_get_long(&retval); @@ -584,8 +505,8 @@ static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, in } long type = Z_LVAL_P(ztype); - int client_type = php_swoole_socktype(type); - if ((client_type == SW_SOCK_TCP || client_type == SW_SOCK_TCP6) && (port <= 0 || port > SW_CLIENT_MAX_PORT)) { + int socket_type = php_swoole_client_get_type(type); + if (Socket::is_tcp(static_cast(socket_type)) && !Address::verify_port(port)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); swoole_set_last_error(SW_ERROR_INVALID_PARAMS); return nullptr; @@ -623,7 +544,7 @@ static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, in } } else { _create_socket: - cli = new Client(php_swoole_socktype(type), false); + cli = new Client(php_swoole_client_get_type(type), false); if (cli->socket == nullptr) { php_swoole_sys_error(E_WARNING, "Client_create() failed"); zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); @@ -631,43 +552,39 @@ static Client *php_swoole_client_new(zval *zobject, char *host, int host_len, in return nullptr; } - // don't forget free it - cli->server_str = sw_strndup(conn_key.c_str(), conn_key.length()); - cli->server_strlen = conn_key.length(); + cli->server_id = std::string(conn_key.c_str(), conn_key.length()); } zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("sock"), cli->socket->fd); if (type & SW_FLAG_KEEP) { - cli->keep = 1; + cli->keep = true; } -#ifdef SW_USE_OPENSSL if (type & SW_SOCK_SSL) { cli->enable_ssl_encrypt(); } -#endif return cli; } static PHP_METHOD(swoole_client, __construct) { zend_long type = 0; - zend_bool async = 0; + zend_bool async = false; char *id = nullptr; size_t len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|bs", &type, &async, &id, &len) == FAILURE) { - zend_throw_error(NULL, "socket type param is required"); + zend_throw_error(nullptr, "socket type param is required"); RETURN_FALSE; } if (async) { - zend_throw_error(NULL, "please install the ext-async extension, using Swoole\\Async\\Client"); + zend_throw_error(nullptr, "The $async parameter is not supported"); RETURN_FALSE; } - int client_type = php_swoole_socktype(type); + int client_type = php_swoole_client_get_type(type); if (client_type < SW_SOCK_TCP || client_type > SW_SOCK_UNIX_DGRAM) { const char *space, *class_name = get_active_class_name(&space); zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", @@ -683,12 +600,6 @@ static PHP_METHOD(swoole_client, __construct) { if (id) { zend_update_property_stringl(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("id"), id, len); } - // init - php_swoole_client_set_cli(ZEND_THIS, nullptr); - php_swoole_client_set_cb(ZEND_THIS, nullptr); -#ifdef SWOOLE_SOCKETS_SUPPORT - php_swoole_client_set_zsocket(ZEND_THIS, nullptr); -#endif RETURN_TRUE; } @@ -700,12 +611,6 @@ static PHP_METHOD(swoole_client, __destruct) { if (cli) { sw_zend_call_method_with_0_params(ZEND_THIS, swoole_client_ce, nullptr, "close", nullptr); } - // free memory - ClientCallback *cb = php_swoole_client_get_cb(ZEND_THIS); - if (cb) { - efree(cb); - php_swoole_client_set_cb(ZEND_THIS, nullptr); - } } static PHP_METHOD(swoole_client, set) { @@ -773,19 +678,19 @@ static PHP_METHOD(swoole_client, connect) { } } - if (cli->connect(cli, host, port, timeout, sock_flag) < 0) { + if (cli->connect(host, port, timeout, sock_flag) < 0) { zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); // async connect if (cli->async_connect) { RETURN_TRUE; } - php_swoole_error(E_WARNING, - "connect to server[%s:%d] failed. Error: %s[%d]", - host, - (int) port, - swoole_strerror(swoole_get_last_error()), - swoole_get_last_error()); + php_swoole_core_error(E_WARNING, + "connect to server[%s:%d] failed. Error: %s[%d]", + host, + (int) port, + swoole_strerror(swoole_get_last_error()), + swoole_get_last_error()); php_swoole_client_free(ZEND_THIS, cli); RETURN_FALSE; } @@ -808,14 +713,14 @@ static PHP_METHOD(swoole_client, send) { RETURN_FALSE; } - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } // clear errno swoole_set_last_error(0); - ssize_t ret = cli->send(cli, data, data_len, flags); + ssize_t ret = cli->send(data, data_len, flags); if (ret < 0) { php_swoole_sys_error(E_WARNING, "failed to send(%d) %zu bytes", cli->socket->fd, data_len); zend_update_property_long( @@ -848,48 +753,15 @@ static PHP_METHOD(swoole_client, sendto) { if (cli == nullptr) { RETURN_FALSE; } - cli->active = 1; + cli->active = true; php_swoole_client_set_cli(ZEND_THIS, cli); } - char addr[SW_IP_MAX_LENGTH]; - char ip[SW_IP_MAX_LENGTH]; - - /** - * udg doesn't need to use ip and port, so we don't need to deal with SW_SOCK_UNIX_DGRAM - */ - if (cli->socket->socket_type != SW_SOCK_UNIX_DGRAM) { - if (swoole::network::gethostbyname(cli->_sock_domain, host, addr) < 0) { - swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); - php_swoole_error(E_WARNING, - "sendto to server[%s:%d] failed. Error: %s[%d]", - host, - (int) port, - swoole_strerror(swoole_get_last_error()), - swoole_get_last_error()); - zend_update_property_long( - swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); - RETURN_FALSE; - } - - if (!inet_ntop(cli->_sock_domain, addr, ip, sizeof(ip))) { - php_swoole_error(E_WARNING, "ip[%s] is invalid", ip); - zend_update_property_long(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), errno); - RETURN_FALSE; - } - } - - double ori_timeout = Socket::default_write_timeout; - Socket::default_write_timeout = cli->timeout; - - ssize_t ret = -1; - if (cli->socket->is_dgram()) { - ret = cli->socket->sendto(ip, port, data, len); - } else { - php_swoole_fatal_error(E_WARNING, "only supports SWOOLE_SOCK_(UDP/UDP6/UNIX_DGRAM)"); + auto rv = cli->sendto(std::string(host, host_len), port, data, len); + if (rv < 0) { + zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), swoole_get_last_error()); } - Socket::default_write_timeout = ori_timeout; - SW_CHECK_RETURN(ret); + SW_CHECK_RETURN(rv); } static PHP_METHOD(swoole_client, sendfile) { @@ -906,7 +778,7 @@ static PHP_METHOD(swoole_client, sendfile) { RETURN_FALSE; } - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } @@ -917,7 +789,7 @@ static PHP_METHOD(swoole_client, sendfile) { } // clear errno swoole_set_last_error(0); - int ret = cli->sendfile(cli, file, offset, length); + int ret = cli->sendfile(file, offset, length); if (ret < 0) { swoole_set_last_error(errno); php_swoole_fatal_error(E_WARNING, @@ -949,7 +821,7 @@ static PHP_METHOD(swoole_client, recv) { flags = MSG_WAITALL; } - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } @@ -969,7 +841,7 @@ static PHP_METHOD(swoole_client, recv) { goto _find_eof; } - while (1) { + while (true) { buf = buffer->str + buffer->length; buf_len = buffer->size - buffer->length; @@ -977,9 +849,8 @@ static PHP_METHOD(swoole_client, recv) { buf_len = SW_BUFFER_SIZE_BIG; } - ret = cli->recv(cli, buf, buf_len, 0); + ret = cli->recv(buf, buf_len, 0); if (ret < 0) { - swoole_set_last_error(errno); php_swoole_sys_error(E_WARNING, "recv() failed"); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); @@ -1026,10 +897,7 @@ static PHP_METHOD(swoole_client, recv) { if (new_size > protocol->package_max_length) { new_size = protocol->package_max_length; } - if (!buffer->extend(new_size)) { - buffer->length = 0; - RETURN_FALSE; - } + buffer->extend(new_size); } } } @@ -1046,8 +914,8 @@ static PHP_METHOD(swoole_client, recv) { uint32_t header_len = protocol->package_length_offset + protocol->package_length_size; - while (1) { - int retval = cli->recv(cli, buffer->str + buffer->length, header_len - buffer->length, 0); + while (true) { + auto retval = cli->recv(buffer->str + buffer->length, header_len - buffer->length, 0); if (retval <= 0) { break; } @@ -1088,10 +956,10 @@ static PHP_METHOD(swoole_client, recv) { return; } - strbuf = zend_string_alloc(buf_len, 0); + strbuf = zend_string_alloc(buf_len, false); memcpy(strbuf->val, buffer->str, buffer->length); swoole_set_last_error(0); - ret = cli->recv(cli, strbuf->val + header_len, buf_len - buffer->length, MSG_WAITALL); + ret = cli->recv(strbuf->val + header_len, buf_len - buffer->length, MSG_WAITALL); if (ret > 0) { ret += header_len; if (ret != buf_len) { @@ -1102,13 +970,12 @@ static PHP_METHOD(swoole_client, recv) { if (!(flags & MSG_WAITALL) && buf_len > SW_PHP_CLIENT_BUFFER_SIZE) { buf_len = SW_PHP_CLIENT_BUFFER_SIZE; } - strbuf = zend_string_alloc(buf_len, 0); + strbuf = zend_string_alloc(buf_len, false); swoole_set_last_error(0); - ret = cli->recv(cli, strbuf->val, buf_len, flags); + ret = cli->recv(strbuf->val, buf_len, flags); } if (ret < 0) { - swoole_set_last_error(errno); php_swoole_sys_error(E_WARNING, "recv() failed"); zend_update_property_long( swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), swoole_get_last_error()); @@ -1142,48 +1009,29 @@ static PHP_METHOD(swoole_client, isConnected) { } static PHP_METHOD(swoole_client, getsockname) { - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } - if (cli->socket->is_local()) { - php_swoole_fatal_error(E_WARNING, "getsockname() only support AF_INET family socket"); - RETURN_FALSE; - } - - if (cli->socket->get_name(&cli->socket->info) < 0) { + if (cli->socket->get_name() < 0) { php_swoole_sys_error(E_WARNING, "getsockname() failed"); + zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), errno); RETURN_FALSE; } array_init(return_value); - if (cli->socket->is_inet6()) { - add_assoc_long(return_value, "port", ntohs(cli->socket->info.addr.inet_v6.sin6_port)); - char tmp[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, &cli->socket->info.addr.inet_v6.sin6_addr, tmp, sizeof(tmp))) { - add_assoc_string(return_value, "host", tmp); - } else { - php_swoole_fatal_error(E_WARNING, "inet_ntop() failed"); - } - } else { - add_assoc_long(return_value, "port", ntohs(cli->socket->info.addr.inet_v4.sin_port)); - char tmp[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, &cli->socket->info.addr.inet_v4.sin_addr, tmp, sizeof(tmp))) { - add_assoc_string(return_value, "host", tmp); - } else { - php_swoole_fatal_error(E_WARNING, "inet_ntop() failed"); - } - } + add_assoc_long(return_value, "port", cli->socket->get_port()); + add_assoc_string(return_value, "host", cli->socket->get_addr()); } #ifdef SWOOLE_SOCKETS_SUPPORT static PHP_METHOD(swoole_client, getSocket) { - zval *zsocket = php_swoole_client_get_zsocket(ZEND_THIS); + zval *zsocket = client_get_zsocket(ZEND_THIS); if (zsocket) { RETURN_ZVAL(zsocket, 1, 0); } - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } @@ -1198,47 +1046,31 @@ static PHP_METHOD(swoole_client, getSocket) { SW_ZVAL_SOCKET(return_value, socket_object); zsocket = sw_zval_dup(return_value); Z_TRY_ADDREF_P(zsocket); - php_swoole_client_set_zsocket(ZEND_THIS, zsocket); + client_set_zsocket(ZEND_THIS, zsocket); } #endif static PHP_METHOD(swoole_client, getpeername) { - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } - if (cli->socket->socket_type == SW_SOCK_UDP) { - array_init(return_value); - add_assoc_long(return_value, "port", ntohs(cli->remote_addr.addr.inet_v4.sin_port)); - char tmp[INET_ADDRSTRLEN]; - - if (inet_ntop(AF_INET, &cli->remote_addr.addr.inet_v4.sin_addr, tmp, sizeof(tmp))) { - add_assoc_string(return_value, "host", tmp); - } else { - php_swoole_fatal_error(E_WARNING, "inet_ntop() failed"); - } - } else if (cli->socket->socket_type == SW_SOCK_UDP6) { - array_init(return_value); - add_assoc_long(return_value, "port", ntohs(cli->remote_addr.addr.inet_v6.sin6_port)); - char tmp[INET6_ADDRSTRLEN]; - - if (inet_ntop(AF_INET6, &cli->remote_addr.addr.inet_v6.sin6_addr, tmp, sizeof(tmp))) { - add_assoc_string(return_value, "host", tmp); - } else { - php_swoole_fatal_error(E_WARNING, "inet_ntop() failed"); - } - } else if (cli->socket->socket_type == SW_SOCK_UNIX_DGRAM) { - add_assoc_string(return_value, "host", cli->remote_addr.addr.un.sun_path); - } else { - php_swoole_fatal_error(E_WARNING, "only supports SWOOLE_SOCK_(UDP/UDP6/UNIX_DGRAM)"); + Address addr; + if (cli->get_peer_name(&addr) < 0) { + php_swoole_sys_error(E_WARNING, "getpeername() failed"); + zend::object_set(ZEND_THIS, ZEND_STRL("errCode"), errno); RETURN_FALSE; } + + array_init(return_value); + add_assoc_long(return_value, "port", addr.get_port()); + add_assoc_string(return_value, "host", addr.get_addr()); } static PHP_METHOD(swoole_client, close) { int ret = 1; - zend_bool force = 0; + zend_bool force = false; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL @@ -1261,12 +1093,11 @@ static PHP_METHOD(swoole_client, close) { php_swoole_client_free(ZEND_THIS, cli); } else { if (cli->keep) { - std::string conn_key(cli->server_str, cli->server_strlen); std::queue *q; - auto i = long_connections.find(conn_key); + auto i = long_connections.find(cli->server_id); if (i == long_connections.end()) { q = new std::queue; - long_connections[conn_key] = q; + long_connections[cli->server_id] = q; } else { q = i->second; } @@ -1278,33 +1109,49 @@ static PHP_METHOD(swoole_client, close) { SW_CHECK_RETURN(ret); } -#ifdef SW_USE_OPENSSL -static PHP_METHOD(swoole_client, enableSSL) { - Client *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } +bool php_swoole_client_enable_ssl_encryption(Client *cli, zval *zobject) { if (cli->socket->socket_type != SW_SOCK_TCP && cli->socket->socket_type != SW_SOCK_TCP6) { php_swoole_fatal_error(E_WARNING, "cannot use enableSSL"); - RETURN_FALSE; + return false; } if (cli->socket->ssl) { php_swoole_fatal_error(E_WARNING, "SSL has been enabled"); - RETURN_FALSE; + return false; } - cli->enable_ssl_encrypt(); - zval *zset = sw_zend_read_property_ex(swoole_client_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); + cli->open_ssl = true; + zval *zset = sw_zend_read_property_ex(swoole_client_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (ZVAL_IS_ARRAY(zset)) { php_swoole_client_check_ssl_setting(cli, zset); } - if (cli->ssl_handshake() < 0) { + return cli->enable_ssl_encrypt() == SW_OK; +} + +static PHP_METHOD(swoole_client, enableSSL) { + zval *zcallback = nullptr; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(zcallback) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (zcallback) { + zend_throw_exception( + swoole_exception_ce, "sync client does not support `onSslReady` callback", SW_ERROR_INVALID_PARAMS); + RETURN_FALSE; + } + + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); + if (!cli) { RETURN_FALSE; } - RETURN_TRUE; + if (!php_swoole_client_enable_ssl_encryption(cli, ZEND_THIS)) { + RETURN_FALSE; + } + RETURN_BOOL(cli->ssl_handshake() == SW_OK); } static PHP_METHOD(swoole_client, getPeerCert) { - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } @@ -1319,7 +1166,7 @@ static PHP_METHOD(swoole_client, getPeerCert) { } static PHP_METHOD(swoole_client, verifyPeerCert) { - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } @@ -1327,40 +1174,42 @@ static PHP_METHOD(swoole_client, verifyPeerCert) { php_swoole_fatal_error(E_WARNING, "SSL is not ready"); RETURN_FALSE; } - zend_bool allow_self_signed = 0; + zend_bool allow_self_signed = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_self_signed) == FAILURE) { RETURN_FALSE; } SW_CHECK_RETURN(cli->ssl_verify(allow_self_signed)); } -#endif static PHP_METHOD(swoole_client, shutdown) { - Client *cli = client_get_ptr(ZEND_THIS); + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); if (!cli) { RETURN_FALSE; } - long __how; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &__how) == FAILURE) { + long _how; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &_how) == FAILURE) { RETURN_FALSE; } - SW_CHECK_RETURN(cli->shutdown(__how)); + SW_CHECK_RETURN(cli->shutdown(_how)); } PHP_FUNCTION(swoole_client_select) { -#ifdef PHP_SWOOLE_CLIENT_USE_POLL zval *r_array, *w_array, *e_array; int retval; uint32_t index = 0; double timeout = SW_CLIENT_CONNECT_TIMEOUT; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a!a!a!|d", &r_array, &w_array, &e_array, &timeout) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_ARRAY_EX2(r_array, 1, 1, 0) + Z_PARAM_ARRAY_EX2(w_array, 1, 1, 0) + Z_PARAM_ARRAY_EX2(e_array, 1, 1, 0) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END(); int maxevents = SW_MAX(SW_MAX(php_swoole_array_length_safe(r_array), php_swoole_array_length_safe(w_array)), php_swoole_array_length_safe(e_array)); - struct pollfd *fds = (struct pollfd *) ecalloc(maxevents, sizeof(struct pollfd)); + auto *fds = static_cast(ecalloc(maxevents, sizeof(struct pollfd))); if (r_array != nullptr && php_swoole_array_length(r_array) > 0) { index = client_poll_add(r_array, index, fds, maxevents, POLLIN); @@ -1398,61 +1247,10 @@ PHP_FUNCTION(swoole_client_select) { } efree(fds); RETURN_LONG(retval); -#else - zval *r_array, *w_array, *e_array; - fd_set rfds, wfds, efds; - - int max_fd = 0; - int retval, sets = 0; - double timeout = SW_CLIENT_CONNECT_TIMEOUT; - struct timeval timeo; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a!a!a!|d", &r_array, &w_array, &e_array, &timeout) == FAILURE) { - RETURN_FALSE; - } - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - FD_ZERO(&efds); - - if (r_array != nullptr) sets += client_select_add(r_array, &rfds, &max_fd); - if (w_array != nullptr) sets += client_select_add(w_array, &wfds, &max_fd); - if (e_array != nullptr) sets += client_select_add(e_array, &efds, &max_fd); - - if (!sets) { - php_swoole_fatal_error(E_WARNING, "no resource arrays were passed to select"); - RETURN_FALSE; - } - - if (max_fd >= FD_SETSIZE) { - php_swoole_fatal_error(E_WARNING, "select max_fd > FD_SETSIZE[%d]", FD_SETSIZE); - RETURN_FALSE; - } - timeo.tv_sec = (int) timeout; - timeo.tv_usec = (int) ((timeout - timeo.tv_sec) * 1000 * 1000); - - retval = select(max_fd + 1, &rfds, &wfds, &efds, &timeo); - if (retval == -1) { - php_swoole_sys_error(E_WARNING, "unable to select"); - RETURN_FALSE; - } - if (r_array != nullptr) { - client_select_wait(r_array, &rfds); - } - if (w_array != nullptr) { - client_select_wait(w_array, &wfds); - } - if (e_array != nullptr) { - client_select_wait(e_array, &efds); - } - RETURN_LONG(retval); -#endif } -#ifdef PHP_SWOOLE_CLIENT_USE_POLL -static inline int client_poll_get(struct pollfd *fds, int maxevents, int fd) { - int i; - for (i = 0; i < maxevents; i++) { +static inline int client_poll_get(const pollfd *fds, int maxevents, int fd) { + for (int i = 0; i < maxevents; i++) { if (fds[i].fd == fd) { return i; } @@ -1460,9 +1258,8 @@ static inline int client_poll_get(struct pollfd *fds, int maxevents, int fd) { return -1; } -static int client_poll_wait(zval *sock_array, struct pollfd *fds, int maxevents, int n_event, int revent) { +static int client_poll_wait(zval *sock_array, const pollfd *fds, int maxevents, int n_event, int revent) { zval *element = nullptr; - int sock; ulong_t num = 0; if (!ZVAL_IS_ARRAY(sock_array)) { @@ -1474,14 +1271,13 @@ static int client_poll_wait(zval *sock_array, struct pollfd *fds, int maxevents, zend_ulong num_key; zend_string *key; zval *dest_element; - int poll_key; ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(sock_array), num_key, key, element) { - sock = php_swoole_convert_to_fd(element); + int sock = php_swoole_convert_to_fd(element); if (sock < 0) { continue; } - poll_key = client_poll_get(fds, maxevents, sock); + int poll_key = client_poll_get(fds, maxevents, sock); if (poll_key == -1) { php_swoole_fatal_error(E_WARNING, "bad fd[%d]", sock); continue; @@ -1506,17 +1302,16 @@ static int client_poll_wait(zval *sock_array, struct pollfd *fds, int maxevents, return num; } -static uint32_t client_poll_add(zval *sock_array, uint32_t index, struct pollfd *fds, int maxevents, int event) { +static uint32_t client_poll_add(const zval *sock_array, uint32_t index, struct pollfd *fds, int maxevents, int event) { zval *element = nullptr; if (!ZVAL_IS_ARRAY(sock_array)) { return 0; } - int sock; int key = -1; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(sock_array), element) - sock = php_swoole_convert_to_fd(element); + int sock = php_swoole_convert_to_fd(element); if (sock < 0) { continue; } @@ -1535,72 +1330,3 @@ static uint32_t client_poll_add(zval *sock_array, uint32_t index, struct pollfd return index; } -#else -static int client_select_wait(zval *sock_array, fd_set *fds) { - zval *element = nullptr; - int sock; - - ulong_t num = 0; - if (!ZVAL_IS_ARRAY(sock_array)) { - return 0; - } - - zval new_array; - array_init(&new_array); - zend_ulong num_key; - zend_string *key; - zval *dest_element; - - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(sock_array), num_key, key, element) { - sock = php_swoole_convert_to_fd(element); - if (sock < 0) { - continue; - } - if ((sock < FD_SETSIZE) && FD_ISSET(sock, fds)) { - if (key) { - dest_element = zend_hash_add(Z_ARRVAL(new_array), key, element); - } else { - dest_element = zend_hash_index_update(Z_ARRVAL(new_array), num_key, element); - } - if (dest_element) { - Z_ADDREF_P(dest_element); - } - } - num++; - } - ZEND_HASH_FOREACH_END(); - - zval_ptr_dtor(sock_array); - ZVAL_COPY_VALUE(sock_array, &new_array); - return num ? 1 : 0; -} - -static int client_select_add(zval *sock_array, fd_set *fds, int *max_fd) { - zval *element = nullptr; - if (!ZVAL_IS_ARRAY(sock_array)) { - return 0; - } - - int sock; - int num = 0; - - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(sock_array), element) - sock = php_swoole_convert_to_fd(element); - if (sock < 0) { - continue; - } - if (sock < FD_SETSIZE) { - FD_SET(sock, fds); - } else { - php_swoole_fatal_error(E_WARNING, "socket[%d] > FD_SETSIZE[%d]", sock, FD_SETSIZE); - continue; - } - if (sock > *max_fd) { - *max_fd = sock; - } - num++; - SW_HASHTABLE_FOREACH_END(); - - return num ? 1 : 0; -} -#endif diff --git a/ext-src/swoole_client_async.cc b/ext-src/swoole_client_async.cc new file mode 100644 index 0000000000..2593ab9f3e --- /dev/null +++ b/ext-src/swoole_client_async.cc @@ -0,0 +1,539 @@ +/* + +----------------------------------------------------------------------+ + | 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_client.h" +#include "swoole_mqtt.h" + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_client_async_arginfo.h" +END_EXTERN_C() + +#include "ext/standard/basic_functions.h" + +using swoole::SocketType; +using swoole::network::Address; +using swoole::network::Client; +using swoole::network::Socket; + +static PHP_METHOD(swoole_client_async, __construct); +static PHP_METHOD(swoole_client_async, __destruct); +static PHP_METHOD(swoole_client_async, connect); +static PHP_METHOD(swoole_client_async, sleep); +static PHP_METHOD(swoole_client_async, wakeup); +static PHP_METHOD(swoole_client_async, enableSSL); +static PHP_METHOD(swoole_client_async, isConnected); +static PHP_METHOD(swoole_client_async, close); +static PHP_METHOD(swoole_client_async, on); + +enum AsyncClientCallbackType { + CLIENT_CB_onConnect = 1, + CLIENT_CB_onReceive, + CLIENT_CB_onClose, + CLIENT_CB_onError, + CLIENT_CB_onBufferFull, + CLIENT_CB_onBufferEmpty, + CLIENT_CB_onSSLReady, +}; + +static void client_onConnect(Client *cli); +static void client_onReceive(const Client *cli, const char *data, size_t length); +static void client_onClose(Client *cli); +static void client_onError(Client *cli); +static void client_onBufferFull(Client *cli); +static void client_onBufferEmpty(Client *cli); + +zend_class_entry *swoole_client_async_ce; +static zend_object_handlers swoole_client_async_handlers; + +void php_swoole_client_async_free_object(const ClientObject *client_obj) { + if (client_obj->async->onConnect) { + sw_callable_free(client_obj->async->onConnect); + } + if (client_obj->async->onReceive) { + sw_callable_free(client_obj->async->onReceive); + } + if (client_obj->async->onClose) { + sw_callable_free(client_obj->async->onClose); + } + if (client_obj->async->onError) { + sw_callable_free(client_obj->async->onError); + } + if (client_obj->async->onBufferFull) { + sw_callable_free(client_obj->async->onBufferFull); + } + if (client_obj->async->onBufferEmpty) { + sw_callable_free(client_obj->async->onBufferEmpty); + } + if (client_obj->async->onSSLReady) { + sw_callable_free(client_obj->async->onSSLReady); + } + delete client_obj->async; +} + +static sw_inline void client_execute_callback(zval *zobject, AsyncClientCallbackType type) { + auto client_obj = php_swoole_client_fetch_object(zobject); + const char *callback_name; + zend::Callable *cb; + + switch (type) { + case CLIENT_CB_onConnect: + callback_name = "onConnect"; + cb = client_obj->async->onConnect; + break; + case CLIENT_CB_onError: + callback_name = "onError"; + cb = client_obj->async->onError; + break; + case CLIENT_CB_onClose: + callback_name = "onClose"; + cb = client_obj->async->onClose; + break; + case CLIENT_CB_onBufferFull: + callback_name = "onBufferFull"; + cb = client_obj->async->onBufferFull; + break; + case CLIENT_CB_onBufferEmpty: + callback_name = "onBufferEmpty"; + cb = client_obj->async->onBufferEmpty; + break; + case CLIENT_CB_onSSLReady: + callback_name = "onSSLReady"; + cb = client_obj->async->onSSLReady; + break; + default: + abort(); + return; + } + + if (!cb) { + php_swoole_fatal_error(E_WARNING, "%s has no %s callback", SW_Z_OBJCE_NAME_VAL_P(zobject), callback_name); + return; + } + + if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, cb->ptr(), 1, zobject, nullptr) != SUCCESS)) { + php_swoole_fatal_error(E_WARNING, "%s->%s handler error", SW_Z_OBJCE_NAME_VAL_P(zobject), callback_name); + } +} + +static sw_inline void client_execute_callback_and_dtor(Client *cli, zval *zobject, AsyncClientCallbackType type) { + client_execute_callback(zobject, type); + php_swoole_client_free(zobject, cli); + zval_delref_p(zobject); +} + +// clang-format off +static const zend_function_entry swoole_client_async_methods[] = { + PHP_ME(swoole_client_async, __construct, arginfo_class_Swoole_Async_Client___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, __destruct, arginfo_class_Swoole_Async_Client___destruct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, connect, arginfo_class_Swoole_Async_Client_connect, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, sleep, arginfo_class_Swoole_Async_Client_sleep, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, wakeup, arginfo_class_Swoole_Async_Client_wakeup, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_client_async, pause, sleep, arginfo_class_Swoole_Async_Client_sleep, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_client_async, resume, wakeup, arginfo_class_Swoole_Async_Client_wakeup, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, enableSSL, arginfo_class_Swoole_Async_Client_enableSSL, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, isConnected, arginfo_class_Swoole_Async_Client_isConnected, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, close, arginfo_class_Swoole_Async_Client_close, ZEND_ACC_PUBLIC) + PHP_ME(swoole_client_async, on, arginfo_class_Swoole_Async_Client_on, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_client_async_minit(int module_number) { + SW_INIT_CLASS_ENTRY_EX( + swoole_client_async, "Swoole\\Async\\Client", nullptr, swoole_client_async_methods, swoole_client); + SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client_async); + SW_SET_CLASS_CLONEABLE(swoole_client_async, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client_async, sw_zend_class_unset_property_deny); + + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onConnect"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onError"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onReceive"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onClose"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onBufferFull"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onBufferEmpty"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_async_ce, ZEND_STRL("onSSLReady"), ZEND_ACC_PRIVATE); +} + +static void client_onReceive(const Client *cli, const char *data, size_t length) { + auto zobject = static_cast(cli->object); + auto client_obj = php_swoole_client_fetch_object(zobject); + zend_fcall_info_cache *fci_cache = client_obj->async->onReceive->ptr(); + zval args[2]; + + args[0] = *zobject; + ZVAL_STRINGL(&args[1], data, length); + + if (UNEXPECTED(sw_zend_call_function_ex2(nullptr, fci_cache, 2, args, nullptr) != SUCCESS)) { + php_swoole_fatal_error(E_WARNING, "%s->onReceive handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + } + + zval_ptr_dtor(&args[1]); +} + +static void client_onConnect(Client *cli) { + auto zobject = static_cast(cli->object); + if (cli->ssl_wait_handshake) { + cli->ssl_wait_handshake = false; + client_execute_callback(zobject, CLIENT_CB_onSSLReady); + return; + } + client_execute_callback(zobject, CLIENT_CB_onConnect); +} + +static void client_onClose(Client *cli) { + auto zobject = static_cast(cli->object); + client_execute_callback_and_dtor(cli, zobject, CLIENT_CB_onClose); +} + +static void client_onError(Client *cli) { + auto zobject = static_cast(cli->object); + zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); + client_execute_callback_and_dtor(cli, zobject, CLIENT_CB_onError); +} + +static void client_onBufferFull(Client *cli) { + auto zobject = static_cast(cli->object); + client_execute_callback(zobject, CLIENT_CB_onBufferFull); +} + +static void client_onBufferEmpty(Client *cli) { + auto zobject = static_cast(cli->object); + client_execute_callback(zobject, CLIENT_CB_onBufferEmpty); +} + +static PHP_METHOD(swoole_client_async, __construct) { + zend_long type = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &type) == FAILURE) { + zend_throw_error(nullptr, "socket type param is required"); + RETURN_FALSE; + } + + int client_type = php_swoole_client_get_type(type); + if (client_type < SW_SOCK_TCP || client_type > SW_SOCK_UNIX_DGRAM) { + const char *space, *class_name = get_active_class_name(&space); + zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", + class_name, + space, + get_active_function_name(), + 1, + type); + RETURN_FALSE; + } + + php_swoole_check_reactor(); + + zend_update_property_long(swoole_client_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("type"), type); + RETURN_TRUE; +} + +static PHP_METHOD(swoole_client_async, __destruct) { + SW_PREVENT_USER_DESTRUCT(); + + const auto cli = php_swoole_client_get_cli(ZEND_THIS); + if (!cli) { + return; + } + if (!cli->closed) { + cli->close(); + } + php_swoole_client_free(ZEND_THIS, cli); +} + +static Client *php_swoole_client_async_new(zval *zobject, char *host, int host_len, int port) { + zval *ztype = sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TYPE), 0); + if (ztype == nullptr || ZVAL_IS_NULL(ztype)) { + php_swoole_fatal_error(E_ERROR, "failed to get client type"); + return nullptr; + } + + long type = Z_LVAL_P(ztype); + int client_type = php_swoole_client_get_type(type); + if (Socket::is_tcp(static_cast(client_type)) && !Address::verify_port(port, true)) { + php_swoole_fatal_error(E_WARNING, "The port is invalid"); + swoole_set_last_error(SW_ERROR_INVALID_PARAMS); + return nullptr; + } + + auto *cli = new Client(php_swoole_client_get_type(type), true); + if (!cli->ready()) { + php_swoole_sys_error(E_WARNING, "failed to create client"); + zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); + delete cli; + return nullptr; + } + + zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("sock"), cli->socket->fd); + + if (type & SW_SOCK_SSL) { + cli->enable_ssl_encrypt(); + } + + return cli; +} + +static PHP_METHOD(swoole_client_async, connect) { + char *host; + size_t host_len; + zend_long port = 0; + double timeout = SW_CLIENT_CONNECT_TIMEOUT; + zend_long sock_flag = 0; + + ZEND_PARSE_PARAMETERS_START(1, 4) + Z_PARAM_STRING(host, host_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(port) + Z_PARAM_DOUBLE(timeout) + Z_PARAM_LONG(sock_flag) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (host_len == 0) { + php_swoole_fatal_error(E_WARNING, "The host is empty"); + RETURN_FALSE; + } + + auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); + if (client_obj->cli) { + php_swoole_fatal_error(E_WARNING, "connection to the server has already been established"); + RETURN_FALSE; + } + + if (!client_obj->async) { + php_swoole_fatal_error(E_WARNING, "async client is not initialized"); + RETURN_FALSE; + } + + auto cli = php_swoole_client_async_new(ZEND_THIS, host, host_len, port); + if (cli == nullptr) { + RETURN_FALSE; + } + + zval *zset = sw_zend_read_property(swoole_client_async_ce, ZEND_THIS, ZEND_STRL("setting"), 0); + if (zset && ZVAL_IS_ARRAY(zset)) { + php_swoole_client_check_setting(cli, zset); + } + if (!client_obj->async->onReceive) { + php_swoole_fatal_error(E_ERROR, "no 'onReceive' callback function"); + RETURN_FALSE; + } + if (cli->get_socket()->is_stream()) { + if (!client_obj->async->onConnect) { + php_swoole_fatal_error(E_ERROR, "no 'onConnect' callback function"); + RETURN_FALSE; + } + if (!client_obj->async->onError) { + php_swoole_fatal_error(E_ERROR, "no 'onError' callback function"); + RETURN_FALSE; + } + if (!client_obj->async->onClose) { + php_swoole_fatal_error(E_ERROR, "no 'onClose' callback function"); + RETURN_FALSE; + } + cli->onConnect = client_onConnect; + cli->onClose = client_onClose; + cli->onError = client_onError; + cli->onReceive = client_onReceive; + if (client_obj->async->onBufferFull) { + cli->onBufferFull = client_onBufferFull; + } + if (client_obj->async->onBufferEmpty) { + cli->onBufferEmpty = client_onBufferEmpty; + } + } else { + if (client_obj->async->onConnect) { + cli->onConnect = client_onConnect; + } + if (client_obj->async->onClose) { + cli->onClose = client_onClose; + } + if (client_obj->async->onError) { + cli->onError = client_onError; + } + cli->onReceive = client_onReceive; + } + + client_obj->async->_zobject = *ZEND_THIS; + client_obj->cli = cli; + cli->object = &client_obj->async->_zobject; + zval_addref_p(ZEND_THIS); + + // nonblock async + if (cli->connect(host, port, timeout, sock_flag) < 0) { + if (errno == 0) { + const auto error = swoole_get_last_error(); + if (error == SW_ERROR_DNSLOOKUP_RESOLVE_FAILED) { + php_swoole_error(E_WARNING, + "connect to server[%s:%d] failed. Error: %s[%d]", + host, + (int) port, + swoole_strerror(error), + error); + } + zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), error); + } else { + php_swoole_sys_error(E_WARNING, "connect to server[%s:%d] failed", host, (int) port); + zend_update_property_long(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), errno); + } + auto _cli = php_swoole_client_get_cli(ZEND_THIS); + if (_cli && _cli->onError == nullptr) { + php_swoole_client_free(ZEND_THIS, _cli); + zval_delref_p(ZEND_THIS); + } + RETURN_FALSE; + } + RETURN_TRUE; +} + +static PHP_METHOD(swoole_client_async, isConnected) { + auto cli = php_swoole_client_get_cli(ZEND_THIS); + if (!cli) { + RETURN_FALSE; + } + if (!cli->socket) { + RETURN_FALSE; + } + RETURN_BOOL(cli->active); +} + +static PHP_METHOD(swoole_client_async, close) { + auto cli = php_swoole_client_get_cli(ZEND_THIS); + if (!cli) { + php_swoole_fatal_error(E_WARNING, "client is not connected to the server"); + RETURN_FALSE; + } + SW_CHECK_RETURN(cli->close()); +} + +static PHP_METHOD(swoole_client_async, on) { + char *cb_name; + size_t cb_name_len; + zval *zcallback; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &cb_name, &cb_name_len, &zcallback) == FAILURE) { + RETURN_FALSE; + } + + auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); + auto cb = sw_callable_create(zcallback); + if (!cb) { + return; + } + + if (!client_obj->async) { + client_obj->async = new AsyncClientObject(); + } + + if (strncasecmp("connect", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onConnect"), zcallback); + if (client_obj->async->onConnect) { + sw_callable_free(client_obj->async->onConnect); + } + client_obj->async->onConnect = cb; + } else if (strncasecmp("receive", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onReceive"), zcallback); + if (client_obj->async->onReceive) { + sw_callable_free(client_obj->async->onReceive); + } + client_obj->async->onReceive = cb; + } else if (strncasecmp("close", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onClose"), zcallback); + if (client_obj->async->onClose) { + sw_callable_free(client_obj->async->onClose); + } + client_obj->async->onClose = cb; + } else if (strncasecmp("error", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onError"), zcallback); + if (client_obj->async->onError) { + sw_callable_free(client_obj->async->onError); + } + client_obj->async->onError = cb; + } else if (strncasecmp("bufferFull", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onBufferFull"), zcallback); + if (client_obj->async->onBufferFull) { + sw_callable_free(client_obj->async->onBufferFull); + } + client_obj->async->onBufferFull = cb; + } else if (strncasecmp("bufferEmpty", cb_name, cb_name_len) == 0) { + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onBufferEmpty"), zcallback); + if (client_obj->async->onBufferEmpty) { + sw_callable_free(client_obj->async->onBufferEmpty); + } + client_obj->async->onBufferEmpty = cb; + } else { + php_swoole_fatal_error(E_WARNING, "Unknown event callback type name '%s'", cb_name); + sw_callable_free(cb); + RETURN_FALSE; + } + RETURN_TRUE; +} + +static PHP_METHOD(swoole_client_async, sleep) { + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); + if (!cli) { + RETURN_FALSE; + } + SW_CHECK_RETURN(cli->sleep()); +} + +static PHP_METHOD(swoole_client_async, wakeup) { + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); + if (!cli) { + RETURN_FALSE; + } + SW_CHECK_RETURN(cli->wakeup()); +} + +static PHP_METHOD(swoole_client_async, enableSSL) { + zval *zcallback = nullptr; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(zcallback) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (zcallback == nullptr) { + zend_throw_exception(swoole_exception_ce, "require `onSslReady` callback", SW_ERROR_INVALID_PARAMS); + RETURN_FALSE; + } + + Client *cli = php_swoole_client_get_cli_safe(ZEND_THIS); + if (!cli) { + RETURN_FALSE; + } + if (!php_swoole_client_enable_ssl_encryption(cli, ZEND_THIS)) { + RETURN_FALSE; + } + + auto client_obj = php_swoole_client_fetch_object(ZEND_THIS); + if (swoole_event_set(cli->socket, SW_EVENT_WRITE) < 0) { + RETURN_FALSE; + } + + if (client_obj->async->onSSLReady) { + sw_callable_free(client_obj->async->onSSLReady); + } + + auto cb = sw_callable_create(zcallback); + if (!cb) { + RETURN_FALSE; + } + zend_update_property(swoole_client_async_ce, Z_OBJ_P(ZEND_THIS), ZEND_STRL("onSSLReady"), zcallback); + client_obj->async->onSSLReady = cb; + cli->ssl_wait_handshake = true; + cli->socket->ssl_state = SW_SSL_STATE_WAIT_STREAM; + + RETURN_TRUE; +} diff --git a/ext-src/swoole_client_coro.cc b/ext-src/swoole_client_coro.cc index f61666950b..9ed23f24f6 100644 --- a/ext-src/swoole_client_coro.cc +++ b/ext-src/swoole_client_coro.cc @@ -14,30 +14,28 @@ +----------------------------------------------------------------------+ */ -#include "php_swoole_cxx.h" +#include "php_swoole_client.h" + #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" -#include "swoole_proxy.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_client_coro_arginfo.h" END_EXTERN_C() -using swoole::HttpProxy; -using swoole::Socks5Proxy; +using swoole::SSLContext; using swoole::String; -using swoole::coroutine::Socket; using swoole::network::Address; -#ifdef SW_USE_OPENSSL -using swoole::SSLContext; -#endif static zend_class_entry *swoole_client_coro_ce; static zend_object_handlers swoole_client_coro_handlers; struct ClientCoroObject { - Socket *sock; + SocketImpl *socket; + zval zsocket; + /* safety zval */ + zval zobject; zend_object std; }; @@ -52,11 +50,9 @@ static PHP_METHOD(swoole_client_coro, send); static PHP_METHOD(swoole_client_coro, sendfile); static PHP_METHOD(swoole_client_coro, sendto); static PHP_METHOD(swoole_client_coro, recvfrom); -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_client_coro, enableSSL); static PHP_METHOD(swoole_client_coro, getPeerCert); static PHP_METHOD(swoole_client_coro, verifyPeerCert); -#endif static PHP_METHOD(swoole_client_coro, exportSocket); static PHP_METHOD(swoole_client_coro, isConnected); static PHP_METHOD(swoole_client_coro, getsockname); @@ -64,9 +60,6 @@ static PHP_METHOD(swoole_client_coro, getpeername); static PHP_METHOD(swoole_client_coro, close); SW_EXTERN_C_END -static Socket *client_coro_new(zval *zobject, int port = 0); -void php_swoole_client_coro_socket_free(Socket *cli); - // clang-format off static const zend_function_entry swoole_client_coro_methods[] = { @@ -80,11 +73,9 @@ static const zend_function_entry swoole_client_coro_methods[] = PHP_ME(swoole_client_coro, sendfile, arginfo_class_Swoole_Coroutine_Client_sendfile, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, sendto, arginfo_class_Swoole_Coroutine_Client_sendto, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, recvfrom, arginfo_class_Swoole_Coroutine_Client_recvfrom, ZEND_ACC_PUBLIC) -#ifdef SW_USE_OPENSSL PHP_ME(swoole_client_coro, enableSSL, arginfo_class_Swoole_Coroutine_Client_enableSSL, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getPeerCert, arginfo_class_Swoole_Coroutine_Client_getPeerCert, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, verifyPeerCert, arginfo_class_Swoole_Coroutine_Client_verifyPeerCert, ZEND_ACC_PUBLIC) -#endif PHP_ME(swoole_client_coro, isConnected, arginfo_class_Swoole_Coroutine_Client_isConnected, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getsockname, arginfo_class_Swoole_Coroutine_Client_getsockname, ZEND_ACC_PUBLIC) PHP_ME(swoole_client_coro, getpeername, arginfo_class_Swoole_Coroutine_Client_getpeername, ZEND_ACC_PUBLIC) @@ -94,32 +85,82 @@ static const zend_function_entry swoole_client_coro_methods[] = }; // clang-format on -static sw_inline ClientCoroObject *php_swoole_client_coro_fetch_object(zend_object *obj) { - return (ClientCoroObject *) ((char *) obj - swoole_client_coro_handlers.offset); +static ClientCoroObject *client_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_client_coro_handlers.offset); } -static sw_inline ClientCoroObject *php_swoole_get_client(zval *zobject) { - return php_swoole_client_coro_fetch_object(Z_OBJ_P(zobject)); +static ClientCoroObject *client_coro_get_client(const zval *zobject) { + return client_coro_fetch_object(Z_OBJ_P(zobject)); } -static sw_inline Socket *php_swoole_get_sock(zval *zobject) { - return php_swoole_get_client(zobject)->sock; +static SocketImpl *client_coro_get_socket(const zval *zobject) { + return client_coro_get_client(zobject)->socket; } -static void php_swoole_client_coro_free_object(zend_object *object) { - ClientCoroObject *client = php_swoole_client_coro_fetch_object(object); - if (client->sock) { - php_swoole_client_coro_socket_free(client->sock); +static void client_coro_free_object(zend_object *object) { + ClientCoroObject *client = client_coro_fetch_object(object); + if (client->socket) { + client->socket->close(); } zend_object_std_dtor(&client->std); } -static zend_object *php_swoole_client_coro_create_object(zend_class_entry *ce) { - ClientCoroObject *sock_t = (ClientCoroObject *) zend_object_alloc(sizeof(ClientCoroObject), ce); - zend_object_std_init(&sock_t->std, ce); - object_properties_init(&sock_t->std, ce); - sock_t->std.handlers = &swoole_client_coro_handlers; - return &sock_t->std; +#define CLIENT_CORO_GET_SOCKET_SAFE(__sock) \ + SW_CLIENT_GET_SOCKET_SAFE(__sock, &client_coro_get_client(ZEND_THIS)->zsocket); \ + if (!__sock) { \ + php_swoole_socket_set_error_properties( \ + ZEND_THIS, SW_ERROR_CLIENT_NO_CONNECTION, swoole_strerror(SW_ERROR_CLIENT_NO_CONNECTION)); \ + RETURN_FALSE; \ + } + +static zend_object *client_coro_create_object(zend_class_entry *ce) { + auto *object = static_cast(zend_object_alloc(sizeof(ClientCoroObject), ce)); + zend_object_std_init(&object->std, ce); + object_properties_init(&object->std, ce); + object->std.handlers = &swoole_client_coro_handlers; + ZVAL_OBJ(&object->zobject, &object->std); + ZVAL_NULL(&object->zsocket); + return &object->std; +} + +static void client_coro_socket_dtor(ClientCoroObject *client) { + if (client->socket->protocol.private_data_1) { + sw_callable_free(client->socket->protocol.private_data_1); + client->socket->protocol.private_data_1 = nullptr; + } + client->socket = nullptr; + zend_update_property_null(Z_OBJCE_P(&client->zobject), SW_Z8_OBJ_P(&client->zobject), ZEND_STRL("socket")); + zend_update_property_bool(Z_OBJCE_P(&client->zobject), SW_Z8_OBJ_P(&client->zobject), ZEND_STRL("connected"), 0); + zval_ptr_dtor(&client->zsocket); + ZVAL_NULL(&client->zsocket); +} + +static SocketImpl *client_coro_create_socket(zval *zobject, zend_long type) { + auto socket_type = php_swoole_client_get_type(type); + auto object = php_swoole_create_socket(socket_type); + if (UNEXPECTED(!object)) { + php_swoole_socket_set_error_properties(zobject, errno); + return nullptr; + } + auto client = client_coro_get_client(zobject); + ZVAL_OBJ(&client->zsocket, object); + auto *socket = php_swoole_get_socket(&client->zsocket); + + socket->set_dtor([client](CoSocket *_socket) { client_coro_socket_dtor(client); }); + + zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("fd"), socket->get_fd()); + zend_update_property(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &client->zsocket); + + socket->set_buffer_allocator(sw_zend_string_allocator()); + socket->set_zero_copy(true); + + if ((type & SW_SOCK_SSL) && !socket->enable_ssl_encrypt()) { + php_swoole_socket_set_error_properties(zobject, EISCONN); + client_coro_socket_dtor(client); + return nullptr; + } + + return socket; } void php_swoole_client_coro_minit(int module_number) { @@ -127,16 +168,13 @@ void php_swoole_client_coro_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_client_coro); SW_SET_CLASS_CLONEABLE(swoole_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_client_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_client_coro, - php_swoole_client_coro_create_object, - php_swoole_client_coro_free_object, - ClientCoroObject, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_client_coro, client_coro_create_object, client_coro_free_object, ClientCoroObject, std); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_client_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_client_coro_ce, ZEND_STRL("type"), SW_SOCK_TCP, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); @@ -147,309 +185,41 @@ void php_swoole_client_coro_minit(int module_number) { zend_declare_class_constant_long(swoole_client_coro_ce, ZEND_STRL("MSG_WAITALL"), MSG_WAITALL); } -static sw_inline Socket *client_get_ptr(zval *zobject, bool silent = false) { - Socket *cli = php_swoole_get_client(zobject)->sock; - if (cli) { - return cli; - } else { - if (!silent) { - zend_update_property_long( - swoole_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), SW_ERROR_CLIENT_NO_CONNECTION); - zend_update_property_string(swoole_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("errMsg"), - swoole_strerror(SW_ERROR_CLIENT_NO_CONNECTION)); - } +static sw_inline SocketImpl *client_coro_get_socket_for_connect(zval *zobject, int port) { + auto client = client_coro_get_client(zobject); + if (client->socket) { + php_swoole_socket_set_error_properties(zobject, EISCONN, strerror(EISCONN)); return nullptr; } -} - -static Socket *client_coro_new(zval *zobject, int port) { - zval *ztype = sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TYPE), 0); - zend_long type = zval_get_long(ztype); - enum swSocketType sock_type = php_swoole_socktype(type); - if ((sock_type == SW_SOCK_TCP || sock_type == SW_SOCK_TCP6) && (port <= 0 || port > SW_CLIENT_MAX_PORT)) { + zval *ztype = sw_zend_read_property(swoole_client_coro_ce, zobject, ZEND_STRL("type"), 1); + auto socket_type = php_swoole_client_get_type(zval_get_long(ztype)); + if (NetSocket::is_tcp(socket_type) && !Address::verify_port(port, true)) { php_swoole_fatal_error(E_WARNING, "The port is invalid"); return nullptr; } - php_swoole_check_reactor(); - Socket *cli = new Socket(sock_type); - if (UNEXPECTED(cli->get_fd() < 0)) { - php_swoole_sys_error(E_WARNING, "new Socket() failed"); - zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); - zend_update_property_string( - Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), swoole_strerror(errno)); - delete cli; + auto sock = client_coro_create_socket(zobject, zval_get_long(ztype)); + if (!sock) { return nullptr; } - - zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("fd"), cli->get_fd()); - - cli->set_buffer_allocator(sw_zend_string_allocator()); - cli->set_zero_copy(true); - -#ifdef SW_USE_OPENSSL - if (type & SW_SOCK_SSL) { - cli->enable_ssl_encrypt(); - } -#endif - - php_swoole_get_client(zobject)->sock = cli; - - return cli; -} - -static bool client_coro_close(zval *zobject) { - Socket *cli = php_swoole_get_sock(zobject); - if (cli) { - zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); - if (!cli->get_bound_cid()) { - php_swoole_get_client(zobject)->sock = nullptr; - } - php_swoole_client_coro_socket_free(cli); - return true; - } - return false; -} - -void php_swoole_client_coro_socket_free(Socket *cli) { - if (!cli->has_bound()) { - if (cli->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) cli->protocol.private_data); - efree(cli->protocol.private_data); - cli->protocol.private_data = nullptr; - } - } - if (cli->close()) { - delete cli; - } -} - -bool php_swoole_client_set(Socket *cli, zval *zset) { - HashTable *vht = Z_ARRVAL_P(zset); - zval *ztmp; - bool ret = true; - - /** - * timeout - */ - if (php_swoole_array_get_value(vht, "timeout", ztmp)) { - cli->set_timeout(zval_get_double(ztmp)); - } - if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { - cli->set_timeout(zval_get_double(ztmp), Socket::TIMEOUT_CONNECT); - } - if (php_swoole_array_get_value(vht, "read_timeout", ztmp)) { - cli->set_timeout(zval_get_double(ztmp), Socket::TIMEOUT_READ); - } - if (php_swoole_array_get_value(vht, "write_timeout", ztmp)) { - cli->set_timeout(zval_get_double(ztmp), Socket::TIMEOUT_WRITE); - } - std::string _bind_address; - int _bind_port = 0; - if (php_swoole_array_get_value(vht, "bind_port", ztmp)) { - zend_long v = zval_get_long(ztmp); - _bind_port = SW_MAX(0, SW_MIN(v, UINT16_MAX)); - } - if (php_swoole_array_get_value(vht, "bind_address", ztmp)) { - zend::String tmp = ztmp; - _bind_address = tmp.to_std_string(); - } - if (!_bind_address.empty() && !cli->bind(_bind_address, _bind_port)) { - ret = false; - } - /** - * socket send/recv buffer size - */ - if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { - zend_long size = zval_get_long(ztmp); - if (size <= 0) { - php_swoole_fatal_error(E_WARNING, "socket buffer size must be greater than 0, got " ZEND_LONG_FMT, size); - ret = false; - } else { - cli->set_option(SOL_SOCKET, SO_RCVBUF, size) && cli->set_option(SOL_SOCKET, SO_SNDBUF, size); - } - } - /** - * client: tcp_nodelay - */ - if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { - if (cli->get_type() == SW_SOCK_TCP || cli->get_type() != SW_SOCK_TCP6) { - cli->get_socket()->set_tcp_nodelay(zval_is_true(ztmp)); - } - } - /** - * openssl and protocol options - */ - if (!php_swoole_socket_set_protocol(cli, zset)) { - ret = false; - } - /** - * socks5 proxy - */ - if (php_swoole_array_get_value(vht, "socks5_host", ztmp)) { - zend::String host(ztmp); - if (php_swoole_array_get_value(vht, "socks5_port", ztmp)) { - if (cli->socks5_proxy == nullptr) { - cli->socks5_proxy = new Socks5Proxy(); - } - cli->socks5_proxy->host = host.to_std_string(); - cli->socks5_proxy->port = zval_get_long(ztmp); - cli->socks5_proxy->dns_tunnel = 1; - if (php_swoole_array_get_value(vht, "socks5_username", ztmp)) { - zend::String username(ztmp); - if (username.len() > 0 && php_swoole_array_get_value(vht, "socks5_password", ztmp)) { - zend::String password(ztmp); - if (password.len() > 0) { - cli->socks5_proxy->method = 0x02; - cli->socks5_proxy->username = username.to_std_string(); - cli->socks5_proxy->password = password.to_std_string(); - } - } else { - php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); - ret = false; - } - } - } else { - php_swoole_fatal_error(E_WARNING, "socks5_port should not be null"); - ret = false; - } - } - /** - * http proxy - */ - else if (php_swoole_array_get_value(vht, "http_proxy_host", ztmp)) { - zend::String host(ztmp); - if (php_swoole_array_get_value(vht, "http_proxy_port", ztmp)) { - if (cli->http_proxy == nullptr) { - cli->http_proxy = new HttpProxy(); - } - cli->http_proxy->proxy_host = host.to_std_string(); - cli->http_proxy->proxy_port = zval_get_long(ztmp); - if (php_swoole_array_get_value(vht, "http_proxy_username", ztmp) || - php_swoole_array_get_value(vht, "http_proxy_user", ztmp)) { - zend::String username(ztmp); - if (username.len() > 0 && php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { - zend::String password(ztmp); - if (password.len() > 0) { - cli->http_proxy->username = username.to_std_string(); - cli->http_proxy->password = password.to_std_string(); - } - } else { - php_swoole_fatal_error(E_WARNING, "http_proxy_password should not be null"); - ret = false; - } - } - } else { - php_swoole_fatal_error(E_WARNING, "http_proxy_port should not be null"); - ret = false; - } - } - - return ret; -} - -#ifdef SW_USE_OPENSSL -bool php_swoole_socket_set_ssl(Socket *sock, zval *zset) { - HashTable *vht = Z_ARRVAL_P(zset); - zval *ztmp; - bool ret = true; - - if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { - zend_long v = zval_get_long(ztmp); - sock->get_ssl_context()->protocols = v; - } - if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { - sock->get_ssl_context()->disable_compress = !zval_is_true(ztmp); - } else if (php_swoole_array_get_value(vht, "ssl_disable_compression", ztmp)) { - sock->get_ssl_context()->disable_compress = !zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { - zend::String str_v(ztmp); - if (access(str_v.val(), R_OK) == 0) { - sock->get_ssl_context()->cert_file = str_v.to_std_string(); - } else { - php_swoole_fatal_error(E_WARNING, "ssl cert file[%s] not found", str_v.val()); - ret = false; - } - } - if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { - zend::String str_v(ztmp); - if (access(str_v.val(), R_OK) == 0) { - sock->get_ssl_context()->key_file = str_v.to_std_string(); - } else { - php_swoole_fatal_error(E_WARNING, "ssl key file[%s] not found", str_v.val()); - ret = false; - } - } - if (!sock->get_ssl_context()->cert_file.empty() && sock->get_ssl_context()->key_file.empty()) { - php_swoole_fatal_error(E_WARNING, "ssl require key file"); - } else if (!sock->get_ssl_context()->key_file.empty() && sock->get_ssl_context()->cert_file.empty()) { - php_swoole_fatal_error(E_WARNING, "ssl require cert file"); - } - if (php_swoole_array_get_value(vht, "ssl_passphrase", ztmp)) { - sock->get_ssl_context()->passphrase = zend::String(ztmp).to_std_string(); - } -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if (php_swoole_array_get_value(vht, "ssl_host_name", ztmp)) { - sock->get_ssl_context()->tls_host_name = zend::String(ztmp).to_std_string(); - /* if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically */ - sock->get_ssl_context()->disable_tls_host_name = sock->get_ssl_context()->tls_host_name.empty(); - } -#endif - if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { - sock->get_ssl_context()->verify_peer = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { - sock->get_ssl_context()->allow_self_signed = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { - sock->get_ssl_context()->cafile = zend::String(ztmp).to_std_string(); - } - if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { - sock->get_ssl_context()->capath = zend::String(ztmp).to_std_string(); - } - if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { - zend_long v = zval_get_long(ztmp); - sock->get_ssl_context()->verify_depth = SW_MAX(0, SW_MIN(v, UINT8_MAX)); - } - if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { - sock->get_ssl_context()->ciphers = zend::String(ztmp).to_std_string(); - } - if (php_swoole_array_get_value(vht, "ssl_ecdh_curve", ztmp)) { - sock->get_ssl_context()->ecdh_curve = zend::String(ztmp).to_std_string(); - } - -#ifdef OPENSSL_IS_BORINGSSL - if (php_swoole_array_get_value(vht, "ssl_grease", ztmp)) { - zend_long v = zval_get_long(ztmp); - sock->get_ssl_context()->grease = SW_MAX(0, SW_MIN(v, UINT8_MAX)); - } -#endif - - if (!sock->ssl_check_context()) { - ret = false; + client->socket = sock; + zval *zset = sw_zend_read_property_ex(swoole_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); + if (zset && ZVAL_IS_ARRAY(zset)) { + php_swoole_socket_set(sock, zset); } - return ret; + return sock; } -#endif static PHP_METHOD(swoole_client_coro, __construct) { - if (php_swoole_get_client(ZEND_THIS)->sock) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); - RETURN_FALSE; - } - zend_long type = 0; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_LONG(type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - int client_type = php_swoole_socktype(type); - if (client_type < SW_SOCK_TCP || client_type > SW_SOCK_UNIX_DGRAM) { + auto socket_type = php_swoole_client_get_type(type); + if (socket_type < SW_SOCK_TCP || socket_type > SW_SOCK_UNIX_DGRAM) { const char *space, *class_name = get_active_class_name(&space); zend_type_error("%s%s%s() expects parameter %d to be client type, unknown type " ZEND_LONG_FMT " given", class_name, @@ -459,17 +229,15 @@ static PHP_METHOD(swoole_client_coro, __construct) { type); RETURN_FALSE; } - + php_swoole_check_reactor(); zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("type"), type); - RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, __destruct) {} static PHP_METHOD(swoole_client_coro, set) { - Socket *cli = client_get_ptr(ZEND_THIS, true); - zval *zset, *zsetting; + zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(zset) @@ -477,14 +245,15 @@ static PHP_METHOD(swoole_client_coro, set) { if (php_swoole_array_length(zset) == 0) { RETURN_FALSE; - } else { - zsetting = sw_zend_read_and_convert_property_array(swoole_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); - php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); - if (cli) { - RETURN_BOOL(php_swoole_client_set(cli, zset)); - } - RETURN_TRUE; } + + zval *zsetting = sw_zend_read_and_convert_property_array(swoole_client_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); + php_array_merge(Z_ARRVAL_P(zsetting), Z_ARRVAL_P(zset)); + SocketImpl *cli = client_coro_get_socket(ZEND_THIS); + if (cli) { + RETURN_BOOL(php_swoole_socket_set(cli, zset)); + } + RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, connect) { @@ -507,32 +276,17 @@ static PHP_METHOD(swoole_client_coro, connect) { RETURN_FALSE; } - Socket *cli = php_swoole_get_sock(ZEND_THIS); - if (cli) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), EISCONN); - zend_update_property_string( - swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), swoole_strerror(EISCONN)); + SocketImpl *socket = client_coro_get_socket_for_connect(ZEND_THIS, port); + if (!socket) { RETURN_FALSE; } - - cli = client_coro_new(ZEND_THIS, (int) port); - if (!cli) { + socket->set_timeout(timeout, SW_TIMEOUT_CONNECT); + if (!socket->connect(host, port, sock_flag)) { + php_swoole_socket_set_error_properties(ZEND_THIS, socket); + socket->close(); RETURN_FALSE; } - - zval *zset = sw_zend_read_property_ex(swoole_client_coro_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); - if (zset && ZVAL_IS_ARRAY(zset)) { - php_swoole_client_set(cli, zset); - } - - cli->set_timeout(timeout, Socket::TIMEOUT_CONNECT); - if (!cli->connect(host, port, sock_flag)) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); - client_coro_close(ZEND_THIS); - RETURN_FALSE; - } - cli->set_timeout(timeout, Socket::TIMEOUT_RDWR); + socket->set_timeout(timeout, SW_TIMEOUT_RDWR); zend_update_property_bool(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 1); RETURN_TRUE; } @@ -553,22 +307,17 @@ static PHP_METHOD(swoole_client_coro, send) { RETURN_FALSE; } - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); - Socket::TimeoutSetter ts(cli, timeout, Socket::TIMEOUT_WRITE); + SocketImpl::TimeoutSetter ts(cli, timeout, SW_TIMEOUT_WRITE); ssize_t ret = cli->send_all(data, data_len); if (ret < 0) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } if ((size_t) ret < data_len && cli->errCode) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); } RETURN_LONG(ret); } @@ -576,30 +325,39 @@ static PHP_METHOD(swoole_client_coro, send) { static PHP_METHOD(swoole_client_coro, sendto) { char *host; size_t host_len; - long port; + zend_long port; char *data; size_t len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &host, &host_len, &port, &data, &len) == FAILURE) { + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_STRING(host, host_len) + Z_PARAM_LONG(port) + Z_PARAM_STRING(data, len) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (len == 0) { RETURN_FALSE; } - if (len == 0) { + SocketImpl *socket = nullptr; + const auto client = client_coro_get_client(ZEND_THIS); + if (client->socket == nullptr) { + socket = client_coro_get_socket_for_connect(ZEND_THIS, 0); + } else { + socket = client->socket; + } + if (!socket) { RETURN_FALSE; } - Socket *cli = php_swoole_get_sock(ZEND_THIS); - if (!cli) { - cli = client_coro_new(ZEND_THIS, (int) port); - if (!cli) { - RETURN_FALSE; - } + if (socket->get_socket()->is_tcp() && !Address::verify_port(port, true)) { + php_swoole_fatal_error(E_WARNING, "The port is invalid"); + RETURN_FALSE; } - ssize_t ret = cli->sendto(std::string(host, host_len), port, data, len); + auto ret = socket->sendto(std::string(host, host_len), port, data, len); if (ret < 0) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, socket); RETURN_FALSE; } RETURN_TRUE; @@ -617,27 +375,29 @@ static PHP_METHOD(swoole_client_coro, recvfrom) { RETURN_FALSE; } - Socket *cli = php_swoole_get_sock(ZEND_THIS); - if (!cli) { - cli = client_coro_new(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + SocketImpl *socket = nullptr; + auto client = client_coro_get_client(ZEND_THIS); + if (client->socket == nullptr) { + socket = client_coro_get_socket_for_connect(ZEND_THIS, 0); + } else { + socket = client->socket; + } + if (!socket) { + RETURN_FALSE; } - zend_string *retval = zend_string_alloc(length, 0); - ssize_t n_bytes = cli->recvfrom(ZSTR_VAL(retval), length); + zend_string *retval = zend_string_alloc(length, false); + ssize_t n_bytes = socket->recvfrom(ZSTR_VAL(retval), length); if (n_bytes < 0) { zend_string_free(retval); - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, socket); RETURN_FALSE; } else { zval_ptr_dtor(address); - ZVAL_STRING(address, cli->get_ip()); + ZVAL_STRING(address, socket->get_addr()); if (port) { zval_ptr_dtor(port); - ZVAL_LONG(port, cli->get_port()); + ZVAL_LONG(port, socket->get_port()); } ZSTR_LEN(retval) = n_bytes; @@ -660,10 +420,8 @@ static PHP_METHOD(swoole_client_coro, sendfile) { RETURN_FALSE; } - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); + // only stream socket can sendfile if (!(cli->get_type() == SW_SOCK_TCP || cli->get_type() == SW_SOCK_TCP6 || cli->get_type() == SW_SOCK_UNIX_STREAM)) { @@ -673,8 +431,7 @@ static PHP_METHOD(swoole_client_coro, sendfile) { RETURN_FALSE; } if (!cli->sendfile(file, offset, length)) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETVAL_FALSE; } else { RETVAL_TRUE; @@ -689,10 +446,7 @@ static PHP_METHOD(swoole_client_coro, recv) { Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); ssize_t retval; zend_string *result = nullptr; @@ -709,16 +463,15 @@ static PHP_METHOD(swoole_client_coro, recv) { } } } else { - result = zend_string_alloc(SW_PHP_CLIENT_BUFFER_SIZE - sizeof(zend_string), 0); - Socket::TimeoutSetter ts(cli, timeout, Socket::TIMEOUT_READ); + result = zend_string_alloc(SW_PHP_CLIENT_BUFFER_SIZE - sizeof(zend_string), false); + SocketImpl::TimeoutSetter ts(cli, timeout, SW_TIMEOUT_READ); retval = cli->recv(ZSTR_VAL(result), SW_PHP_CLIENT_BUFFER_SIZE - sizeof(zend_string)); if (retval <= 0) { zend_string_free(result); } } if (retval < 0) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } else if (retval == 0) { RETURN_EMPTY_STRING(); @@ -731,7 +484,6 @@ static PHP_METHOD(swoole_client_coro, recv) { static PHP_METHOD(swoole_client_coro, peek) { zend_long buf_len = SW_PHP_CLIENT_BUFFER_SIZE; - int ret; char *buf = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -739,16 +491,12 @@ static PHP_METHOD(swoole_client_coro, peek) { Z_PARAM_LONG(buf_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); - buf = (char *) emalloc(buf_len + 1); - ret = cli->peek(buf, buf_len); + buf = static_cast(emalloc((size_t) buf_len + 1)); + auto ret = cli->peek(buf, buf_len); if (ret < 0) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); efree(buf); RETURN_FALSE; } else { @@ -759,7 +507,7 @@ static PHP_METHOD(swoole_client_coro, peek) { } static PHP_METHOD(swoole_client_coro, isConnected) { - Socket *cli = php_swoole_get_sock(ZEND_THIS); + SocketImpl *cli = client_coro_get_socket(ZEND_THIS); if (cli && cli->is_connected()) { RETURN_TRUE; } else { @@ -768,65 +516,41 @@ static PHP_METHOD(swoole_client_coro, isConnected) { } static PHP_METHOD(swoole_client_coro, getsockname) { - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); - Address sa; - if (!cli->getsockname(&sa)) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + if (!cli->getsockname()) { + php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } array_init(return_value); zval zaddress; - ZVAL_STRING(&zaddress, sa.get_ip()); + ZVAL_STRING(&zaddress, cli->get_addr()); add_assoc_zval(return_value, "host", &zaddress); /* backward compatibility */ - Z_ADDREF(zaddress); - add_assoc_zval(return_value, "address", &zaddress); - add_assoc_long(return_value, "port", sa.get_port()); + zend::array_set(return_value, SW_STRL("address"), &zaddress); + add_assoc_long(return_value, "port", cli->get_port()); } /** * export Swoole\Coroutine\Socket object */ static PHP_METHOD(swoole_client_coro, exportSocket) { - zval rv; - zval *zsocket = - zend_read_property_ex(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), 1, &rv); - if (!ZVAL_IS_NULL(zsocket)) { - RETURN_ZVAL(zsocket, 1, 0); - } - - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } - if (!php_swoole_export_socket(return_value, cli)) { - RETURN_FALSE; - } - zend_update_property_ex( - swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), return_value); + auto cli = client_coro_get_client(ZEND_THIS); + RETURN_ZVAL(&cli->zsocket, 1, 0); } static PHP_METHOD(swoole_client_coro, getpeername) { - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); Address sa; if (!cli->getpeername(&sa)) { - zend_update_property_long(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), cli->errCode); - zend_update_property_string(swoole_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), cli->errMsg); + php_swoole_socket_set_error_properties(ZEND_THIS, cli); RETURN_FALSE; } array_init(return_value); zval zaddress; - ZVAL_STRING(&zaddress, sa.get_ip()); + ZVAL_STRING(&zaddress, sa.get_addr()); add_assoc_zval(return_value, "host", &zaddress); /* backward compatibility */ Z_ADDREF(zaddress); add_assoc_zval(return_value, "address", &zaddress); @@ -834,42 +558,44 @@ static PHP_METHOD(swoole_client_coro, getpeername) { } static PHP_METHOD(swoole_client_coro, close) { - RETURN_BOOL(client_coro_close(ZEND_THIS)); -} - -#ifdef SW_USE_OPENSSL -static PHP_METHOD(swoole_client_coro, enableSSL) { - Socket *cli = client_get_ptr(ZEND_THIS); - - if (!cli) { + CLIENT_CORO_GET_SOCKET_SAFE(_socket); + zend_update_property_bool(Z_OBJCE_P(ZEND_THIS), SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 0); + if (!_socket->close()) { + php_swoole_socket_set_error_properties(ZEND_THIS, _socket); RETURN_FALSE; } + RETURN_TRUE; +} +static PHP_METHOD(swoole_client_coro, enableSSL) { + CLIENT_CORO_GET_SOCKET_SAFE(cli); if (cli->get_type() != SW_SOCK_TCP && cli->get_type() != SW_SOCK_TCP6) { - php_swoole_fatal_error(E_WARNING, "cannot use enableSSL"); + php_swoole_socket_set_error_properties(ZEND_THIS, ESOCKTNOSUPPORT); RETURN_FALSE; } if (cli->get_ssl()) { - php_swoole_fatal_error(E_WARNING, "SSL has been enabled"); + php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); + RETURN_FALSE; + } + if (!cli->enable_ssl_encrypt()) { + php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); RETURN_FALSE; } - - cli->enable_ssl_encrypt(); - zval *zset = sw_zend_read_property_ex(swoole_client_coro_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0); if (php_swoole_array_length_safe(zset) > 0) { php_swoole_socket_set_ssl(cli, zset); } - RETURN_BOOL(cli->ssl_handshake()); + if (!cli->ssl_handshake()) { + php_swoole_socket_set_error_properties(ZEND_THIS, cli); + RETURN_FALSE; + } + RETURN_TRUE; } static PHP_METHOD(swoole_client_coro, getPeerCert) { - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); if (!cli->get_ssl()) { - php_swoole_fatal_error(E_WARNING, "SSL is not ready"); + php_swoole_socket_set_error_properties(ZEND_THIS, EISCONN); RETURN_FALSE; } if (!cli->get_socket()->ssl_get_peer_certificate(sw_tg_buffer())) { @@ -879,18 +605,14 @@ static PHP_METHOD(swoole_client_coro, getPeerCert) { } static PHP_METHOD(swoole_client_coro, verifyPeerCert) { - Socket *cli = client_get_ptr(ZEND_THIS); - if (!cli) { - RETURN_FALSE; - } + CLIENT_CORO_GET_SOCKET_SAFE(cli); if (!cli->get_ssl()) { - php_swoole_fatal_error(E_WARNING, "SSL is not ready"); + php_swoole_socket_set_error_properties(ZEND_THIS, ENOTCONN); RETURN_FALSE; } - zend_bool allow_self_signed = 0; + zend_bool allow_self_signed = false; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &allow_self_signed) == FAILURE) { RETURN_FALSE; } RETURN_BOOL(cli->ssl_verify(allow_self_signed)); } -#endif diff --git a/ext-src/swoole_coroutine.cc b/ext-src/swoole_coroutine.cc index 2e0a94eec6..2fa34eb250 100644 --- a/ext-src/swoole_coroutine.cc +++ b/ext-src/swoole_coroutine.cc @@ -22,59 +22,63 @@ #include "swoole_server.h" #include "swoole_signal.h" +#include "swoole_async.h" +#include "swoole_iouring.h" +BEGIN_EXTERN_C() #include "zend_builtin_functions.h" +#include "ext/standard/basic_functions.h" #include "ext/spl/spl_array.h" +#include "stubs/php_swoole_coroutine_arginfo.h" +END_EXTERN_C() + #include #include -BEGIN_EXTERN_C() -#include "stubs/php_swoole_coroutine_arginfo.h" -END_EXTERN_C() +/** + * The coroutine canceled exception must be explicitly caught in the php code. + * If the underlying layer implicitly catches this exception, `Co::cancel()` may be abused, + * resulting in transactional problems and serious bugs. + */ +#define SW_RECOVER_CANCELED_EXCEPTION 0 + +#define INVALID_PTR -1 using std::unordered_map; using swoole::Coroutine; using swoole::PHPContext; using swoole::PHPCoroutine; -using swoole::coroutine::Socket; using swoole::coroutine::System; -#define PHP_CORO_TASK_SLOT \ - ((int) ((ZEND_MM_ALIGNED_SIZE(sizeof(PHPContext)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / \ - ZEND_MM_ALIGNED_SIZE(sizeof(zval)))) - enum sw_exit_flags { SW_EXIT_IN_COROUTINE = 1 << 1, SW_EXIT_IN_SERVER = 1 << 2 }; -bool PHPCoroutine::activated = false; -uint32_t PHPCoroutine::concurrency = 0; -zend_array *PHPCoroutine::options = nullptr; +SW_THREAD_LOCAL bool PHPCoroutine::activated = false; +SW_THREAD_LOCAL zend_array *PHPCoroutine::options = nullptr; -PHPCoroutine::Config PHPCoroutine::config{ +SW_THREAD_LOCAL PHPCoroutine::Config PHPCoroutine::config{ SW_DEFAULT_MAX_CORO_NUM, - UINT_MAX, 0, false, true, }; -PHPContext PHPCoroutine::main_task{}; -std::thread PHPCoroutine::interrupt_thread; -bool PHPCoroutine::interrupt_thread_running = false; +SW_THREAD_LOCAL PHPContext PHPCoroutine::main_context{}; +SW_THREAD_LOCAL std::thread PHPCoroutine::interrupt_thread; +SW_THREAD_LOCAL bool PHPCoroutine::interrupt_thread_running = false; extern void php_swoole_load_library(); -static zend_atomic_bool *zend_vm_interrupt = nullptr; +static SW_THREAD_LOCAL zend_atomic_bool *zend_vm_interrupt = nullptr; +static SW_THREAD_LOCAL unordered_map user_yield_coros; + +#if PHP_VERSION_ID < 80400 static user_opcode_handler_t ori_exit_handler = nullptr; +#endif static user_opcode_handler_t ori_begin_silence_handler = nullptr; static user_opcode_handler_t ori_end_silence_handler = nullptr; -static unordered_map user_yield_coros; static void (*orig_interrupt_function)(zend_execute_data *execute_data) = nullptr; -static void (*orig_error_function)(int type, - error_filename_t *error_filename, - const uint32_t error_lineno, - ZEND_ERROR_CB_LAST_ARG_D) = nullptr; static zend_class_entry *swoole_coroutine_util_ce; static zend_class_entry *swoole_exit_exception_ce; @@ -82,12 +86,18 @@ static zend_object_handlers swoole_exit_exception_handlers; static zend_class_entry *swoole_coroutine_iterator_ce; static zend_class_entry *swoole_coroutine_context_ce; +static zend_class_entry *swoole_coroutine_canceled_exception_ce; +static zend_object_handlers swoole_coroutine_canceled_exception_handlers; +static zend_class_entry *swoole_coroutine_timeout_exception_ce; +static zend_object_handlers swoole_coroutine_timeout_exception_handlers; + SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_coroutine, exists); static PHP_METHOD(swoole_coroutine, yield); static PHP_METHOD(swoole_coroutine, resume); static PHP_METHOD(swoole_coroutine, join); static PHP_METHOD(swoole_coroutine, cancel); +static PHP_METHOD(swoole_coroutine, setTimeLimit); static PHP_METHOD(swoole_coroutine, isCanceled); static PHP_METHOD(swoole_coroutine, stats); static PHP_METHOD(swoole_coroutine, getCid); @@ -120,6 +130,7 @@ static const zend_function_entry swoole_coroutine_methods[] = PHP_ME(swoole_coroutine, cancel, arginfo_class_Swoole_Coroutine_cancel, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, join, arginfo_class_Swoole_Coroutine_join, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, isCanceled, arginfo_class_Swoole_Coroutine_isCanceled, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_coroutine, setTimeLimit, arginfo_class_Swoole_Coroutine_setTimeLimit, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_MALIAS(swoole_coroutine, suspend, yield, arginfo_class_Swoole_Coroutine_suspend, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, resume, arginfo_class_Swoole_Coroutine_resume, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine, stats, arginfo_class_Swoole_Coroutine_stats, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) @@ -153,10 +164,6 @@ static const zend_function_entry swoole_coroutine_methods[] = PHP_ME(swoole_coroutine_system, waitPid, arginfo_class_Swoole_Coroutine_System_waitPid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitSignal, arginfo_class_Swoole_Coroutine_System_waitSignal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitEvent, arginfo_class_Swoole_Coroutine_System_waitEvent, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) - /* Deprecated file methods */ - PHP_ME(swoole_coroutine_system, fread, arginfo_class_Swoole_Coroutine_System_fread, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_coroutine_system, fgets, arginfo_class_Swoole_Coroutine_System_fgets, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_coroutine_system, fwrite, arginfo_class_Swoole_Coroutine_System_fwrite, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) PHP_FE_END }; // clang-format on @@ -168,16 +175,16 @@ static PHP_METHOD(swoole_exit_exception, getFlags); static PHP_METHOD(swoole_exit_exception, getStatus); // clang-format off -static const zend_function_entry swoole_exit_exception_methods[] = { +static constexpr zend_function_entry swoole_exit_exception_methods[] = { PHP_ME(swoole_exit_exception, getFlags, arginfo_class_Swoole_ExitException_getFlags, ZEND_ACC_PUBLIC) PHP_ME(swoole_exit_exception, getStatus, arginfo_class_Swoole_ExitException_getStatus, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on +#if PHP_VERSION_ID < 80400 static int coro_exit_handler(zend_execute_data *execute_data) { zval ex; - zend_object *obj; zend_long flags = 0; if (Coroutine::get_current()) { flags |= SW_EXIT_IN_COROUTINE; @@ -187,7 +194,7 @@ static int coro_exit_handler(zend_execute_data *execute_data) { } if (flags) { const zend_op *opline = EX(opline); - zval _exit_status {}; + zval _exit_status{}; zval *exit_status = nullptr; if (opline->op1_type != IS_UNUSED) { @@ -210,7 +217,7 @@ static int coro_exit_handler(zend_execute_data *execute_data) { exit_status = &_exit_status; ZVAL_NULL(exit_status); } - obj = zend_throw_exception(swoole_exit_exception_ce, "swoole exit", 0); + zend_object *obj = zend_throw_exception(swoole_exit_exception_ce, "swoole exit", 0); ZVAL_OBJ(&ex, obj); zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("flags"), flags); Z_TRY_ADDREF_P(exit_status); @@ -219,6 +226,48 @@ static int coro_exit_handler(zend_execute_data *execute_data) { return ZEND_USER_OPCODE_DISPATCH; } +#else +SW_EXTERN_C_BEGIN +PHP_FUNCTION(swoole_exit) { + zend_long flags = 0; + if (Coroutine::get_current()) { + flags |= SW_EXIT_IN_COROUTINE; + } + + if (sw_server() && sw_server()->is_started()) { + flags |= SW_EXIT_IN_SERVER; + } + + zend_string *message = NULL; + zend_long status = 0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_LONG(message, status) + ZEND_PARSE_PARAMETERS_END(); + + if (flags) { + zval ex = {}; + zend_object *obj = + zend_throw_exception(swoole_exit_exception_ce, (message ? ZSTR_VAL(message) : "swoole exit"), 0); + ZVAL_OBJ(&ex, obj); + zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("flags"), flags); + if (message) { + zend_update_property_str(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), message); + } else { + zend_update_property_long(swoole_exit_exception_ce, SW_Z8_OBJ_P(&ex), ZEND_STRL("status"), status); + } + } else { + if (!php_swoole_call_original_handler(ZEND_STRL("exit"), INTERNAL_FUNCTION_PARAM_PASSTHRU)) { + if (message) { + php_write(ZSTR_VAL(message), ZSTR_LEN(message)); + } + sw_php_exit(status); + } + } +} +SW_EXTERN_C_END +#endif static int coro_begin_silence_handler(zend_execute_data *execute_data) { PHPContext *task = PHPCoroutine::get_context(); @@ -234,7 +283,7 @@ static int coro_end_silence_handler(zend_execute_data *execute_data) { } static void coro_interrupt_resume(void *data) { - Coroutine *co = (Coroutine *) data; + auto *co = static_cast(data); if (co && !co->is_end()) { swoole_trace_log(SW_TRACE_COROUTINE, "interrupt_callback cid=%ld ", co->get_cid()); co->resume(); @@ -252,45 +301,103 @@ static void coro_interrupt_function(zend_execute_data *execute_data) { } } -void PHPCoroutine::init() { - Coroutine::set_on_yield(on_yield); - Coroutine::set_on_resume(on_resume); - Coroutine::set_on_close(on_close); +PHPContext *PHPCoroutine::create_context(const Args *args) { + auto *ctx = static_cast(emalloc(sizeof(PHPContext))); + ctx->output_ptr = nullptr; + ctx->serialize_lock = 0; + ctx->serialize = {}; + ctx->unserialize = {}; + ctx->in_silence = false; + + ctx->co = Coroutine::get_current(); + ctx->co->set_task((void *) ctx); + ctx->defer_tasks = nullptr; + ctx->pcid = ctx->co->get_origin_cid(); + ctx->context = nullptr; + ctx->on_yield = nullptr; + ctx->on_resume = nullptr; + ctx->on_close = nullptr; + ctx->enable_scheduler = true; + + if (UNEXPECTED(SWOOLE_G(enable_fiber_mock))) { + fiber_context_init(ctx); + } else { + ctx->fiber_context = nullptr; + } + ctx->fiber_init_notified = false; + + EG(vm_stack) = zend_vm_stack_new_page(SW_DEFAULT_PHP_STACK_PAGE_SIZE, nullptr); + EG(vm_stack_top) = EG(vm_stack)->top + ZEND_CALL_FRAME_SLOT; + EG(vm_stack_end) = EG(vm_stack)->end; + EG(vm_stack_page_size) = SW_DEFAULT_PHP_STACK_PAGE_SIZE; + + zend_function *func = EG(current_execute_data)->func; + auto *call = reinterpret_cast((EG(vm_stack_top))); + EG(current_execute_data) = call; + memset(EG(current_execute_data), 0, sizeof(zend_execute_data)); + + EG(error_handling) = EH_NORMAL; + EG(exception_class) = nullptr; + EG(exception) = nullptr; + EG(jit_trace_num) = 0; + + call->func = func; + EG(vm_stack_top) += ZEND_CALL_FRAME_SLOT; + +#ifdef ZEND_CHECK_STACK_LIMIT + EG(stack_base) = stack_base(ctx); + EG(stack_limit) = stack_limit(ctx); +#endif + + save_vm_stack(ctx); + record_last_msec(ctx); + + ctx->fci_cache = *args->fci_cache; + ctx->fci.size = sizeof(ctx->fci); + ctx->fci.object = nullptr; + ctx->fci.param_count = args->argc; + ctx->fci.params = args->argv; + ctx->fci.named_params = nullptr; + ctx->return_value = {}; + ctx->fci.retval = &ctx->return_value; + + if (args->callable) { + ctx->fci.function_name = *args->callable; + Z_TRY_ADDREF(ctx->fci.function_name); + } else { + ZVAL_UNDEF(&ctx->fci.function_name); + } + sw_zend_fci_cache_persist(&ctx->fci_cache); + + return ctx; } -void PHPCoroutine::error_cb(int type, - error_filename_t *error_filename, - const uint32_t error_lineno, - ZEND_ERROR_CB_LAST_ARG_D) { - if (sw_unlikely(type & E_FATAL_ERRORS)) { +void PHPCoroutine::bailout() { + Coroutine::bailout([]() { if (sw_reactor()) { sw_reactor()->running = false; sw_reactor()->bailout = true; } - if (swoole_coroutine_is_in()) { - // update the last coroutine's info - save_task(get_context()); - Coroutine::bailout([=]() { - zend_error_cb = orig_error_function; - orig_error_function(type, error_filename, error_lineno, ZEND_ERROR_CB_LAST_ARG_RELAY); - zend_bailout(); - }); - } - } - if (orig_error_function) { - orig_error_function(type, error_filename, error_lineno, ZEND_ERROR_CB_LAST_ARG_RELAY); - } + zend_bailout(); + }); } -void PHPCoroutine::catch_exception() { +bool PHPCoroutine::catch_exception() { if (UNEXPECTED(EG(exception))) { - zend_error_cb = orig_error_function; - // the exception error messages MUST be output on the current coroutine stack - zend_exception_error(EG(exception), E_ERROR); -#if PHP_VERSION_ID >= 80000 - zend_bailout(); +#if SW_RECOVER_CANCELED_EXCEPTION + if (EG(exception)->ce == swoole_coroutine_canceled_exception_ce) { + OBJ_RELEASE(EG(exception)); + EG(exception) = nullptr; + } else { +#endif + // the exception error messages MUST be output on the current coroutine stack + zend_exception_error(EG(exception), E_ERROR); + return true; +#if SW_RECOVER_CANCELED_EXCEPTION + } #endif } + return false; } void PHPCoroutine::activate() { @@ -298,14 +405,8 @@ void PHPCoroutine::activate() { return; } - if (zend_hash_str_find_ptr(&module_registry, ZEND_STRL("xdebug"))) { - php_swoole_fatal_error( - E_WARNING, - "Using Xdebug in coroutines is extremely dangerous, please notice that it may lead to coredump!"); - } - zval *enable_library = zend_get_constant_str(ZEND_STRL("SWOOLE_LIBRARY")); - if (enable_library == NULL || !zval_is_true(enable_library)) { + if (enable_library == nullptr || !zval_is_true(enable_library)) { php_swoole_load_library(); } @@ -316,55 +417,68 @@ void PHPCoroutine::activate() { orig_interrupt_function = zend_interrupt_function; zend_interrupt_function = coro_interrupt_function; - /* replace the error function to save execute_data */ - orig_error_function = zend_error_cb; - zend_error_cb = PHPCoroutine::error_cb; - if (SWOOLE_G(enable_preemptive_scheduler) || config.enable_preemptive_scheduler) { /* create a thread to interrupt the coroutine that takes up too much time */ interrupt_thread_start(); } - if (config.hook_flags) { - enable_hook(config.hook_flags); + if (sw_is_main_thread()) { + if (config.hook_flags) { + enable_hook(config.hook_flags); + } + disable_unsafe_function(); } - disable_unsafe_function(); - /* deactivate when reactor free */ sw_reactor()->add_destroy_callback(deactivate, nullptr); Coroutine::activate(); + + Coroutine::set_on_yield(on_yield); + Coroutine::set_on_resume(on_resume); + Coroutine::set_on_close(on_close); + activated = true; } void PHPCoroutine::deactivate(void *ptr) { + if (sw_unlikely(!activated)) { + return; + } + activated = false; interrupt_thread_stop(); - /** - * reset runtime hook - */ - disable_hook(); + + if (sw_is_main_thread()) { + disable_hook(); + enable_unsafe_function(); + } + + Coroutine::set_on_yield(nullptr); + Coroutine::set_on_resume(nullptr); + Coroutine::set_on_close(nullptr); zend_interrupt_function = orig_interrupt_function; - zend_error_cb = orig_error_function; if (config.enable_deadlock_check) { deadlock_check(); } - enable_unsafe_function(); Coroutine::deactivate(); - activated = false; } void PHPCoroutine::shutdown() { - interrupt_thread_stop(); - Coroutine::bailout(nullptr); + if (activated) { + deactivate(nullptr); + } if (options) { zend_array_destroy(options); options = nullptr; } + free_main_context(); } +// !!! [Danger Warning] +// The deadlock_check function can only print information and cannot execute any async functions +// This function can only execute internal code and cannot provide any callbacks, allowing the execution of user code void PHPCoroutine::deadlock_check() { if (Coroutine::count() == 0) { return; @@ -373,11 +487,12 @@ void PHPCoroutine::deadlock_check() { return; } if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Coroutine\\deadlock_check", 0, nullptr); + zend::function::call(R"(\Swoole\Coroutine\deadlock_check)", 0, nullptr); } else { - printf("\n===================================================================" - "\n [FATAL ERROR]: all coroutines (count: %lu) are asleep - deadlock!" - "\n===================================================================\n", + printf("\n ===================================================================" + "\n [FATAL ERROR]: all coroutines (count: %lu) are asleep - deadlock! " + "\n ===================================================================" + "\n", Coroutine::count()); } } @@ -405,31 +520,6 @@ void PHPCoroutine::interrupt_thread_start() { }); } -inline void PHPCoroutine::vm_stack_init(void) { - uint32_t size = SW_DEFAULT_PHP_STACK_PAGE_SIZE; - zend_vm_stack page = (zend_vm_stack) emalloc(size); - - page->top = ZEND_VM_STACK_ELEMENTS(page); - page->end = (zval *) ((char *) page + size); - page->prev = nullptr; - - EG(vm_stack) = page; - EG(vm_stack)->top++; - EG(vm_stack_top) = EG(vm_stack)->top; - EG(vm_stack_end) = EG(vm_stack)->end; - EG(vm_stack_page_size) = size; -} - -inline void PHPCoroutine::vm_stack_destroy(void) { - zend_vm_stack stack = EG(vm_stack); - - while (stack != nullptr) { - zend_vm_stack p = stack->prev; - efree(stack); - stack = p; - } -} - /** * The meaning of the task argument in coro switch functions * @@ -439,145 +529,174 @@ inline void PHPCoroutine::vm_stack_destroy(void) { * close: current_task * */ -inline void PHPCoroutine::save_vm_stack(PHPContext *task) { -#ifdef SW_CORO_SWAP_BAILOUT - task->bailout = EG(bailout); +inline void PHPCoroutine::save_vm_stack(PHPContext *ctx) { + ctx->bailout = EG(bailout); + ctx->vm_stack_top = EG(vm_stack_top); + ctx->vm_stack_end = EG(vm_stack_end); + ctx->vm_stack = EG(vm_stack); + ctx->vm_stack_page_size = EG(vm_stack_page_size); + ctx->execute_data = EG(current_execute_data); + ctx->jit_trace_num = EG(jit_trace_num); + ctx->error_handling = EG(error_handling); + ctx->exception_class = EG(exception_class); + ctx->exception = EG(exception); + if (UNEXPECTED(ctx->in_silence)) { + ctx->tmp_error_reporting = EG(error_reporting); + EG(error_reporting) = ctx->ori_error_reporting; + } +#ifdef ZEND_CHECK_STACK_LIMIT + ctx->stack_base = EG(stack_base); + ctx->stack_limit = EG(stack_limit); #endif - task->vm_stack_top = EG(vm_stack_top); - task->vm_stack_end = EG(vm_stack_end); - task->vm_stack = EG(vm_stack); - task->vm_stack_page_size = EG(vm_stack_page_size); - task->execute_data = EG(current_execute_data); - task->jit_trace_num = EG(jit_trace_num); - task->error_handling = EG(error_handling); - task->exception_class = EG(exception_class); - task->exception = EG(exception); -#if PHP_VERSION_ID < 80100 - if (UNEXPECTED(BG(array_walk_fci).size != 0)) { - if (!task->array_walk_fci) { - task->array_walk_fci = (zend::Function *) emalloc(sizeof(*task->array_walk_fci)); - } - memcpy(task->array_walk_fci, &BG(array_walk_fci), sizeof(*task->array_walk_fci)); - memset(&BG(array_walk_fci), 0, sizeof(*task->array_walk_fci)); - } -#endif - if (UNEXPECTED(task->in_silence)) { - task->tmp_error_reporting = EG(error_reporting); - EG(error_reporting) = task->ori_error_reporting; - } } -inline void PHPCoroutine::restore_vm_stack(PHPContext *task) { -#ifdef SW_CORO_SWAP_BAILOUT - EG(bailout) = task->bailout; -#endif - EG(vm_stack_top) = task->vm_stack_top; - EG(vm_stack_end) = task->vm_stack_end; - EG(vm_stack) = task->vm_stack; - EG(vm_stack_page_size) = task->vm_stack_page_size; - EG(current_execute_data) = task->execute_data; - EG(jit_trace_num) = task->jit_trace_num; - EG(error_handling) = task->error_handling; - EG(exception_class) = task->exception_class; - EG(exception) = task->exception; -#if PHP_VERSION_ID < 80100 - if (UNEXPECTED(task->array_walk_fci && task->array_walk_fci->fci.size != 0)) { - memcpy(&BG(array_walk_fci), task->array_walk_fci, sizeof(*task->array_walk_fci)); - task->array_walk_fci->fci.size = 0; - } +inline void PHPCoroutine::restore_vm_stack(PHPContext *ctx) { + EG(bailout) = ctx->bailout; + EG(vm_stack_top) = ctx->vm_stack_top; + EG(vm_stack_end) = ctx->vm_stack_end; + EG(vm_stack) = ctx->vm_stack; + EG(vm_stack_page_size) = ctx->vm_stack_page_size; + EG(current_execute_data) = ctx->execute_data; + EG(jit_trace_num) = ctx->jit_trace_num; + EG(error_handling) = ctx->error_handling; + EG(exception_class) = ctx->exception_class; + EG(exception) = ctx->exception; + if (UNEXPECTED(ctx->in_silence)) { + EG(error_reporting) = ctx->tmp_error_reporting; + } +#ifdef ZEND_CHECK_STACK_LIMIT + EG(stack_base) = ctx->stack_base; + EG(stack_limit) = ctx->stack_limit; #endif - if (UNEXPECTED(task->in_silence)) { - EG(error_reporting) = task->tmp_error_reporting; - } } -inline void PHPCoroutine::save_og(PHPContext *task) { +inline void PHPCoroutine::save_og(PHPContext *ctx) { if (OG(handlers).elements) { - task->output_ptr = (zend_output_globals *) emalloc(sizeof(zend_output_globals)); - memcpy(task->output_ptr, SWOG, sizeof(zend_output_globals)); + ctx->output_ptr = (zend_output_globals *) emalloc(sizeof(zend_output_globals)); + memcpy(ctx->output_ptr, SWOG, sizeof(zend_output_globals)); php_output_activate(); } else { - task->output_ptr = nullptr; + ctx->output_ptr = nullptr; } } -inline void PHPCoroutine::restore_og(PHPContext *task) { - if (task->output_ptr) { - memcpy(SWOG, task->output_ptr, sizeof(zend_output_globals)); - efree(task->output_ptr); - task->output_ptr = nullptr; +inline void PHPCoroutine::restore_og(PHPContext *ctx) { + if (ctx->output_ptr) { + memcpy(SWOG, ctx->output_ptr, sizeof(zend_output_globals)); + efree(ctx->output_ptr); + ctx->output_ptr = nullptr; + } +} + +void PHPCoroutine::save_bg(PHPContext *ctx) { + if (BG(serialize_lock)) { + ctx->serialize_lock = BG(serialize_lock); + } + if (BG(serialize).data) { + memcpy(&ctx->serialize, &BG(serialize), sizeof(BG(serialize))); + } + if (BG(unserialize).data) { + memcpy(&ctx->unserialize, &BG(unserialize), sizeof(BG(unserialize))); + } +} + +void PHPCoroutine::restore_bg(PHPContext *ctx) { + if (ctx->serialize_lock) { + BG(serialize_lock) = ctx->serialize_lock; + ctx->serialize_lock = 0; + } + if (ctx->serialize.data) { + memcpy(&BG(serialize), &ctx->serialize, sizeof(BG(serialize))); + ctx->serialize = {}; + } + if (ctx->unserialize.data) { + memcpy(&BG(unserialize), &ctx->unserialize, sizeof(BG(unserialize))); + ctx->unserialize = {}; } } void PHPCoroutine::set_hook_flags(uint32_t flags) { - zval options; - array_init(&options); - add_assoc_long(&options, "hook_flags", flags); + zval zoptions; + array_init(&zoptions); + add_assoc_long(&zoptions, "hook_flags", flags); - if (PHPCoroutine::options) { - zend_hash_merge(PHPCoroutine::options, Z_ARRVAL(options), nullptr, true); - zval_ptr_dtor(&options); + if (options) { + zend_hash_merge(options, Z_ARRVAL(zoptions), nullptr, true); + zval_ptr_dtor(&zoptions); } else { - PHPCoroutine::options = Z_ARRVAL(options); + options = Z_ARRVAL(zoptions); } config.hook_flags = flags; } -void PHPCoroutine::save_task(PHPContext *task) { - save_vm_stack(task); - save_og(task); +void PHPCoroutine::save_context(PHPContext *ctx) { + save_vm_stack(ctx); + save_og(ctx); + save_bg(ctx); } -void PHPCoroutine::restore_task(PHPContext *task) { - restore_vm_stack(task); - restore_og(task); +void PHPCoroutine::restore_context(PHPContext *ctx) { + restore_vm_stack(ctx); + restore_og(ctx); + restore_bg(ctx); } void PHPCoroutine::on_yield(void *arg) { - PHPContext *task = (PHPContext *) arg; - PHPContext *origin_task = get_origin_context(task); - save_task(task); - restore_task(origin_task); + auto *ctx = static_cast(arg); + auto *origin_ctx = get_origin_context(ctx); + + fiber_context_switch_try_notify(ctx, origin_ctx); + save_context(ctx); + restore_context(origin_ctx); - if (task->on_yield) { - (*task->on_yield)(task); + if (ctx->on_yield) { + (*ctx->on_yield)(ctx); } - swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", task->co->get_cid(), task->co->get_origin_cid()); + swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", ctx->co->get_cid(), ctx->co->get_origin_cid()); } void PHPCoroutine::on_resume(void *arg) { - PHPContext *task = (PHPContext *) arg; - PHPContext *current_task = get_context(); - save_task(current_task); - restore_task(task); - record_last_msec(task); + auto *ctx = static_cast(arg); + auto *current_ctx = get_context(); - if (task->on_resume) { - (*task->on_resume)(task); + fiber_context_switch_try_notify(current_ctx, ctx); + save_context(current_ctx); + restore_context(ctx); + record_last_msec(ctx); + + if (ctx->on_resume) { + (*ctx->on_resume)(ctx); } - swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", Coroutine::get_current_cid(), task->co->get_cid()); + swoole_trace_log(SW_TRACE_COROUTINE, "from cid=%ld to cid=%ld", Coroutine::get_current_cid(), ctx->co->get_cid()); } void PHPCoroutine::on_close(void *arg) { - PHPContext *task = (PHPContext *) arg; - PHPContext *origin_task = get_origin_context(task); + auto *ctx = static_cast(arg); + if (ctx->on_close) { + (*ctx->on_close)(ctx); + } + efree(ctx); +} + +void PHPCoroutine::destroy_context(PHPContext *ctx) { + PHPContext *origin_ctx = get_origin_context(ctx); #ifdef SW_LOG_TRACE_OPEN // MUST be assigned here, the task memory may have been released - long cid = task->co->get_cid(); - long origin_cid = task->co->get_origin_cid(); + long cid = ctx->co->get_cid(); + long origin_cid = ctx->co->get_origin_cid(); #endif if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_CORO_STOP)) { - swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_STOP, task); + swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_STOP, ctx); } if (OG(handlers).elements) { zend_bool no_headers = SG(request_info).no_headers; /* Do not send headers by SAPI */ - SG(request_info).no_headers = 1; + SG(request_info).no_headers = true; if (OG(active)) { php_output_end_all(); } @@ -585,22 +704,32 @@ void PHPCoroutine::on_close(void *arg) { php_output_activate(); SG(request_info).no_headers = no_headers; } -#if PHP_VERSION_ID < 80100 - if (task->array_walk_fci) { - efree(task->array_walk_fci); - } -#endif - if (task->on_close) { - (*task->on_close)(task); + if (ctx->defer_tasks) { + while (!ctx->defer_tasks->empty()) { + zend::Function *defer_fci = ctx->defer_tasks->top(); + ctx->defer_tasks->pop(); + sw_zend_fci_cache_discard(&defer_fci->fci_cache); + efree(defer_fci); + } + delete ctx->defer_tasks; + ctx->defer_tasks = nullptr; } - if (task->pcid == -1) { - concurrency--; + // Release resources + if (ctx->context) { + zend_object *context = ctx->context; + ctx->context = reinterpret_cast(INVALID_PTR); + OBJ_RELEASE(context); } - vm_stack_destroy(); - restore_task(origin_task); + Z_TRY_DELREF(ctx->fci.function_name); + ZVAL_UNDEF(&ctx->fci.function_name); + sw_zend_fci_cache_discard(&ctx->fci_cache); + + Z_TRY_DELREF(ctx->return_value); + + fiber_context_try_destroy(ctx, origin_ctx); swoole_trace_log(SW_TRACE_COROUTINE, "coro close cid=%ld and resume to %ld, %zu remained. usage size: %zu. malloc size: %zu", @@ -609,185 +738,79 @@ void PHPCoroutine::on_close(void *arg) { (uintmax_t) Coroutine::count() - 1, (uintmax_t) zend_memory_usage(0), (uintmax_t) zend_memory_usage(1)); -} - -void PHPCoroutine::main_func(void *arg) { -#ifdef SW_CORO_SUPPORT_BAILOUT - zend_first_try { -#endif - Args *php_arg = (Args *) arg; - zend_fcall_info_cache fci_cache = *php_arg->fci_cache; - zend_function *func = fci_cache.function_handler; - zval *argv = php_arg->argv; - int argc = php_arg->argc; - PHPContext *task; - zend_execute_data *call; - zval _retval, *retval = &_retval; - - if (fci_cache.object) { - GC_ADDREF(fci_cache.object); - } - - vm_stack_init(); - call = (zend_execute_data *) (EG(vm_stack_top)); - task = (PHPContext *) EG(vm_stack_top); - EG(vm_stack_top) = (zval *) ((char *) call + PHP_CORO_TASK_SLOT * sizeof(zval)); - - do { - uint32_t call_info; - void *object_or_called_scope; - if ((func->common.fn_flags & ZEND_ACC_STATIC) || !fci_cache.object) { - object_or_called_scope = fci_cache.called_scope; - call_info = ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC; - } else { - object_or_called_scope = fci_cache.object; - call_info = ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_HAS_THIS; - } - call = zend_vm_stack_push_call_frame(call_info, func, argc, object_or_called_scope); - } while (0); - - SW_LOOP_N(argc) { - zval *param; - zval *arg = &argv[i]; - if (Z_ISREF_P(arg) && !(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { - /* don't separate references for __call */ - arg = Z_REFVAL_P(arg); - } - param = ZEND_CALL_ARG(call, i + 1); - ZVAL_COPY(param, arg); - } - call->symbol_table = nullptr; - - if (func->op_array.fn_flags & ZEND_ACC_CLOSURE) { - uint32_t call_info; - GC_ADDREF(ZEND_CLOSURE_OBJECT(func)); - call_info = ZEND_CALL_CLOSURE; - ZEND_ADD_CALL_FLAG(call, call_info); - } - -#if defined(SW_CORO_SWAP_BAILOUT) && !defined(SW_CORO_SUPPORT_BAILOUT) - EG(bailout) = nullptr; -#endif - EG(current_execute_data) = call; - EG(error_handling) = EH_NORMAL; - EG(exception_class) = nullptr; - EG(exception) = nullptr; - EG(jit_trace_num) = 0; - - task->output_ptr = nullptr; -#if PHP_VERSION_ID < 80100 - task->array_walk_fci = nullptr; -#endif - task->in_silence = false; - - task->co = Coroutine::get_current(); - task->co->set_task((void *) task); - task->defer_tasks = nullptr; - task->pcid = task->co->get_origin_cid(); - task->context = nullptr; - task->on_yield = nullptr; - task->on_resume = nullptr; - task->on_close = nullptr; - task->enable_scheduler = true; + zend_vm_stack_destroy(); + restore_context(origin_ctx); +} - save_vm_stack(task); - record_last_msec(task); +void PHPCoroutine::main_func(void *_args) { + bool exception_caught = false; + const auto args = static_cast(_args); + PHPContext *ctx = create_context(args); + zend_first_try { swoole_trace_log(SW_TRACE_COROUTINE, "Create coro id: %ld, origin cid: %ld, coro total count: %zu, heap size: %zu", - task->co->get_cid(), - task->co->get_origin_cid(), + ctx->co->get_cid(), + ctx->co->get_origin_cid(), (uintmax_t) Coroutine::count(), (uintmax_t) zend_memory_usage(0)); - if (task->pcid == -1) { - // wait until concurrency slots are available - while (concurrency > config.max_concurrency - 1) { - swoole_trace_log(SW_TRACE_COROUTINE, - "php_coro cid=%ld waiting for concurrency slots: max: %d, used: %d", - task->co->get_cid(), - config.max_concurrency, - concurrency); - - swoole_event_defer( - [](void *data) { - Coroutine *co = (Coroutine *) data; - co->resume(); - }, - (void *) task->co); - task->co->yield(); - } - concurrency++; - } - if (swoole_isset_hook(SW_GLOBAL_HOOK_ON_CORO_START)) { - swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_START, task); + swoole_call_hook(SW_GLOBAL_HOOK_ON_CORO_START, ctx); } - if (EXPECTED(func->type == ZEND_USER_FUNCTION)) { - ZVAL_UNDEF(retval); - // TODO: enhancement it, separate execute data is necessary, but we lose the backtrace - EG(current_execute_data) = nullptr; - zend_init_func_execute_data(call, &func->op_array, retval); - zend_execute_ex(EG(current_execute_data)); - } else { /* ZEND_INTERNAL_FUNCTION */ - ZVAL_NULL(retval); - call->prev_execute_data = nullptr; - call->return_value = nullptr; /* this is not a constructor call */ - execute_internal(call, retval); - zend_vm_stack_free_args(call); + if (UNEXPECTED(ctx->fiber_context && ctx->fci_cache.function_handler->type == ZEND_USER_FUNCTION)) { + zend_execute_data *tmp = EG(current_execute_data); + zend_execute_data call = {}; + EG(current_execute_data) = &call; + EG(current_execute_data)->opline = ctx->fci_cache.function_handler->op_array.opcodes; + call.func = ctx->fci_cache.function_handler; + fiber_context_switch_try_notify(get_origin_context(ctx), ctx); + EG(current_execute_data) = tmp; } - if (task->defer_tasks) { - std::stack *tasks = task->defer_tasks; + zend_call_function(&ctx->fci, &ctx->fci_cache); + + // Catch exception in main function of the coroutine + exception_caught = catch_exception(); + + // The defer tasks still need to be executed after an exception occurs + if (ctx->defer_tasks) { + std::stack *tasks = ctx->defer_tasks; while (!tasks->empty()) { zend::Function *defer_fci = tasks->top(); tasks->pop(); - - if (Z_TYPE_P(retval) != IS_UNDEF) { + if (Z_TYPE_P(&ctx->return_value) != IS_UNDEF) { defer_fci->fci.param_count = 1; - defer_fci->fci.params = retval; + defer_fci->fci.params = &ctx->return_value; } - if (UNEXPECTED(sw_zend_call_function_anyway(&defer_fci->fci, &defer_fci->fci_cache) != SUCCESS)) { php_swoole_fatal_error(E_WARNING, "defer callback handler error"); } + if (EG(exception)) { + zend_bailout(); + } sw_zend_fci_cache_discard(&defer_fci->fci_cache); efree(defer_fci); } - delete task->defer_tasks; - task->defer_tasks = nullptr; - } - - // resources release - if (task->context) { - zend_object *context = task->context; - task->context = (zend_object *) ~0; - OBJ_RELEASE(context); - } - if (fci_cache.object) { - OBJ_RELEASE(fci_cache.object); + delete ctx->defer_tasks; + ctx->defer_tasks = nullptr; } - zval_ptr_dtor(retval); - catch_exception(); -#ifdef SW_CORO_SUPPORT_BAILOUT } zend_catch { + // zend_bailout is executed in the c function catch_exception(); - Coroutine::bailout([]() { - if (sw_reactor()) { - sw_reactor()->running = false; - sw_reactor()->bailout = true; - } - zend_bailout(); - }); + exception_caught = true; } zend_end_try(); -#endif + destroy_context(ctx); + if (exception_caught) { + bailout(); + } } -long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv) { +long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval *argv, zval *callable) { if (sw_unlikely(Coroutine::count() >= config.max_num)) { php_swoole_fatal_error(E_WARNING, "exceed max number of coroutine %zu", (uintmax_t) Coroutine::count()); return Coroutine::ERR_LIMIT; @@ -810,22 +833,185 @@ long PHPCoroutine::create(zend_fcall_info_cache *fci_cache, uint32_t argc, zval _args.fci_cache = fci_cache; _args.argv = argv; _args.argc = argc; - save_task(get_context()); + _args.callable = callable; + save_context(get_context()); return Coroutine::create(main_func, (void *) &_args); } void PHPCoroutine::defer(zend::Function *fci) { - PHPContext *task = get_context(); - if (task->defer_tasks == nullptr) { - task->defer_tasks = new std::stack; + PHPContext *ctx = get_context(); + if (ctx->defer_tasks == nullptr) { + ctx->defer_tasks = new std::stack; } - task->defer_tasks->push(fci); + ctx->defer_tasks->push(fci); } -void php_swoole_coroutine_minit(int module_number) { - PHPCoroutine::init(); +void PHPCoroutine::fiber_context_init(PHPContext *ctx) { + auto *fiber_context = static_cast(emalloc(sizeof(zend_fiber_context))); + fiber_context->handle = reinterpret_cast(INVALID_PTR); + fiber_context->kind = reinterpret_cast(INVALID_PTR); + fiber_context->function = reinterpret_cast(INVALID_PTR); + fiber_context->stack = reinterpret_cast(INVALID_PTR); + ctx->fiber_context = fiber_context; + + zend_observer_fiber_init_notify(fiber_context); +} + +void PHPCoroutine::fiber_context_try_destroy(const PHPContext *ctx, PHPContext *origin_ctx) { + if (UNEXPECTED(ctx->fiber_context)) { + ctx->fiber_context->status = ZEND_FIBER_STATUS_DEAD; + fiber_context_switch_try_notify(ctx, origin_ctx); + + zend_observer_fiber_destroy_notify(ctx->fiber_context); + + if (ctx->fiber_context != nullptr) { + efree(ctx->fiber_context); + } + } +} + +zend_fiber_status PHPCoroutine::fiber_get_status(const PHPContext *ctx) { + // main_context + if (ctx->fiber_context == EG(main_fiber_context)) { + return ZEND_FIBER_STATUS_RUNNING; + } + + switch (ctx->co->get_state()) { + case Coroutine::STATE_INIT: + return ZEND_FIBER_STATUS_INIT; + case Coroutine::STATE_WAITING: + return ZEND_FIBER_STATUS_SUSPENDED; + case Coroutine::STATE_RUNNING: + return ZEND_FIBER_STATUS_RUNNING; + case Coroutine::STATE_END: + return ZEND_FIBER_STATUS_DEAD; + default: + php_swoole_fatal_error(E_ERROR, "Unexpected state when get fiber status"); + return ZEND_FIBER_STATUS_DEAD; + } +} + +void PHPCoroutine::fiber_context_switch_notify(const PHPContext *from, PHPContext *to) { + zend_fiber_context *from_context = from->fiber_context; + zend_fiber_context *to_context = to->fiber_context; + + from_context->status = fiber_get_status(from); + to_context->status = fiber_get_status(to); + if (!to->fiber_init_notified) { + to_context->status = ZEND_FIBER_STATUS_INIT; + zend_observer_fiber_switch_notify(from_context, to_context); + to_context->status = fiber_get_status(to); + to->fiber_init_notified = true; + } else { + zend_observer_fiber_switch_notify(from_context, to_context); + } +} + +void PHPCoroutine::fiber_context_switch_try_notify(const PHPContext *from, PHPContext *to) { + if (UNEXPECTED(from->fiber_context && to->fiber_context)) { + fiber_context_switch_notify(from, to); + } +} + +#ifdef ZEND_CHECK_STACK_LIMIT +void *PHPCoroutine::stack_limit(PHPContext *ctx) { +#ifdef SW_USE_THREAD_CONTEXT + return nullptr; +#else + zend_ulong reserve = EG(reserved_stack_size); + +#ifdef __APPLE__ + /* On Apple Clang, the stack probing function ___chkstk_darwin incorrectly + * probes a location that is twice the entered function's stack usage away + * from the stack pointer, when using an alternative stack. + * https://openradar.appspot.com/radar?id=5497722702397440 + */ + reserve = reserve * 2; +#endif + + if (!ctx->co) { + return nullptr; + } + + /* stack->pointer is the end of the stack */ + return (int8_t *) ctx->co->get_ctx().get_stack() + reserve; +#endif +} +void *PHPCoroutine::stack_base(PHPContext *ctx) { +#ifdef SW_USE_THREAD_CONTEXT + return nullptr; +#else + if (!ctx->co) { + return nullptr; + } + + return (void *) ((uintptr_t) ctx->co->get_ctx().get_stack() + ctx->co->get_ctx().get_stack_size()); +#endif +} +#endif /* ZEND_CHECK_STACK_LIMIT */ + +/* hook autoload */ + +static zend_class_entry *(*original_zend_autoload)(zend_string *name, zend_string *lc_name); + +struct AutoloadContext { + Coroutine *coroutine; + zend_class_entry *ce; +}; + +struct AutoloadQueue { + Coroutine *coroutine; + std::queue *queue; +}; + +static zend_class_entry *swoole_coroutine_autoload(zend_string *name, zend_string *lc_name) { + auto current = Coroutine::get_current(); + if (!current) { + return original_zend_autoload(name, lc_name); + } + + ZEND_ASSERT(EG(in_autoload) != nullptr); + zend_hash_del(EG(in_autoload), lc_name); + + if (UNEXPECTED(SWOOLE_G(in_autoload) == nullptr)) { + ALLOC_HASHTABLE(SWOOLE_G(in_autoload)); + zend_hash_init(SWOOLE_G(in_autoload), 8, nullptr, nullptr, 0); + } + zval *z_queue = zend_hash_find(SWOOLE_G(in_autoload), lc_name); + if (z_queue != nullptr) { + auto queue = (AutoloadQueue *) Z_PTR_P(z_queue); + if (queue->coroutine == current) { + return nullptr; + } + AutoloadContext context; + context.coroutine = current; + context.ce = nullptr; + queue->queue->push(&context); + current->yield(); + return context.ce; + } + AutoloadQueue queue; + queue.coroutine = current; + std::queue queue_object; + queue.queue = &queue_object; + + zend_hash_add_ptr(SWOOLE_G(in_autoload), lc_name, &queue); + zend_class_entry *ce = original_zend_autoload(name, lc_name); + zend_hash_del(SWOOLE_G(in_autoload), lc_name); + + AutoloadContext *pending_context = nullptr; + while (!queue_object.empty()) { + pending_context = queue_object.front(); + queue_object.pop(); + pending_context->ce = ce; + pending_context->coroutine->resume(); + } + return ce; +} + +void php_swoole_coroutine_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE(swoole_coroutine_util, "Swoole\\Coroutine", "Co", swoole_coroutine_methods, nullptr); SW_SET_CLASS_CREATE(swoole_coroutine_util, sw_zend_create_object_deny); @@ -847,14 +1033,30 @@ void php_swoole_coroutine_minit(int module_number) { zend_declare_property_long(swoole_exit_exception_ce, ZEND_STRL("flags"), 0, ZEND_ACC_PRIVATE); zend_declare_property_long(swoole_exit_exception_ce, ZEND_STRL("status"), 0, ZEND_ACC_PRIVATE); + SW_INIT_CLASS_ENTRY_EX2(swoole_coroutine_canceled_exception, + "Swoole\\Coroutine\\CanceledException", + nullptr, + nullptr, + zend_ce_exception, + zend_get_std_object_handlers()); + swoole_coroutine_canceled_exception_ce->ce_flags |= ZEND_ACC_FINAL; + + SW_INIT_CLASS_ENTRY_EX2(swoole_coroutine_timeout_exception, + "Swoole\\Coroutine\\TimeoutException", + nullptr, + nullptr, + zend_ce_exception, + zend_get_std_object_handlers()); + swoole_coroutine_timeout_exception_ce->ce_flags |= ZEND_ACC_FINAL; + SW_REGISTER_LONG_CONSTANT("SWOOLE_EXIT_IN_COROUTINE", SW_EXIT_IN_COROUTINE); SW_REGISTER_LONG_CONSTANT("SWOOLE_EXIT_IN_SERVER", SW_EXIT_IN_SERVER); -} -void php_swoole_coroutine_rinit() { if (SWOOLE_G(cli)) { +#if PHP_VERSION_ID < 80400 ori_exit_handler = zend_get_user_opcode_handler(ZEND_EXIT); zend_set_user_opcode_handler(ZEND_EXIT, coro_exit_handler); +#endif ori_begin_silence_handler = zend_get_user_opcode_handler(ZEND_BEGIN_SILENCE); zend_set_user_opcode_handler(ZEND_BEGIN_SILENCE, coro_begin_silence_handler); @@ -862,9 +1064,24 @@ void php_swoole_coroutine_rinit() { ori_end_silence_handler = zend_get_user_opcode_handler(ZEND_END_SILENCE); zend_set_user_opcode_handler(ZEND_END_SILENCE, coro_end_silence_handler); } + + /* hook autoload */ + original_zend_autoload = zend_autoload; + zend_autoload = swoole_coroutine_autoload; + SWOOLE_G(in_autoload) = nullptr; +} + +void php_swoole_coroutine_rinit() { + PHPCoroutine::init_main_context(); } void php_swoole_coroutine_rshutdown() { + if (SWOOLE_G(in_autoload)) { + zend_hash_destroy(SWOOLE_G(in_autoload)); + FREE_HASHTABLE(SWOOLE_G(in_autoload)); + SWOOLE_G(in_autoload) = nullptr; + } + PHPCoroutine::shutdown(); } @@ -886,7 +1103,7 @@ PHP_FUNCTION(swoole_coroutine_create) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (sw_unlikely(SWOOLE_G(req_status) == PHP_SWOOLE_CALL_USER_SHUTDOWNFUNC_BEGIN)) { - zend_function *func = (zend_function *) EG(current_execute_data)->prev_execute_data->func; + auto *func = EG(current_execute_data)->prev_execute_data->func; if (func->common.function_name && sw_unlikely(memcmp(ZSTR_VAL(func->common.function_name), ZEND_STRS("__destruct")) == 0)) { php_swoole_fatal_error(E_ERROR, "can not use coroutine in __destruct after php_request_shutdown"); @@ -894,7 +1111,7 @@ PHP_FUNCTION(swoole_coroutine_create) { } } - long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params); + long cid = PHPCoroutine::create(&fci_cache, fci.param_count, fci.params, &fci.function_name); if (sw_likely(cid > 0)) { RETURN_LONG(cid); } else { @@ -905,14 +1122,13 @@ PHP_FUNCTION(swoole_coroutine_create) { PHP_FUNCTION(swoole_coroutine_defer) { zend_fcall_info fci; zend_fcall_info_cache fci_cache; - zend::Function *defer_fci; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_FUNC(fci, fci_cache) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Coroutine::get_current_safe(); - defer_fci = (zend::Function *) emalloc(sizeof(zend::Function)); + auto *defer_fci = static_cast(emalloc(sizeof(zend::Function))); defer_fci->fci = fci; defer_fci->fci_cache = fci_cache; sw_zend_fci_cache_persist(&defer_fci->fci_cache); @@ -921,23 +1137,33 @@ PHP_FUNCTION(swoole_coroutine_defer) { static PHP_METHOD(swoole_coroutine, stats) { array_init(return_value); + add_assoc_long_ex(return_value, ZEND_STRL("event_num"), sw_reactor() ? sw_reactor()->get_event_num() : 0); - add_assoc_long_ex( - return_value, ZEND_STRL("signal_listener_num"), SwooleTG.signal_listener_num + SwooleTG.co_signal_listener_num); + add_assoc_long_ex(return_value, ZEND_STRL("signal_listener_num"), swoole_signal_get_listener_num()); - if (SwooleTG.async_threads) { - add_assoc_long_ex(return_value, ZEND_STRL("aio_task_num"), SwooleTG.async_threads->get_task_num()); - add_assoc_long_ex(return_value, ZEND_STRL("aio_worker_num"), SwooleTG.async_threads->get_worker_num()); - add_assoc_long_ex(return_value, ZEND_STRL("aio_queue_size"), SwooleTG.async_threads->get_queue_size()); + if (sw_async_threads()) { + add_assoc_long_ex(return_value, ZEND_STRL("aio_task_num"), sw_async_threads()->get_task_num()); + add_assoc_long_ex(return_value, ZEND_STRL("aio_worker_num"), sw_async_threads()->get_worker_num()); + add_assoc_long_ex(return_value, ZEND_STRL("aio_queue_size"), sw_async_threads()->get_queue_size()); } else { add_assoc_long_ex(return_value, ZEND_STRL("aio_task_num"), 0); add_assoc_long_ex(return_value, ZEND_STRL("aio_worker_num"), 0); add_assoc_long_ex(return_value, ZEND_STRL("aio_queue_size"), 0); } + add_assoc_long_ex(return_value, ZEND_STRL("c_stack_size"), Coroutine::get_stack_size()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_num"), Coroutine::count()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_peak_num"), Coroutine::get_peak_num()); add_assoc_long_ex(return_value, ZEND_STRL("coroutine_last_cid"), Coroutine::get_last_cid()); + +#ifdef SW_USE_IOURING + auto iouring = SwooleTG.iouring; + if (iouring) { + add_assoc_long_ex(return_value, ZEND_STRL("iouring_task_num"), iouring->get_task_num()); + add_assoc_long_ex(return_value, ZEND_STRL("iouring_sq_usage_percent"), iouring->get_sq_usage_percent()); + add_assoc_long_ex(return_value, ZEND_STRL("iouring_waiting_task_num"), iouring->get_waiting_task_num()); + } +#endif } PHP_METHOD(swoole_coroutine, getCid) { @@ -946,18 +1172,16 @@ PHP_METHOD(swoole_coroutine, getCid) { PHP_METHOD(swoole_coroutine, getPcid) { zend_long cid = 0; - zend_long ret; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ret = PHPCoroutine::get_pcid(cid); + zend_long ret = PHPCoroutine::get_pcid(cid); if (ret == 0) { RETURN_FALSE; } - RETURN_LONG(ret); } @@ -969,61 +1193,48 @@ static PHP_METHOD(swoole_coroutine, getContext) { Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - PHPContext *task = - (PHPContext *) (EXPECTED(cid == 0) ? Coroutine::get_current_task() : Coroutine::get_task_by_cid(cid)); - if (UNEXPECTED(!task)) { + auto *ctx = static_cast( + (EXPECTED(cid == 0) ? Coroutine::get_current_task() : Coroutine::get_task_by_cid(cid))); + if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); RETURN_NULL(); } - if (UNEXPECTED(task->context == (zend_object *) ~0)) { + if (UNEXPECTED(ctx->context == reinterpret_cast(INVALID_PTR))) { /* bad context (has been destroyed), see: https://github.com/swoole/swoole-src/issues/2991 */ - php_swoole_fatal_error(E_WARNING, "Context of this coroutine has been destroyed"); + php_swoole_fatal_error(E_WARNING, "The context of coroutine has been destroyed"); RETURN_NULL(); } - if (UNEXPECTED(!task->context)) { + if (UNEXPECTED(!ctx->context)) { object_init_ex(return_value, swoole_coroutine_context_ce); - task->context = Z_OBJ_P(return_value); + ctx->context = Z_OBJ_P(return_value); } - GC_ADDREF(task->context); - RETURN_OBJ(task->context); + GC_ADDREF(ctx->context); + RETURN_OBJ(ctx->context); } static PHP_METHOD(swoole_coroutine, getElapsed) { zend_long cid = 0; - zend_long ret; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ret = PHPCoroutine::get_elapsed(cid); - RETURN_LONG(ret); + RETURN_LONG(PHPCoroutine::get_elapsed(cid)); } static PHP_METHOD(swoole_coroutine, getStackUsage) { - zend_long current_cid = PHPCoroutine::get_cid(); - zend_long cid = current_cid; + zend_long cid = 0; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(cid) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - PHPContext *task = (PHPContext *) PHPCoroutine::get_context_by_cid(cid); - if (UNEXPECTED(!task)) { - swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); + ssize_t usage = PHPCoroutine::get_stack_usage(cid); + if (usage < 0) { RETURN_FALSE; } - - zend_vm_stack stack = cid == current_cid ? EG(vm_stack) : task->vm_stack; - size_t usage = 0; - - while (stack) { - usage += (stack->end - stack->top) * sizeof(zval); - stack = stack->prev; - } - RETURN_LONG(usage); } @@ -1038,10 +1249,11 @@ static PHP_METHOD(swoole_coroutine, exists) { } static PHP_METHOD(swoole_coroutine, resume) { - long cid; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &cid) == FAILURE) { - RETURN_FALSE; - } + zend_long cid; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(cid) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); auto coroutine_iterator = user_yield_coros.find(cid); if (coroutine_iterator == user_yield_coros.end()) { @@ -1091,7 +1303,7 @@ static PHP_METHOD(swoole_coroutine, join) { } std::set co_set; - bool *canceled = new bool(false); + std::shared_ptr canceled = std::make_shared(false); PHPContext::SwapCallback join_fn = [&co_set, canceled, co](PHPContext *task) { co_set.erase(task); @@ -1103,7 +1315,6 @@ static PHP_METHOD(swoole_coroutine, join) { if (*canceled == false) { co->resume(); } - delete canceled; }, nullptr); }; @@ -1112,9 +1323,7 @@ static PHP_METHOD(swoole_coroutine, join) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cid_array), zcid) { long cid = zval_get_long(zcid); if (co->get_cid() == cid) { - swoole_set_last_error(SW_ERROR_WRONG_OPERATION); - php_swoole_error(E_WARNING, "can not join self"); - delete canceled; + php_swoole_error_ex(E_WARNING, SW_ERROR_WRONG_OPERATION, "can not join self"); RETURN_FALSE; } auto ctx = PHPCoroutine::get_context_by_cid(cid); @@ -1122,8 +1331,7 @@ static PHP_METHOD(swoole_coroutine, join) { continue; } if (ctx->on_close) { - swoole_set_last_error(SW_ERROR_CO_HAS_BEEN_BOUND); - delete canceled; + swoole_set_last_error(SW_ERROR_WRONG_OPERATION); RETURN_FALSE; } ctx->on_close = &join_fn; @@ -1133,7 +1341,6 @@ static PHP_METHOD(swoole_coroutine, join) { if (co_set.empty()) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); - delete canceled; RETURN_FALSE; } @@ -1142,28 +1349,90 @@ static PHP_METHOD(swoole_coroutine, join) { for (auto ctx : co_set) { ctx->on_close = nullptr; } - delete canceled; - } else { - *canceled = true; } + *canceled = true; RETURN_FALSE; } RETURN_TRUE; } -static PHP_METHOD(swoole_coroutine, cancel) { - long cid; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &cid) == FAILURE) { - RETURN_FALSE; +static bool php_swoole_coroutine_throw_exception(long cid, bool throw_exception, zend_class_entry *ce) { + if (throw_exception) { + auto *task = PHPCoroutine::get_context_by_cid(cid); + // The coroutine does not exist or is not a PHP coroutine, and cannot be canceled or throw an exception + if (!task) { + swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); + return false; + } + + if (task->exception && task->exception_class == ce) { + swoole_set_last_error(SW_ERROR_CO_CANCELED); + return false; + } + + zend_object *previous = task->exception; + task->exception_class = ce; + task->exception = zend_objects_new(task->exception_class); + object_properties_init(task->exception, task->exception_class); + if (previous) { + zend_exception_set_previous(task->exception, previous); + } + + zend_execute_data *ex_backup = EG(current_execute_data); + EG(current_execute_data) = task->execute_data; + zend::object_set(task->exception, ZSTR_KNOWN(ZEND_STR_FILE), zend_get_executed_filename_ex()); + zend::object_set(task->exception, ZSTR_KNOWN(ZEND_STR_LINE), zend_get_executed_lineno()); + EG(current_execute_data) = ex_backup; + + // Ignore the result of Co::cancel(). Even if the coroutine is in an irrevocable state, + // it will directly throw an exception and terminate the coroutine + task->co->cancel(); + return true; + } else { + Coroutine *co = swoole_coroutine_get(cid); + if (!co) { + swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); + return false; + } + return co->cancel(); } +} - Coroutine *co = swoole_coroutine_get(cid); - if (!co) { - swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); +static PHP_METHOD(swoole_coroutine, cancel) { + zend_long cid; + bool throw_exception = false; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(cid) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(throw_exception) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_BOOL(php_swoole_coroutine_throw_exception(cid, throw_exception, swoole_coroutine_canceled_exception_ce)); +} + +static PHP_METHOD(swoole_coroutine, setTimeLimit) { + double timeout = 0; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + Coroutine *co = Coroutine::get_current_safe(); + if (timeout == 0 || co == nullptr) { RETURN_FALSE; } - RETURN_BOOL(co->cancel()); + + long cid = co->get_cid(); + swoole_timer_add((long) timeout * 1000, false, [cid](swoole::Timer *, swoole::TimerNode *tnode) { + swoole_timer_del(tnode); + if (PHPCoroutine::get_context_by_cid(cid) != nullptr) { + php_swoole_coroutine_throw_exception(cid, true, swoole_coroutine_timeout_exception_ce); + } + }); + + RETURN_TRUE; } static PHP_METHOD(swoole_coroutine, isCanceled) { @@ -1207,7 +1476,7 @@ static PHP_METHOD(swoole_coroutine, getBackTrace) { if (!cid || cid == PHPCoroutine::get_cid()) { zend_fetch_debug_backtrace(return_value, 0, options, limit); } else { - PHPContext *task = (PHPContext *) PHPCoroutine::get_context_by_cid(cid); + auto *task = PHPCoroutine::get_context_by_cid(cid); if (UNEXPECTED(!task)) { swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); RETURN_FALSE; @@ -1219,9 +1488,39 @@ static PHP_METHOD(swoole_coroutine, getBackTrace) { } } +void sw_php_print_backtrace_impl(int skip_last, int options, int limit, bool include_main) { + zval backtrace; + zend_fetch_debug_backtrace(&backtrace, skip_last, options, limit); + ZEND_ASSERT(Z_TYPE(backtrace) == IS_ARRAY); + + zend_string *str = zend_trace_to_string(Z_ARRVAL(backtrace), include_main); + ZEND_WRITE(ZSTR_VAL(str), ZSTR_LEN(str)); + zend_string_release(str); + zval_ptr_dtor(&backtrace); +} + +void sw_php_print_backtrace(zend_long cid, zend_long options, zend_long limit, zval *return_value) { + if (!cid || cid == PHPCoroutine::get_cid()) { + sw_php_print_backtrace_impl(1, options, limit); + } else { + PHPContext *ctx = PHPCoroutine::get_context_by_cid(cid); + if (UNEXPECTED(!ctx)) { + swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); + if (return_value) { + RETVAL_FALSE; + } + return; + } + zend_execute_data *ex_backup = EG(current_execute_data); + EG(current_execute_data) = ctx->execute_data; + sw_php_print_backtrace_impl(0, options, limit); + EG(current_execute_data) = ex_backup; + } +} + static PHP_METHOD(swoole_coroutine, printBackTrace) { zend_long cid = 0; - zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT; + zend_long options = 0; zend_long limit = 0; ZEND_PARSE_PARAMETERS_START(0, 3) @@ -1231,23 +1530,7 @@ static PHP_METHOD(swoole_coroutine, printBackTrace) { Z_PARAM_LONG(limit) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - zval argv[2]; - ZVAL_LONG(&argv[0], options); - ZVAL_LONG(&argv[1], limit); - - if (!cid || cid == PHPCoroutine::get_cid()) { - zend::function::call("debug_print_backtrace", 2, argv); - } else { - PHPContext *task = (PHPContext *) PHPCoroutine::get_context_by_cid(cid); - if (UNEXPECTED(!task)) { - swoole_set_last_error(SW_ERROR_CO_NOT_EXISTS); - RETURN_FALSE; - } - zend_execute_data *ex_backup = EG(current_execute_data); - EG(current_execute_data) = task->execute_data; - zend::function::call("debug_print_backtrace", 2, argv); - EG(current_execute_data) = ex_backup; - } + sw_php_print_backtrace(cid, options, limit, return_value); } static PHP_METHOD(swoole_coroutine, list) { diff --git a/ext-src/swoole_coroutine_lock.cc b/ext-src/swoole_coroutine_lock.cc new file mode 100644 index 0000000000..e05da2a145 --- /dev/null +++ b/ext-src/swoole_coroutine_lock.cc @@ -0,0 +1,131 @@ +/* + +----------------------------------------------------------------------+ + | 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_private.h" +#include "swoole_memory.h" +#include "swoole_lock.h" + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_coroutine_lock_arginfo.h" +END_EXTERN_C() + +using swoole::CoroutineLock; + +static zend_class_entry *swoole_coroutine_lock_ce; +static zend_object_handlers swoole_coroutine_lock_handlers; + +struct CoLockObject { + CoroutineLock *lock; + bool shared; + zend_object std; +}; + +static CoLockObject *co_lock_fetch_object(zend_object *obj) { + return (CoLockObject *) ((char *) obj - swoole_coroutine_lock_handlers.offset); +} + +static CoroutineLock *co_lock_get_ptr(zval *zobject) { + return co_lock_fetch_object(Z_OBJ_P(zobject))->lock; +} + +static CoroutineLock *co_lock_get_and_check_ptr(zval *zobject) { + CoroutineLock *lock = co_lock_get_ptr(zobject); + if (UNEXPECTED(!lock)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); + } + return lock; +} + +static void co_lock_set_ptr(zval *zobject, CoroutineLock *ptr) { + co_lock_fetch_object(Z_OBJ_P(zobject))->lock = ptr; +} + +static void co_lock_free_object(zend_object *object) { + CoLockObject *o = co_lock_fetch_object(object); + if (o->lock && !o->shared) { + delete o->lock; + } + zend_object_std_dtor(object); +} + +static zend_object *co_lock_create_object(zend_class_entry *ce) { + auto *lock = static_cast(zend_object_alloc(sizeof(CoLockObject), ce)); + zend_object_std_init(&lock->std, ce); + object_properties_init(&lock->std, ce); + lock->std.handlers = &swoole_coroutine_lock_handlers; + return &lock->std; +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_coroutine_lock, __construct); +static PHP_METHOD(swoole_coroutine_lock, lock); +static PHP_METHOD(swoole_coroutine_lock, unlock); +SW_EXTERN_C_END + +// clang-format off +static constexpr zend_function_entry swoole_coroutine_lock_methods[] = +{ + PHP_ME(swoole_coroutine_lock, __construct, arginfo_class_Swoole_Coroutine_Lock___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_coroutine_lock, lock, arginfo_class_Swoole_Coroutine_Lock_lock, ZEND_ACC_PUBLIC) + PHP_ME(swoole_coroutine_lock, unlock, arginfo_class_Swoole_Coroutine_Lock_unlock, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_coroutine_lock_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_coroutine_lock, "Swoole\\Coroutine\\Lock", nullptr, swoole_coroutine_lock_methods); + SW_SET_CLASS_NOT_SERIALIZABLE(swoole_coroutine_lock); + SW_SET_CLASS_CLONEABLE(swoole_coroutine_lock, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_coroutine_lock, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_coroutine_lock, co_lock_create_object, co_lock_free_object, CoLockObject, std); + zend_declare_property_long(swoole_coroutine_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); +} + +static PHP_METHOD(swoole_coroutine_lock, __construct) { + CoroutineLock *lock = co_lock_get_ptr(ZEND_THIS); + if (lock != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + RETURN_FALSE; + } + + zend_bool shared = false; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(shared) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + lock = new CoroutineLock(shared); + co_lock_set_ptr(ZEND_THIS, lock); + RETURN_TRUE; +} + +static PHP_METHOD(swoole_coroutine_lock, lock) { + zend_long operation = LOCK_EX; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(operation) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + CoroutineLock *lock = co_lock_get_and_check_ptr(ZEND_THIS); + SW_LOCK_CHECK_RETURN(lock->lock(operation)); +} + +static PHP_METHOD(swoole_coroutine_lock, unlock) { + CoroutineLock *lock = co_lock_get_and_check_ptr(ZEND_THIS); + SW_LOCK_CHECK_RETURN(lock->unlock()); +} diff --git a/ext-src/swoole_coroutine_scheduler.cc b/ext-src/swoole_coroutine_scheduler.cc index 072fa0b36d..dd079c0839 100644 --- a/ext-src/swoole_coroutine_scheduler.cc +++ b/ext-src/swoole_coroutine_scheduler.cc @@ -28,7 +28,6 @@ using swoole::Coroutine; using swoole::NameResolver; using swoole::PHPCoroutine; using swoole::Reactor; -using swoole::coroutine::Socket; using swoole::coroutine::System; struct SchedulerTask { @@ -53,11 +52,12 @@ static PHP_METHOD(swoole_coroutine_scheduler, start); SW_EXTERN_C_END static sw_inline SchedulerObject *scheduler_get_object(zend_object *obj) { - return (SchedulerObject *) ((char *) obj - swoole_coroutine_scheduler_handlers.offset); + return reinterpret_cast(reinterpret_cast(obj) - + swoole_coroutine_scheduler_handlers.offset); } static zend_object *scheduler_create_object(zend_class_entry *ce) { - SchedulerObject *s = (SchedulerObject *) zend_object_alloc(sizeof(SchedulerObject), ce); + auto *s = static_cast(zend_object_alloc(sizeof(SchedulerObject), ce)); zend_object_std_init(&s->std, ce); object_properties_init(&s->std, ce); s->std.handlers = &swoole_coroutine_scheduler_handlers; @@ -106,23 +106,21 @@ void php_swoole_coroutine_scheduler_minit(int module_number) { swoole_coroutine_scheduler_ce->ce_flags |= ZEND_ACC_FINAL; } -static zend_fcall_info_cache exit_condition_fci_cache; -static bool exit_condition_cleaner; +static zend::Callable *exit_condition_fci_cache = nullptr; static bool php_swoole_coroutine_reactor_can_exit(Reactor *reactor, size_t &event_num) { zval retval; - int success; - SW_ASSERT(exit_condition_fci_cache.function_handler); + SW_ASSERT(exit_condition_fci_cache); ZVAL_NULL(&retval); - success = sw_zend_call_function_ex(nullptr, &exit_condition_fci_cache, 0, nullptr, &retval); + int success = sw_zend_call_function_ex(nullptr, exit_condition_fci_cache->ptr(), 0, nullptr, &retval); if (UNEXPECTED(success != SUCCESS)) { php_swoole_fatal_error(E_ERROR, "Coroutine can_exit callback handler error"); } if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); } - return !(Z_TYPE_P(&retval) == IS_FALSE); + return Z_TYPE_P(&retval) != IS_FALSE; } void php_swoole_coroutine_scheduler_rshutdown() { @@ -135,6 +133,11 @@ void php_swoole_coroutine_scheduler_rshutdown() { return SW_TRAVERSE_KEEP; } }); + + if (exit_condition_fci_cache) { + sw_callable_free(exit_condition_fci_cache); + exit_condition_fci_cache = nullptr; + } } void php_swoole_set_coroutine_option(zend_array *vht) { @@ -154,7 +157,7 @@ void php_swoole_set_coroutine_option(zend_array *vht) { PHPCoroutine::enable_preemptive_scheduler(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "c_stack_size", ztmp) || php_swoole_array_get_value(vht, "stack_size", ztmp)) { - Coroutine::set_stack_size(zval_get_long(ztmp)); + Coroutine::set_stack_size(php_swoole_parse_to_size(ztmp)); } if (php_swoole_array_get_value(vht, "name_resolver", ztmp)) { if (!ZVAL_IS_ARRAY(ztmp)) { @@ -192,38 +195,18 @@ PHP_METHOD(swoole_coroutine_scheduler, set) { if (php_swoole_array_get_value(vht, "dns_cache_capacity", ztmp)) { System::set_dns_cache_capacity((size_t) zval_get_long(ztmp)); } - if (php_swoole_array_get_value(vht, "max_concurrency", ztmp)) { - PHPCoroutine::set_max_concurrency((uint32_t) SW_MAX(1, zval_get_long(ztmp))); - } /* Reactor can exit */ if ((ztmp = zend_hash_str_find(vht, ZEND_STRL("exit_condition")))) { - char *func_name; - if (exit_condition_fci_cache.function_handler) { - sw_zend_fci_cache_discard(&exit_condition_fci_cache); - exit_condition_fci_cache.function_handler = nullptr; + if (exit_condition_fci_cache) { + sw_callable_free(exit_condition_fci_cache); } - if (!ZVAL_IS_NULL(ztmp)) { - if (!sw_zend_is_callable_ex(ztmp, nullptr, 0, &func_name, nullptr, &exit_condition_fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "exit_condition '%s' is not callable", func_name); - } else { - efree(func_name); - sw_zend_fci_cache_persist(&exit_condition_fci_cache); - if (!exit_condition_cleaner) { - php_swoole_register_rshutdown_callback( - [](void *data) { - if (exit_condition_fci_cache.function_handler) { - sw_zend_fci_cache_discard(&exit_condition_fci_cache); - exit_condition_fci_cache.function_handler = nullptr; - } - }, - nullptr); - exit_condition_cleaner = true; - } - SwooleG.user_exit_condition = php_swoole_coroutine_reactor_can_exit; - if (sw_reactor()) { - sw_reactor()->set_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT, - SwooleG.user_exit_condition); - } + + exit_condition_fci_cache = sw_callable_create(ztmp); + if (exit_condition_fci_cache) { + SwooleG.user_exit_condition = php_swoole_coroutine_reactor_can_exit; + if (sw_reactor()) { + sw_reactor()->set_exit_condition(Reactor::EXIT_CONDITION_USER_AFTER_DEFAULT, + SwooleG.user_exit_condition); } } else { if (sw_reactor()) { @@ -258,7 +241,7 @@ static PHP_METHOD(swoole_coroutine_scheduler, add) { RETURN_FALSE; } - SchedulerTask *task = (SchedulerTask *) ecalloc(1, sizeof(SchedulerTask)); + auto *task = static_cast(ecalloc(1, sizeof(SchedulerTask))); ZEND_PARSE_PARAMETERS_START(1, -1) Z_PARAM_FUNC(task->fci, task->fci_cache) @@ -277,7 +260,7 @@ static PHP_METHOD(swoole_coroutine_scheduler, parallel) { RETURN_FALSE; } - SchedulerTask *task = (SchedulerTask *) ecalloc(1, sizeof(SchedulerTask)); + auto *task = static_cast(ecalloc(1, sizeof(SchedulerTask))); zend_long count; ZEND_PARSE_PARAMETERS_START(2, -1) @@ -293,11 +276,6 @@ static PHP_METHOD(swoole_coroutine_scheduler, parallel) { static PHP_METHOD(swoole_coroutine_scheduler, start) { SchedulerObject *s = scheduler_get_object(Z_OBJ_P(ZEND_THIS)); - if (SwooleTG.reactor) { - php_swoole_fatal_error( - E_WARNING, "eventLoop has already been created. unable to start %s", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); - RETURN_FALSE; - } if (s->started) { php_swoole_fatal_error( E_WARNING, "scheduler is started, unable to execute %s->start", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); @@ -318,7 +296,7 @@ static PHP_METHOD(swoole_coroutine_scheduler, start) { SchedulerTask *task = s->list->front(); s->list->pop(); for (zend_long i = 0; i < task->count; i++) { - PHPCoroutine::create(&task->fci_cache, task->fci.param_count, task->fci.params); + PHPCoroutine::create(&task->fci_cache, task->fci.param_count, task->fci.params, &task->fci.function_name); } sw_zend_fci_cache_discard(&task->fci_cache); sw_zend_fci_params_discard(&task->fci); diff --git a/ext-src/swoole_coroutine_system.cc b/ext-src/swoole_coroutine_system.cc index 8d3f5d88ae..36160944ac 100644 --- a/ext-src/swoole_coroutine_system.cc +++ b/ext-src/swoole_coroutine_system.cc @@ -27,9 +27,13 @@ using swoole::PHPCoroutine; using swoole::Reactor; using swoole::String; using swoole::TimerNode; -using swoole::coroutine::Socket; using swoole::coroutine::System; +enum FileOperateFlag { + SW_FILE_LOCK = LOCK_EX, + SW_FILE_APPEND = PHP_FILE_APPEND, +}; + static zend_class_entry *swoole_coroutine_system_ce; // clang-format off @@ -47,10 +51,6 @@ static const zend_function_entry swoole_coroutine_system_methods[] = PHP_ME(swoole_coroutine_system, waitPid, arginfo_class_Swoole_Coroutine_System_waitPid, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitSignal, arginfo_class_Swoole_Coroutine_System_waitSignal, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_coroutine_system, waitEvent, arginfo_class_Swoole_Coroutine_System_waitEvent, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) - /* Deprecated file methods */ - PHP_ME(swoole_coroutine_system, fread, arginfo_class_Swoole_Coroutine_System_fread, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_coroutine_system, fwrite, arginfo_class_Swoole_Coroutine_System_fwrite, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_coroutine_system, fgets, arginfo_class_Swoole_Coroutine_System_fgets, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) PHP_FE_END }; @@ -60,6 +60,8 @@ void php_swoole_coroutine_system_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE( swoole_coroutine_system, "Swoole\\Coroutine\\System", "Co\\System", swoole_coroutine_system_methods, nullptr); SW_SET_CLASS_CREATE(swoole_coroutine_system, sw_zend_create_object_deny); + + zend::add_constant("FILE_LOCK", SW_FILE_LOCK); } PHP_METHOD(swoole_coroutine_system, sleep) { @@ -76,228 +78,6 @@ PHP_METHOD(swoole_coroutine_system, sleep) { RETURN_BOOL(System::sleep(seconds) == 0); } -static void co_socket_read(int fd, zend_long length, INTERNAL_FUNCTION_PARAMETERS) { - php_swoole_check_reactor(); - Socket _socket(fd, SW_SOCK_RAW); - - zend_string *buf = zend_string_alloc(length + 1, 0); - size_t nbytes = length <= 0 ? SW_BUFFER_SIZE_STD : length; - ssize_t n = _socket.read(ZSTR_VAL(buf), nbytes); - if (n < 0) { - ZVAL_FALSE(return_value); - zend_string_free(buf); - } else if (n == 0) { - ZVAL_EMPTY_STRING(return_value); - zend_string_free(buf); - } else { - ZSTR_VAL(buf)[n] = 0; - ZSTR_LEN(buf) = n; - ZVAL_STR(return_value, buf); - } - _socket.move_fd(); -} - -static void co_socket_write(int fd, char *str, size_t l_str, INTERNAL_FUNCTION_PARAMETERS) { - php_swoole_check_reactor(); - Socket _socket(fd, SW_SOCK_RAW); - - ssize_t n = _socket.write(str, l_str); - if (n < 0) { - swoole_set_last_error(errno); - ZVAL_FALSE(return_value); - } else { - ZVAL_LONG(return_value, n); - } - _socket.move_fd(); -} - -PHP_METHOD(swoole_coroutine_system, fread) { - Coroutine::get_current_safe(); - - zval *handle; - zend_long length = 0; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_RESOURCE(handle) - Z_PARAM_OPTIONAL - Z_PARAM_LONG(length) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - int async; - int fd = php_swoole_convert_to_fd_ex(handle, &async); - if (fd < 0) { - RETURN_FALSE; - } - - if (async) { - co_socket_read(fd, length, INTERNAL_FUNCTION_PARAM_PASSTHRU); - return; - } - - if (length <= 0) { - struct stat file_stat; - if (swoole_coroutine_fstat(fd, &file_stat) < 0) { - swoole_set_last_error(errno); - RETURN_FALSE; - } - off_t _seek = swoole_coroutine_lseek(fd, 0, SEEK_CUR); - if (_seek < 0) { - swoole_set_last_error(errno); - RETURN_FALSE; - } - if (_seek >= file_stat.st_size) { - length = SW_BUFFER_SIZE_STD; - } else { - length = file_stat.st_size - _seek; - } - } - - char *buf = (char *) emalloc(length + 1); - if (!buf) { - RETURN_FALSE; - } - buf[length] = 0; - int ret = -1; - swoole_trace("fd=%d, length=" ZEND_LONG_FMT, fd, length); - php_swoole_check_reactor(); - bool async_success = swoole::coroutine::async([&]() { - while (1) { - ret = read(fd, buf, length); - if (ret < 0 && errno == EINTR) { - continue; - } - break; - } - }); - - if (async_success && ret >= 0) { - // TODO: Optimization: reduce memory copy - ZVAL_STRINGL(return_value, buf, ret); - } else { - ZVAL_FALSE(return_value); - } - - efree(buf); -} - -PHP_METHOD(swoole_coroutine_system, fgets) { - Coroutine::get_current_safe(); - - zval *handle; - php_stream *stream; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_RESOURCE(handle) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - int async; - int fd = php_swoole_convert_to_fd_ex(handle, &async); - if (fd < 0) { - RETURN_FALSE; - } - - if (async == 1) { - php_swoole_fatal_error(E_WARNING, "only support file resources"); - RETURN_FALSE; - } - - php_stream_from_res(stream, Z_RES_P(handle)); - - FILE *file; - if (stream->stdiocast) { - file = stream->stdiocast; - } else { - if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void **) &file, 1) != SUCCESS || file == nullptr) { - RETURN_FALSE; - } - } - - if (stream->readbuf == nullptr) { - stream->readbuflen = stream->chunk_size; - stream->readbuf = (uchar *) emalloc(stream->chunk_size); - } - - if (!stream->readbuf) { - RETURN_FALSE; - } - - int ret = 0; - swoole_trace("fd=%d, length=%ld", fd, stream->readbuflen); - php_swoole_check_reactor(); - bool async_success = swoole::coroutine::async([&]() { - char *data = fgets((char *) stream->readbuf, stream->readbuflen, file); - if (data == nullptr) { - ret = -1; - stream->eof = 1; - } - }); - - if (async_success && ret != -1) { - ZVAL_STRING(return_value, (char *) stream->readbuf); - } else { - ZVAL_FALSE(return_value); - } -} - -PHP_METHOD(swoole_coroutine_system, fwrite) { - Coroutine::get_current_safe(); - - zval *handle; - char *str; - size_t l_str; - zend_long length = 0; - - ZEND_PARSE_PARAMETERS_START(2, 3) - Z_PARAM_RESOURCE(handle) - Z_PARAM_STRING(str, l_str) - Z_PARAM_OPTIONAL - Z_PARAM_LONG(length) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - int async; - int fd = php_swoole_convert_to_fd_ex(handle, &async); - if (fd < 0) { - RETURN_FALSE; - } - - if (async) { - co_socket_write( - fd, str, (length <= 0 || (size_t) length > l_str) ? l_str : length, INTERNAL_FUNCTION_PARAM_PASSTHRU); - return; - } - - if (length <= 0 || (size_t) length > l_str) { - length = l_str; - } - - char *buf = estrndup(str, length); - - if (!buf) { - RETURN_FALSE; - } - - int ret = -1; - swoole_trace("fd=%d, length=" ZEND_LONG_FMT, fd, length); - php_swoole_check_reactor(); - bool async_success = swoole::coroutine::async([&]() { - while (1) { - ret = write(fd, buf, length); - if (ret < 0 && errno == EINTR) { - continue; - } - break; - } - }); - - if (async_success && ret >= 0) { - ZVAL_LONG(return_value, ret); - } else { - ZVAL_FALSE(return_value); - } - - efree(buf); -} - PHP_METHOD(swoole_coroutine_system, readFile) { char *filename; size_t l_filename; @@ -309,7 +89,7 @@ PHP_METHOD(swoole_coroutine_system, readFile) { Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - auto result = System::read_file(filename, flags & LOCK_EX); + auto result = System::read_file(filename, flags & SW_FILE_LOCK); if (result == nullptr) { RETURN_FALSE; } else { @@ -332,13 +112,13 @@ PHP_METHOD(swoole_coroutine_system, writeFile) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int _flags = 0; - if (flags & PHP_FILE_APPEND) { + if (flags & SW_FILE_APPEND) { _flags |= O_APPEND; } else { _flags |= O_TRUNC; } - ssize_t retval = System::write_file(filename, data, l_data, flags & LOCK_EX, _flags); + ssize_t retval = System::write_file(filename, data, l_data, flags & SW_FILE_LOCK, _flags); if (retval < 0) { RETURN_FALSE; } else { @@ -354,9 +134,12 @@ PHP_FUNCTION(swoole_coroutine_gethostbyname) { zend_long family = AF_INET; double timeout = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ld", &domain_name, &l_domain_name, &family, &timeout) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(domain_name, l_domain_name) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(family) + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (l_domain_name == 0) { php_swoole_fatal_error(E_WARNING, "domain name is empty"); @@ -390,18 +173,15 @@ PHP_METHOD(swoole_coroutine_system, getaddrinfo) { size_t l_service = 0; double timeout = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), - "s|lllsd", - &hostname, - &l_hostname, - &family, - &socktype, - &protocol, - &service, - &l_service, - &timeout) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 6) + Z_PARAM_STRING(hostname, l_hostname) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(family) + Z_PARAM_LONG(socktype) + Z_PARAM_LONG(protocol) + Z_PARAM_STRING(service, l_service) + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (l_hostname == 0) { php_swoole_fatal_error(E_WARNING, "hostname is empty"); @@ -421,8 +201,8 @@ PHP_METHOD(swoole_coroutine_system, getaddrinfo) { } array_init(return_value); - for (auto i = result.begin(); i != result.end(); i++) { - add_next_index_stringl(return_value, i->c_str(), i->length()); + for (auto &i : result) { + add_next_index_stringl(return_value, i.c_str(), i.length()); } } @@ -454,7 +234,7 @@ PHP_METHOD(swoole_coroutine_system, statvfs) { PHP_METHOD(swoole_coroutine_system, exec) { char *command; size_t command_len; - zend_bool get_error_stream = 0; + zend_bool get_error_stream = false; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STRING(command, command_len) @@ -462,56 +242,24 @@ PHP_METHOD(swoole_coroutine_system, exec) { Z_PARAM_BOOL(get_error_stream) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (php_swoole_signal_isset_handler(SIGCHLD)) { - php_swoole_error(E_WARNING, "The signal [SIGCHLD] is registered, cannot execute swoole_coroutine_exec"); - RETURN_FALSE; - } - - Coroutine::get_current_safe(); - - pid_t pid; - int fd = swoole_shell_exec(command, &pid, get_error_stream); - if (fd < 0) { - php_swoole_error(E_WARNING, "Unable to execute '%s'", command); + int status; + auto buffer = std::shared_ptr(swoole::make_string(1024, sw_zend_string_allocator())); + if (!System::exec(command, get_error_stream, buffer, &status)) { RETURN_FALSE; } - String *buffer = new String(1024); - Socket socket(fd, SW_SOCK_UNIX_STREAM); - while (1) { - ssize_t retval = socket.read(buffer->str + buffer->length, buffer->size - buffer->length); - if (retval > 0) { - buffer->length += retval; - if (buffer->length == buffer->size) { - if (!buffer->extend()) { - break; - } - } - } else { - break; - } - } - socket.close(); + auto str = zend::fetch_zend_string_by_val(buffer->str); + buffer->set_null_terminated(); + str->len = buffer->length; + buffer->release(); zval zdata; - if (buffer->length == 0) { - ZVAL_EMPTY_STRING(&zdata); - } else { - ZVAL_STRINGL(&zdata, buffer->str, buffer->length); - } - delete buffer; + ZVAL_STR(&zdata, str); - int status; - pid_t _pid = swoole_coroutine_waitpid(pid, &status, 0); - if (_pid > 0) { - array_init(return_value); - add_assoc_long(return_value, "code", WEXITSTATUS(status)); - add_assoc_long(return_value, "signal", WTERMSIG(status)); - add_assoc_zval(return_value, "output", &zdata); - } else { - zval_ptr_dtor(&zdata); - RETVAL_FALSE; - } + array_init(return_value); + add_assoc_long(return_value, "code", WEXITSTATUS(status)); + add_assoc_long(return_value, "signal", WTERMSIG(status)); + add_assoc_zval(return_value, "output", &zdata); } static void swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAMETERS, pid_t pid, double timeout) { @@ -530,12 +278,12 @@ static void swoole_coroutine_system_wait(INTERNAL_FUNCTION_PARAMETERS, pid_t pid add_assoc_long(return_value, "code", WEXITSTATUS(status)); add_assoc_long(return_value, "signal", WTERMSIG(status)); } else { - swoole_set_last_error(errno); RETURN_FALSE; } } PHP_METHOD(swoole_coroutine_system, wait) { + SW_MUST_BE_MAIN_THREAD(); double timeout = -1; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -547,6 +295,7 @@ PHP_METHOD(swoole_coroutine_system, wait) { } PHP_METHOD(swoole_coroutine_system, waitPid) { + SW_MUST_BE_MAIN_THREAD(); zend_long pid; double timeout = -1; @@ -560,26 +309,40 @@ PHP_METHOD(swoole_coroutine_system, waitPid) { } PHP_METHOD(swoole_coroutine_system, waitSignal) { - zend_long signo; + SW_MUST_BE_MAIN_THREAD(); + zval *zsignals; double timeout = -1; ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_LONG(signo) + Z_PARAM_ZVAL(zsignals) Z_PARAM_OPTIONAL Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (!System::wait_signal(signo, timeout)) { + std::vector signals; + + if (ZVAL_IS_ARRAY(zsignals)) { + zval *item; + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zsignals), item) { + signals.push_back(zval_get_long(item)); + } + ZEND_HASH_FOREACH_END(); + } else { + signals.push_back(zval_get_long(zsignals)); + } + + int signo = System::wait_signal(signals, timeout); + if (signo == -1) { if (swoole_get_last_error() == EBUSY) { php_swoole_fatal_error(E_WARNING, "Unable to wait signal, async signal listener has been registered"); } else if (swoole_get_last_error() == EINVAL) { - php_swoole_fatal_error(E_WARNING, "Invalid signal [" ZEND_LONG_FMT "]", signo); + php_swoole_fatal_error(E_WARNING, "Invalid signal in the given list"); } errno = swoole_get_last_error(); RETURN_FALSE; } - RETURN_TRUE; + RETURN_LONG(signo); } PHP_METHOD(swoole_coroutine_system, waitEvent) { diff --git a/ext-src/swoole_curl.cc b/ext-src/swoole_curl.cc index a68b5d2b29..fd61e91e83 100644 --- a/ext-src/swoole_curl.cc +++ b/ext-src/swoole_curl.cc @@ -22,14 +22,54 @@ namespace swoole { namespace curl { +Handle *get_handle(CURL *cp) { + Handle *handle; + if (curl_easy_getinfo(cp, CURLINFO_PRIVATE, (void *) &handle) == CURLE_OK) { + return handle; + } else { + return nullptr; + } +} + +Handle *create_handle(CURL *cp) { + auto *handle = new Handle(cp); + curl_easy_setopt(cp, CURLOPT_PRIVATE, handle); + swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_MAGENTA " handle=%p, curl=%p", "[CREATE]", handle, cp); + return handle; +} + +void destroy_handle(CURL *cp) { + auto handle = get_handle(cp); + if (!handle) { + return; + } + delete handle->easy_multi; + curl_easy_setopt(cp, CURLOPT_PRIVATE, nullptr); + swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " handle=%p, curl=%p", "[DESTROY]", handle, cp); + delete handle; +} + static int execute_callback(Event *event, int bitmask) { - Handle *handle = (Handle *) event->socket->object; - handle->event_bitmask |= bitmask; - handle->event_fd = event->fd; - handle->multi->callback(handle, bitmask); + auto curl_socket = static_cast(event->socket->object); + curl_socket->bitmask |= bitmask; + curl_socket->multi->callback(curl_socket, bitmask, event->fd); return 0; } +Multi::Multi() { + multi_handle_ = curl_multi_init(); + co = nullptr; + curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETFUNCTION, handle_socket); + curl_multi_setopt(multi_handle_, CURLMOPT_TIMERFUNCTION, handle_timeout); + curl_multi_setopt(multi_handle_, CURLMOPT_SOCKETDATA, this); + curl_multi_setopt(multi_handle_, CURLMOPT_TIMERDATA, this); +} + +Multi::~Multi() { + del_timer(); + curl_multi_cleanup(multi_handle_); +} + int Multi::cb_readable(Reactor *reactor, Event *event) { return execute_callback(event, CURL_CSELECT_IN); } @@ -42,67 +82,93 @@ int Multi::cb_error(Reactor *reactor, Event *event) { return execute_callback(event, CURL_CSELECT_ERR); } -int Multi::handle_socket(CURL *easy, curl_socket_t s, int action, void *userp, void *socketp) { - Multi *multi = (Multi *) userp; - swoole_trace_log( - SW_TRACE_CO_CURL, SW_ECHO_CYAN "action=%d, userp=%p, socketp=%p", "[HANDLE_SOCKET]", action, userp, socketp); +int Multi::handle_socket(CURL *cp, curl_socket_t sockfd, int action, void *userp, void *socketp) { + auto *multi = static_cast(userp); + swoole_trace_log(SW_TRACE_CO_CURL, + SW_ECHO_CYAN "curl=%p, sockfd=%d, action=%d, userp=%p, socketp=%p", + "[HANDLE_SOCKET]", + cp, + sockfd, + action, + userp, + socketp); switch (action) { case CURL_POLL_IN: case CURL_POLL_OUT: case CURL_POLL_INOUT: - multi->set_event(easy, socketp, s, action); - break; + return multi->set_event(socketp, sockfd, action); case CURL_POLL_REMOVE: - if (socketp) { - multi->del_event(easy, socketp, s); - } - break; + return multi->del_event(socketp, sockfd); default: abort(); } return 0; } -Socket *Multi::create_socket(CURL *cp, curl_socket_t sockfd) { - if (!swoole_event_isset_handler(PHP_SWOOLE_FD_CO_CURL)) { - swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL | SW_EVENT_READ, cb_readable); - swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL | SW_EVENT_WRITE, cb_writable); - swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL | SW_EVENT_ERROR, cb_error); +int Multi::del_event(void *socket_ptr, curl_socket_t sockfd) { + sockets.erase(sockfd); + curl_multi_assign(multi_handle_, sockfd, nullptr); + + if (sw_unlikely(!socket_ptr)) { + return SW_ERR; + } + + auto curl_socket = static_cast(socket_ptr); + if (curl_socket->socket->events && sw_likely(swoole_event_is_available())) { + curl_socket->socket->silent_remove = 1; + swoole_event_del(curl_socket->socket); } - Socket *socket = new Socket(); - socket->fd = sockfd; - socket->removed = 1; - socket->fd_type = (FdType) PHP_SWOOLE_FD_CO_CURL; - curl_multi_assign(multi_handle_, sockfd, (void *) socket); - Handle *handle = get_handle(cp); - handle->socket = socket; - handle->cp = cp; - socket->object = handle; + swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " socket_ptr=%p, fd=%d", "[DEL_EVENT]", socket_ptr, sockfd); + + curl_socket->socket->fd = -1; + curl_socket->socket->free(); - return socket; + if (selector.executing) { + curl_socket->deleted = true; + selector.release_sockets.insert(curl_socket); + } else { + delete curl_socket; + } + + return SW_OK; } -void Multi::del_event(CURL *cp, void *socket_ptr, curl_socket_t sockfd) { - Socket *socket = (Socket *) socket_ptr; - socket->silent_remove = 1; - if (socket->events && swoole_event_is_available() && swoole_event_del(socket) == SW_OK) { - event_count_--; +int Multi::set_event(void *socket_ptr, curl_socket_t sockfd, int action) { + if (sw_unlikely(!swoole_event_is_available())) { + return -1; } - socket->fd = -1; - socket->free(); - curl_multi_assign(multi_handle_, sockfd, NULL); - Handle *handle = get_handle(cp); - if (handle) { - handle->socket = nullptr; + if (sw_unlikely(!swoole_event_isset_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_READ))) { + swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_READ, cb_readable); + swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_WRITE, cb_writable); + swoole_event_set_handler(PHP_SWOOLE_FD_CO_CURL, SW_EVENT_ERROR, cb_error); } - swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " handle=%p, curl=%p, fd=%d", "[DEL_EVENT]", handle, cp, sockfd); -} + Socket *curl_socket; + + if (socket_ptr) { + curl_socket = (Socket *) socket_ptr; + } else { + curl_socket = new Socket(); + if (sw_unlikely(curl_multi_assign(multi_handle_, sockfd, curl_socket) != CURLM_OK)) { + delete curl_socket; + return -1; + } + + curl_socket->socket = new network::Socket(); + curl_socket->socket->fd = sockfd; + curl_socket->socket->removed = 1; + curl_socket->socket->fd_type = static_cast(PHP_SWOOLE_FD_CO_CURL); + curl_socket->socket->object = curl_socket; + curl_socket->multi = this; + + sockets[sockfd] = curl_socket; + } + + curl_socket->sockfd = sockfd; + curl_socket->action = action; -void Multi::set_event(CURL *cp, void *socket_ptr, curl_socket_t sockfd, int action) { - Socket *socket = socket_ptr ? (Socket *) socket_ptr : create_socket(cp, sockfd); int events = 0; if (action != CURL_POLL_IN) { events |= SW_EVENT_WRITE; @@ -110,118 +176,101 @@ void Multi::set_event(CURL *cp, void *socket_ptr, curl_socket_t sockfd, int acti if (action != CURL_POLL_OUT) { events |= SW_EVENT_READ; } - assert(socket->fd > 0); - socket->fd = sockfd; - if (socket->events) { - swoole_event_set(socket, events); - } else { - if (swoole_event_add(socket, events) == SW_OK) { - event_count_++; - } - } - Handle *handle = get_handle(cp); - handle->action = action; swoole_trace_log(SW_TRACE_CO_CURL, - SW_ECHO_GREEN " handle=%p, curl=%p, fd=%d, events=%d", + SW_ECHO_GREEN " curl_socket=%p, fd=%d, events=%d", "[ADD_EVENT]", - handle, - cp, + curl_socket, sockfd, events); + + if (curl_socket->socket->events) { + return swoole_event_set(curl_socket->socket, events); + } else { + return swoole_event_add(curl_socket->socket, events); + } } -CURLMcode Multi::add_handle(CURL *cp) { - auto retval = curl_multi_add_handle(multi_handle_, cp); +CURLMcode Multi::add_handle(Handle *handle) { + auto retval = curl_multi_add_handle(multi_handle_, handle->cp); if (retval == CURLM_OK) { - auto handle = get_handle(cp); - if (handle == nullptr) { - handle = new Handle{}; - handle->cp = cp; - curl_easy_setopt(cp, CURLOPT_PRIVATE, handle); - } handle->multi = this; - swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_GREEN " handle=%p, curl=%p", "[ADD_HANDLE]", handle, cp); + swoole_trace_log(SW_TRACE_CO_CURL, + SW_ECHO_GREEN " handle=%p, curl=%p, multi=%p, running_handles=%d", + "[ADD_HANDLE]", + handle, + handle->cp, + this, + running_handles_); } return retval; } -CURLMcode Multi::remove_handle(CURL *cp) { - auto retval = curl_multi_remove_handle(multi_handle_, cp); - if (retval == CURLM_OK) { - auto handle = get_handle(cp); - if (handle) { - handle->multi = nullptr; +CURLMcode Multi::remove_handle(Handle *handle) const { + swoole_trace_log(SW_TRACE_CO_CURL, + SW_ECHO_RED " handle=%p, curl=%p, multi=%p, running_handles=%d", + "[REMOVE_HANDLE]", + handle, + handle->cp, + handle->multi, + handle->multi->running_handles_); + + const auto rc = curl_multi_remove_handle(multi_handle_, handle->cp); + handle->multi = nullptr; + return rc; +} + +void Multi::selector_prepare() { + for (auto it : sockets) { + Socket *curl_socket = it.second; + if (curl_socket->socket->removed) { + swoole_event_add(curl_socket->socket, get_event(curl_socket->action)); + swoole_trace_log(SW_TRACE_CO_CURL, + "resume, curl_socket=%p, fd=%d, action=%d", + curl_socket, + curl_socket->socket->get_fd(), + curl_socket->action); } - swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_RED " handle=%p, curl=%p", "[REMOVE_HANDLE]", handle, cp); } - return retval; } -CURLcode Multi::exec(php_curl *ch) { - if (add_handle(ch->cp) != CURLM_OK) { +CURLcode Multi::exec(Handle *handle) { + if (add_handle(handle) != CURLM_OK) { return CURLE_FAILED_INIT; } - Handle *handle = get_handle(ch->cp); bool is_canceled = false; SW_LOOP { - if (handle->socket && handle->socket->removed) { - if (swoole_event_add(handle->socket, get_event(handle->action)) == SW_OK) { - event_count_++; - } - swoole_trace_log( - SW_TRACE_CO_CURL, "resume, handle=%p, curl=%p, fd=%d", handle, ch->cp, handle->socket->get_fd()); - } + selector_prepare(); - co = check_bound_co(); - co->yield_ex(-1); - is_canceled = co->is_canceled(); - co = nullptr; + if (wait_event()) { + co = check_bound_co(); + co->yield_ex(-1); + is_canceled = co->is_canceled(); + co = nullptr; - if (is_canceled) { - swoole_set_last_error(SW_ERROR_CO_CANCELED); - break; - } - - int sockfd = last_sockfd; - int bitmask = 0; - if (sockfd >= 0) { - bitmask = handle->event_bitmask; - if (handle->socket && !handle->socket->removed && swoole_event_del(handle->socket) == SW_OK) { - event_count_--; + if (is_canceled) { + swoole_set_last_error(SW_ERROR_CO_CANCELED); + break; } } - del_timer(); - curl_multi_socket_action(multi_handle_, sockfd, bitmask, &running_handles_); - swoole_trace_log(SW_TRACE_CO_CURL, - "curl_multi_socket_action: handle=%p, sockfd=%d, bitmask=%d, running_handles_=%d", - handle, - sockfd, - bitmask, - running_handles_); + selector_finish(); if (running_handles_ == 0) { break; } set_timer(); - if (sockfd >= 0 && handle->socket && handle->socket->removed) { - if (swoole_event_add(handle->socket, get_event(handle->action)) == SW_OK) { - event_count_++; - } - } - if (!timer && handle->socket->removed) { - break; - } } + del_timer(); + CURLcode retval = read_info(); - remove_handle(ch->cp); + remove_handle(handle); return is_canceled ? CURLE_ABORTED_BY_CALLBACK : retval; } -CURLcode Multi::read_info() { +CURLcode Multi::read_info() const { CURLMsg *message; int pending; @@ -243,13 +292,17 @@ CURLcode Multi::read_info() { } int Multi::handle_timeout(CURLM *mh, long timeout_ms, void *userp) { - Multi *multi = (Multi *) userp; - swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_BLUE "timeout_ms=%ld", "[HANDLE_TIMEOUT]", timeout_ms); - if (!swoole_event_is_available()) { - return 0; + auto *multi = static_cast(userp); + swoole_trace_log(SW_TRACE_CO_CURL, SW_ECHO_BLUE " timeout_ms=%ld", "[HANDLE_TIMEOUT]", timeout_ms); + if (sw_unlikely(!swoole_event_is_available())) { + return -1; } if (timeout_ms < 0) { - multi->del_timer(); + if (multi->timer) { + multi->del_timer(); + } else { + multi->add_timer(1000); + } } else { if (timeout_ms == 0) { timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in a bit */ @@ -259,6 +312,49 @@ int Multi::handle_timeout(CURLM *mh, long timeout_ms, void *userp) { return 0; } +void Multi::selector_finish() { + del_timer(); + + selector.executing = true; + + if (selector.timer_callback) { + selector.timer_callback = false; + curl_multi_socket_action(multi_handle_, CURL_SOCKET_TIMEOUT, 0, &running_handles_); + swoole_trace_log(SW_TRACE_CO_CURL, "socket_action[timer], running_handles=%d", running_handles_); + } + + while (!selector.active_sockets.empty()) { + auto active_sockets = selector.active_sockets; + selector.active_sockets.clear(); + + for (auto curl_socket : active_sockets) { + /** + * In `curl_multi_socket_action`, `Handle::destroy_socket()` may be invoked, + * which will remove entries from the `unordered_map`. + * In C++, removing elements during iteration can render the iterator invalid; hence, + * it's necessary to copy `handle->sockets` into a new `unordered_map`. + */ + swoole_trace_log(SW_TRACE_CO_CURL, + "curl_multi_socket_action(): sockfd=%d, bitmask=%d, running_handles_=%d", + curl_socket->sockfd, + curl_socket->bitmask, + running_handles_); + + if (!curl_socket->deleted) { + int bitmask = curl_socket->bitmask; + curl_socket->bitmask = 0; + curl_multi_socket_action(multi_handle_, curl_socket->sockfd, bitmask, &running_handles_); + } + } + } + + selector.executing = false; + for (auto curl_socket : selector.release_sockets) { + delete curl_socket; + } + selector.release_sockets.clear(); +} + long Multi::select(php_curlm *mh, double timeout) { if (zend_llist_count(&mh->easyh) == 0) { return 0; @@ -268,32 +364,11 @@ long Multi::select(php_curlm *mh, double timeout) { return CURLE_FAILED_INIT; } - for (zend_llist_element *element = mh->easyh.head; element; element = element->next) { - zval *z_ch = (zval *) element->data; - php_curl *ch; - if ((ch = swoole_curl_get_handle(z_ch, false)) == NULL) { - continue; - } - Handle *handle = get_handle(ch->cp); - - swoole_trace_log(SW_TRACE_CO_CURL, - "handle=%p, handle->socket=%p, handle->socket->removed=%d", - handle, - handle ? handle->socket : nullptr, - handle ? (handle->socket ? handle->socket->removed : 1) : 1); - - if (handle && handle->socket && handle->socket->removed) { - if (swoole_event_add(handle->socket, get_event(handle->action)) == SW_OK) { - event_count_++; - } - swoole_trace_log( - SW_TRACE_CO_CURL, "resume, handle=%p, curl=%p, fd=%d", handle, ch->cp, handle->socket->get_fd()); - } - } + selector_prepare(); set_timer(); // no events and timers, should not be suspended - if (!timer && event_count_ == 0) { + if (!wait_event()) { return 0; } @@ -303,64 +378,28 @@ long Multi::select(php_curlm *mh, double timeout) { swoole_trace_log(SW_TRACE_CO_CURL, "yield timeout, count=%lu", zend_llist_count(&mh->easyh)); - auto count = selector->active_handles.size(); - - for (zend_llist_element *element = mh->easyh.head; element; element = element->next) { - zval *z_ch = (zval *) element->data; - php_curl *ch; - if ((ch = swoole_curl_get_handle(z_ch, false)) == NULL) { - continue; - } - Handle *handle = get_handle(ch->cp); - if (handle && handle->socket && !handle->socket->removed && swoole_event_del(handle->socket) == SW_OK) { - swoole_trace_log( - SW_TRACE_CO_CURL, "suspend, handle=%p, curl=%p, fd=%d", handle, ch->cp, handle->socket->get_fd()); - event_count_--; - } - } - del_timer(); + const auto count = selector.active_sockets.size(); + selector_finish(); - if (selector->timer_callback) { - selector->timer_callback = false; - curl_multi_socket_action(multi_handle_, CURL_SOCKET_TIMEOUT, 0, &running_handles_); - swoole_trace_log(SW_TRACE_CO_CURL, "socket_action[timer], running_handles=%d", running_handles_); - } - - for (auto iter = selector->active_handles.begin(); iter != selector->active_handles.end(); iter++) { - Handle *handle = *iter; - curl_multi_socket_action(multi_handle_, handle->event_fd, handle->event_bitmask, &running_handles_); - swoole_trace_log(SW_TRACE_CO_CURL, "socket_action[socket], running_handles=%d", running_handles_); - } - - selector->active_handles.clear(); - - return count; + return static_cast(count); } -void Multi::callback(Handle *handle, int event_bitmask) { - swoole_trace_log(SW_TRACE_CO_CURL, "handle=%p, event_bitmask=%d, co=%p", handle, event_bitmask, co); - if (handle) { - last_sockfd = handle->event_fd; - } else { - last_sockfd = -1; - } - if (selector.get()) { - if (!handle) { - selector->timer_callback = true; - } +void Multi::callback(Socket *curl_socket, int bitmask, int sockfd) { + swoole_trace_log( + SW_TRACE_CO_CURL, "curl_socket=%p, bitmask=%d, co=%p, sockfd=%d", curl_socket, bitmask, co, sockfd); + if (!curl_socket) { + selector.timer_callback = true; } if (!co) { - if (handle) { - if (swoole_event_del(handle->socket) == SW_OK) { - event_count_--; - } + if (curl_socket) { + swoole_event_del(curl_socket->socket); } else { del_timer(); } return; } - if (selector.get() && handle) { - selector->active_handles.insert(handle); + if (curl_socket) { + selector.active_sockets.insert(curl_socket); } if (defer_callback) { return; @@ -377,4 +416,37 @@ void Multi::callback(Handle *handle, int event_bitmask) { } } // namespace curl } // namespace swoole + +CURLcode swoole_curl_easy_perform(CURL *cp) { + auto handle = swoole::curl::get_handle(cp); + if (!handle->easy_multi) { + handle->easy_multi = new Multi(); + } + return handle->easy_multi->exec(handle); +} + +void swoole_curl_easy_reset(CURL *cp) { + auto handle = swoole::curl::get_handle(cp); + curl_easy_reset(cp); + curl_easy_setopt(cp, CURLOPT_PRIVATE, handle); +} + +php_curl *swoole_curl_get_handle(zval *zid, bool exclusive, bool required) { + php_curl *ch = Z_CURL_P(zid); + if (SWOOLE_G(req_status) == PHP_SWOOLE_RSHUTDOWN_END) { + exclusive = false; + } + if (exclusive && swoole_coroutine_is_in()) { + auto handle = swoole::curl::get_handle(ch->cp); + if (required && !handle) { + php_swoole_fatal_error(E_WARNING, "The given handle is not initialized in coroutine"); + return nullptr; + } + if (handle && handle->multi && handle->multi->check_bound_co() == nullptr) { + return nullptr; + } + } + return ch; +} + #endif diff --git a/ext-src/swoole_curl_interface.h b/ext-src/swoole_curl_interface.h new file mode 100644 index 0000000000..829a85cdc2 --- /dev/null +++ b/ext-src/swoole_curl_interface.h @@ -0,0 +1,62 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2015 The Swoole Group | + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + */ + +#pragma once + +#include "php_swoole_cxx.h" + +#ifdef SW_USE_CURL +SW_EXTERN_C_BEGIN + +#include +#include + +#define fread swoole_coroutine_fread +#define fwrite swoole_coroutine_fwrite +#define curl_easy_reset swoole_curl_easy_reset + +void swoole_native_curl_minit(int module_number); +void swoole_native_curl_mshutdown(); + +PHP_FUNCTION(swoole_native_curl_close); +PHP_FUNCTION(swoole_native_curl_copy_handle); +PHP_FUNCTION(swoole_native_curl_errno); +PHP_FUNCTION(swoole_native_curl_error); +PHP_FUNCTION(swoole_native_curl_exec); +PHP_FUNCTION(swoole_native_curl_getinfo); +PHP_FUNCTION(swoole_native_curl_init); +PHP_FUNCTION(swoole_native_curl_setopt); +PHP_FUNCTION(swoole_native_curl_setopt_array); +PHP_FUNCTION(swoole_native_curl_reset); +PHP_FUNCTION(swoole_native_curl_escape); +PHP_FUNCTION(swoole_native_curl_unescape); +PHP_FUNCTION(swoole_native_curl_pause); +PHP_FUNCTION(swoole_native_curl_multi_add_handle); +PHP_FUNCTION(swoole_native_curl_multi_close); +PHP_FUNCTION(swoole_native_curl_multi_errno); +PHP_FUNCTION(swoole_native_curl_multi_exec); +PHP_FUNCTION(swoole_native_curl_multi_select); +PHP_FUNCTION(swoole_native_curl_multi_remove_handle); +PHP_FUNCTION(swoole_native_curl_multi_setopt); +PHP_FUNCTION(swoole_native_curl_multi_getcontent); +PHP_FUNCTION(swoole_native_curl_multi_info_read); +PHP_FUNCTION(swoole_native_curl_multi_init); +#if LIBCURL_VERSION_NUM >= 0x073E00 +PHP_FUNCTION(swoole_native_curl_upkeep); +#endif + +SW_EXTERN_C_END +#endif diff --git a/ext-src/swoole_event.cc b/ext-src/swoole_event.cc index 1e38626f76..0892ebadf7 100644 --- a/ext-src/swoole_event.cc +++ b/ext-src/swoole_event.cc @@ -34,15 +34,15 @@ static zend_object_handlers swoole_event_handlers; struct EventObject { zval zsocket; - zend_fcall_info_cache fci_cache_read; - zend_fcall_info_cache fci_cache_write; + zend::Callable *readable_callback; + zend::Callable *writable_callback; }; static int event_readable_callback(Reactor *reactor, Event *event); static int event_writable_callback(Reactor *reactor, Event *event); static int event_error_callback(Reactor *reactor, Event *event); static void event_defer_callback(void *data); -static void event_end_callback(void *data); +static void event_cycle_callback(void *data); SW_EXTERN_C_BEGIN static PHP_FUNCTION(swoole_event_add); @@ -80,39 +80,70 @@ void php_swoole_event_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_event, "Swoole\\Event", nullptr, swoole_event_methods); SW_SET_CLASS_CREATE(swoole_event, sw_zend_create_object_deny); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "add", CG(function_table), "swoole_event_add"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "del", CG(function_table), "swoole_event_del"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "set", CG(function_table), "swoole_event_set"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "isset", CG(function_table), "swoole_event_isset"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "dispatch", CG(function_table), "swoole_event_dispatch"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "defer", CG(function_table), "swoole_event_defer"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "cycle", CG(function_table), "swoole_event_cycle"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "write", CG(function_table), "swoole_event_write"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "wait", CG(function_table), "swoole_event_wait"); - SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, "exit", CG(function_table), "swoole_event_exit"); + SW_FUNCTION_ALIAS( + &swoole_event_ce->function_table, "add", CG(function_table), "swoole_event_add", arginfo_swoole_event_add); + SW_FUNCTION_ALIAS( + &swoole_event_ce->function_table, "del", CG(function_table), "swoole_event_del", arginfo_swoole_event_del); + SW_FUNCTION_ALIAS( + &swoole_event_ce->function_table, "set", CG(function_table), "swoole_event_set", arginfo_swoole_event_set); + SW_FUNCTION_ALIAS( + &swoole_event_ce->function_table, "wait", CG(function_table), "swoole_event_wait", arginfo_swoole_event_wait); + + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "isset", + CG(function_table), + "swoole_event_isset", + arginfo_swoole_event_isset); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "dispatch", + CG(function_table), + "swoole_event_dispatch", + arginfo_swoole_event_dispatch); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "defer", + CG(function_table), + "swoole_event_defer", + arginfo_swoole_event_defer); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "cycle", + CG(function_table), + "swoole_event_cycle", + arginfo_swoole_event_cycle); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "write", + CG(function_table), + "swoole_event_write", + arginfo_swoole_event_write); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "exit", + CG(function_table), + "swoole_event_exit", + arginfo_swoole_event_exit); + SW_FUNCTION_ALIAS(&swoole_event_ce->function_table, + "rshutdown", + CG(function_table), + "swoole_event_rshutdown", + arginfo_swoole_event_rshutdown); } static void event_object_free(void *data) { - EventObject *peo = (EventObject *) data; - if (peo->fci_cache_read.function_handler) { - sw_zend_fci_cache_discard(&peo->fci_cache_read); - } - if (peo->fci_cache_write.function_handler) { - sw_zend_fci_cache_discard(&peo->fci_cache_write); - } + auto *peo = static_cast(data); + delete peo->readable_callback; + delete peo->writable_callback; zval_ptr_dtor((&peo->zsocket)); efree(peo); } static int event_readable_callback(Reactor *reactor, Event *event) { - EventObject *peo = (EventObject *) event->socket->object; + auto *peo = static_cast(event->socket->object); zval argv[1]; argv[0] = peo->zsocket; + auto fcc = peo->readable_callback->ptr(); - if (UNEXPECTED(!zend::function::call(&peo->fci_cache_read, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(fcc, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error(E_WARNING, - "%s: onRead callback handler error, fd [%d] will be removed from reactor", + "%s: readable callback handler error, fd [%d] will be removed from reactor", ZSTR_VAL(swoole_event_ce->name), php_swoole_convert_to_fd(&peo->zsocket)); event->socket->object = nullptr; @@ -125,14 +156,15 @@ static int event_readable_callback(Reactor *reactor, Event *event) { } static int event_writable_callback(Reactor *reactor, Event *event) { - EventObject *peo = (EventObject *) event->socket->object; + auto *peo = static_cast(event->socket->object); zval argv[1]; argv[0] = peo->zsocket; + auto fcc = peo->writable_callback->ptr(); - if (UNEXPECTED(!zend::function::call(&peo->fci_cache_write, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(fcc, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error(E_WARNING, - "%s: onWrite callback handler error, fd [%d] will be removed from reactor", + "%s: writable callback handler error, fd [%d] will be removed from reactor", ZSTR_VAL(swoole_event_ce->name), php_swoole_convert_to_fd(&peo->zsocket)); event->socket->object = nullptr; @@ -147,9 +179,9 @@ static int event_writable_callback(Reactor *reactor, Event *event) { static int event_error_callback(Reactor *reactor, Event *event) { if (!(event->socket->events & SW_EVENT_ERROR)) { if (event->socket->events & SW_EVENT_READ) { - return reactor->get_handler(SW_EVENT_READ, event->socket->fd_type)(reactor, event); + return reactor->get_handler(event->socket->fd_type, SW_EVENT_READ)(reactor, event); } else { - return reactor->get_handler(SW_EVENT_WRITE, event->socket->fd_type)(reactor, event); + return reactor->get_handler(event->socket->fd_type, SW_EVENT_WRITE)(reactor, event); } } @@ -170,19 +202,16 @@ static int event_error_callback(Reactor *reactor, Event *event) { } static void event_defer_callback(void *data) { - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) data; - - if (UNEXPECTED(!zend::function::call(fci_cache, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { + auto *cb = static_cast(data); + if (UNEXPECTED(!zend::function::call(cb, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s::defer callback handler error", ZSTR_VAL(swoole_event_ce->name)); } - - sw_zend_fci_cache_discard(fci_cache); - efree(fci_cache); + delete cb; } -static void event_end_callback(void *data) { - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) data; - if (UNEXPECTED(!zend::function::call(fci_cache, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { +static void event_cycle_callback(void *data) { + auto *cb = static_cast(data); + if (UNEXPECTED(!zend::function::call(cb, 0, nullptr, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s::end callback handler error", ZSTR_VAL(swoole_event_ce->name)); } } @@ -212,7 +241,7 @@ int php_swoole_reactor_init() { return SW_ERR; } - php_swoole_register_shutdown_function("Swoole\\Event::rshutdown"); + php_swoole_register_shutdown_function("swoole_event_rshutdown"); } if (sw_reactor() && SwooleG.user_exit_condition && @@ -227,6 +256,10 @@ void php_swoole_event_wait() { if (php_swoole_is_fatal_error() || !sw_reactor()) { return; } + if (swoole_coroutine_is_in()) { + php_swoole_fatal_error(E_ERROR, "Unable to call Event::wait() in coroutine"); + return; + } if (!sw_reactor()->if_exit() && !sw_reactor()->bailout) { // Don't disable object slot reuse while running shutdown functions: // https://github.com/php/php-src/commit/bd6eabd6591ae5a7c9ad75dfbe7cc575fa907eac @@ -234,7 +267,7 @@ void php_swoole_event_wait() { zend_bool in_shutdown = EG(flags) & EG_FLAGS_IN_SHUTDOWN; EG(flags) &= ~EG_FLAGS_IN_SHUTDOWN; #endif - if (sw_reactor()->wait(nullptr) < 0) { + if (sw_reactor()->wait() < 0) { php_swoole_sys_error(E_ERROR, "reactor wait failed"); } #if defined(EG_FLAGS_IN_SHUTDOWN) && !defined(EG_FLAGS_OBJECT_STORE_NO_REUSE) @@ -266,15 +299,6 @@ int php_swoole_convert_to_fd(zval *zsocket) { return fd; } } -#ifdef SWOOLE_SOCKETS_SUPPORT - else { - php_socket *php_sock; - if ((php_sock = SW_Z_SOCKET_P(zsocket))) { - fd = php_sock->bsd_socket; - return fd; - } - } -#endif php_swoole_fatal_error(E_WARNING, "fd argument must be either valid PHP stream or valid PHP socket resource"); return SW_ERR; } @@ -288,12 +312,21 @@ int php_swoole_convert_to_fd(zval *zsocket) { } case IS_OBJECT: { zval *zfd = nullptr; - if (instanceof_function(Z_OBJCE_P(zsocket), swoole_socket_coro_ce)) { + if (sw_zval_is_co_socket(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_FD), 0); - } else if (instanceof_function(Z_OBJCE_P(zsocket), swoole_client_ce)) { + } else if (sw_zval_is_client(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_SOCK), 0); - } else if (instanceof_function(Z_OBJCE_P(zsocket), swoole_process_ce)) { + } else if (sw_zval_is_process(zsocket)) { zfd = sw_zend_read_property_ex(Z_OBJCE_P(zsocket), zsocket, SW_ZSTR_KNOWN(SW_ZEND_STR_PIPE), 0); +#ifdef SWOOLE_SOCKETS_SUPPORT + } else if (sw_zval_is_php_socket(zsocket)) { + php_socket *php_sock = SW_Z_SOCKET_P(zsocket); + if (IS_INVALID_SOCKET(php_sock)) { + php_swoole_fatal_error(E_WARNING, "contains a closed socket"); + return SW_ERR; + } + return php_sock->bsd_socket; +#endif } if (zfd == nullptr || Z_TYPE_P(zfd) != IS_LONG) { return SW_ERR; @@ -341,7 +374,11 @@ php_socket *php_swoole_convert_to_socket(int sock) { zval zsocket; object_init_ex(&zsocket, socket_ce); socket_object = Z_SOCKET_P(&zsocket); - socket_import_file_descriptor(sock, socket_object); + auto new_sock = dup(sock); + if (new_sock < 0) { + return nullptr; + } + socket_import_file_descriptor(new_sock, socket_object); return socket_object; } #endif @@ -349,10 +386,10 @@ php_socket *php_swoole_convert_to_socket(int sock) { static void event_check_reactor() { php_swoole_check_reactor(); - if (!swoole_event_isset_handler(SW_FD_USER)) { - swoole_event_set_handler(SW_FD_USER | SW_EVENT_READ, event_readable_callback); - swoole_event_set_handler(SW_FD_USER | SW_EVENT_WRITE, event_writable_callback); - swoole_event_set_handler(SW_FD_USER | SW_EVENT_ERROR, event_error_callback); + if (!swoole_event_isset_handler(SW_FD_USER, SW_EVENT_READ)) { + swoole_event_set_handler(SW_FD_USER, SW_EVENT_READ, event_readable_callback); + swoole_event_set_handler(SW_FD_USER, SW_EVENT_WRITE, event_writable_callback); + swoole_event_set_handler(SW_FD_USER, SW_EVENT_ERROR, event_error_callback); } } @@ -366,24 +403,19 @@ static Socket *event_get_socket(int socket_fd) { static PHP_FUNCTION(swoole_event_add) { zval *zfd; - zend_fcall_info fci_read = empty_fcall_info; - zend_fcall_info_cache fci_cache_read = empty_fcall_info_cache; - zend_fcall_info fci_write = empty_fcall_info; - zend_fcall_info_cache fci_cache_write = empty_fcall_info_cache; zend_long events = SW_EVENT_READ; + zval *zreadable_callback = nullptr; + zval *zwritable_callback = nullptr; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_ZVAL(zfd) Z_PARAM_OPTIONAL - Z_PARAM_FUNC_EX(fci_read, fci_cache_read, 1, 0) - Z_PARAM_FUNC_EX(fci_write, fci_cache_write, 1, 0) + Z_PARAM_ZVAL(zreadable_callback) + Z_PARAM_ZVAL(zwritable_callback) Z_PARAM_LONG(events) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (fci_read.size == 0 && fci_write.size == 0) { - php_swoole_fatal_error(E_WARNING, "both read and write callbacks are empty"); - RETURN_FALSE; - } + event_check_reactor(); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { @@ -403,27 +435,29 @@ static PHP_FUNCTION(swoole_event_add) { RETURN_FALSE; } - EventObject *peo = (EventObject *) ecalloc(1, sizeof(*peo)); - - Z_TRY_ADDREF_P(zfd); - peo->zsocket = *zfd; - - if (fci_read.size != 0) { - sw_zend_fci_cache_persist(&fci_cache_read); - peo->fci_cache_read = fci_cache_read; - } - if (fci_write.size != 0) { - sw_zend_fci_cache_persist(&fci_cache_write); - peo->fci_cache_write = fci_cache_write; + auto readable_callback = sw_callable_create_ex(zreadable_callback, "readable_callback", true); + if ((events & SW_EVENT_READ) && readable_callback == nullptr) { + php_swoole_fatal_error( + E_WARNING, "%s: unable to find readable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); + RETURN_FALSE; } - event_check_reactor(); - - Socket *socket = swoole::make_socket(socket_fd, SW_FD_USER); - if (!socket) { + auto writable_callback = sw_callable_create_ex(zwritable_callback, "writable_callback", true); + if ((events & SW_EVENT_WRITE) && writable_callback == nullptr) { + php_swoole_fatal_error( + E_WARNING, "%s: unable to find writable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); + delete readable_callback; RETURN_FALSE; } + auto *peo = static_cast(ecalloc(1, sizeof(EventObject))); + + Z_TRY_ADDREF_P(zfd); + peo->zsocket = *zfd; + peo->readable_callback = readable_callback; + peo->writable_callback = writable_callback; + + Socket *socket = make_socket(socket_fd, SW_FD_USER); socket->set_nonblock(); socket->object = peo; @@ -444,9 +478,10 @@ static PHP_FUNCTION(swoole_event_write) { char *data; size_t len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", &zfd, &data, &len) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zfd) + Z_PARAM_STRING(data, len) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (len == 0) { php_swoole_fatal_error(E_WARNING, "data empty"); @@ -480,23 +515,20 @@ static PHP_FUNCTION(swoole_event_set) { } zval *zfd; - zend_fcall_info fci_read = empty_fcall_info; - zend_fcall_info_cache fci_cache_read = empty_fcall_info_cache; - zend_fcall_info fci_write = empty_fcall_info; - zend_fcall_info_cache fci_cache_write = empty_fcall_info_cache; zend_long events = 0; + zval *zreadable_callback = nullptr; + zval *zwritable_callback = nullptr; ZEND_PARSE_PARAMETERS_START(1, 4) Z_PARAM_ZVAL(zfd) Z_PARAM_OPTIONAL - Z_PARAM_FUNC_EX(fci_read, fci_cache_read, 1, 0) - Z_PARAM_FUNC_EX(fci_write, fci_cache_write, 1, 0) + Z_PARAM_ZVAL(zreadable_callback) + Z_PARAM_ZVAL(zwritable_callback) Z_PARAM_LONG(events) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { - php_swoole_fatal_error(E_WARNING, "unknown type"); RETURN_FALSE; } @@ -506,30 +538,29 @@ static PHP_FUNCTION(swoole_event_set) { RETURN_FALSE; } - EventObject *reactor_fd = (EventObject *) socket->object; - if (fci_read.size != 0) { - if (reactor_fd->fci_cache_read.function_handler) { - sw_zend_fci_cache_discard(&reactor_fd->fci_cache_read); + auto *peo = static_cast(socket->object); + auto readable_callback = sw_callable_create_ex(zreadable_callback, "readable_callback"); + auto writable_callback = sw_callable_create_ex(zwritable_callback, "writable_callback"); + if (readable_callback) { + if (peo->readable_callback) { + swoole_event_defer(sw_callable_free, peo->readable_callback); } - sw_zend_fci_cache_persist(&fci_cache_read); - reactor_fd->fci_cache_read = fci_cache_read; + peo->readable_callback = readable_callback; } - if (fci_write.size != 0) { - if (reactor_fd->fci_cache_write.function_handler) { - sw_zend_fci_cache_discard(&reactor_fd->fci_cache_write); + if (writable_callback) { + if (peo->writable_callback) { + swoole_event_defer(sw_callable_free, peo->writable_callback); } - sw_zend_fci_cache_persist(&fci_cache_write); - reactor_fd->fci_cache_write = fci_cache_write; + peo->writable_callback = writable_callback; } - - if ((events & SW_EVENT_READ) && reactor_fd->fci_cache_read.function_handler == nullptr) { + if ((events & SW_EVENT_READ) && peo->readable_callback == nullptr) { php_swoole_fatal_error( - E_WARNING, "%s: unable to find read callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); + E_WARNING, "%s: unable to find readable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); RETURN_FALSE; } - if ((events & SW_EVENT_WRITE) && reactor_fd->fci_cache_write.function_handler == nullptr) { + if ((events & SW_EVENT_WRITE) && peo->writable_callback == nullptr) { php_swoole_fatal_error( - E_WARNING, "%s: unable to find write callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); + E_WARNING, "%s: unable to find writable callback of fd [%d]", ZSTR_VAL(swoole_event_ce->name), socket_fd); RETURN_FALSE; } if (swoole_event_set(socket, events) < 0) { @@ -548,9 +579,9 @@ static PHP_FUNCTION(swoole_event_del) { RETURN_FALSE; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zfd) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zfd) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int socket_fd = php_swoole_convert_to_fd(zfd); if (socket_fd < 0) { @@ -571,69 +602,48 @@ static PHP_FUNCTION(swoole_event_del) { } static PHP_FUNCTION(swoole_event_defer) { - zend_fcall_info fci = empty_fcall_info; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); + zval *zfn; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_FUNC(fci, *fci_cache) - ZEND_PARSE_PARAMETERS_END_EX(efree(fci_cache); RETURN_FALSE); + Z_PARAM_ZVAL(zfn) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); php_swoole_check_reactor(); - - sw_zend_fci_cache_persist(fci_cache); - swoole_event_defer(event_defer_callback, fci_cache); + auto fn = sw_callable_create(zfn); + swoole_event_defer(event_defer_callback, fn); RETURN_TRUE; } static PHP_FUNCTION(swoole_event_cycle) { - if (!sw_reactor()) { - php_swoole_fatal_error(E_WARNING, "reactor is not ready, cannot call %s", ZSTR_VAL(swoole_event_ce->name)); - RETURN_FALSE; - } - - zend_fcall_info _fci = empty_fcall_info; - zend_fcall_info_cache _fci_cache = empty_fcall_info_cache; - zend_bool before = 0; + zval *zcallback; + zend_bool before = false; ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_FUNC_EX(_fci, _fci_cache, 1, 0) + Z_PARAM_ZVAL(zcallback) Z_PARAM_OPTIONAL Z_PARAM_BOOL(before) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (_fci.size == 0) { - if (sw_reactor()->idle_task.callback == nullptr) { + event_check_reactor(); + auto reactor = sw_reactor(); + auto defer_task = before ? &reactor->future_task : &reactor->idle_task; + + if (ZVAL_IS_NULL(zcallback)) { + if (defer_task->callback == nullptr) { RETURN_FALSE; } else { - swoole_event_defer(sw_zend_fci_cache_free, sw_reactor()->idle_task.data); - sw_reactor()->idle_task.callback = nullptr; - sw_reactor()->idle_task.data = nullptr; - RETURN_TRUE; - } - } - - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - - *fci_cache = _fci_cache; - sw_zend_fci_cache_persist(fci_cache); - - if (!before) { - if (sw_reactor()->idle_task.data != nullptr) { - swoole_event_defer(sw_zend_fci_cache_free, sw_reactor()->idle_task.data); + swoole_event_defer(sw_callable_free, defer_task->data); + defer_task->callback = nullptr; + defer_task->data = nullptr; } - - sw_reactor()->idle_task.callback = event_end_callback; - sw_reactor()->idle_task.data = fci_cache; } else { - if (sw_reactor()->future_task.data != nullptr) { - swoole_event_defer(sw_zend_fci_cache_free, sw_reactor()->future_task.data); + if (defer_task->data != nullptr) { + swoole_event_defer(sw_callable_free, defer_task->data); } - - sw_reactor()->future_task.callback = event_end_callback; - sw_reactor()->future_task.data = fci_cache; - // Registration onBegin callback function - sw_reactor()->activate_future_task(); + auto callback = sw_callable_create(zcallback); + defer_task->callback = event_cycle_callback; + defer_task->data = callback; } RETURN_TRUE; @@ -653,14 +663,13 @@ static PHP_FUNCTION(swoole_event_wait) { static PHP_FUNCTION(swoole_event_rshutdown) { /* prevent the program from jumping out of the rshutdown */ zend_try { - if (!sw_reactor()) { - return; - } // when throw Exception, do not show the info - if (!sw_reactor()->bailout) { - php_swoole_fatal_error(E_DEPRECATED, "Event::wait() in shutdown function is deprecated"); + if (!php_swoole_is_fatal_error() && sw_reactor()) { + if (!sw_reactor()->bailout) { + php_swoole_fatal_error(E_DEPRECATED, "Event::wait() in shutdown function is deprecated"); + } + php_swoole_event_wait(); } - php_swoole_event_wait(); } zend_end_try(); } @@ -670,7 +679,7 @@ static PHP_FUNCTION(swoole_event_dispatch) { RETURN_FALSE; } sw_reactor()->once = true; - if (sw_reactor()->wait(nullptr) < 0) { + if (sw_reactor()->wait() < 0) { php_swoole_sys_error(E_ERROR, "reactor wait failed"); } sw_reactor()->once = false; diff --git a/ext-src/swoole_firebird.cc b/ext-src/swoole_firebird.cc new file mode 100644 index 0000000000..838672d104 --- /dev/null +++ b/ext-src/swoole_firebird.cc @@ -0,0 +1,269 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ +#include "php_swoole_private.h" +#include "php_swoole_cxx.h" +#include "swoole_coroutine.h" + +#ifdef SW_USE_FIREBIRD +#include "php_swoole_firebird.h" + +using swoole::Coroutine; + +static SW_THREAD_LOCAL bool swoole_firebird_blocking = true; + +void swoole_firebird_set_blocking(bool blocking) { + swoole_firebird_blocking = blocking; +} + +ISC_STATUS swoole_isc_attach_database( + ISC_STATUS *_0, short _1, const ISC_SCHAR *_2, isc_db_handle *_3, short _4, const ISC_SCHAR *_5) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_attach_database"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_attach_database(_0, _1, _2, _3, _4, _5); }); + + return result; +} + +ISC_STATUS swoole_isc_detach_database(ISC_STATUS *_0, isc_db_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_detach_database"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_detach_database(_0, _1); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_execute( + ISC_STATUS *_0, isc_tr_handle *_1, isc_stmt_handle *_2, unsigned short _3, const XSQLDA *_4) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_execute"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_execute(_0, _1, _2, _3, _4); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_execute2( + ISC_STATUS *_0, isc_tr_handle *_1, isc_stmt_handle *_2, unsigned short _3, const XSQLDA *_4, const XSQLDA *_5) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_execute2"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_execute2(_0, _1, _2, _3, _4, _5); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_sql_info( + ISC_STATUS *_0, isc_stmt_handle *_1, short _2, const ISC_SCHAR *_3, short _4, ISC_SCHAR *_5) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_sql_info"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_sql_info(_0, _1, _2, _3, _4, _5); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_free_statement(ISC_STATUS *_0, isc_stmt_handle *_1, unsigned short _2) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_free_statement"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_free_statement(_0, _1, _2); }); + + return result; +} + +ISC_STATUS swoole_isc_start_transaction( + ISC_STATUS *_0, isc_tr_handle *_1, short _2, isc_db_handle *_3, size_t _4, char *_5) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_start_transaction"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_start_transaction(_0, _1, _2, _3, _4, _5); }); + + return result; +} + +ISC_STATUS swoole_isc_commit_retaining(ISC_STATUS *_0, isc_tr_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_commit_retaining"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_commit_retaining(_0, _1); }); + + return result; +} + +ISC_STATUS swoole_isc_commit_transaction(ISC_STATUS *_0, isc_tr_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_commit_transaction"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_commit_transaction(_0, _1); }); + + return result; +} + +ISC_STATUS swoole_isc_rollback_transaction(ISC_STATUS *_0, isc_tr_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_rollback_transaction"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_rollback_transaction(_0, _1); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_allocate_statement(ISC_STATUS *_0, isc_db_handle *_1, isc_stmt_handle *_2) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_allocate_statement"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_allocate_statement(_0, _1, _2); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_prepare(ISC_STATUS *_0, + isc_tr_handle *_1, + isc_stmt_handle *_2, + unsigned short _3, + const ISC_SCHAR *_4, + unsigned short _5, + XSQLDA *_6) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_prepare"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_prepare(_0, _1, _2, _3, _4, _5, _6); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_fetch(ISC_STATUS *_0, isc_stmt_handle *_1, unsigned short _2, const XSQLDA *_3) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_fetch"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_fetch(_0, _1, _2, _3); }); + + return result; +} + +ISC_STATUS swoole_isc_open_blob( + ISC_STATUS *_0, isc_db_handle *_1, isc_tr_handle *_2, isc_blob_handle *_3, ISC_QUAD *_4) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_open_blob"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_open_blob(_0, _1, _2, _3, _4); }); + + return result; +} + +ISC_STATUS swoole_isc_blob_info( + ISC_STATUS *_0, isc_blob_handle *_1, short _2, const ISC_SCHAR *_3, short _4, ISC_SCHAR *_5) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_blob_info"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_blob_info(_0, _1, _2, _3, _4, _5); }); + + return result; +} + +ISC_STATUS swoole_isc_get_segment( + ISC_STATUS *_0, isc_blob_handle *_1, unsigned short *_2, unsigned short _3, ISC_SCHAR *_4) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_get_segment"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_get_segment(_0, _1, _2, _3, _4); }); + + return result; +} + +ISC_STATUS swoole_isc_put_segment(ISC_STATUS *_0, isc_blob_handle *_1, unsigned short _2, const ISC_SCHAR *_3) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_put_segment"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_put_segment(_0, _1, _2, _3); }); + + return result; +} + +ISC_STATUS swoole_isc_create_blob( + ISC_STATUS *_0, isc_db_handle *_1, isc_tr_handle *_2, isc_blob_handle *_3, ISC_QUAD *_4) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_create_blob"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_create_blob(_0, _1, _2, _3, _4); }); + + return result; +} + +ISC_STATUS swoole_isc_close_blob(ISC_STATUS *_0, isc_blob_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_close_blob"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_close_blob(_0, _1); }); + + return result; +} + +ISC_STATUS swoole_isc_dsql_set_cursor_name(ISC_STATUS *_0, + isc_stmt_handle *_1, + const ISC_SCHAR *_2, + unsigned short _3) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_dsql_set_cursor_name"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_dsql_set_cursor_name(_0, _1, _2, _3); }); + + return result; +} + +ISC_STATUS swoole_fb_ping(ISC_STATUS *_0, isc_db_handle *_1) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "fb_ping"); + + ISC_STATUS result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = fb_ping(_0, _1); }); + + return result; +} + +int swoole_isc_version(isc_db_handle *_0, ISC_VERSION_CALLBACK _1, void *_2) { + swoole_trace_log(SW_TRACE_CO_FIREBIRD, "isc_version"); + + int result; + php_swoole_async(swoole_firebird_blocking, [&]() { result = isc_version(_0, _1, _2); }); + + return result; +} + +void php_swoole_firebird_minit(int module_id) { + if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("FB_ATTR_DATE_FORMAT")) == nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); +#else + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); +#endif + } + + php_pdo_unregister_driver(&swoole_pdo_firebird_driver); + php_pdo_register_driver(&swoole_pdo_firebird_driver); +} + +void php_swoole_firebird_mshutdown() { + php_pdo_unregister_driver(&swoole_pdo_firebird_driver); +} +#endif diff --git a/ext-src/swoole_http2_client_coro.cc b/ext-src/swoole_http2_client_coro.cc index 3199691dfd..27df796477 100644 --- a/ext-src/swoole_http2_client_coro.cc +++ b/ext-src/swoole_http2_client_coro.cc @@ -31,7 +31,7 @@ END_EXTERN_C() #define HTTP2_CLIENT_HOST_HEADER_INDEX 3 using namespace swoole; -using swoole::coroutine::Socket; +using swoole::http2::get_default_setting; namespace Http2 = swoole::http2; @@ -74,35 +74,37 @@ class Client { bool open_ssl; double timeout = network::Socket::default_read_timeout; - Socket *client = nullptr; - - nghttp2_hd_inflater *inflater = nullptr; - nghttp2_hd_deflater *deflater = nullptr; - uint32_t stream_id = 0; // the next send stream id uint32_t last_stream_id = 0; // the last received stream id Http2::Settings local_settings = {}; Http2::Settings remote_settings = {}; + // flow control + uint32_t remote_window_size = 0; + uint32_t local_window_size = 0; + std::unordered_map streams; std::queue send_queue; /* safety zval */ zval _zobject; zval *zobject; + SocketImpl *socket_ = nullptr; + zval zsocket; - Client(const char *_host, size_t _host_len, int _port, bool _ssl, zval *__zobject) { + Client(const char *_host, size_t _host_len, int _port, bool _ssl, const zval *zobj) { host = std::string(_host, _host_len); port = _port; open_ssl = _ssl; - _zobject = *__zobject; + _zobject = *zobj; zobject = &_zobject; Http2::init_settings(&local_settings); + local_window_size = local_settings.init_window_size; } - inline Stream *get_stream(uint32_t stream_id) { - auto i = streams.find(stream_id); + Stream *get_stream(uint32_t _stream_id) { + auto i = streams.find(_stream_id); if (i == streams.end()) { return nullptr; } else { @@ -110,43 +112,36 @@ class Client { } } - ssize_t build_header(zval *zobject, zval *zrequest, char *buffer); + ssize_t build_header(const zval *zobj, zval *zrequest, char *buffer); - inline void update_error_properties(int code, const char *msg) { - zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), code); - zend_update_property_string(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), msg); + void update_error_properties(int code, const char *msg) const { + php_swoole_socket_set_error_properties(zobject, code, msg); } - inline void io_error() { - update_error_properties(client->errCode, client->errMsg); + void io_error() const { + update_error_properties(socket_->errCode, socket_->errMsg); } - inline void nghttp2_error(int code, const char *msg) { + void nghttp2_error(int code, const char *msg) const { update_error_properties(code, std_string::format("%s with error: %s", msg, nghttp2_strerror(code)).c_str()); } - inline bool is_available() { - if (sw_unlikely(!client || !client->is_connected())) { - swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION); - zend_update_property_long( - swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), SW_ERROR_CLIENT_NO_CONNECTION); - zend_update_property_string(swoole_http2_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("errMsg"), - "client is not connected to server"); + bool is_available() const { + if (sw_unlikely(!socket_ || !socket_->is_connected())) { + php_swoole_socket_set_error_properties(zobject, SW_ERROR_CLIENT_NO_CONNECTION); return false; } return true; } - inline void apply_setting(zval *zset) { - if (client && ZVAL_IS_ARRAY(zset)) { - php_swoole_client_set(client, zset); + void apply_setting(const zval *zset) const { + if (socket_ && ZVAL_IS_ARRAY(zset)) { + php_swoole_socket_set(socket_, zset); } } - inline bool recv_packet(double timeout) { - if (sw_unlikely(client->recv_packet(timeout) <= 0)) { + bool recv_packet(double _timeout) const { + if (sw_unlikely(socket_->recv_packet(_timeout) <= 0)) { io_error(); return false; } @@ -154,11 +149,11 @@ class Client { } bool connect(); - Stream *create_stream(uint32_t stream_id, uint8_t flags); + Stream *create_stream(uint32_t _stream_id, uint8_t flags); void destroy_stream(Stream *stream); - inline bool delete_stream(uint32_t stream_id) { - auto i = streams.find(stream_id); + bool delete_stream(uint32_t _stream_id) { + const auto i = streams.find(_stream_id); if (i == streams.end()) { return false; } @@ -169,49 +164,53 @@ class Client { return true; } - bool send_window_update(int stream_id, uint32_t size); + bool send_window_update(int _stream_id, uint32_t size); bool send_ping_frame(); - bool send_data(uint32_t stream_id, const char *p, size_t len, int flag); + bool send_data(uint32_t _stream_id, const char *p, size_t len, int flag); uint32_t send_request(zval *zrequest); - bool write_data(uint32_t stream_id, zval *zdata, bool end); + bool write_data(uint32_t _stream_id, zval *zdata, bool end); bool send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len); ReturnCode parse_frame(zval *return_value, bool pipeline_read = false); - bool close(); + bool close() const; + void socket_dtor(); ~Client() { close(); } private: + nghttp2_hd_inflater *inflater = nullptr; + nghttp2_hd_deflater *deflater = nullptr; + bool send_setting(); - int parse_header(Stream *stream, int flags, char *in, size_t inlen); + int parse_header(Stream *stream, int flags, char *in, size_t inlen) const; void clean_send_queue() { - while (send_queue.size() > 0) { + while (!send_queue.empty()) { zend_string *frame = send_queue.front(); send_queue.pop(); zend_string_release(frame); } } - inline bool send(const char *buf, size_t len) { - if (client->has_bound(SW_EVENT_WRITE)) { + bool send(const char *buf, size_t len) { + if (socket_->has_bound(SW_EVENT_WRITE)) { if (send_queue.size() > remote_settings.max_concurrent_streams) { - client->errCode = SW_ERROR_QUEUE_FULL; - client->errMsg = "the send queue is full, try again later"; + socket_->errCode = SW_ERROR_QUEUE_FULL; + socket_->errMsg = "the send queue is full, try again later"; io_error(); return false; } - send_queue.push(zend_string_init(buf, len, 0)); + send_queue.push(zend_string_init(buf, len, false)); return true; } - if (sw_unlikely(client->send_all(buf, len) != (ssize_t) len)) { + if (sw_unlikely(socket_->send_all(buf, len) != (ssize_t) len)) { io_error(); return false; } - while (send_queue.size() > 0) { + while (!send_queue.empty()) { zend_string *frame = send_queue.front(); - if (sw_unlikely(client->send_all(frame->val, frame->len) != (ssize_t) frame->len)) { + if (sw_unlikely(socket_->send_all(frame->val, frame->len) != (ssize_t) frame->len)) { io_error(); zend_throw_exception(swoole_http2_client_coro_exception_ce, "failed to send control frame", @@ -234,34 +233,30 @@ using swoole::coroutine::http2::Stream; using swoole::http2::HeaderSet; struct Http2ClientObject { - Client *h2c; + Client *client; zend_object std; }; -static sw_inline Http2ClientObject *php_swoole_http2_client_coro_fetch_object(zend_object *obj) { - return (Http2ClientObject *) ((char *) obj - swoole_http2_client_coro_handlers.offset); -} - -static sw_inline Client *php_swoole_get_h2c(zval *zobject) { - return php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c; +static sw_inline Http2ClientObject *http2_client_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - + swoole_http2_client_coro_handlers.offset); } -static sw_inline void php_swoole_set_h2c(zval *zobject, Client *h2c) { - php_swoole_http2_client_coro_fetch_object(Z_OBJ_P(zobject))->h2c = h2c; +static sw_inline Client *http2_client_coro_get_client(const zval *zobject) { + return http2_client_coro_fetch_object(Z_OBJ_P(zobject))->client; } -static void php_swoole_http2_client_coro_free_object(zend_object *object) { - Http2ClientObject *request = php_swoole_http2_client_coro_fetch_object(object); - Client *h2c = request->h2c; - - if (h2c) { - delete h2c; +static void http2_client_coro_free_object(zend_object *object) { + Http2ClientObject *h2o = http2_client_coro_fetch_object(object); + if (h2o->client) { + delete h2o->client; + h2o->client = nullptr; } - zend_object_std_dtor(&request->std); + zend_object_std_dtor(&h2o->std); } -static zend_object *php_swoole_http2_client_coro_create_object(zend_class_entry *ce) { - Http2ClientObject *request = (Http2ClientObject *) zend_object_alloc(sizeof(Http2ClientObject), ce); +static zend_object *http2_client_coro_create_object(zend_class_entry *ce) { + auto *request = static_cast(zend_object_alloc(sizeof(Http2ClientObject), ce)); zend_object_std_init(&request->std, ce); object_properties_init(&request->std, ce); request->std.handlers = &swoole_http2_client_coro_handlers; @@ -311,8 +306,8 @@ void php_swoole_http2_client_coro_minit(int module_number) { SW_SET_CLASS_CLONEABLE(swoole_http2_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http2_client_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_http2_client_coro, - php_swoole_http2_client_coro_create_object, - php_swoole_http2_client_coro_free_object, + http2_client_coro_create_object, + http2_client_coro_free_object, Http2ClientObject, std); @@ -339,10 +334,12 @@ void php_swoole_http2_client_coro_minit(int module_number) { zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); + zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http2_client_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_client_coro_ce, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC); + zend_declare_property_long(swoole_http2_client_coro_ce, ZEND_STRL("serverLastStreamId"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("path"), "/", ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("method"), "GET", ZEND_ACC_PUBLIC); @@ -350,6 +347,7 @@ void php_swoole_http2_client_coro_minit(int module_number) { zend_declare_property_null(swoole_http2_request_ce, ZEND_STRL("cookies"), ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_http2_request_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC); zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("pipeline"), 0, ZEND_ACC_PUBLIC); + zend_declare_property_bool(swoole_http2_request_ce, ZEND_STRL("usePipelineRead"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http2_response_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); @@ -384,40 +382,63 @@ void php_swoole_http2_client_coro_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_CONNECT_ERROR", SW_HTTP2_ERROR_CONNECT_ERROR); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_ENHANCE_YOUR_CALM", SW_HTTP2_ERROR_ENHANCE_YOUR_CALM); SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_INADEQUATE_SECURITY", SW_HTTP2_ERROR_INADEQUATE_SECURITY); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP2_ERROR_HTTP_1_1_REQUIRED", SW_HTTP2_ERROR_HTTP_1_1_REQUIRED); +} + +void Client::socket_dtor() { + socket_ = nullptr; + clean_send_queue(); + auto i = streams.begin(); + while (i != streams.end()) { + destroy_stream(i->second); + streams.erase(i++); + } + if (inflater) { + nghttp2_hd_inflate_del(inflater); + inflater = nullptr; + } + if (deflater) { + nghttp2_hd_deflate_del(deflater); + deflater = nullptr; + } + zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); + zend_update_property_null(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("socket")); + zval_ptr_dtor(&zsocket); + ZVAL_NULL(&zsocket); } bool Client::connect() { - if (sw_unlikely(client != nullptr)) { + if (sw_unlikely(socket_ != nullptr)) { + update_error_properties(EISCONN, strerror(EISCONN)); return false; } - client = new Socket(network::Socket::convert_to_type(host)); - if (UNEXPECTED(client->get_fd() < 0)) { - php_swoole_sys_error(E_WARNING, "new Socket() failed"); - zend_update_property_long(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), errno); - zend_update_property_string( - swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), swoole_strerror(errno)); - delete client; - client = nullptr; + auto object = php_swoole_create_socket(network::Socket::convert_to_type(host)); + if (UNEXPECTED(!object)) { + php_swoole_socket_set_error_properties(zobject, errno, strerror(errno)); return false; } - client->set_zero_copy(true); -#ifdef SW_USE_OPENSSL - if (open_ssl) { - client->enable_ssl_encrypt(); + + ZVAL_OBJ(&zsocket, object); + socket_ = php_swoole_get_socket(&zsocket); + socket_->set_dtor([this](Socket *_socket) { socket_dtor(); }); + socket_->set_zero_copy(true); + if (open_ssl && !socket_->enable_ssl_encrypt()) { + io_error(); + close(); + return false; } -#endif - client->http2 = 1; - client->open_length_check = 1; - client->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; - client->protocol.package_length_offset = 0; - client->protocol.package_body_offset = 0; - client->protocol.get_package_length = Http2::get_frame_length; + socket_->http2 = true; + socket_->open_length_check = true; + socket_->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; + socket_->protocol.package_length_offset = 0; + socket_->protocol.package_body_offset = 0; + socket_->protocol.get_package_length = Http2::get_frame_length; apply_setting( sw_zend_read_property_ex(swoole_http2_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0)); - if (!client->connect(host, port)) { + if (!socket_->connect(host, port)) { io_error(); close(); return false; @@ -426,6 +447,7 @@ bool Client::connect() { stream_id = 1; // [init]: we must set default value, server is not always send all the settings Http2::init_settings(&remote_settings); + remote_window_size = remote_settings.init_window_size; int ret = nghttp2_hd_inflate_new2(&inflater, php_nghttp2_mem()); if (ret != 0) { @@ -450,68 +472,54 @@ bool Client::connect() { return false; } + zend_update_property(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &zsocket); zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1); return true; } -bool Client::close() { - Socket *_client = client; - if (!_client) { +bool Client::close() const { + /* + * The socket_ pointer MUST be staged, + * when client close the member variable may be set to nullptr in socket dtor + */ + SocketImpl *_socket = socket_; + if (_socket == nullptr) { + update_error_properties(EBADF, strerror(EBADF)); return false; } - clean_send_queue(); - zend_update_property_bool(swoole_http2_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); - if (!_client->has_bound()) { - auto i = streams.begin(); - while (i != streams.end()) { - destroy_stream(i->second); - streams.erase(i++); - } - if (inflater) { - nghttp2_hd_inflate_del(inflater); - inflater = nullptr; - } - if (deflater) { - nghttp2_hd_deflate_del(deflater); - deflater = nullptr; - } - client = nullptr; - } - if (_client->close()) { - delete _client; + zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); + if (!_socket->close()) { + update_error_properties(_socket->errCode, _socket->errMsg); + return false; } return true; } ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { - char *buf = client->get_read_buffer()->str; + char *buf = socket_->get_read_buffer()->str; uint8_t type = buf[3]; uint8_t flags = buf[4]; - uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; + uint32_t stream_error = 0; + uint32_t _stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; ssize_t length = Http2::get_length(buf); buf += SW_HTTP2_FRAME_HEADER_SIZE; char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; - if (stream_id > last_stream_id) { - last_stream_id = stream_id; + if (_stream_id > last_stream_id) { + last_stream_id = _stream_id; } - uint16_t id = 0; - uint32_t value = 0; - switch (type) { case SW_HTTP2_TYPE_SETTINGS: { if (flags & SW_HTTP2_FLAG_ACK) { - swoole_http2_frame_trace_log(recv, "ACK"); + swoole_http2_frame_trace_log("ACK"); return SW_CONTINUE; } - while (length > 0) { - id = ntohs(*(uint16_t *) (buf)); - value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t))); - swoole_http2_frame_trace_log(recv, "id=%d, value=%d", id, value); + auto rc = Http2::unpack_setting_data(buf, length, [&](uint16_t id, uint32_t value) -> ReturnCode { + swoole_http2_frame_trace_log("id=%d, value=%d", id, value); switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: if (value != remote_settings.header_table_size) { @@ -529,7 +537,7 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value); break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: - remote_settings.window_size = value; + remote_window_size = remote_settings.init_window_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: init_send_window=%u", value); break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: @@ -539,14 +547,13 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: if (value != remote_settings.max_header_list_size) { remote_settings.max_header_list_size = value; - /* +#if 0 int ret = nghttp2_hd_inflate_change_table_size(inflater, value); - if (ret != 0) - { + if (ret != 0) { nghttp2_error(ret, "nghttp2_hd_inflate_change_table_size() failed"); return SW_ERROR; } - */ +#endif } swoole_trace_log(SW_TRACE_HTTP2, "setting: max_header_list_size=%u", value); break; @@ -555,23 +562,27 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { // swoole_warning("unknown option[%d]: %d", id, value); break; } - buf += sizeof(id) + sizeof(value); - length -= sizeof(id) + sizeof(value); + return SW_SUCCESS; + }); + + if (rc != SW_SUCCESS) { + return rc; } - Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, stream_id); + Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, _stream_id); if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE)) { return SW_ERROR; } return SW_CONTINUE; } case SW_HTTP2_TYPE_WINDOW_UPDATE: { - value = ntohl(*(uint32_t *) buf); - swoole_http2_frame_trace_log(recv, "window_size_increment=%d", value); - if (stream_id == 0) { - remote_settings.window_size += value; + uint32_t value = ntohl(*(uint32_t *) buf); + swoole_trace_log( + SW_TRACE_HTTP2, "[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, value); + if (_stream_id == 0) { + remote_window_size += value; } else { - Stream *stream = get_stream(stream_id); + Stream *stream = get_stream(_stream_id); if (stream) { stream->remote_window_size += value; } @@ -579,10 +590,10 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { return SW_CONTINUE; } case SW_HTTP2_TYPE_PING: { - swoole_http2_frame_trace_log(recv, "ping"); + swoole_http2_frame_trace_log("ping"); if (!(flags & SW_HTTP2_FLAG_ACK)) { Http2::set_frame_header( - frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, stream_id); + frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_ACK, _stream_id); memcpy( frame + SW_HTTP2_FRAME_HEADER_SIZE, buf + SW_HTTP2_FRAME_HEADER_SIZE, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); if (!send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE)) { @@ -594,10 +605,9 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { case SW_HTTP2_TYPE_GOAWAY: { uint32_t server_last_stream_id = ntohl(*(uint32_t *) (buf)); buf += 4; - value = ntohl(*(uint32_t *) (buf)); + uint32_t value = ntohl(*(uint32_t *) (buf)); buf += 4; - swoole_http2_frame_trace_log(recv, - "last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", + swoole_http2_frame_trace_log("last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", server_last_stream_id, value, (int) (length - SW_HTTP2_GOAWAY_SIZE), @@ -613,11 +623,11 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { return SW_CLOSE; } case SW_HTTP2_TYPE_RST_STREAM: { - value = ntohl(*(uint32_t *) (buf)); - swoole_http2_frame_trace_log(recv, "error_code=%d", value); + stream_error = ntohl(*(uint32_t *) (buf)); + swoole_http2_frame_trace_log("error_code=%d", stream_error); // delete and free quietly - delete_stream(stream_id); + delete_stream(_stream_id); return SW_CONTINUE; } @@ -627,7 +637,7 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { case SW_HTTP2_TYPE_PUSH_PROMISE: { #ifdef SW_DEBUG uint32_t promise_stream_id = ntohl(*(uint32_t *) (buf)) & 0x7fffffff; - swoole_http2_frame_trace_log(recv, "promise_stream_id=%d", promise_stream_id); + swoole_http2_frame_trace_log("promise_stream_id=%d", promise_stream_id); #endif // auto promise_stream = create_stream(promise_stream_id, false); // RETVAL_ZVAL(promise_stream->response_object, 0, 0); @@ -635,14 +645,14 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { return SW_CONTINUE; } default: { - swoole_http2_frame_trace_log(recv, ""); + swoole_http2_frame_trace_log(""); } } - Stream *stream = get_stream(stream_id); + Stream *stream = get_stream(_stream_id); // The stream is not found or has closed if (stream == nullptr) { - swoole_notice("http2 stream#%d belongs to an unknown type or it never registered", stream_id); + swoole_notice("http2 stream#%d belongs to an unknown type or it never registered", _stream_id); return SW_CONTINUE; } if (type == SW_HTTP2_TYPE_HEADERS) { @@ -670,19 +680,19 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { // now we control the connection flow only (not stream) // our window size is unlimited, so we don't worry about subtraction overflow - local_settings.window_size -= length; + local_window_size -= length; stream->local_window_size -= length; - if (local_settings.window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) { - if (!send_window_update(0, SW_HTTP2_MAX_WINDOW_SIZE - local_settings.window_size)) { + if (local_window_size < (local_settings.init_window_size / 4)) { + if (!send_window_update(0, local_settings.init_window_size - local_window_size)) { return SW_ERROR; } - local_settings.window_size = SW_HTTP2_MAX_WINDOW_SIZE; + local_window_size = local_settings.init_window_size; } - if (stream->local_window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) { - if (!send_window_update(stream_id, SW_HTTP2_MAX_WINDOW_SIZE - stream->local_window_size)) { + if (stream->local_window_size < (local_settings.init_window_size / 4)) { + if (!send_window_update(_stream_id, local_settings.init_window_size - stream->local_window_size)) { return SW_ERROR; } - stream->local_window_size = SW_HTTP2_MAX_WINDOW_SIZE; + stream->local_window_size = local_settings.init_window_size; } } } @@ -693,11 +703,8 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { if (end || pipeline_read) { zval *zresponse = &stream->zresponse; if (type == SW_HTTP2_TYPE_RST_STREAM) { - zend_update_property_long(swoole_http2_response_ce, - SW_Z8_OBJ_P(zresponse), - ZEND_STRL("statusCode"), - -3 /* HTTP_CLIENT_ESTATUS_SERVER_RESET */); - zend_update_property_long(swoole_http2_response_ce, SW_Z8_OBJ_P(zresponse), ZEND_STRL("errCode"), value); + zend::object_set(zresponse, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); + zend::object_set(zresponse, ZEND_STRL("errCode"), stream_error); } if (stream->buffer && stream->buffer->length > 0) { zend_update_property_stringl(swoole_http2_response_ce, @@ -716,9 +723,9 @@ ReturnCode Client::parse_frame(zval *return_value, bool pipeline_read) { // reinit response object for the following frames object_init_ex(zresponse, swoole_http2_response_ce); zend_update_property_long( - swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), stream_id); + swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), _stream_id); } else { - delete_stream(stream_id); + delete_stream(_stream_id); } return SW_READY; @@ -736,23 +743,31 @@ int php_swoole_zlib_decompress(z_stream *stream, String *buffer, char *body, int stream->total_in = 0; stream->total_out = 0; -#if 0 - printf(SW_START_LINE"\nstatus=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld\n", status, - stream->avail_in, stream->avail_out, stream->total_in, stream->total_out); -#endif + swoole_trace_log(SW_TRACE_ZLIB, + SW_START_LINE "\nstatus=%d\tavail_in=%u,\tavail_out=%u,\ttotal_in=%lu,\ttotal_out=%lu\n", + status, + stream->avail_in, + stream->avail_out, + stream->total_in, + stream->total_out); buffer->clear(); - while (1) { + while (true) { stream->avail_out = buffer->size - buffer->length; stream->next_out = (Bytef *) (buffer->str + buffer->length); status = inflate(stream, Z_SYNC_FLUSH); -#if 0 - printf("status=%d\tavail_in=%ld,\tavail_out=%ld,\ttotal_in=%ld,\ttotal_out=%ld,\tlength=%ld\n", status, - stream->avail_in, stream->avail_out, stream->total_in, stream->total_out, buffer->length); -#endif + swoole_trace_log(SW_TRACE_ZLIB, + "status=%d\tavail_in=%d,\tavail_out=%d,\ttotal_in=%lu,\ttotal_out=%lu,\tlength=%lu\n", + status, + stream->avail_in, + stream->avail_out, + stream->total_in, + stream->total_out, + buffer->length); + if (status >= 0) { buffer->length = stream->total_out; } @@ -760,9 +775,7 @@ int php_swoole_zlib_decompress(z_stream *stream, String *buffer, char *body, int return SW_OK; } else if (status == Z_OK) { if (buffer->length + 4096 >= buffer->size) { - if (!buffer->extend()) { - return SW_ERR; - } + buffer->extend(); } if (stream->avail_in == 0) { return SW_OK; @@ -793,19 +806,8 @@ static PHP_METHOD(swoole_http2_client_coro, __construct) { RETURN_FALSE; } - Client *h2c = new Client(host, host_len, port, ssl, ZEND_THIS); - if (ssl) { -#ifndef SW_USE_OPENSSL - zend_throw_exception_ex( - swoole_http2_client_coro_exception_ce, - EPROTONOSUPPORT, - "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"); - delete h2c; - RETURN_FALSE; -#endif - } - - php_swoole_set_h2c(ZEND_THIS, h2c); + auto *client = new Client(host, host_len, port, ssl, ZEND_THIS); + http2_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS))->client = client; zend_update_property_stringl( swoole_http2_client_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("host"), host, host_len); @@ -814,7 +816,7 @@ static PHP_METHOD(swoole_http2_client_coro, __construct) { } static PHP_METHOD(swoole_http2_client_coro, set) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -833,11 +835,11 @@ static PHP_METHOD(swoole_http2_client_coro, set) { /** * called in read channel */ -bool Client::send_window_update(int stream_id, uint32_t size) { +bool Client::send_window_update(int _stream_id, uint32_t size) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE]; - swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, size); + swoole_http2_send_trace_log("[" SW_ECHO_YELLOW "] stream_id=%d, size=%d", "WINDOW_UPDATE", stream_id, size); *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(size); - Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, stream_id); + Http2::set_frame_header(frame, SW_HTTP2_TYPE_WINDOW_UPDATE, SW_HTTP2_WINDOW_UPDATE_SIZE, 0, _stream_id); return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE); } @@ -845,50 +847,24 @@ bool Client::send_window_update(int stream_id, uint32_t size) { * called on connect */ bool Client::send_setting() { - Http2::Settings *settings = &local_settings; - uint16_t id = 0; - uint32_t value = 0; - - char frame[SW_HTTP2_FRAME_HEADER_SIZE + 18]; - memset(frame, 0, sizeof(frame)); - Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 18, 0, 0); - - char *p = frame + SW_HTTP2_FRAME_HEADER_SIZE; - /** - * HEADER_TABLE_SIZE - */ - id = htons(SW_HTTP2_SETTING_HEADER_TABLE_SIZE); - memcpy(p, &id, sizeof(id)); - p += 2; - value = htonl(settings->header_table_size); - memcpy(p, &value, sizeof(value)); - p += 4; - /** - * MAX_CONCURRENT_STREAMS - */ - id = htons(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - memcpy(p, &id, sizeof(id)); - p += 2; - value = htonl(settings->max_concurrent_streams); - memcpy(p, &value, sizeof(value)); - p += 4; - /** - * INIT_WINDOW_SIZE - */ - id = htons(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE); - memcpy(p, &id, sizeof(id)); - p += 2; - value = htonl(settings->window_size); - memcpy(p, &value, sizeof(value)); - p += 4; - - swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN "]\t[length=%d]", Http2::get_type(SW_HTTP2_TYPE_SETTINGS), 18); - return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + 18); + char frame[SW_HTTP2_SETTING_FRAME_SIZE]; + size_t n = Http2::pack_setting_frame(frame, local_settings, false); + swoole_http2_send_trace_log("[" SW_ECHO_MAGENTA + "] ", + Http2::get_type(SW_HTTP2_TYPE_SETTINGS), + local_settings.header_table_size, + local_settings.enable_push, + local_settings.max_concurrent_streams, + local_settings.init_window_size, + local_settings.max_frame_size, + local_settings.max_header_list_size); + return send(frame, n); } -void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers); +void php_swoole_http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers); -int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) { +int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) const { zval *zresponse = &stream->zresponse; if (flags & SW_HTTP2_FLAG_PRIORITY) { @@ -906,12 +882,11 @@ int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) { swoole_http2_response_ce, zresponse, ZEND_STRL("set_cookie_headers"), 0); int inflate_flags = 0; - ssize_t rv; do { nghttp2_nv nv; - rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); + ssize_t rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); if (rv < 0) { nghttp2_error(rv, "nghttp2_hd_inflate_hd failed"); return SW_ERR; @@ -933,15 +908,12 @@ int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) { if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { if (nv.name[0] == ':') { if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "status")) { - zend_update_property_long(swoole_http2_response_ce, - SW_Z8_OBJ_P(zresponse), - ZEND_STRL("statusCode"), - atoi((char *) nv.value)); + zend::object_set(zresponse, ZEND_STRL("statusCode"), sw_atol((char *) nv.value)); } } else { #ifdef SW_HAVE_ZLIB if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-encoding") && - SW_STRCASECT((char *) nv.value, nv.valuelen, "gzip")) { + SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "gzip")) { /** * init zlib stream */ @@ -960,7 +932,7 @@ int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) { } else #endif if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "set-cookie")) { - http_parse_set_cookies((char *) nv.value, nv.valuelen, zcookies, zset_cookie_headers); + php_swoole_http_parse_set_cookies((char *) nv.value, nv.valuelen, zcookies, zset_cookie_headers); } add_assoc_stringl_ex(zheaders, (char *) nv.name, nv.namelen, (char *) nv.value, nv.valuelen); } @@ -976,14 +948,14 @@ int Client::parse_header(Stream *stream, int flags, char *in, size_t inlen) { return SW_OK; } -ssize_t Client::build_header(zval *zobject, zval *zrequest, char *buffer) { - Client *h2c = php_swoole_get_h2c(zobject); +ssize_t Client::build_header(const zval *zobj, zval *zrequest, char *buffer) { + Client *h2c = http2_client_coro_get_client(zobj); zval *zmethod = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_METHOD), 0); zval *zpath = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PATH), 0); zval *zheaders = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADERS), 0); zval *zcookies = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIES), 0); HeaderSet headers(8 + php_swoole_array_length_safe(zheaders) + php_swoole_array_length_safe(zcookies)); - bool find_host = 0; + bool find_host = false; if (Z_TYPE_P(zmethod) != IS_STRING || Z_STRLEN_P(zmethod) == 0) { headers.add(ZEND_STRL(":method"), ZEND_STRL("GET")); @@ -1022,53 +994,50 @@ ssize_t Client::build_header(zval *zobject, zval *zrequest, char *buffer) { ZEND_HASH_FOREACH_END(); } if (!find_host) { - const std::string *host; + const std::string *host_ptr; std::string _host; -#ifndef SW_USE_OPENSSL - if (h2c->port != 80) -#else - if (!h2c->open_ssl ? h2c->port != 80 : h2c->port != 443) -#endif - { + if (!h2c->open_ssl ? h2c->port != 80 : h2c->port != 443) { _host = std_string::format("%s:%d", h2c->host.c_str(), h2c->port); - host = &_host; + host_ptr = &_host; } else { - host = &h2c->host; + host_ptr = &h2c->host; } - headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), host->c_str(), host->length()); + headers.add(HTTP2_CLIENT_HOST_HEADER_INDEX, ZEND_STRL(":authority"), host_ptr->c_str(), host_ptr->length()); } // http cookies if (ZVAL_IS_ARRAY(zcookies)) { zend_string *key; zval *zvalue; - char *encoded_value; - int encoded_value_len; - String *buffer = sw_tg_buffer(); + size_t encoded_value_len; + String *header_buffer = sw_tg_buffer(); ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zcookies), key, zvalue) { if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { continue; } zend::String str_value(zvalue); - buffer->clear(); - buffer->append(ZSTR_VAL(key), ZSTR_LEN(key)); - buffer->append("=", 1); - encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len); + header_buffer->clear(); + header_buffer->append(ZSTR_VAL(key), ZSTR_LEN(key)); + header_buffer->append("=", 1); + char *encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len); if (encoded_value) { - buffer->append(encoded_value, encoded_value_len); + header_buffer->append(encoded_value, encoded_value_len); efree(encoded_value); - headers.add(ZEND_STRL("cookie"), buffer->str, buffer->length); + headers.add(ZEND_STRL("cookie"), header_buffer->str, header_buffer->length); } } ZEND_HASH_FOREACH_END(); } size_t buflen = nghttp2_hd_deflate_bound(h2c->deflater, headers.get(), headers.len()); - // if (buflen > h2c->remote_settings.max_header_list_size) { - // php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", - // h2c->remote_settings.max_header_list_size); - // return -1; - // } +#if 0 + if (buflen > h2c->remote_settings.max_header_list_size) { + php_swoole_error(E_WARNING, + "header cannot bigger than remote max_header_list_size %u", + client->remote_settings.max_header_list_size); + return -1; + } +#endif ssize_t rv = nghttp2_hd_deflate_hd(h2c->deflater, (uchar *) buffer, buflen, headers.get(), headers.len()); if (rv < 0) { h2c->nghttp2_error(rv, "nghttp2_hd_deflate_hd() failed"); @@ -1078,32 +1047,30 @@ ssize_t Client::build_header(zval *zobject, zval *zrequest, char *buffer) { } void Client::destroy_stream(Stream *stream) { - if (stream->buffer) { - delete (stream->buffer); - } + delete (stream->buffer); #ifdef SW_HAVE_ZLIB if (stream->gzip) { inflateEnd(&stream->gzip_stream); - delete (stream->gzip_buffer); } + delete (stream->gzip_buffer); #endif zval_ptr_dtor(&stream->zresponse); efree(stream); } -Stream *Client::create_stream(uint32_t stream_id, uint8_t flags) { +Stream *Client::create_stream(uint32_t _stream_id, uint8_t flags) { // malloc - Stream *stream = (Stream *) ecalloc(1, sizeof(Stream)); + auto *stream = static_cast(ecalloc(1, sizeof(Stream))); // init - stream->stream_id = stream_id; + stream->stream_id = _stream_id; stream->flags = flags; - stream->remote_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE; - stream->local_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE; - streams.emplace(stream_id, stream); + stream->remote_window_size = remote_settings.init_window_size; + stream->local_window_size = local_settings.init_window_size; + streams.emplace(_stream_id, stream); // create response object object_init_ex(&stream->zresponse, swoole_http2_response_ce); zend_update_property_long( - swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), stream_id); + swoole_http2_response_ce, SW_Z8_OBJ_P(&stream->zresponse), ZEND_STRL("streamId"), _stream_id); return stream; } @@ -1114,10 +1081,11 @@ Stream *Client::create_stream(uint32_t stream_id, uint8_t flags) { bool Client::send_ping_frame() { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0); + swoole_http2_send_trace_log("[" SW_ECHO_CYAN "]", "PING"); return send(frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); } -bool Client::send_data(uint32_t stream_id, const char *p, size_t len, int flag) { +bool Client::send_data(uint32_t _stream_id, const char *p, size_t len, int flag) { uint8_t send_flag; uint32_t send_len; char header[SW_HTTP2_FRAME_HEADER_SIZE]; @@ -1129,7 +1097,7 @@ bool Client::send_data(uint32_t stream_id, const char *p, size_t len, int flag) send_len = len; send_flag = flag; } - Http2::set_frame_header(header, SW_HTTP2_TYPE_DATA, send_len, send_flag, stream_id); + Http2::set_frame_header(header, SW_HTTP2_TYPE_DATA, send_len, send_flag, _stream_id); if (!send(header, SW_HTTP2_FRAME_HEADER_SIZE)) { return false; } @@ -1151,8 +1119,9 @@ uint32_t Client::send_request(zval *zrequest) { zval *zdata = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 0); zval *zpipeline = sw_zend_read_property_ex(swoole_http2_request_ce, zrequest, SW_ZSTR_KNOWN(SW_ZEND_STR_PIPELINE), 0); - zval ztmp, *zuse_pipeline_read = zend_read_property_ex( - Z_OBJCE_P(zrequest), SW_Z8_OBJ_P(zrequest), SW_ZSTR_KNOWN(SW_ZEND_STR_USE_PIPELINE_READ), 1, &ztmp); + zval ztmp, + *zuse_pipeline_read = zend_read_property_ex( + Z_OBJCE_P(zrequest), SW_Z8_OBJ_P(zrequest), SW_ZSTR_KNOWN(SW_ZEND_STR_USE_PIPELINE_READ), true, &ztmp); bool is_data_empty = Z_TYPE_P(zdata) == IS_STRING ? Z_STRLEN_P(zdata) == 0 : !zval_is_true(zdata); if (ZVAL_IS_ARRAY(zdata)) { @@ -1188,11 +1157,8 @@ uint32_t Client::send_request(zval *zrequest) { Http2::set_frame_header(buffer, SW_HTTP2_TYPE_HEADERS, bytes, flags, stream->stream_id); - swoole_trace_log(SW_TRACE_HTTP2, - "[" SW_ECHO_GREEN ", STREAM#%d] length=%zd", - Http2::get_type(SW_HTTP2_TYPE_HEADERS), - stream->stream_id, - bytes); + swoole_http2_send_trace_log( + "[" SW_ECHO_GREEN ", STREAM#%d] length=%zd", Http2::get_type(SW_HTTP2_TYPE_HEADERS), stream->stream_id, bytes); if (!send(buffer, SW_HTTP2_FRAME_HEADER_SIZE + bytes)) { return 0; } @@ -1219,11 +1185,10 @@ uint32_t Client::send_request(zval *zrequest) { len = str_zpost_data.len(); } - swoole_trace_log(SW_TRACE_HTTP2, - "[" SW_ECHO_GREEN ", END, STREAM#%d] length=%zu", - Http2::get_type(SW_HTTP2_TYPE_DATA), - stream->stream_id, - len); + swoole_http2_send_trace_log("[" SW_ECHO_GREEN ", END, STREAM#%d] length=%zu", + Http2::get_type(SW_HTTP2_TYPE_DATA), + stream->stream_id, + len); if (!send_data(stream->stream_id, p, len, flag)) { return 0; @@ -1242,15 +1207,15 @@ uint32_t Client::send_request(zval *zrequest) { /** * called in write channel */ -bool Client::write_data(uint32_t stream_id, zval *zdata, bool end) { +bool Client::write_data(uint32_t _stream_id, zval *zdata, bool end) { char buffer[SW_HTTP2_FRAME_HEADER_SIZE]; - Stream *stream = get_stream(stream_id); + Stream *stream = get_stream(_stream_id); int flag = end ? SW_HTTP2_FLAG_END_STREAM : 0; if (stream == nullptr || !(stream->flags & SW_HTTP2_STREAM_PIPELINE_REQUEST) || (stream->flags & SW_HTTP2_STREAM_REQUEST_END)) { update_error_properties(EINVAL, - std_string::format("unable to found active pipeline stream#%u", stream_id).c_str()); + std_string::format("unable to found active pipeline stream#%u", _stream_id).c_str()); return false; } @@ -1262,7 +1227,7 @@ bool Client::write_data(uint32_t stream_id, zval *zdata, bool end) { php_swoole_error(E_WARNING, "http_build_query failed"); return false; } - Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, len, flag, stream_id); + Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, len, flag, _stream_id); swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", Http2::get_type(SW_HTTP2_TYPE_DATA), @@ -1276,7 +1241,7 @@ bool Client::write_data(uint32_t stream_id, zval *zdata, bool end) { smart_str_free(&formstr_s); } else { zend::String data(zdata); - Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, data.len(), flag, stream_id); + Http2::set_frame_header(buffer, SW_HTTP2_TYPE_DATA, data.len(), flag, _stream_id); swoole_trace_log(SW_TRACE_HTTP2, "[" SW_ECHO_GREEN ",%s STREAM#%d] length=%zu", Http2::get_type(SW_HTTP2_TYPE_DATA), @@ -1300,26 +1265,24 @@ bool Client::write_data(uint32_t stream_id, zval *zdata, bool end) { */ bool Client::send_goaway_frame(zend_long error_code, const char *debug_data, size_t debug_data_len) { size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len; - char *frame = (char *) ecalloc(1, length); - bool ret; + auto frame = (char *) ecalloc(1, length); Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE + 4) = htonl(error_code); if (debug_data_len > 0) { memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len); } - swoole_trace_log(SW_TRACE_HTTP2, - "[" SW_ECHO_GREEN "] Send: last-sid=%u, error-code=" ZEND_LONG_FMT, - Http2::get_type(SW_HTTP2_TYPE_GOAWAY), - last_stream_id, - error_code); - ret = send(frame, length); + swoole_http2_send_trace_log("[" SW_ECHO_RED "] last-sid=%u, error-code=" ZEND_LONG_FMT, + Http2::get_type(SW_HTTP2_TYPE_GOAWAY), + last_stream_id, + error_code); + bool ret = send(frame, length); efree(frame); return ret; } static PHP_METHOD(swoole_http2_client_coro, send) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; @@ -1331,6 +1294,8 @@ static PHP_METHOD(swoole_http2_client_coro, send) { Z_PARAM_OBJECT_OF_CLASS(zrequest, swoole_http2_request_ce) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); + uint32_t stream_id = h2c->send_request(zrequest); if (stream_id == 0) { RETURN_FALSE; @@ -1339,9 +1304,8 @@ static PHP_METHOD(swoole_http2_client_coro, send) { } } -static void php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool pipeline_read) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); - +static void http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool pipeline_read) { + Client *h2c = http2_client_coro_get_client(ZEND_THIS); double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -1349,6 +1313,8 @@ static void php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + SW_CLIENT_PRESERVE_SOCKET(ZEND_THIS); + while (true) { if (!h2c->is_available()) { RETURN_FALSE; @@ -1368,47 +1334,51 @@ static void php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAMETERS, bool } static PHP_METHOD(swoole_http2_client_coro, recv) { - php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); + http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_http2_client_coro, __destruct) {} static PHP_METHOD(swoole_http2_client_coro, close) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); + SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); RETURN_BOOL(h2c->close()); } static PHP_METHOD(swoole_http2_client_coro, connect) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); RETURN_BOOL(h2c->connect()); } -static sw_inline void http2_settings_to_array(Http2::Settings *settings, zval *zarray) { +static sw_inline void http2_client_settings_to_array(const Http2::Settings *settings, zval *zarray) { array_init(zarray); add_assoc_long_ex(zarray, ZEND_STRL("header_table_size"), settings->header_table_size); - add_assoc_long_ex(zarray, ZEND_STRL("window_size"), settings->window_size); + add_assoc_long_ex(zarray, ZEND_STRL("init_window_size"), settings->init_window_size); add_assoc_long_ex(zarray, ZEND_STRL("max_concurrent_streams"), settings->max_concurrent_streams); add_assoc_long_ex(zarray, ZEND_STRL("max_frame_size"), settings->max_frame_size); add_assoc_long_ex(zarray, ZEND_STRL("max_header_list_size"), settings->max_header_list_size); } static PHP_METHOD(swoole_http2_client_coro, stats) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); zval _zarray, *zarray = &_zarray; String key = {}; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &key.str, &key.length) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(key.str, key.length); + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + if (key.length > 0) { if (SW_STREQ(key.str, key.length, "current_stream_id")) { RETURN_LONG(h2c->stream_id); } else if (SW_STREQ(key.str, key.length, "last_stream_id")) { RETURN_LONG(h2c->last_stream_id); } else if (SW_STREQ(key.str, key.length, "local_settings")) { - http2_settings_to_array(&h2c->local_settings, zarray); + http2_client_settings_to_array(&h2c->local_settings, zarray); RETURN_ZVAL(zarray, 0, 0); } else if (SW_STREQ(key.str, key.length, "remote_settings")) { - http2_settings_to_array(&h2c->remote_settings, zarray); + http2_client_settings_to_array(&h2c->remote_settings, zarray); RETURN_ZVAL(zarray, 0, 0); } else if (SW_STREQ(key.str, key.length, "active_stream_num")) { RETURN_LONG(h2c->streams.size()); @@ -1417,9 +1387,9 @@ static PHP_METHOD(swoole_http2_client_coro, stats) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("current_stream_id"), h2c->stream_id); add_assoc_long_ex(return_value, ZEND_STRL("last_stream_id"), h2c->last_stream_id); - http2_settings_to_array(&h2c->local_settings, zarray); + http2_client_settings_to_array(&h2c->local_settings, zarray); add_assoc_zval_ex(return_value, ZEND_STRL("local_settings"), zarray); - http2_settings_to_array(&h2c->remote_settings, zarray); + http2_client_settings_to_array(&h2c->remote_settings, zarray); add_assoc_zval_ex(return_value, ZEND_STRL("remote_settings"), zarray); add_assoc_long_ex(return_value, ZEND_STRL("active_stream_num"), h2c->streams.size()); } @@ -1427,15 +1397,17 @@ static PHP_METHOD(swoole_http2_client_coro, stats) { static PHP_METHOD(swoole_http2_client_coro, isStreamExist) { zend_long stream_id = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &stream_id) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(stream_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + if (stream_id < 0) { RETURN_FALSE; } - Client *h2c = php_swoole_get_h2c(ZEND_THIS); - if (!h2c->client) { + Client *h2c = http2_client_coro_get_client(ZEND_THIS); + if (!h2c->socket_) { RETURN_FALSE; } else if (stream_id == 0) { RETURN_TRUE; @@ -1445,7 +1417,7 @@ static PHP_METHOD(swoole_http2_client_coro, isStreamExist) { } static PHP_METHOD(swoole_http2_client_coro, write) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; @@ -1453,19 +1425,26 @@ static PHP_METHOD(swoole_http2_client_coro, write) { zend_long stream_id; zval *data; - zend_bool end = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz|b", &stream_id, &data, &end) == FAILURE) { - RETURN_FALSE; - } + zend_bool end = false; + + ZEND_PARSE_PARAMETERS_START(2, 3) + Z_PARAM_LONG(stream_id); + Z_PARAM_ZVAL(data); + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(end); + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); + RETURN_BOOL(h2c->write_data(stream_id, data, end)); } static PHP_METHOD(swoole_http2_client_coro, read) { - php_swoole_http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); + http2_client_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_http2_client_coro, ping) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); if (!h2c->is_available()) { RETURN_FALSE; @@ -1484,7 +1463,7 @@ static PHP_METHOD(swoole_http2_client_coro, ping) { * +---------------------------------------------------------------+ */ static PHP_METHOD(swoole_http2_client_coro, goaway) { - Client *h2c = php_swoole_get_h2c(ZEND_THIS); + Client *h2c = http2_client_coro_get_client(ZEND_THIS); zend_long error_code = SW_HTTP2_ERROR_NO_ERROR; char *debug_data = nullptr; size_t debug_data_len = 0; @@ -1493,9 +1472,12 @@ static PHP_METHOD(swoole_http2_client_coro, goaway) { RETURN_FALSE; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ls", &error_code, &debug_data, &debug_data_len) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(error_code); + Z_PARAM_STRING(debug_data, debug_data_len); + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + SW_CLIENT_PRESERVE_SOCKET(&h2c->zsocket); RETURN_BOOL(h2c->send_goaway_frame(error_code, debug_data, debug_data_len)); } diff --git a/ext-src/swoole_http2_server.cc b/ext-src/swoole_http2_server.cc index 8cca4cbf01..6c07e55b5c 100644 --- a/ext-src/swoole_http2_server.cc +++ b/ext-src/swoole_http2_server.cc @@ -16,6 +16,9 @@ #include "php_swoole_http_server.h" +#include +#include + #include "swoole_static_handler.h" #include "main/php_variables.h" @@ -23,6 +26,7 @@ using namespace swoole; using std::string; using swoole::coroutine::System; +using swoole::http2::get_default_setting; using swoole::http_server::StaticHandler; namespace Http2 = swoole::http2; @@ -31,61 +35,94 @@ using HttpContext = swoole::http::Context; using Http2Stream = Http2::Stream; using Http2Session = Http2::Session; -static std::unordered_map http2_sessions; +static SW_THREAD_LOCAL std::unordered_map> http2_sessions; +static SW_THREAD_LOCAL std::unordered_map server_ips; +static SW_THREAD_LOCAL std::unordered_map client_ips; static bool http2_server_respond(HttpContext *ctx, const String *body); +static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler); +static bool http2_server_send_status_code(HttpContext *ctx, int status_code); -Http2Stream::Stream(Http2Session *client, uint32_t _id) { +Http2Stream::Stream(const Http2Session *client, uint32_t _id) { ctx = swoole_http_context_new(client->fd); ctx->copy(client->default_ctx); ctx->http2 = true; - ctx->stream = this; + ctx->stream_id = _id; ctx->keepalive = true; id = _id; - local_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE; - remote_window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE; + local_window_size = client->local_settings.init_window_size; + remote_window_size = client->remote_settings.init_window_size; } Http2Stream::~Stream() { - ctx->stream = nullptr; + ctx->stream_id = 0; ctx->end_ = true; ctx->free(); } -void Http2Stream::reset(uint32_t error_code) { +void Http2Stream::reset(uint32_t error_code) const { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE]; swoole_trace_log( SW_TRACE_HTTP2, "send [" SW_ECHO_YELLOW "] stream_id=%u, error_code=%u", "RST_STREAM", id, error_code); *(uint32_t *) ((char *) frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code); - http2::set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, SW_HTTP2_RST_STREAM_SIZE, 0, id); + set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, SW_HTTP2_RST_STREAM_SIZE, 0, id); ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_RST_STREAM_SIZE); } Http2Session::Session(SessionId _fd) { fd = _fd; - Http2::init_settings(&local_settings); + init_settings(&local_settings); // [init]: we must set default value, peer is not always send all the settings - Http2::init_settings(&remote_settings); + init_settings(&remote_settings); + local_window_size = local_settings.init_window_size; + remote_window_size = remote_settings.init_window_size; last_stream_id = 0; shutting_down = false; is_coro = false; - http2_sessions[_fd] = this; + max_body_size = 0; } -Http2Session::~Session() { - for (auto iter = streams.begin(); iter != streams.end(); iter++) { - delete iter->second; +std::shared_ptr Http2Session::get_stream(uint32_t stream_id) { + auto iter = streams.find(stream_id); + if (iter == streams.end()) { + return {}; + } else { + return iter->second; + } +} + +bool Http2Session::remove_stream(uint32_t stream_id) { + auto iter = streams.find(stream_id); + if (iter == streams.end()) { + return false; + } + + auto stream = iter->second; + streams.erase(iter); + return true; +} + +std::shared_ptr Http2Session::create_stream(uint32_t stream_id) { + auto stream = std::make_shared(this, stream_id); + streams.emplace(stream_id, stream); + if (sw_unlikely(!stream->ctx)) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 create stream#%d context error", stream_id); + return {}; } + auto ctx = stream->ctx; + zend::object_set(ctx->request.zobject, ZEND_STRL("streamId"), stream_id); + return stream; +} + +Http2Session::~Session() { if (inflater) { nghttp2_hd_inflate_del(inflater); } if (deflater) { nghttp2_hd_deflate_del(deflater); } - if (default_ctx) { - delete default_ctx; - } - http2_sessions.erase(fd); + delete default_ctx; } static void http2_server_send_window_update(HttpContext *ctx, uint32_t stream_id, uint32_t size) { @@ -97,7 +134,7 @@ static void http2_server_send_window_update(HttpContext *ctx, uint32_t stream_id ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_WINDOW_UPDATE_SIZE); } -static ssize_t http2_server_build_trailer(HttpContext *ctx, uchar *buffer) { +static ssize_t http2_server_build_trailer(const HttpContext *ctx, uchar *buffer) { zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); uint32_t size = php_swoole_array_length_safe(ztrailer); @@ -117,9 +154,8 @@ static ssize_t http2_server_build_trailer(HttpContext *ctx, uchar *buffer) { ZEND_HASH_FOREACH_END(); ssize_t rv; - size_t buflen; - Http2Session *client = http2_sessions[ctx->fd]; - nghttp2_hd_deflater *deflater = client->deflater; + auto client = http2_sessions[ctx->fd]; + auto deflater = client->deflater; if (!deflater) { int ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem()); @@ -130,14 +166,15 @@ static ssize_t http2_server_build_trailer(HttpContext *ctx, uchar *buffer) { client->deflater = deflater; } - buflen = nghttp2_hd_deflate_bound(deflater, trailer.get(), trailer.len()); - /* - if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE) - { - php_swoole_error(E_WARNING, "header cannot bigger than remote max_header_list_size %u", - SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); return -1; + size_t buflen = nghttp2_hd_deflate_bound(deflater, trailer.get(), trailer.len()); +#if 0 + if (buflen > SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE) { + php_swoole_error(E_WARNING, + "header cannot bigger than remote max_header_list_size %u", + SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE); + return -1; } - */ +#endif rv = nghttp2_hd_deflate_hd(deflater, (uchar *) buffer, buflen, trailer.get(), trailer.len()); if (rv < 0) { swoole_warning("nghttp2_hd_deflate_hd() failed with error: %s", nghttp2_strerror((int) rv)); @@ -153,7 +190,7 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) { zval *zrequest_uri = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_uri")); if (zrequest_uri && Z_TYPE_P(zrequest_uri) == IS_STRING) { StaticHandler handler(serv, Z_STRVAL_P(zrequest_uri), Z_STRLEN_P(zrequest_uri)); - if (!handler.hit()) { + if (!handler.try_serve()) { return false; } @@ -168,7 +205,7 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) { * 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()) { + if (!handler.try_serve_index_file()) { return false; } @@ -186,8 +223,8 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) { auto date_str = handler.get_date(); auto date_str_last_modified = handler.get_date_last_modified(); - zval *zheader = ctx->request.zserver; - ctx->set_header(ZEND_STRL("Last-Modified"), date_str.c_str(), date_str.length(), 0); + zval *zheader = ctx->request.zheader; + ctx->set_header(ZEND_STRL("Last-Modified"), date_str_last_modified, false); zval *zdate_if_modified_since = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-modified-since")); if (zdate_if_modified_since) { @@ -198,49 +235,70 @@ static bool http2_server_is_static_file(Server *serv, HttpContext *ctx) { } } - zend::String _filename(handler.get_filename_std_string()); - zval zfilename; - ZVAL_STR(&zfilename, _filename.get()); - zval retval; /* do not care the retval (the connection will be closed if failed) */ + zval *zrange = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("range")); + zval *zif_range = zend_hash_str_find(Z_ARR_P(zheader), ZEND_STRL("if-range")); + handler.parse_range(zrange ? Z_STRVAL_P(zrange) : nullptr, zif_range ? Z_STRVAL_P(zif_range) : nullptr); + ctx->response.status = handler.status_code; + auto tasks = handler.get_tasks(); + if (1 == tasks.size()) { + if (SW_HTTP_PARTIAL_CONTENT == handler.status_code) { + std::stringstream content_range; + content_range << "bytes " << tasks[0].offset << "-" << (tasks[0].length + tasks[0].offset - 1) << "/" + << handler.get_filesize() << "\r\n"; + auto content_range_str = content_range.str(); + ctx->set_header(ZEND_STRL("Content-Range"), content_range_str, false); + } else { + ctx->set_header(ZEND_STRL("Accept-Ranges"), SW_STRL("bytes"), false); + } + } + ctx->onAfterResponse = nullptr; ctx->onBeforeRequest = nullptr; - sw_zend_call_method_with_1_params( - ctx->response.zobject, swoole_http_response_ce, nullptr, "sendfile", &retval, &zfilename); - return true; + // request_method + zval *zrequest_method = zend_hash_str_find(Z_ARR_P(zserver), ZEND_STRL("request_method")); + if (zrequest_method && Z_TYPE_P(zrequest_method) == IS_STRING && + SW_STRCASEEQ(Z_STRVAL_P(zrequest_method), Z_STRLEN_P(zrequest_method), "HEAD")) { + String empty_body; + http2_server_respond(ctx, &empty_body); + return true; + } else { + return http2_server_send_range_file(ctx, &handler); + } } return false; } -static void http2_server_onRequest(Http2Session *client, Http2Stream *stream) { +static void http2_server_onRequest(const std::shared_ptr &client, + const std::shared_ptr &stream) { HttpContext *ctx = stream->ctx; - zval *zserver = ctx->request.zserver; - Server *serv = (Server *) ctx->private_data; + auto serv = ctx->get_async_server(); zval args[2]; - zend_fcall_info_cache *fci_cache = nullptr; + zend::Callable *cb = nullptr; + Connection *conn = serv->get_connection_by_session_id(ctx->fd); - int server_fd = conn->server_fd; - Connection *serv_sock = serv->get_connection(server_fd); + if (!conn) { + goto _destroy; + } ctx->request.version = SW_HTTP_VERSION_2; - if (serv->enable_static_handler && http2_server_is_static_file(serv, ctx)) { goto _destroy; } - add_assoc_long(zserver, "request_time", time(nullptr)); - add_assoc_double(zserver, "request_time_float", microtime()); - if (serv_sock) { - add_assoc_long(zserver, "server_port", serv_sock->info.get_port()); - } - add_assoc_long(zserver, "remote_port", conn->info.get_port()); - add_assoc_string(zserver, "remote_addr", (char *) conn->info.get_ip()); - add_assoc_long(zserver, "master_time", conn->last_recv_time); - add_assoc_string(zserver, "server_protocol", (char *) "HTTP/2"); + do { + zval *zserver = ctx->request.zserver; + HashTable *ht = Z_ARR_P(zserver); + swoole_http_server_populate_ip_and_port(serv, ht, conn, client->fd, ctx->keepalive); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_MASTER_TIME), (zend_long) conn->last_recv_time); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP2)); + } while (false); - fci_cache = php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onRequest); - ctx->private_data_2 = fci_cache; + cb = php_swoole_server_get_callback(serv, conn->server_fd, SW_SERVER_CB_onRequest); + ctx->private_data_2 = cb; if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) { return; @@ -248,7 +306,7 @@ static void http2_server_onRequest(Http2Session *client, Http2Stream *stream) { args[0] = *ctx->request.zobject; args[1] = *ctx->response.zobject; - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { stream->reset(SW_HTTP2_ERROR_INTERNAL_ERROR); php_swoole_error(E_WARNING, "%s->onRequest[v2] handler error", ZSTR_VAL(swoole_http_server_ce->name)); } @@ -258,24 +316,6 @@ static void http2_server_onRequest(Http2Session *client, Http2Stream *stream) { zval_ptr_dtor(ctx->response.zobject); } -static void http2_server_set_date_header(Http2::HeaderSet *headers) { - static struct { - time_t time; - size_t len; - char buf[64]; - } cache{}; - - 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); - cache.time = now; - efree(date_str); - } - headers->add(ZEND_STRL("date"), cache.buf, cache.len); -} - static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const String *body) { zval *zheader = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER), 0); @@ -283,7 +323,6 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE), 0); Http2::HeaderSet headers(32 + php_swoole_array_length_safe(zheader) + php_swoole_array_length_safe(zcookie)); char intbuf[2][16]; - int ret; assert(ctx->send_header_ == 0); @@ -291,7 +330,7 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const if (ctx->response.status == 0) { ctx->response.status = SW_HTTP_OK; } - ret = swoole_itoa(intbuf[0], ctx->response.status); + int ret = swoole_itoa(intbuf[0], ctx->response.status); headers.add(ZEND_STRL(":status"), intbuf[0], ret); uint32_t header_flags = 0x0; @@ -323,9 +362,11 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const header_flags |= HTTP_HEADER_DATE; } else if (SW_STRCASEEQ(key, l_key, "content-type")) { header_flags |= HTTP_HEADER_CONTENT_TYPE; +#ifdef SW_HAVE_COMPRESSION if (ctx->accept_compression && ctx->compression_types) { content_type = zval_get_string(value); } +#endif } headers.add(key, l_key, str_value.val(), str_value.len()); }; @@ -347,6 +388,7 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const SW_HASHTABLE_FOREACH_END(); (void) type; +#ifdef SW_HAVE_COMPRESSION if (ctx->accept_compression && ctx->compression_types) { std::string str_content_type = content_type ? std::string(ZSTR_VAL(content_type), ZSTR_LEN(content_type)) : std::string(ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); @@ -355,13 +397,15 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const zend_string_release(content_type); } } +#endif } if (!(header_flags & HTTP_HEADER_SERVER)) { headers.add(ZEND_STRL("server"), ZEND_STRL(SW_HTTP_SERVER_SOFTWARE)); } if (!(header_flags & HTTP_HEADER_DATE)) { - http2_server_set_date_header(&headers); + auto date_str = php_swoole_http_get_date(); + headers.add(ZEND_STRL("date"), ZSTR_VAL(date_str), ZSTR_LEN(date_str)); } if (!(header_flags & HTTP_HEADER_CONTENT_TYPE)) { headers.add(ZEND_STRL("content-type"), ZEND_STRL("text/html")); @@ -379,21 +423,23 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const SW_HASHTABLE_FOREACH_END(); } - size_t content_length = body->length; - // content length + if (body) { + size_t content_length = body->length; + // content length #ifdef SW_HAVE_COMPRESSION - if (ctx->compress(body->str, body->length)) { - content_length = ctx->zlib_buffer->length; - // content encoding - const char *content_encoding = ctx->get_content_encoding(); - headers.add(ZEND_STRL("content-encoding"), (char *) content_encoding, strlen(content_encoding)); - } + if (ctx->compress(body->str, body->length)) { + content_length = ctx->zlib_buffer->length; + // content encoding + const char *content_encoding = ctx->get_content_encoding(); + headers.add(ZEND_STRL("content-encoding"), (char *) content_encoding, strlen(content_encoding)); + } #endif - ret = swoole_itoa(intbuf[1], content_length); - headers.add(ZEND_STRL("content-length"), intbuf[1], ret); + ret = swoole_itoa(intbuf[1], content_length); + headers.add(ZEND_STRL("content-length"), intbuf[1], ret); + } - Http2Session *client = http2_sessions[ctx->fd]; - nghttp2_hd_deflater *deflater = client->deflater; + auto client = http2_sessions[ctx->fd]; + auto deflater = client->deflater; if (!deflater) { ret = nghttp2_hd_deflate_new2(&deflater, client->remote_settings.header_table_size, php_nghttp2_mem()); if (ret != 0) { @@ -421,17 +467,31 @@ static ssize_t http2_server_build_header(HttpContext *ctx, uchar *buffer, const return rv; } -int swoole_http2_server_ping(HttpContext *ctx) { +bool swoole_http2_server_ping(HttpContext *ctx) { char frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header(frame, SW_HTTP2_TYPE_PING, SW_HTTP2_FRAME_PING_PAYLOAD_SIZE, SW_HTTP2_FLAG_NONE, 0); - return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE) ? SW_OK : SW_ERR; + return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE); +} + +static bool http2_server_send_setting_ack(HttpContext *ctx) { + char frame[SW_HTTP2_FRAME_HEADER_SIZE]; + Http2::set_frame_header(frame, SW_HTTP2_TYPE_SETTINGS, 0, SW_HTTP2_FLAG_ACK, 0); + return ctx->send(ctx, frame, SW_HTTP2_FRAME_HEADER_SIZE); } -int swoole_http2_server_goaway(HttpContext *ctx, zend_long error_code, const char *debug_data, size_t debug_data_len) { +static bool http2_server_send_rst_stream(HttpContext *ctx, int error_code) { + char frame[SW_HTTP2_FRAME_HEADER_SIZE + sizeof(uint32_t)]; + Http2::set_frame_header(frame, SW_HTTP2_TYPE_RST_STREAM, 0, SW_HTTP2_FLAG_ACK, 0); + *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(error_code); + return ctx->send(ctx, frame, sizeof(frame)); +} + +bool swoole_http2_server_goaway(HttpContext *ctx, zend_long error_code, zend_string *sdata) { + const char *debug_data = sdata ? ZSTR_VAL(sdata) : nullptr; + size_t debug_data_len = sdata ? ZSTR_LEN(sdata) : 0; size_t length = SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE + debug_data_len; char *frame = (char *) ecalloc(1, length); - bool ret; - Http2Session *client = http2_sessions[ctx->fd]; + auto client = http2_sessions[ctx->fd]; uint32_t last_stream_id = client->last_stream_id; Http2::set_frame_header(frame, SW_HTTP2_TYPE_GOAWAY, SW_HTTP2_GOAWAY_SIZE + debug_data_len, error_code, 0); *(uint32_t *) (frame + SW_HTTP2_FRAME_HEADER_SIZE) = htonl(last_stream_id); @@ -439,13 +499,13 @@ int swoole_http2_server_goaway(HttpContext *ctx, zend_long error_code, const cha if (debug_data_len > 0) { memcpy(frame + SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_GOAWAY_SIZE, debug_data, debug_data_len); } - ret = ctx->send(ctx, frame, length); + bool rv = ctx->send(ctx, frame, length); efree(frame); client->shutting_down = true; - return ret; + return rv; } -bool Http2Stream::send_header(const String *body, bool end_stream) { +bool Http2Stream::send_header(const String *body, bool end_stream) const { char header_buffer[SW_BUFFER_SIZE_STD]; ssize_t bytes = http2_server_build_header(ctx, (uchar *) header_buffer, body); if (bytes < 0) { @@ -470,9 +530,10 @@ bool Http2Stream::send_header(const String *body, bool end_stream) { */ char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; - if (end_stream && body->length == 0) { + if (end_stream && (!body || body->length == 0)) { http2::set_frame_header( frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS | SW_HTTP2_FLAG_END_STREAM, id); + ctx->end_ = 1; } else { http2::set_frame_header(frame_header, SW_HTTP2_TYPE_HEADERS, bytes, SW_HTTP2_FLAG_END_HEADERS, id); } @@ -488,13 +549,21 @@ bool Http2Stream::send_header(const String *body, bool end_stream) { return true; } -bool Http2Stream::send_body(const String *body, bool end_stream, size_t max_frame_size, off_t offset, size_t length) { +bool Http2Stream::send_end_stream_data_frame() const { + char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; + http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, 0, SW_HTTP2_FLAG_END_STREAM, id); + return ctx->send(ctx, frame_header, SW_HTTP2_FRAME_HEADER_SIZE); +} + +bool Http2Stream::send_body( + const String *body, bool end_stream, const std::shared_ptr &session, off_t offset, size_t length) { char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; char *p = body->str + offset; size_t l = length == 0 ? body->length : length; int flags = end_stream ? SW_HTTP2_FLAG_END_STREAM : SW_HTTP2_FLAG_NONE; String *http_buffer = ctx->get_write_buffer(); + auto max_frame_size = session->local_settings.max_frame_size; while (l > 0) { size_t send_n; @@ -506,6 +575,26 @@ bool Http2Stream::send_body(const String *body, bool end_stream, size_t max_fram send_n = l; _send_flags = flags; } + + if (send_n > remote_window_size) { + // The coroutine server receives HTTP2 frames serially and cannot be suspended, + // therefore it does not support flow control + // TODO: The coroutine http server also needs to support HTTP2 flow control + if (ctx->is_co_socket() || !swoole_coroutine_is_in()) { + swoole_warning("The data sent exceeded remote_window_size"); + } else { + if (remote_window_size == 0) { + waiting_coroutine = Coroutine::get_current(); + waiting_coroutine->yield(); + waiting_coroutine = nullptr; + continue; + } else { + send_n = remote_window_size; + _send_flags = 0; + } + } + } + http2::set_frame_header(frame_header, SW_HTTP2_TYPE_DATA, send_n, _send_flags, id); // send twice to reduce memory copy @@ -530,12 +619,15 @@ bool Http2Stream::send_body(const String *body, bool end_stream, size_t max_fram l -= send_n; p += send_n; + + remote_window_size -= send_n; + session->remote_window_size -= send_n; } return true; } -bool Http2Stream::send_trailer() { +bool Http2Stream::send_trailer() const { char header_buffer[SW_BUFFER_SIZE_STD] = {}; char frame_header[SW_HTTP2_FRAME_HEADER_SIZE]; String *http_buffer = ctx->get_write_buffer(); @@ -555,9 +647,46 @@ bool Http2Stream::send_trailer() { return true; } +bool swoole_http2_server_end(HttpContext *ctx, zend_string *sdata) { + String http_body = {}; + if (sdata) { + http_body.length = ZSTR_LEN(sdata); + http_body.str = ZSTR_VAL(sdata); + } else { + http_body.length = 0; + http_body.str = nullptr; + } + return http2_server_respond(ctx, &http_body); +} + +bool swoole_http2_server_write(HttpContext *ctx, zend_string *sdata) { + String chunk = {}; + chunk.length = ZSTR_LEN(sdata); + chunk.str = ZSTR_VAL(sdata); + if (chunk.length == 0) { + php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); + return false; + } + + auto client = http2_sessions[ctx->fd]; + auto stream = client->get_stream(ctx->stream_id); + + ctx->send_chunked = 1; + + if (!ctx->send_header_ && !stream->send_header(nullptr, false)) { + return false; + } + + if (!stream->send_body(&chunk, false, client)) { + return false; + } + + return true; +} + static bool http2_server_respond(HttpContext *ctx, const String *body) { - Http2Session *client = http2_sessions[ctx->fd]; - Http2Stream *stream = ctx->stream; + auto client = http2_sessions[ctx->fd]; + auto stream = client->get_stream(ctx->stream_id); zval *ztrailer = sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); @@ -566,14 +695,15 @@ static bool http2_server_respond(HttpContext *ctx, const String *body) { } bool end_stream = (ztrailer == nullptr); - if (!stream->send_header(body, end_stream)) { + + if (!ctx->send_header_ && !stream->send_header(body, end_stream)) { return false; } - // The headers has already been sent, retries are no longer allowed (even if send body failed) + // The headers have already been sent, retries are no longer allowed (even if send body failed) ctx->end_ = 1; - bool error = false; + bool error = true; #ifdef SW_HAVE_COMPRESSION if (ctx->content_compressed) { @@ -581,112 +711,179 @@ static bool http2_server_respond(HttpContext *ctx, const String *body) { } #endif - // If send_yield is not supported, ignore flow control - if (ctx->co_socket || !((Server *) ctx->private_data)->send_yield || !swoole_coroutine_is_in()) { - if (body->length > client->remote_settings.window_size) { - swoole_warning("The data sent exceeded remote_window_size"); - } - if (!stream->send_body(body, end_stream, client->local_settings.max_frame_size)) { - error = true; + SW_LOOP { + if (ctx->send_chunked && body->length == 0 && !stream->send_end_stream_data_frame()) { + break; + } else if (!stream->send_body(body, end_stream, client)) { + break; + } else if (ztrailer && !stream->send_trailer()) { + break; } + error = false; + break; + } + + if (error) { + ctx->close(ctx); } else { - off_t offset = body->offset; - while (true) { - size_t send_len = body->length - offset; + client->remove_stream(stream->id); + } - if (send_len == 0) { - break; - } + if (client->shutting_down && client->streams.empty()) { + ctx->close(ctx); + } - if (stream->remote_window_size == 0) { - stream->waiting_coroutine = Coroutine::get_current(); - stream->waiting_coroutine->yield(); - stream->waiting_coroutine = nullptr; - continue; - } + return !error; +} - bool _end_stream; - if (send_len > stream->remote_window_size) { - send_len = stream->remote_window_size; - _end_stream = false; - } else { - _end_stream = true && end_stream; +static bool http2_server_send_status_code(HttpContext *ctx, int status_code) { + auto client = http2_sessions[ctx->fd]; + auto stream = client->get_stream(ctx->stream_id); + ctx->response.status = status_code; + return stream->send_header(nullptr, true); +} + +static bool http2_server_send_range_file(HttpContext *ctx, StaticHandler *handler) { + auto client = http2_sessions[ctx->fd]; + auto stream = client->get_stream(ctx->stream_id); + +#ifdef SW_HAVE_COMPRESSION + ctx->accept_compression = 0; +#endif + bool error = false; + zval *ztrailer = + sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); + if (php_swoole_array_length_safe(ztrailer) == 0) { + ztrailer = nullptr; + } + zval *zheader = + sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0); + if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) { + ctx->set_header(ZEND_STRL("content-type"), handler->get_content_type(), false); + } + + bool end_stream = (ztrailer == nullptr); + auto body = std::make_shared(); + body->length = handler->get_content_length(); + if (!stream->send_header(body.get(), end_stream)) { + return false; + } + + /* headers has already been sent, retries are no longer allowed (even if send body failed) */ + ctx->end_ = 1; + + auto tasks = handler->get_tasks(); + if (!tasks.empty()) { + File fp(handler->get_filename(), O_RDONLY); + if (!fp.ready()) { + return false; + } + + char *buf; + if (tasks.size() > 1) { + for (auto i = tasks.begin(); i != tasks.end(); ++i) { + body = std::make_shared(i->part_header, strlen(i->part_header)); + if (!stream->send_body(body.get(), false, client)) { + error = true; + break; + } + + fp.set_offset(i->offset); + buf = (char *) emalloc(i->length); + auto n_reads = fp.read(buf, i->length); + if (n_reads < 0) { + efree(buf); + return false; + } + body = std::make_shared(buf, i->length); + efree(buf); + if (!stream->send_body(body.get(), false, client)) { + error = true; + break; + } } - error = !stream->send_body(body, _end_stream, client->local_settings.max_frame_size, offset, send_len); if (!error) { - swoole_trace_log(SW_TRACE_HTTP2, - "body: send length=%zu, stream->remote_window_size=%u", - send_len, - stream->remote_window_size); - - offset += send_len; - if (send_len > stream->remote_window_size) { - stream->remote_window_size = 0; - } else { - stream->remote_window_size -= send_len; + body = std::make_shared(handler->get_end_part()); + if (!stream->send_body(body.get(), end_stream, client)) { + error = true; } } + } else if (tasks[0].length > 0) { + auto callback = [&]() -> bool { + fp.set_offset(tasks[0].offset); + buf = (char *) emalloc(tasks[0].length); + auto n_reads = fp.read(buf, tasks[0].length); + if (n_reads < 0) { + efree(buf); + return false; + } + body = std::make_shared(buf, n_reads); + efree(buf); + return true; + }; + if (swoole_coroutine_is_in()) { + if (!swoole::coroutine::async(callback)) { + return false; + } + } else { + if (!callback()) { + return false; + } + } + if (!stream->send_body(body.get(), end_stream, client)) { + error = true; + } } } - if (!error && ztrailer && !stream->send_trailer()) { - error = true; + if (!error && ztrailer) { + if (!stream->send_trailer()) { + error = true; + } } if (error) { ctx->close(ctx); } else { - client->streams.erase(stream->id); - delete stream; + client->remove_stream(ctx->stream_id); } - if (client->shutting_down && client->streams.size() == 0) { - ctx->close(ctx); - } - - return !error; + return true; } -bool HttpContext::http2_send_file(const char *file, uint32_t l_file, off_t offset, size_t length) { - Http2Session *client = http2_sessions[fd]; +bool swoole_http2_server_send_file(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { + auto client = http2_sessions[ctx->fd]; + auto stream = client->get_stream(ctx->stream_id); std::shared_ptr body; #ifdef SW_HAVE_COMPRESSION - accept_compression = 0; + ctx->accept_compression = 0; #endif if (swoole_coroutine_is_in()) { - body = System::read_file(file, false); + body = System::read_file(ZSTR_VAL(file), false); if (!body) { return false; } - if (!stream) { - /* closed */ - return false; - } } else { - File fp(file, O_RDONLY); + File fp(ZSTR_VAL(file), O_RDONLY); if (!fp.ready()) { return false; } body = fp.read_content(); - if (body->empty()) { - return false; - } } body->length = SW_MIN(length, body->length); zval *ztrailer = - sw_zend_read_property_ex(swoole_http_response_ce, response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); + sw_zend_read_property_ex(swoole_http_response_ce, ctx->response.zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TRAILER), 0); if (php_swoole_array_length_safe(ztrailer) == 0) { ztrailer = nullptr; } zval *zheader = - sw_zend_read_and_convert_property_array(swoole_http_response_ce, response.zobject, ZEND_STRL("header"), 0); + sw_zend_read_and_convert_property_array(swoole_http_response_ce, ctx->response.zobject, ZEND_STRL("header"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("content-type"))) { - const char *mimetype = swoole::mime_type::get(file).c_str(); - set_header(ZEND_STRL("content-type"), mimetype, strlen(mimetype), 0); + ctx->set_header(ZEND_STRL("content-type"), swoole::mime_type::get({ZSTR_VAL(file), ZSTR_LEN(file)}), false); } bool end_stream = (ztrailer == nullptr); @@ -695,15 +892,13 @@ bool HttpContext::http2_send_file(const char *file, uint32_t l_file, off_t offse } /* headers has already been sent, retries are no longer allowed (even if send body failed) */ - end_ = 1; + ctx->end_ = 1; bool error = false; if (body->length > 0) { - if (!stream->send_body(body.get(), end_stream, client->local_settings.max_frame_size, offset, length)) { + if (!stream->send_body(body.get(), end_stream, client, offset, length)) { error = true; - } else { - client->remote_settings.window_size -= length; // TODO: flow control? } } @@ -714,17 +909,16 @@ bool HttpContext::http2_send_file(const char *file, uint32_t l_file, off_t offse } if (error) { - close(this); + ctx->close(ctx); } else { - client->streams.erase(stream->id); - delete stream; + client->remove_stream(stream->id); } return true; } -static bool http2_server_context_onBeforeRequest(HttpContext *ctx) { - Server *serv = (Server *) ctx->private_data; +static bool http2_server_onBeforeRequest(HttpContext *ctx) { + auto serv = ctx->get_async_server(); if (serv->is_unavailable()) { String null_body{}; ctx->response.status = SW_HTTP_SERVICE_UNAVAILABLE; @@ -736,7 +930,8 @@ static bool http2_server_context_onBeforeRequest(HttpContext *ctx) { return swoole_http_server_onBeforeRequest(ctx); } -static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int flags, const char *in, size_t inlen) { +static int http2_server_parse_header( + const std::shared_ptr &client, HttpContext *ctx, int flags, const char *in, size_t inlen) { nghttp2_hd_inflater *inflater = client->inflater; if (!inflater) { @@ -758,19 +953,17 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int 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); + ssize_t rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, (uchar *) in, inlen, 1); if (rv < 0) { swoole_warning("inflate failed, Error: %s[%zd]", nghttp2_strerror(rv), rv); return SW_ERR; } - proclen = (size_t) rv; + auto proclen = (size_t) rv; in += proclen; inlen -= proclen; @@ -797,7 +990,7 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int memcpy(pathbuf, nv.value, k_len); pathbuf[k_len] = 0; add_assoc_stringl_ex(zserver, ZEND_STRL("query_string"), v_str, v_len); - zstr_path = zend_string_init(pathbuf, k_len, 0); + zstr_path = zend_string_init(pathbuf, k_len, false); // parse url params sapi_module.treat_data( PARSE_STRING, @@ -805,13 +998,13 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int swoole_http_init_and_read_property( swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, ZEND_STRL("get"))); } else { - zstr_path = zend_string_init((char *) nv.value, nv.valuelen, 0); + zstr_path = zend_string_init((char *) nv.value, nv.valuelen, false); } - ctx->request.path = (char *) estrndup((char *) nv.value, nv.valuelen); + ctx->request.path = estrndup((char *) nv.value, nv.valuelen); ctx->request.path_len = nv.valuelen; add_assoc_str_ex(zserver, ZEND_STRL("request_uri"), zstr_path); // path_info should be decoded - zstr_path = zend_string_dup(zstr_path, 0); + zstr_path = zend_string_dup(zstr_path, false); ZSTR_LEN(zstr_path) = php_url_decode(ZSTR_VAL(zstr_path), ZSTR_LEN(zstr_path)); add_assoc_str_ex(zserver, ZEND_STRL("path_info"), zstr_path); } else if (SW_STRCASEEQ((char *) nv.name + 1, nv.namelen - 1, "authority")) { @@ -819,9 +1012,9 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int } } else { if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-type")) { - if (SW_STRCASECT((char *) nv.value, nv.valuelen, "application/x-www-form-urlencoded")) { + if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "application/x-www-form-urlencoded")) { ctx->request.post_form_urlencoded = 1; - } else if (SW_STRCASECT((char *) nv.value, nv.valuelen, "multipart/form-data")) { + } else if (SW_STR_ISTARTS_WITH((char *) nv.value, nv.valuelen, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; @@ -839,6 +1032,12 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int (const char *) nv.value, nv.valuelen); continue; + } else if (SW_STRCASEEQ((char *) nv.name, nv.namelen, "content-length")) { + char *end; + zend_long content_length = std::strtol((char *) nv.value, &end, 10); + if (end != (char *) nv.value + nv.valuelen || content_length > client->max_body_size) { + http2_server_send_status_code(ctx, SW_HTTP_REQUEST_ENTITY_TOO_LARGE); + } } #ifdef SW_HAVE_COMPRESSION else if (ctx->enable_compression && SW_STRCASEEQ((char *) nv.name, nv.namelen, "accept-encoding")) { @@ -862,11 +1061,10 @@ static int http2_server_parse_header(Http2Session *client, HttpContext *ctx, int return SW_OK; } -int swoole_http2_server_parse(Http2Session *client, const char *buf) { - Http2Stream *stream = nullptr; +int swoole_http2_server_parse(const std::shared_ptr &client, const char *buf) { int type = buf[3]; int flags = buf[4]; - int retval = SW_ERR; + uint32_t stream_id = ntohl((*(int *) (buf + 5))) & 0x7fffffff; if (stream_id > client->last_stream_id) { @@ -876,26 +1074,26 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { if (client->shutting_down) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_IGNORE, "ignore http2 stream#%d after sending goaway", stream_id); - return retval; + return SW_ERR; } ssize_t length = Http2::get_length(buf); buf += SW_HTTP2_FRAME_HEADER_SIZE; - uint16_t id = 0; uint32_t value = 0; switch (type) { case SW_HTTP2_TYPE_SETTINGS: { + uint16_t id = 0; if (flags & SW_HTTP2_FLAG_ACK) { - swoole_http2_frame_trace_log(recv, "ACK"); + swoole_http2_frame_trace_log("ACK"); break; } while (length > 0) { id = ntohs(*(uint16_t *) (buf)); value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t))); - swoole_http2_frame_trace_log(recv, "id=%d, value=%d", id, value); + swoole_http2_frame_trace_log("id=%d, value=%d", id, value); switch (id) { case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: if (value != client->remote_settings.header_table_size) { @@ -917,7 +1115,7 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { swoole_trace_log(SW_TRACE_HTTP2, "setting: max_concurrent_streams=%u", value); break; case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: - client->remote_settings.window_size = value; + client->remote_window_size = client->remote_settings.init_window_size = value; swoole_trace_log(SW_TRACE_HTTP2, "setting: init_window_size=%u", value); break; case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: @@ -936,26 +1134,21 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { buf += sizeof(id) + sizeof(value); length -= sizeof(id) + sizeof(value); } + // After receiving the setting frame sent by the client, it is necessary to reply with an ACK, + // otherwise the client will assume that the server has not applied the setting + http2_server_send_setting_ack(client->default_ctx); break; } case SW_HTTP2_TYPE_HEADERS: { - stream = client->streams[stream_id]; - swoole_http2_frame_trace_log(recv, "%s", (stream ? "exist stream" : "new stream")); - HttpContext *ctx; + auto stream = client->get_stream(stream_id); + swoole_http2_frame_trace_log("%s", (stream ? "exist stream" : "new stream")); if (!stream) { - stream = new Http2Stream(client, stream_id); - if (sw_unlikely(!stream->ctx)) { - swoole_error_log( - SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NO_HEADER, "http2 create stream#%d context error", stream_id); + stream = client->create_stream(stream_id); + if (!stream) { return SW_ERR; } - ctx = stream->ctx; - client->streams[stream_id] = stream; - zend_update_property_long( - swoole_http_request_ce, SW_Z8_OBJ_P(ctx->request.zobject), ZEND_STRL("streamId"), stream_id); - } else { - ctx = stream->ctx; } + HttpContext *ctx = stream->ctx; if (http2_server_parse_header(client, ctx, flags, buf, length) < 0) { return SW_ERR; } @@ -968,61 +1161,64 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { break; } case SW_HTTP2_TYPE_DATA: { - swoole_http2_frame_trace_log(recv, "data"); - auto stream_iterator = client->streams.find(stream_id); - if (stream_iterator == client->streams.end()) { + swoole_http2_frame_trace_log("data"); + auto stream = client->get_stream(stream_id); + if (!stream) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP2_STREAM_NOT_FOUND, "http2 stream#%d not found", stream_id); return SW_ERR; } - stream = stream_iterator->second; + HttpContext *ctx = stream->ctx; - zend_update_property_long( - swoole_http_request_ce, SW_Z8_OBJ_P(ctx->request.zobject), ZEND_STRL("streamId"), stream_id); + if (length > 0) { + auto buffer = ctx->get_http2_data_buffer(); - String *buffer = ctx->request.h2_data_buffer; - if (!buffer) { - buffer = new String(SW_HTTP2_DATA_BUFFER_SIZE); - ctx->request.h2_data_buffer = buffer; - } - buffer->append(buf, length); + // Exceeded the max_body_size, or if the stream has ended, sends RST_STREAM frame, stops receiving data + if (buffer->length + length > client->max_body_size || ctx->end_) { + http2_server_send_rst_stream(ctx, 0); + client->remove_stream(stream_id); + break; + } - // flow control - client->local_settings.window_size -= length; - stream->local_window_size -= length; + buffer->append(buf, length); - if (length > 0) { - if (client->local_settings.window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) { - http2_server_send_window_update(ctx, 0, SW_HTTP2_MAX_WINDOW_SIZE - client->local_settings.window_size); - client->local_settings.window_size = SW_HTTP2_MAX_WINDOW_SIZE; + // flow control + client->local_window_size -= length; + stream->local_window_size -= length; + + if (client->local_window_size < (client->local_settings.init_window_size / 4)) { + http2_server_send_window_update( + ctx, 0, client->local_settings.init_window_size - client->local_window_size); + client->local_window_size = client->local_settings.init_window_size; } - if (stream->local_window_size < (SW_HTTP2_MAX_WINDOW_SIZE / 4)) { - http2_server_send_window_update(ctx, stream_id, SW_HTTP2_MAX_WINDOW_SIZE - stream->local_window_size); - stream->local_window_size = SW_HTTP2_MAX_WINDOW_SIZE; + if (stream->local_window_size < (client->local_settings.init_window_size / 4)) { + http2_server_send_window_update( + ctx, stream_id, client->local_settings.init_window_size - stream->local_window_size); + stream->local_window_size = client->local_settings.init_window_size; } } if (flags & SW_HTTP2_FLAG_END_STREAM) { - if (ctx->parse_body && ctx->request.post_form_urlencoded) { - sapi_module.treat_data( - PARSE_STRING, - estrndup(buffer->str, buffer->length), // it will be freed by treat_data - swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post"))); - } else if (ctx->mt_parser != nullptr) { - ctx->parse_multipart_data(buffer->str, buffer->length); - } - - if (!client->is_coro) { - retval = SW_OK; + if (ctx->get_http2_data_length() > 0) { + auto buffer = ctx->get_http2_data_buffer(); + if (ctx->parse_body && ctx->request.post_form_urlencoded) { + auto post_prop = swoole_http_init_and_read_property( + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post")); + // it will be freed by sapi_module.treat_data() + auto post_str = estrndup(buffer->str, buffer->length); + sapi_module.treat_data(PARSE_STRING, post_str, post_prop); + } else if (ctx->mt_parser != nullptr) { + if (!ctx->parse_multipart_data(buffer->str, buffer->length)) { + return SW_ERR; + } + } } - client->handle(client, stream); } break; } case SW_HTTP2_TYPE_PING: { - swoole_http2_frame_trace_log(recv, "ping"); + swoole_http2_frame_trace_log("ping"); if (!(flags & SW_HTTP2_FLAG_ACK)) { char ping_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_FRAME_PING_PAYLOAD_SIZE]; Http2::set_frame_header( @@ -1036,32 +1232,23 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { case SW_HTTP2_TYPE_WINDOW_UPDATE: { value = ntohl(*(uint32_t *) buf); if (stream_id == 0) { - client->remote_settings.window_size += value; + client->remote_window_size += value; } else { - if (client->streams.find(stream_id) != client->streams.end()) { - stream = client->streams[stream_id]; + auto stream = client->get_stream(stream_id); + if (stream) { stream->remote_window_size += value; - if (!client->is_coro) { - Server *serv = (Server *) stream->ctx->private_data; - if (serv->send_yield && stream->waiting_coroutine) { - stream->waiting_coroutine->resume(); - } + if (!client->is_coro && stream->waiting_coroutine) { + stream->waiting_coroutine->resume(); } } } - swoole_http2_frame_trace_log(recv, "window_size_increment=%d", value); + swoole_http2_frame_trace_log("window_size_increment=%d", value); break; } case SW_HTTP2_TYPE_RST_STREAM: { value = ntohl(*(int *) (buf)); - swoole_http2_frame_trace_log(recv, "error_code=%d", value); - if (client->streams.find(stream_id) != client->streams.end()) { - // TODO: i onRequest and use request->recv - // stream exist - stream = client->streams[stream_id]; - client->streams.erase(stream_id); - delete stream; - } + swoole_http2_frame_trace_log("error_code=%d", value); + client->remove_stream(stream_id); break; } case SW_HTTP2_TYPE_GOAWAY: { @@ -1069,8 +1256,7 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { buf += 4; value = ntohl(*(uint32_t *) (buf)); buf += 4; - swoole_http2_frame_trace_log(recv, - "last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", + swoole_http2_frame_trace_log("last_stream_id=%d, error_code=%d, opaque_data=[%.*s]", server_last_stream_id, value, (int) (length - SW_HTTP2_GOAWAY_SIZE), @@ -1081,32 +1267,30 @@ int swoole_http2_server_parse(Http2Session *client, const char *buf) { break; } default: { - swoole_http2_frame_trace_log(recv, ""); + swoole_http2_frame_trace_log(""); } } - return retval; + return SW_OK; } -/** - * Http2 - */ int swoole_http2_server_onReceive(Server *serv, Connection *conn, RecvData *req) { - int session_id = req->info.fd; - Http2Session *client = http2_sessions[session_id]; - if (client == nullptr) { - client = new Http2Session(session_id); - } - - client->handle = http2_server_onRequest; - if (!client->default_ctx) { + SessionId session_id = req->info.fd; + auto iter = http2_sessions.find(session_id); + std::shared_ptr client; + if (iter == http2_sessions.end()) { + client = swoole_http2_server_session_new(session_id); client->default_ctx = new HttpContext(); client->default_ctx->init(serv); client->default_ctx->fd = session_id; client->default_ctx->http2 = true; - client->default_ctx->stream = (Http2Stream *) -1; client->default_ctx->keepalive = true; - client->default_ctx->onBeforeRequest = http2_server_context_onBeforeRequest; + client->default_ctx->onBeforeRequest = http2_server_onBeforeRequest; + client->max_body_size = serv->get_package_max_length(conn); + client->handle = http2_server_onRequest; + http2_sessions.emplace(session_id, client); + } else { + client = iter->second; } zval zdata; @@ -1117,23 +1301,24 @@ int swoole_http2_server_onReceive(Server *serv, Connection *conn, RecvData *req) return retval; } -void swoole_http2_server_session_free(Connection *conn) { - auto session_iterator = http2_sessions.find(conn->session_id); - if (session_iterator == http2_sessions.end()) { - return; - } - Http2Session *client = session_iterator->second; - delete client; +std::shared_ptr swoole_http2_server_session_new(SessionId fd) { + auto session = std::make_shared(fd); + http2_sessions.emplace(fd, session); + return session; } -void HttpContext::http2_end(zval *zdata, zval *return_value) { - String http_body = {}; - if (zdata) { - http_body.length = php_swoole_get_send_data(zdata, &http_body.str); - } else { - http_body.length = 0; - http_body.str = nullptr; - } +void php_swoole_http2_server_onClose(Server *serv, SessionId session_id) { + server_ips.erase(session_id); + client_ips.erase(session_id); + swoole_http2_server_session_free(session_id); +} - RETURN_BOOL(http2_server_respond(this, &http_body)); +void swoole_http2_server_session_free(SessionId session_id) { + auto iter = http2_sessions.find(session_id); + if (iter == http2_sessions.end()) { + return; + } + /* default_ctx does not blong to session object */ + iter->second->default_ctx = nullptr; + http2_sessions.erase(iter); } diff --git a/ext-src/swoole_http_client_coro.cc b/ext-src/swoole_http_client_coro.cc index ccb5efa7ee..b50e860e36 100644 --- a/ext-src/swoole_http_client_coro.cc +++ b/ext-src/swoole_http_client_coro.cc @@ -19,83 +19,75 @@ #include "php_swoole_cxx.h" #include "php_swoole_http.h" +#include "php_swoole_websocket.h" -#include "swoole_string.h" #include "swoole_protocol.h" #include "swoole_file.h" #include "swoole_util.h" -#include "swoole_websocket.h" #include "swoole_mime_type.h" #include "swoole_base64.h" #include "swoole_socket.h" SW_EXTERN_C_BEGIN - -#include "thirdparty/swoole_http_parser.h" - -#include "ext/standard/base64.h" - -#ifdef SW_HAVE_ZLIB -#include -#endif - #include "stubs/php_swoole_http_client_coro_arginfo.h" - +#include "ext/standard/base64.h" SW_EXTERN_C_END -#ifdef SW_HAVE_BROTLI -#include -#endif - -using swoole::File; +using swoole::AsyncFile; using swoole::String; -using swoole::coroutine::Socket; using swoole::network::Address; +using swoole::websocket::FrameObject; namespace WebSocket = swoole::websocket; -enum http_client_error_status_code { - HTTP_CLIENT_ESTATUS_CONNECT_FAILED = -1, - HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT = -2, - HTTP_CLIENT_ESTATUS_SERVER_RESET = -3, - HTTP_CLIENT_ESTATUS_SEND_FAILED = -4, -}; - -extern void php_swoole_client_coro_socket_free(Socket *cli); - -static int http_parser_on_header_field(swoole_http_parser *parser, const char *at, size_t length); -static int http_parser_on_header_value(swoole_http_parser *parser, const char *at, size_t length); -static int http_parser_on_headers_complete(swoole_http_parser *parser); -static int http_parser_on_body(swoole_http_parser *parser, const char *at, size_t length); -static int http_parser_on_message_complete(swoole_http_parser *parser); +static int http_parser_on_header_field(llhttp_t *parser, const char *at, size_t length); +static int http_parser_on_header_value(llhttp_t *parser, const char *at, size_t length); +static int http_parser_on_headers_complete(llhttp_t *parser); +static int http_parser_on_body(llhttp_t *parser, const char *at, size_t length); +static int http_parser_on_message_complete(llhttp_t *parser); // clang-format off -static const swoole_http_parser_settings http_parser_settings = +static constexpr llhttp_settings_t http_parser_settings = { - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - http_parser_on_header_field, - http_parser_on_header_value, - http_parser_on_headers_complete, - http_parser_on_body, - http_parser_on_message_complete + nullptr, // on_message_begin + nullptr, // on_protocol + nullptr, // on_url + nullptr, // on_status + nullptr, // on_method + nullptr, // on_version + http_parser_on_header_field, // on_header_field + http_parser_on_header_value, // on_header_value + nullptr, // on_chunk_extension_name + nullptr, // on_chunk_extension_value + http_parser_on_headers_complete, // on_headers_complete + http_parser_on_body, // on_body + http_parser_on_message_complete, // on_message_complete + nullptr, // on_protocol_complete + nullptr, // on_url_complete + nullptr, // on_status_complete + nullptr, // on_method_complete + nullptr, // on_version_complete + nullptr, // on_header_field_complete + nullptr, // on_header_value_complete + nullptr, // on_chunk_extension_name_complete + nullptr, // on_chunk_extension_value_complete + nullptr, // on_chunk_header + nullptr, // on_chunk_complete + nullptr, // on_reset }; // clang-format on namespace swoole { namespace coroutine { -class HttpClient { +namespace http { +class Client { public: /* request info */ std::string host; uint16_t port; -#ifdef SW_USE_OPENSSL uint8_t ssl; -#endif - double connect_timeout = network::Socket::default_connect_timeout; + double connect_timeout = 0; + double response_timeout = 0; bool defer = false; bool lowercase_header = true; bool use_default_port; @@ -105,7 +97,7 @@ class HttpClient { std::string basic_auth; /* for response parser */ - char *tmp_header_field_name = nullptr; + const char *tmp_header_field_name = nullptr; int tmp_header_field_name_len = 0; String *body = nullptr; #ifdef SW_HAVE_COMPRESSION @@ -115,26 +107,47 @@ class HttpClient { /* options */ uint8_t max_retries = 0; - bool keep_alive = true; // enable by default - bool websocket = false; // if upgrade successfully - bool chunked = false; // Transfer-Encoding: chunked - bool websocket_mask = true; // enable websocket mask + bool keep_alive = true; // enable by default + bool websocket = false; // if upgrade successfully + bool chunked = false; // Transfer-Encoding: chunked + bool body_decompression = true; bool http_compression = true; -#ifdef SW_HAVE_ZLIB - bool websocket_compression = false; // allow to compress websocket messages -#endif - File *download_file = nullptr; // save http response to file - zend::String download_file_name; // unlink the file on error - zend_long download_offset = 0; + + bool accept_websocket_compression = false; // websocket server accepts compression + WebSocketSettings websocket_settings; + + bool in_callback = false; bool has_upload_files = false; + std::shared_ptr download_file; // save http response to file + zend::String download_file_name; // unlink the file on error + zend_long download_offset = 0; + /* safety zval */ zval _zobject; zval *zobject = &_zobject; + zval zsocket; + zend::Callable *write_func = nullptr; + /** + * Retain the send buffer object of the Socket after the Socket object is destroyed, + * allowing access to the sent Request data even after the connection has been closed. + */ String *tmp_write_buffer = nullptr; + std::shared_ptr continue_frame_buffer; + bool connection_close = false; + bool completed = false; + bool event_stream = false; - HttpClient(zval *zobject, std::string host, zend_long port = 80, zend_bool ssl = false); + Client(const zval *zobject, const std::string &host, zend_long port = 80, zend_bool ssl = false); + + bool is_available() const { + if (sw_unlikely(!socket || !socket->is_connected())) { + php_swoole_socket_set_error_properties(zobject, SW_ERROR_CLIENT_NO_CONNECTION); + return false; + } + return true; + } private: #ifdef SW_HAVE_ZLIB @@ -144,11 +157,13 @@ class HttpClient { #ifdef SW_HAVE_BROTLI BrotliDecoderState *brotli_decoder_state = nullptr; #endif - bool bind(std::string address, int port = 0); +#ifdef SW_HAVE_ZSTD + ZSTD_DStream *zstd_stream = nullptr; +#endif bool connect(); - void set_error(int error, const char *msg, int status); + void set_error(int error, const char *msg, int status) const; bool keep_liveness(); - bool send(); + bool send_request(); void reset(); static void add_headers(String *buf, const char *key, size_t key_len, const char *data, size_t data_len) { @@ -166,10 +181,9 @@ class HttpClient { static void create_token(int length, char *buf) { char characters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"§$%&/()=[]{}"; - int i; assert(length < 1024); - for (i = 0; i < length; i++) { - buf[i] = characters[rand() % (sizeof(characters) - 1)]; + for (int i = 0; i < length; i++) { + buf[i] = characters[swoole_random_int() % (sizeof(characters) - 1)]; } buf[length] = '\0'; } @@ -178,28 +192,31 @@ class HttpClient { #ifdef SW_HAVE_COMPRESSION bool decompress_response(const char *in, size_t in_len); #endif - void apply_setting(zval *zset, const bool check_all = true); + void apply_setting(zval *zset, bool check_all = true); void set_basic_auth(const std::string &username, const std::string &password); - bool exec(std::string _path); - bool recv(double timeout = 0); - void recv(zval *zframe, double timeout = 0); - bool recv_http_response(double timeout = 0); - bool upgrade(std::string path); - bool push(zval *zdata, zend_long opcode = websocket::OPCODE_TEXT, uint8_t flags = websocket::FLAG_FIN); - bool close(const bool should_be_reset = true); - - void get_header_out(zval *return_value) { + bool exec(const std::string &_path); + bool recv_response(double timeout = 0); + void recv_websocket_frame(zval *return_value, double timeout = 0); + void add_header(const char *key, size_t key_len, const char *str, size_t length) const; + bool upgrade(const std::string &_path); + bool push(zval *zdata, + zend_long opcode = websocket::OPCODE_TEXT, + uint8_t flags = websocket::FLAG_FIN, + zend_long code = websocket::CLOSE_NORMAL); + bool close(bool should_be_reset = true); + void socket_dtor(); + + void get_header_out(zval *return_value) const { String *buffer = nullptr; if (socket == nullptr) { - if (tmp_write_buffer) { - buffer = tmp_write_buffer; - } + buffer = tmp_write_buffer; } else { buffer = socket->get_write_buffer(); } if (buffer == nullptr) { RETURN_FALSE; } + off_t offset = swoole_strnpos(buffer->str, buffer->length, ZEND_STRL("\r\n\r\n")); if (offset <= 0) { RETURN_FALSE; @@ -208,52 +225,57 @@ class HttpClient { RETURN_STRINGL(buffer->str, offset); } - void getsockname(zval *return_value) { - Address sa; - if (!socket || !socket->getsockname(&sa)) { - ZVAL_FALSE(return_value); - return; + void getsockname(zval *return_value) const { + if (!is_available()) { + RETURN_FALSE; + } + if (!socket->getsockname()) { + php_swoole_socket_set_error_properties(zobject, socket); + RETURN_FALSE; } - array_init(return_value); - add_assoc_string(return_value, "address", (char *) sa.get_ip()); - add_assoc_long(return_value, "port", sa.get_port()); + add_assoc_string(return_value, "address", socket->get_addr()); + add_assoc_long(return_value, "port", socket->get_port()); } - void getpeername(zval *return_value) { + void getpeername(zval *return_value) const { Address sa; - if (!socket || !socket->getpeername(&sa)) { - ZVAL_FALSE(return_value); - return; + if (!is_available()) { + RETURN_FALSE; + } + if (!socket->getpeername(&sa)) { + php_swoole_socket_set_error_properties(zobject, socket); + RETURN_FALSE; } - array_init(return_value); - add_assoc_string(return_value, "address", (char *) sa.get_ip()); + add_assoc_string(return_value, "address", sa.get_addr()); add_assoc_long(return_value, "port", sa.get_port()); } -#ifdef SW_USE_OPENSSL - void getpeercert(zval *return_value) { + void getpeercert(zval *return_value) const { + if (!is_available()) { + RETURN_FALSE; + } auto cert = socket->ssl_get_peer_cert(); if (cert.empty()) { - ZVAL_FALSE(return_value); - return; + php_swoole_socket_set_error_properties(zobject, socket); + RETURN_FALSE; } else { - ZVAL_STRINGL(return_value, cert.c_str(), cert.length()); + RETURN_STRINGL(cert.c_str(), cert.length()); } } -#endif - ~HttpClient(); + ~Client(); private: - Socket *socket = nullptr; + SocketImpl *socket = nullptr; NameResolver::Context resolve_context_ = {}; SocketType socket_type = SW_SOCK_TCP; - swoole_http_parser parser = {}; - bool wait = false; + llhttp_t parser = {}; + bool wait_response = false; }; +} // namespace http } // namespace coroutine } // namespace swoole @@ -263,10 +285,10 @@ static zend_object_handlers swoole_http_client_coro_handlers; static zend_class_entry *swoole_http_client_coro_exception_ce; static zend_object_handlers swoole_http_client_coro_exception_handlers; -using swoole::coroutine::HttpClient; +using swoole::coroutine::http::Client; struct HttpClientObject { - HttpClient *phc; + Client *client; zend_object std; }; @@ -294,13 +316,13 @@ static PHP_METHOD(swoole_http_client_coro, getHeaders); static PHP_METHOD(swoole_http_client_coro, getCookies); static PHP_METHOD(swoole_http_client_coro, getStatusCode); static PHP_METHOD(swoole_http_client_coro, getHeaderOut); -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_http_client_coro, getPeerCert); -#endif static PHP_METHOD(swoole_http_client_coro, upgrade); static PHP_METHOD(swoole_http_client_coro, push); static PHP_METHOD(swoole_http_client_coro, recv); static PHP_METHOD(swoole_http_client_coro, close); +static PHP_METHOD(swoole_http_client_coro, ping); +static PHP_METHOD(swoole_http_client_coro, disconnect); SW_EXTERN_C_END // clang-format off @@ -329,25 +351,25 @@ static const zend_function_entry swoole_http_client_coro_methods[] = PHP_ME(swoole_http_client_coro, getCookies, arginfo_class_Swoole_Coroutine_Http_Client_getCookies, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getStatusCode, arginfo_class_Swoole_Coroutine_Http_Client_getStatusCode, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, getHeaderOut, arginfo_class_Swoole_Coroutine_Http_Client_getHeaderOut, ZEND_ACC_PUBLIC) -#ifdef SW_USE_OPENSSL PHP_ME(swoole_http_client_coro, getPeerCert, arginfo_class_Swoole_Coroutine_Http_Client_getPeerCert, ZEND_ACC_PUBLIC) -#endif PHP_ME(swoole_http_client_coro, upgrade, arginfo_class_Swoole_Coroutine_Http_Client_upgrade, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, push, arginfo_class_Swoole_Coroutine_Http_Client_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, recv, arginfo_class_Swoole_Coroutine_Http_Client_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_client_coro, close, arginfo_class_Swoole_Coroutine_Http_Client_close, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_client_coro, ping, arginfo_class_Swoole_Coroutine_Http_Client_ping, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_client_coro, disconnect, arginfo_class_Swoole_Coroutine_Http_Client_disconnect, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on -void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers) { - const char *p, *eof = at + length; +void php_swoole_http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval *zset_cookie_headers) { + const char *eof = at + length; size_t key_len = 0, value_len = 0; zval zvalue; // key - p = (char *) memchr(at, '=', length); + const char *p = (char *) memchr(at, '=', length); if (p) { key_len = p - at; p++; // point to value @@ -376,39 +398,39 @@ void http_parse_set_cookies(const char *at, size_t length, zval *zcookies, zval add_next_index_stringl(zset_cookie_headers, (char *) at, length); } -static int http_parser_on_header_field(swoole_http_parser *parser, const char *at, size_t length) { - HttpClient *http = (HttpClient *) parser->data; - http->tmp_header_field_name = (char *) at; +static int http_parser_on_header_field(llhttp_t *parser, const char *at, size_t length) { + auto *http = static_cast(parser->data); + http->tmp_header_field_name = at; http->tmp_header_field_name_len = length; return 0; } -static int http_parser_on_header_value(swoole_http_parser *parser, const char *at, size_t length) { - HttpClient *http = (HttpClient *) parser->data; - zval *zobject = (zval *) http->zobject; - zval *zheaders = - sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0); - char *header_name = http->tmp_header_field_name; +static int http_parser_on_header_value(llhttp_t *parser, const char *at, size_t length) { + auto *http = static_cast(parser->data); + zval *zobject = static_cast(http->zobject); + + const char *header_name = http->tmp_header_field_name; size_t header_len = http->tmp_header_field_name_len; + zend::CharPtr _header_name; if (http->lowercase_header) { - header_name = zend_str_tolower_dup(header_name, header_len); + _header_name.assign_tolower(header_name, header_len); + header_name = _header_name.get(); } - add_assoc_stringl_ex(zheaders, header_name, header_len, (char *) at, length); + http->add_header(header_name, header_len, (char *) at, length); if (parser->status_code == SW_HTTP_SWITCHING_PROTOCOLS && SW_STREQ(header_name, header_len, "upgrade")) { - if (SW_STRCASEEQ(at, length, "websocket")) { + if (swoole_http_token_list_contains_value(at, length, "websocket")) { http->websocket = true; } /* TODO: protocol error? */ } #ifdef SW_HAVE_ZLIB - else if (http->websocket && http->websocket_compression && + else if (http->websocket && http->websocket_settings.compression && SW_STREQ(header_name, header_len, "sec-websocket-extensions")) { - if (SW_STRCASECT(at, length, "permessage-deflate") && SW_STRCASECT(at, length, "client_no_context_takeover") && - SW_STRCASECT(at, length, "server_no_context_takeover")) { - http->websocket_compression = true; + if (swoole_strncasestr(at, length, SW_STRL("permessage-deflate"))) { + http->accept_websocket_compression = true; } } #endif @@ -417,86 +439,98 @@ static int http_parser_on_header_value(swoole_http_parser *parser, const char *a sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("cookies"), 0); zval *zset_cookie_headers = sw_zend_read_and_convert_property_array( swoole_http_client_coro_ce, zobject, ZEND_STRL("set_cookie_headers"), 0); - http_parse_set_cookies(at, length, zcookies, zset_cookie_headers); + php_swoole_http_parse_set_cookies(at, length, zcookies, zset_cookie_headers); } #ifdef SW_HAVE_COMPRESSION else if (SW_STREQ(header_name, header_len, "content-encoding")) { - if (0) { + if (false) { } #ifdef SW_HAVE_BROTLI - else if (SW_STRCASECT(at, length, "br")) { + else if (SW_STR_ISTARTS_WITH(at, length, "br")) { http->compress_method = HTTP_COMPRESS_BR; } #endif #ifdef SW_HAVE_ZLIB - else if (SW_STRCASECT(at, length, "gzip")) { + else if (SW_STR_ISTARTS_WITH(at, length, "gzip")) { http->compress_method = HTTP_COMPRESS_GZIP; - } else if (SW_STRCASECT(at, length, "deflate")) { + } else if (SW_STR_ISTARTS_WITH(at, length, "deflate")) { http->compress_method = HTTP_COMPRESS_DEFLATE; } +#endif +#ifdef SW_HAVE_ZSTD + else if (SW_STR_ISTARTS_WITH(at, length, "zstd")) { + http->compress_method = HTTP_COMPRESS_ZSTD; + } #endif } #endif - else if (SW_STREQ(header_name, header_len, "transfer-encoding") && SW_STRCASECT(at, length, "chunked")) { + else if (SW_STREQ(header_name, header_len, "transfer-encoding") && SW_STR_ISTARTS_WITH(at, length, "chunked")) { http->chunked = true; - } - - if (http->lowercase_header) { - efree(header_name); + } else if (SW_STREQ(header_name, header_len, "connection")) { + http->connection_close = SW_STR_ISTARTS_WITH(at, length, "close"); + } else if (SW_STREQ(header_name, header_len, "content-type")) { + http->event_stream = SW_STR_ISTARTS_WITH(at, length, "text/event-stream"); } return 0; } -static int http_parser_on_headers_complete(swoole_http_parser *parser) { - HttpClient *http = (HttpClient *) parser->data; +static int http_parser_on_headers_complete(llhttp_t *parser) { + auto *http = static_cast(parser->data); if (http->method == SW_HTTP_HEAD || parser->status_code == SW_HTTP_NO_CONTENT) { return 1; } return 0; } -static int http_parser_on_body(swoole_http_parser *parser, const char *at, size_t length) { - HttpClient *http = (HttpClient *) parser->data; +static int http_parser_on_body(llhttp_t *parser, const char *at, size_t length) { + auto *http = static_cast(parser->data); + if (http->write_func) { + zval zargv[2]; + zargv[0] = *http->zobject; + ZVAL_STRINGL(&zargv[1], at, length); + http->in_callback = true; + bool success = http->write_func->call(2, zargv, nullptr); + http->in_callback = false; + zval_ptr_dtor(&zargv[1]); + return success ? 0 : -1; + } #ifdef SW_HAVE_COMPRESSION - if (http->body_decompression && !http->compression_error && http->compress_method != HTTP_COMPRESS_NONE) { + else if (http->body_decompression && !http->compression_error && http->compress_method != HTTP_COMPRESS_NONE) { if (!http->decompress_response(at, length)) { http->compression_error = true; goto _append_raw; } - } else + } #endif - { + else { #ifdef SW_HAVE_COMPRESSION _append_raw: #endif - if (http->body->append(at, length) < 0) { - return -1; - } + http->body->append(at, length); } if (http->download_file_name.get() && http->body->length > 0) { if (http->download_file == nullptr) { char *download_file_name = http->download_file_name.val(); - std::unique_ptr fp(new File(download_file_name, O_CREAT | O_WRONLY, 0664)); + auto fp = std::make_shared(download_file_name, O_CREAT | O_WRONLY, 0664); if (!fp->ready()) { swoole_sys_warning("open(%s, O_CREAT | O_WRONLY) failed", download_file_name); - return false; + return -1; } if (http->download_offset == 0) { if (!fp->truncate(0)) { swoole_sys_warning("ftruncate(%s) failed", download_file_name); - return false; + return -1; } } else { - if (!fp->set_offest(http->download_offset)) { + if (!fp->set_offset(http->download_offset)) { swoole_sys_warning("fseek(%s, %jd) failed", download_file_name, (intmax_t) http->download_offset); - return false; + return -1; } } - http->download_file = fp.release(); + http->download_file = fp; } - if (swoole_coroutine_write(http->download_file->get_fd(), SW_STRINGL(http->body)) != - (ssize_t) http->body->length) { + if (http->download_file->write(http->body) != (ssize_t) http->body->length) { return -1; } http->body->clear(); @@ -504,14 +538,14 @@ static int http_parser_on_body(swoole_http_parser *parser, const char *at, size_ return 0; } -static int http_parser_on_message_complete(swoole_http_parser *parser) { - HttpClient *http = (HttpClient *) parser->data; - zval *zobject = (zval *) http->zobject; - +static int http_parser_on_message_complete(llhttp_t *parser) { + auto *http = static_cast(parser->data); + zval *zobject = static_cast(http->zobject); + http->completed = true; if (parser->upgrade && !http->websocket) { // not support, continue. parser->upgrade = 0; - return 0; + return HPE_PAUSED; } zend_update_property_long( @@ -523,31 +557,23 @@ static int http_parser_on_message_complete(swoole_http_parser *parser) { http->download_file_name.release(); } - if (parser->upgrade) { - // return 1 will finish the parser and means yes we support it. - return 1; - } else { - return 0; - } + return HPE_PAUSED; } -HttpClient::HttpClient(zval *zobject, std::string host, zend_long port, zend_bool ssl) { - this->socket_type = network::Socket::convert_to_type(host); +Client::Client(const zval *zobject, const std::string &host, zend_long port, zend_bool ssl) { this->host = host; + this->socket_type = network::Socket::convert_to_type(this->host); this->use_default_port = port == 0; if (this->use_default_port) { port = ssl ? 443 : 80; } this->port = port; -#ifdef SW_USE_OPENSSL this->ssl = ssl; -#endif _zobject = *zobject; - // TODO: zend_read_property cache here (strong type properties) } #ifdef SW_HAVE_COMPRESSION -bool HttpClient::decompress_response(const char *in, size_t in_len) { +bool Client::decompress_response(const char *in, size_t in_len) { if (in_len == 0) { return false; } @@ -561,7 +587,6 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { int status; int encoding = compress_method == HTTP_COMPRESS_GZIP ? SW_ZLIB_ENCODING_GZIP : SW_ZLIB_ENCODING_DEFLATE; bool first_decompress = !gzip_stream_active; - size_t total_out; if (!gzip_stream_active) { _retry: @@ -581,8 +606,8 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { gzip_stream.avail_in = in_len; gzip_stream.total_in = 0; - while (1) { - total_out = gzip_stream.total_out; + while (true) { + const size_t total_out = gzip_stream.total_out; gzip_stream.avail_out = body->size - body->length; gzip_stream.next_out = (Bytef *) (body->str + body->length); SW_ASSERT(body->length <= body->size); @@ -590,10 +615,7 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { if (status >= 0) { body->length += (gzip_stream.total_out - total_out); if (body->length + (SW_BUFFER_SIZE_STD / 2) >= body->size) { - if (!body->extend()) { - status = Z_MEM_ERROR; - break; - } + body->extend(); } } if (status == Z_STREAM_END || (status == Z_OK && gzip_stream.avail_in == 0)) { @@ -629,26 +651,22 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { const char *next_in = in; size_t available_in = in_len; - while (1) { + while (true) { size_t available_out = body->size - body->length, reserved_available_out = available_out; char *next_out = body->str + body->length; size_t total_out; - BrotliDecoderResult result; SW_ASSERT(body->length <= body->size); - result = BrotliDecoderDecompressStream(brotli_decoder_state, - &available_in, - (const uint8_t **) &next_in, - &available_out, - (uint8_t **) &next_out, - &total_out); + BrotliDecoderResult result = BrotliDecoderDecompressStream(brotli_decoder_state, + &available_in, + (const uint8_t **) &next_in, + &available_out, + (uint8_t **) &next_out, + &total_out); body->length += reserved_available_out - available_out; if (result == BROTLI_DECODER_RESULT_SUCCESS || result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { return true; } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { - if (!body->extend()) { - swoole_warning("BrotliDecoderDecompressStream() failed, no memory is available"); - break; - } + body->extend(); } else { swoole_warning("BrotliDecoderDecompressStream() failed, %s", BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotli_decoder_state))); @@ -659,6 +677,44 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { body->length = reserved_body_length; return false; } +#endif +#ifdef SW_HAVE_ZSTD + case HTTP_COMPRESS_ZSTD: { + size_t zstd_result = 0; + if (zstd_stream == nullptr) { + zstd_stream = ZSTD_createDStream(); + if (!zstd_stream) { + swoole_warning("ZSTD_createDStream() failed, can not create ZSTD stream"); + return false; + } + + zstd_result = ZSTD_initDStream(zstd_stream); + if (ZSTD_isError(zstd_result)) { + swoole_warning("ZSTD_initDStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); + return false; + } + } + + size_t recommended_size = ZSTD_DStreamOutSize(); + ZSTD_inBuffer in_buffer = {in, in_len, 0}; + ZSTD_outBuffer out_buffer = {body->str + body->length, body->size - body->length, 0}; + while (in_buffer.pos < in_buffer.size) { + if (sw_unlikely(out_buffer.pos == out_buffer.size)) { + body->extend(recommended_size + body->size); + body->length += out_buffer.pos; + out_buffer = {body->str + body->length, body->size - body->length, 0}; + } + + zstd_result = ZSTD_decompressStream(zstd_stream, &out_buffer, &in_buffer); + if (ZSTD_isError(zstd_result)) { + swoole_warning("ZSTD_decompressStream() failed, Error: [%s]", ZSTD_getErrorName(zstd_result)); + return false; + } + } + + body->length += out_buffer.pos; + return true; + } #endif default: break; @@ -669,7 +725,7 @@ bool HttpClient::decompress_response(const char *in, size_t in_len) { } #endif -void HttpClient::apply_setting(zval *zset, const bool check_all) { +void Client::apply_setting(zval *zset, const bool check_all) { if (!ZVAL_IS_ARRAY(zset) || php_swoole_array_length(zset) == 0) { return; } @@ -677,10 +733,12 @@ void HttpClient::apply_setting(zval *zset, const bool check_all) { zval *ztmp; HashTable *vht = Z_ARRVAL_P(zset); - if (php_swoole_array_get_value(vht, "connect_timeout", ztmp) || - php_swoole_array_get_value(vht, "timeout", ztmp) /* backward compatibility */) { + if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { connect_timeout = zval_get_double(ztmp); } + if (php_swoole_array_get_value(vht, "timeout", ztmp)) { + response_timeout = zval_get_double(ztmp); + } if (php_swoole_array_get_value(vht, "max_retries", ztmp)) { max_retries = (uint8_t) SW_MIN(zval_get_long(ztmp), UINT8_MAX); } @@ -693,35 +751,27 @@ void HttpClient::apply_setting(zval *zset, const bool check_all) { if (php_swoole_array_get_value(vht, "keep_alive", ztmp)) { keep_alive = zval_is_true(ztmp); } - if (php_swoole_array_get_value(vht, "websocket_mask", ztmp)) { - websocket_mask = zval_is_true(ztmp); - } if (php_swoole_array_get_value(vht, "http_compression", ztmp)) { http_compression = zval_is_true(ztmp); } if (php_swoole_array_get_value(vht, "body_decompression", ztmp)) { body_decompression = zval_is_true(ztmp); } -#ifdef SW_HAVE_ZLIB - if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) { - websocket_compression = zval_is_true(ztmp); + if (php_swoole_array_get_value(vht, "write_func", ztmp)) { + delete write_func; + write_func = sw_callable_create(ztmp); } -#endif + WebSocket::apply_setting(websocket_settings, vht, false); } if (socket) { - php_swoole_client_set(socket, zset); -#ifdef SW_USE_OPENSSL - if (socket->http_proxy && !socket->ssl_is_enable()) -#else - if (socket->http_proxy) -#endif - { + php_swoole_socket_set(socket, zset); + if (socket->http_proxy && !socket->ssl_is_enable()) { socket->http_proxy->dont_handshake = 1; } } } -void HttpClient::set_basic_auth(const std::string &username, const std::string &password) { +void Client::set_basic_auth(const std::string &username, const std::string &password) { std::string input = username + ":" + password; size_t output_size = sizeof("Basic ") + BASE64_ENCODE_OUT_SIZE(input.size()); char *output = (char *) emalloc(output_size); @@ -733,51 +783,70 @@ void HttpClient::set_basic_auth(const std::string &username, const std::string & } } -bool HttpClient::connect() { +void Client::add_header(const char *key, size_t key_len, const char *str, size_t length) const { + zval *zheaders = + sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("headers"), 0); + + zval zheader_new; + ZVAL_STRINGL(&zheader_new, str, length); + + zend::array_add_or_merge(zheaders, key, key_len, &zheader_new); +} + +bool Client::connect() { if (socket) { return true; } if (!body) { body = new String(SW_HTTP_RESPONSE_INIT_SIZE); if (!body) { - set_error(ENOMEM, swoole_strerror(ENOMEM), HTTP_CLIENT_ESTATUS_CONNECT_FAILED); + set_error(ENOMEM, swoole_strerror(ENOMEM), HTTP_ESTATUS_CONNECT_FAILED); return false; } } php_swoole_check_reactor(); - socket = new Socket(socket_type); - if (UNEXPECTED(socket->get_fd() < 0)) { - php_swoole_sys_error(E_WARNING, "new Socket() failed"); - set_error(errno, swoole_strerror(errno), HTTP_CLIENT_ESTATUS_CONNECT_FAILED); - delete socket; - socket = nullptr; + auto object = php_swoole_create_socket(socket_type); + if (UNEXPECTED(!object)) { + set_error(errno, swoole_strerror(errno), HTTP_ESTATUS_CONNECT_FAILED); return false; } -#ifdef SW_USE_OPENSSL - if (ssl) { - socket->enable_ssl_encrypt(); + ZVAL_OBJ(&zsocket, object); + socket = php_swoole_get_socket(&zsocket); + + if (ssl && !socket->enable_ssl_encrypt()) { + set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_CONNECT_FAILED); + close(); + return false; } -#endif + // apply settings - apply_setting(sw_zend_read_property_ex(swoole_http_client_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0), - false); + apply_setting(sw_zend_read_property_ex(Z_OBJCE_P(zobject), zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_SETTING), 0), false); - // socket->set_buffer_allocator(&SWOOLE_G(zend_string_allocator)); - // connect - socket->set_timeout(connect_timeout, Socket::TIMEOUT_CONNECT); + // reset the properties that depend on the connection + websocket = false; +#ifdef SW_HAVE_ZLIB + accept_websocket_compression = false; +#endif + + double _timeout = connect_timeout == 0 ? network::Socket::default_connect_timeout : connect_timeout; + socket->set_timeout(_timeout, SW_TIMEOUT_CONNECT); socket->set_resolve_context(&resolve_context_); + socket->set_dtor([this](Socket *_socket) { socket_dtor(); }); + socket->set_buffer_allocator(sw_zend_string_allocator()); + if (!socket->connect(host, port)) { - set_error(socket->errCode, socket->errMsg, HTTP_CLIENT_ESTATUS_CONNECT_FAILED); + set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_CONNECT_FAILED); close(); return false; } + zend_update_property(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("socket"), &zsocket); zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1); return true; } -void HttpClient::set_error(int error, const char *msg, int status) { +void Client::set_error(int error, const char *msg, int status) const { auto ce = swoole_http_client_coro_ce; auto obj = SW_Z8_OBJ_P(zobject); zend_update_property_long(ce, obj, ZEND_STRL("errCode"), error); @@ -785,12 +854,12 @@ void HttpClient::set_error(int error, const char *msg, int status) { zend_update_property_long(ce, obj, ZEND_STRL("statusCode"), status); } -bool HttpClient::keep_liveness() { +bool Client::keep_liveness() { if (!socket || !socket->check_liveness()) { if (socket) { /* in progress */ socket->check_bound_co(SW_EVENT_RDWR); - set_error(socket->errCode, socket->errMsg, HTTP_CLIENT_ESTATUS_SERVER_RESET); + set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_SERVER_RESET); close(false); } SW_LOOP_N(max_retries + 1) { @@ -803,13 +872,13 @@ bool HttpClient::keep_liveness() { return true; } -bool HttpClient::send() { +bool Client::send_request() { zval *zvalue = nullptr; uint32_t header_flag = 0x0; zval *zmethod, *zheaders, *zbody, *zupload_files, *zcookies, *z_download_file; - if (path.length() == 0) { - php_swoole_fatal_error(E_WARNING, "path is empty"); + if (path.empty()) { + php_swoole_socket_set_error_properties(zobject, SW_ERROR_INVALID_PARAMS); return false; } @@ -852,8 +921,8 @@ bool HttpClient::send() { // ============ host ============ zend::String str_host; - if ((ZVAL_IS_ARRAY(zheaders)) && ((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host"))) || - (zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("host"))))) { + if ((ZVAL_IS_ARRAY(zheaders)) && (((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("Host")))) || + ((zvalue = zend_hash_str_find(Z_ARRVAL_P(zheaders), ZEND_STRL("host")))))) { str_host = zvalue; } @@ -867,29 +936,24 @@ bool HttpClient::send() { // ============ method ============ { zend::String str_method; - const char *method; + const char *_method; size_t method_len; if (zmethod) { str_method = zmethod; - method = str_method.val(); + _method = str_method.val(); method_len = str_method.len(); } else { - method = zbody ? "POST" : "GET"; - method_len = strlen(method); + _method = zbody ? "POST" : "GET"; + method_len = strlen(_method); } - this->method = http_server::get_method(method, method_len); - buffer->append(method, method_len); + this->method = http_server::get_method(_method, method_len); + buffer->append(_method, method_len); buffer->append(ZEND_STRL(" ")); } // ============ path & proxy ============ bool require_proxy_authentication = false; -#ifdef SW_USE_OPENSSL - if (socket->http_proxy && !socket->ssl_is_enable()) -#else - if (socket->http_proxy) -#endif - { + if (socket->http_proxy && !socket->ssl_is_enable()) { const static char *pre = "https://"; char *_host = (char *) host.c_str(); size_t _host_len = host.length(); @@ -928,15 +992,10 @@ bool HttpClient::send() { } else { // See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.23 const std::string *_host; - std::string __host; -#ifndef SW_USE_OPENSSL - if (port != 80) -#else - if (!ssl ? port != 80 : port != 443) -#endif - { - __host = std_string::format("%s:%u", host.c_str(), port); - _host = &__host; + std::string real_host; + if (!ssl ? port != 80 : port != 443) { + real_host = std_string::format("%s:%u", host.c_str(), port); + _host = &real_host; } else { _host = &host; } @@ -956,16 +1015,24 @@ bool HttpClient::send() { // ignore custom Content-Length value continue; } + + if (SW_STRCASEEQ(key, keylen, "Accept-Encoding")) { +#ifdef SW_HAVE_COMPRESSION + header_flag |= HTTP_HEADER_ACCEPT_ENCODING; +#else + php_swoole_error(E_WARNING, "Missing a compression package, 'Accept-Encoding' is ignored"); + continue; +#endif + } + zend::String str_value(zvalue); add_headers(buffer, key, keylen, str_value.val(), str_value.len()); if (SW_STRCASEEQ(key, keylen, "Connection")) { header_flag |= HTTP_HEADER_CONNECTION; if (SW_STRCASEEQ(str_value.val(), str_value.len(), "close")) { - keep_alive = 0; + keep_alive = false; } - } else if (SW_STRCASEEQ(key, keylen, "Accept-Encoding")) { - header_flag |= HTTP_HEADER_ACCEPT_ENCODING; } } SW_HASHTABLE_FOREACH_END(); @@ -998,6 +1065,10 @@ bool HttpClient::send() { #else #ifdef SW_HAVE_BROTLI ZEND_STRL("br") +#else +#ifdef SW_HAVE_ZSTD + ZEND_STRL("zstd") +#endif #endif #endif #endif @@ -1024,7 +1095,7 @@ bool HttpClient::send() { buffer->append(key, keylen); buffer->append("=", 1); - int encoded_value_len; + size_t encoded_value_len; encoded_value = php_swoole_url_encode(str_value.val(), str_value.len(), &encoded_value_len); if (encoded_value) { buffer->append(encoded_value, encoded_value_len); @@ -1041,18 +1112,17 @@ bool HttpClient::send() { // ============ multipart/form-data ============ if ((has_upload_files = (php_swoole_array_length_safe(zupload_files) > 0))) { char header_buf[2048]; - char boundary_str[SW_HTTP_CLIENT_BOUNDARY_TOTAL_SIZE]; - int n; + char boundary_str[SW_HTTP_CLIENT_BOUNDARY_TOTAL_SIZE + 1]; // ============ content-type ============ memcpy(boundary_str, SW_HTTP_CLIENT_BOUNDARY_PREKEY, sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1); swoole_random_string(boundary_str + sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY) - 1, sizeof(boundary_str) - sizeof(SW_HTTP_CLIENT_BOUNDARY_PREKEY)); - n = sw_snprintf(header_buf, - sizeof(header_buf), - "Content-Type: multipart/form-data; boundary=%.*s\r\n", - (int) (sizeof(boundary_str) - 1), - boundary_str); + ssize_t n = sw_snprintf(header_buf, + sizeof(header_buf), + "Content-Type: multipart/form-data; boundary=%.*s\r\n", + (int) (sizeof(boundary_str) - 1), + boundary_str); buffer->append(header_buf, n); // ============ content-length ============ @@ -1085,16 +1155,16 @@ bool HttpClient::send() { // upload files SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) { HashTable *ht = Z_ARRVAL_P(zvalue); - if (!(zname = zend_hash_str_find(ht, ZEND_STRL("name")))) { + if (!((zname = zend_hash_str_find(ht, ZEND_STRL("name"))))) { continue; } - if (!(zfilename = zend_hash_str_find(ht, ZEND_STRL("filename")))) { + if (!((zfilename = zend_hash_str_find(ht, ZEND_STRL("filename"))))) { continue; } - if (!(zsize = zend_hash_str_find(ht, ZEND_STRL("size")))) { + if (!((zsize = zend_hash_str_find(ht, ZEND_STRL("size"))))) { continue; } - if (!(ztype = zend_hash_str_find(ht, ZEND_STRL("type")))) { + if (!((ztype = zend_hash_str_find(ht, ZEND_STRL("type"))))) { continue; } // strlen("%.*s")*4 = 16 @@ -1136,22 +1206,22 @@ bool HttpClient::send() { { // upload files SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zupload_files), key, keylen, keytype, zvalue) { - if (!(zname = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("name")))) { + if (!((zname = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("name"))))) { continue; } - if (!(zfilename = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("filename")))) { + if (!((zfilename = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("filename"))))) { continue; } /** * from disk file */ - if (!(zcontent = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("content")))) { + if (!((zcontent = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("content"))))) { // file path - if (!(zpath = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("path")))) { + if (!((zpath = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("path"))))) { continue; } // file offset - if (!(zoffset = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("offset")))) { + if (!((zoffset = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("offset"))))) { continue; } zcontent = nullptr; @@ -1159,10 +1229,10 @@ bool HttpClient::send() { zpath = nullptr; zoffset = nullptr; } - if (!(zsize = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("size")))) { + if (!((zsize = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("size"))))) { continue; } - if (!(ztype = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("type")))) { + if (!((ztype = zend_hash_str_find(Z_ARRVAL_P(zvalue), ZEND_STRL("type"))))) { continue; } /** @@ -1214,7 +1284,7 @@ bool HttpClient::send() { if (socket->send_all(header_buf, n) != n) { goto _send_fail; } - wait = true; + wait_response = true; return true; } // ============ x-www-form-urlencoded or raw ============ @@ -1236,10 +1306,10 @@ bool HttpClient::send() { add_content_length(buffer, 0); } } else { - char *body; - size_t body_length = php_swoole_get_send_data(zbody, &body); - add_content_length(buffer, body_length); - buffer->append(body, body_length); + auto sdata = zval_get_string(zbody); + add_content_length(buffer, ZSTR_LEN(sdata)); + buffer->append(ZSTR_VAL(sdata), ZSTR_LEN(sdata)); + zend_string_release(sdata); } } // ============ no body ============ @@ -1264,26 +1334,29 @@ bool HttpClient::send() { if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { _send_fail: - set_error(socket->errCode, socket->errMsg, HTTP_CLIENT_ESTATUS_SEND_FAILED); + set_error(socket->errCode, socket->errMsg, HTTP_ESTATUS_SEND_FAILED); close(); return false; } - wait = true; + wait_response = true; return true; } -bool HttpClient::exec(std::string _path) { +bool Client::exec(const std::string &_path) { path = _path; // bzero when make a new reqeust resolve_context_ = {}; if (use_default_port) { resolve_context_.with_port = true; } - if (defer) { - return send(); - } SW_LOOP_N(max_retries + 1) { - if (send() == false || recv() == false) { + if (send_request() == false) { + return false; + } + if (defer) { + return true; + } + if (recv_response() == false) { return false; } if (max_retries > 0 && @@ -1296,98 +1369,10 @@ bool HttpClient::exec(std::string _path) { return false; } -bool HttpClient::recv(double timeout) { - if (!wait) { - return false; - } - if (!socket || !socket->is_connected()) { - swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION); - zend_update_property_long( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available"); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_SERVER_RESET); +bool Client::recv_response(double timeout) { + if (!wait_response) { return false; } - if (!recv_http_response(timeout)) { - zend_update_property_long( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg); - zend_update_property_long( - swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - socket->errCode == ETIMEDOUT ? HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT : HTTP_CLIENT_ESTATUS_SERVER_RESET); - close(); - return false; - } - /** - * TODO: Sec-WebSocket-Accept check - */ - if (websocket) { - socket->open_length_check = 1; - socket->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN; - socket->protocol.package_length_offset = 0; - socket->protocol.package_body_offset = 0; - socket->protocol.get_package_length = websocket::get_package_length; - } - // handler keep alive - if (!keep_alive && !websocket) { - close(); - } else { - reset(); - } - - return true; -} - -void HttpClient::recv(zval *zframe, double timeout) { - SW_ASSERT(websocket); - ZVAL_FALSE(zframe); - if (!socket || !socket->is_connected()) { - swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION); - zend_update_property_long( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available"); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_SERVER_RESET); - return; - } - - ssize_t retval = socket->recv_packet(timeout); - if (retval <= 0) { - zend_update_property_long( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_SERVER_RESET); - if (socket->errCode != ETIMEDOUT) { - close(); - } - } else { - String msg; - msg.length = retval; - msg.str = socket->get_read_buffer()->str; -#ifdef SW_HAVE_ZLIB - php_swoole_websocket_frame_unpack_ex(&msg, zframe, websocket_compression); -#else - php_swoole_websocket_frame_unpack(&msg, zframe); -#endif - zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("fd"), socket->get_fd()); - } -} - -bool HttpClient::recv_http_response(double timeout) { ssize_t retval = 0; size_t total_bytes = 0, parsed_n = 0; String *buffer = socket->get_read_buffer(); @@ -1395,27 +1380,28 @@ bool HttpClient::recv_http_response(double timeout) { off_t header_crlf_offset = 0; // re-init http response parser - swoole_http_parser_init(&parser, PHP_HTTP_RESPONSE); - parser.data = this; + swoole_llhttp_parser_init(&parser, HTTP_RESPONSE, (void *) this); if (timeout == 0) { - timeout = socket->get_timeout(Socket::TIMEOUT_READ); + timeout = response_timeout == 0 ? network::Socket::default_read_timeout : response_timeout; } - Socket::timeout_controller tc(socket, timeout, Socket::TIMEOUT_READ); + Socket::TimeoutController tc(socket, timeout, SW_TIMEOUT_READ); + bool success = false; while (true) { - if (sw_unlikely(tc.has_timedout(Socket::TIMEOUT_READ))) { - return false; + if (sw_unlikely(tc.has_timedout(SW_TIMEOUT_READ))) { + break; } retval = socket->recv(buffer->str + buffer->length, buffer->size - buffer->length); if (sw_unlikely(retval <= 0)) { if (retval == 0) { socket->set_err(ECONNRESET); - if (total_bytes > 0 && !swoole_http_should_keep_alive(&parser)) { - http_parser_on_message_complete(&parser); - return true; + if (total_bytes > 0 && !llhttp_should_keep_alive(&parser)) { + llhttp_finish(&parser); + success = true; + break; } } - return false; + break; } if (!header_completed) { @@ -1425,7 +1411,7 @@ bool HttpClient::recv_http_response(double timeout) { if (buffer->length == buffer->size) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_HTTP_INVALID_PROTOCOL, "Http header too large"); socket->set_err(SW_ERROR_HTTP_INVALID_PROTOCOL); - return false; + break; } header_crlf_offset = buffer->length > 4 ? buffer->length - 4 : 0; continue; @@ -1438,56 +1424,108 @@ bool HttpClient::recv_http_response(double timeout) { } total_bytes += retval; - parsed_n = swoole_http_parser_execute(&parser, &http_parser_settings, buffer->str, retval); + parsed_n = swoole_llhttp_parser_execute(&parser, &http_parser_settings, buffer->str, retval); swoole_trace_log(SW_TRACE_HTTP_CLIENT, "parsed_n=%ld, retval=%ld, total_bytes=%ld, completed=%d", parsed_n, retval, total_bytes, - parser.state == s_start_res); - if (parser.state == s_start_res) { - // handle redundant data (websocket packet) - if (parser.upgrade && (size_t) retval > parsed_n + SW_WEBSOCKET_HEADER_LEN) { - buffer->length = retval; - buffer->offset = parsed_n; - buffer->reduce(parsed_n); - } - return true; + completed); + + if (sw_unlikely(socket->get_socket()->close_wait)) { + success = false; + break; } - if (sw_unlikely(parser.state == s_dead)) { + + if (sw_likely(parser.error == HPE_OK)) { + if (sw_unlikely(event_stream && llhttp_message_needs_eof(&parser)) == 1) { + llhttp_finish(&parser); + } + + if (completed) { + if (parser.upgrade && (size_t) retval > parsed_n + SW_WEBSOCKET_HEADER_LEN) { + buffer->length = retval; + buffer->offset = parsed_n; + buffer->reduce(parsed_n); + } + success = true; + break; + } + } else { socket->set_err(SW_ERROR_HTTP_INVALID_PROTOCOL); - return false; + break; + } + } + + if (!success) { + php_swoole_socket_set_error_properties(zobject, socket); + zend::object_set(zobject, + ZEND_STRL("statusCode"), + socket->errCode == ETIMEDOUT ? HTTP_ESTATUS_REQUEST_TIMEOUT : HTTP_ESTATUS_SERVER_RESET); + close(); + return false; + } + /** + * TODO: Sec-WebSocket-Accept check + */ + if (websocket) { + socket->open_length_check = true; + socket->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN; + socket->protocol.package_length_offset = 0; + socket->protocol.package_body_offset = 0; + socket->protocol.get_package_length = websocket::get_package_length; + } + // handler keep alive + if (!websocket && (!keep_alive || connection_close)) { + close(); + } else { + reset(); + } + + return true; +} + +void Client::recv_websocket_frame(zval *return_value, double timeout) { + WebSocket::recv_frame(websocket_settings, continue_frame_buffer, socket, return_value, timeout); + if (ZVAL_IS_EMPTY_STRING(return_value)) { + close(); + return; + } + if (sw_unlikely(ZVAL_IS_FALSE(return_value))) { + php_swoole_socket_set_error_properties(zobject, socket); + zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); + if (socket->errCode != ETIMEDOUT) { + close(); } + return; + } + if (sw_unlikely(ZVAL_IS_NULL(return_value))) { + ZVAL_FALSE(return_value); } } -bool HttpClient::upgrade(std::string path) { +bool Client::upgrade(const std::string &_path) { defer = false; - if (!websocket) { - char buf[SW_WEBSOCKET_KEY_LENGTH + 1]; - zval *zheaders = sw_zend_read_and_convert_property_array( - swoole_http_client_coro_ce, zobject, ZEND_STRL("requestHeaders"), 0); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("requestMethod"), "GET"); - create_token(SW_WEBSOCKET_KEY_LENGTH, buf); - add_assoc_string(zheaders, "Connection", (char *) "Upgrade"); - add_assoc_string(zheaders, "Upgrade", (char *) "websocket"); - add_assoc_string(zheaders, "Sec-WebSocket-Version", (char *) SW_WEBSOCKET_VERSION); - add_assoc_str_ex(zheaders, - ZEND_STRL("Sec-WebSocket-Key"), - php_base64_encode((const unsigned char *) buf, SW_WEBSOCKET_KEY_LENGTH)); + char buf[SW_WEBSOCKET_KEY_LENGTH + 1]; + zval *zheaders = + sw_zend_read_and_convert_property_array(swoole_http_client_coro_ce, zobject, ZEND_STRL("requestHeaders"), 0); + zend_update_property_string(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("requestMethod"), "GET"); + create_token(SW_WEBSOCKET_KEY_LENGTH, buf); + add_assoc_string(zheaders, "Connection", "Upgrade"); + add_assoc_string(zheaders, "Upgrade", "websocket"); + add_assoc_string(zheaders, "Sec-WebSocket-Version", SW_WEBSOCKET_VERSION); + add_assoc_str_ex( + zheaders, ZEND_STRL("Sec-WebSocket-Key"), php_base64_encode((const uchar *) buf, SW_WEBSOCKET_KEY_LENGTH)); #ifdef SW_HAVE_ZLIB - if (websocket_compression) { - add_assoc_string(zheaders, "Sec-Websocket-Extensions", (char *) SW_WEBSOCKET_EXTENSION_DEFLATE); - } -#endif - exec(path); + if (websocket_settings.compression) { + add_assoc_string(zheaders, "Sec-Websocket-Extensions", SW_WEBSOCKET_EXTENSION_DEFLATE); } - return websocket; +#endif + return exec(_path); } -bool HttpClient::push(zval *zdata, zend_long opcode, uint8_t flags) { - if (!websocket) { +bool Client::push(zval *zdata, zend_long opcode, uint8_t flags, zend_long code) { + if (sw_unlikely(!websocket)) { swoole_set_last_error(SW_ERROR_WEBSOCKET_HANDSHAKE_FAILED); php_swoole_fatal_error(E_WARNING, "websocket handshake failed, cannot push data"); zend_update_property_long( @@ -1496,46 +1534,33 @@ bool HttpClient::push(zval *zdata, zend_long opcode, uint8_t flags) { SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "websocket handshake failed, cannot push data"); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_CONNECT_FAILED); + zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_CONNECT_FAILED); return false; } - if (!socket || !socket->is_connected()) { - swoole_set_last_error(SW_ERROR_CLIENT_NO_CONNECTION); + + String *buffer = socket->get_write_buffer(); + FrameObject frame(zdata, opcode, flags, code); + + if (websocket_settings.mask) { + frame.flags |= WebSocket::FLAG_MASK; + } + + if (accept_websocket_compression) { + sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); + } + + if (sw_unlikely(!frame.pack(buffer))) { + swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); zend_update_property_long( swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), swoole_get_last_error()); zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "connection is not available"); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_SERVER_RESET); + swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "websocket frame pack failed"); return false; } - String *buffer = socket->get_write_buffer(); - buffer->clear(); - if (php_swoole_websocket_frame_is_object(zdata)) { - if (php_swoole_websocket_frame_object_pack(buffer, zdata, websocket_mask, websocket_compression) < 0) { - return false; - } - } else { - if (php_swoole_websocket_frame_pack(buffer, zdata, opcode, flags, websocket_mask, websocket_compression) < 0) { - return false; - } - } - if (socket->send_all(buffer->str, buffer->length) != (ssize_t) buffer->length) { - zend_update_property_long( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), socket->errCode); - zend_update_property_string( - swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), socket->errMsg); - zend_update_property_long(swoole_http_client_coro_ce, - SW_Z8_OBJ_P(zobject), - ZEND_STRL("statusCode"), - HTTP_CLIENT_ESTATUS_SERVER_RESET); + php_swoole_socket_set_error_properties(zobject, socket); + zend::object_set(zobject, ZEND_STRL("statusCode"), HTTP_ESTATUS_SERVER_RESET); close(); return false; } else { @@ -1543,8 +1568,10 @@ bool HttpClient::push(zval *zdata, zend_long opcode, uint8_t flags) { } } -void HttpClient::reset() { - wait = false; +void Client::reset() { + wait_response = false; + completed = false; + event_stream = false; #ifdef SW_HAVE_COMPRESSION compress_method = HTTP_COMPRESS_NONE; compression_error = false; @@ -1560,13 +1587,18 @@ void HttpClient::reset() { BrotliDecoderDestroyInstance(brotli_decoder_state); brotli_decoder_state = nullptr; } +#endif +#ifdef SW_HAVE_ZSTD + if (zstd_stream) { + ZSTD_freeDStream(zstd_stream); + zstd_stream = nullptr; + } #endif if (has_upload_files) { zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("uploadFiles")); } if (download_file != nullptr) { - delete download_file; - download_file = nullptr; + download_file.reset(); download_file_name.release(); download_offset = 0; zend_update_property_null(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("downloadFile")); @@ -1574,65 +1606,73 @@ void HttpClient::reset() { } } -bool HttpClient::close(const bool should_be_reset) { - Socket *_socket = socket; +void Client::socket_dtor() { + delete tmp_write_buffer; + tmp_write_buffer = socket->pop_write_buffer(); + socket = nullptr; + zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); + zend_update_property_null(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("socket")); + zval_ptr_dtor(&zsocket); + ZVAL_NULL(&zsocket); +} + +/** + * The socket member variables cannot be read after Socket::close(), + * MUST return to the php layer, otherwise a memory error will occur. + * The client, mysql client, http2 client also need to follow this coding convention. + */ +bool Client::close(const bool should_be_reset) { + SocketImpl *_socket = socket; if (!_socket) { return false; } + if (in_callback) { + _socket->get_socket()->close_wait = 1; + return true; + } - zend_update_property_bool(swoole_http_client_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); - if (!_socket->has_bound()) { - if (should_be_reset) { - reset(); - } - // reset the properties that depend on the connection - websocket = false; -#ifdef SW_HAVE_ZLIB - websocket_compression = false; -#endif - if (tmp_write_buffer) { - delete tmp_write_buffer; - } - tmp_write_buffer = socket->pop_write_buffer(); - socket = nullptr; + zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 0); + if (!_socket->close()) { + php_swoole_socket_set_error_properties(zobject, _socket); + return false; + } + if (should_be_reset) { + reset(); } - php_swoole_client_coro_socket_free(_socket); return true; } -HttpClient::~HttpClient() { +Client::~Client() { close(); - if (body) { - delete body; - } - if (tmp_write_buffer) { - delete tmp_write_buffer; - } + delete body; + delete tmp_write_buffer; + delete write_func; } -static sw_inline HttpClientObject *php_swoole_http_client_coro_fetch_object(zend_object *obj) { - return (HttpClientObject *) ((char *) obj - swoole_http_client_coro_handlers.offset); +static sw_inline HttpClientObject *http_client_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - + swoole_http_client_coro_handlers.offset); } -static sw_inline HttpClient *php_swoole_get_phc(zval *zobject) { - HttpClient *phc = php_swoole_http_client_coro_fetch_object(Z_OBJ_P(zobject))->phc; +static sw_inline Client *http_client_coro_get_client(const zval *zobject) { + Client *phc = http_client_coro_fetch_object(Z_OBJ_P(zobject))->client; if (UNEXPECTED(!phc)) { - php_swoole_fatal_error(E_ERROR, "you must call Http Client constructor first"); + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return phc; } -static void php_swoole_http_client_coro_free_object(zend_object *object) { - HttpClientObject *hcc = php_swoole_http_client_coro_fetch_object(object); - if (hcc->phc) { - delete hcc->phc; - hcc->phc = nullptr; +static void http_client_coro_free_object(zend_object *object) { + HttpClientObject *hcc = http_client_coro_fetch_object(object); + if (hcc->client) { + delete hcc->client; + hcc->client = nullptr; } zend_object_std_dtor(&hcc->std); } -static zend_object *php_swoole_http_client_coro_create_object(zend_class_entry *ce) { - HttpClientObject *hcc = (HttpClientObject *) zend_object_alloc(sizeof(HttpClientObject), ce); +static zend_object *http_client_coro_create_object(zend_class_entry *ce) { + auto *hcc = (HttpClientObject *) zend_object_alloc(sizeof(HttpClientObject), ce); zend_object_std_init(&hcc->std, ce); object_properties_init(&hcc->std, ce); hcc->std.handlers = &swoole_http_client_coro_handlers; @@ -1647,11 +1687,15 @@ void php_swoole_http_client_coro_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_client_coro); SW_SET_CLASS_CLONEABLE(swoole_http_client_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_client_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_client_coro, - php_swoole_http_client_coro_create_object, - php_swoole_http_client_coro_free_object, - HttpClientObject, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_http_client_coro, http_client_coro_create_object, http_client_coro_free_object, HttpClientObject, std); + zend_add_parameter_attribute( + (zend_function *) zend_hash_str_find_ptr(&swoole_http_client_coro_ce->function_table, SW_STRL("setbasicauth")), + 1, + ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), + 0); + + zend_declare_property_null(swoole_http_client_coro_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); // client status zend_declare_property_long(swoole_http_client_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); @@ -1686,18 +1730,18 @@ void php_swoole_http_client_coro_minit(int module_number) { nullptr, swoole_exception); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED", HTTP_CLIENT_ESTATUS_CONNECT_FAILED); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT", HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET", HTTP_CLIENT_ESTATUS_SERVER_RESET); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SEND_FAILED", HTTP_CLIENT_ESTATUS_SEND_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_CONNECT_FAILED", HTTP_ESTATUS_CONNECT_FAILED); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_REQUEST_TIMEOUT", HTTP_ESTATUS_REQUEST_TIMEOUT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET", HTTP_ESTATUS_SERVER_RESET); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HTTP_CLIENT_ESTATUS_SEND_FAILED", HTTP_ESTATUS_SEND_FAILED); } static PHP_METHOD(swoole_http_client_coro, __construct) { - HttpClientObject *hcc = php_swoole_http_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); + HttpClientObject *hcc = http_client_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); char *host; size_t host_len; zend_long port = 0; - zend_bool ssl = 0; + zend_bool ssl = false; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 3) Z_PARAM_STRING(host, host_len) @@ -1714,23 +1758,13 @@ static PHP_METHOD(swoole_http_client_coro, __construct) { zend_throw_exception_ex(swoole_http_client_coro_exception_ce, EINVAL, "host is empty"); RETURN_FALSE; } - // check ssl -#ifndef SW_USE_OPENSSL - if (ssl) { - zend_throw_exception_ex( - swoole_http_client_coro_exception_ce, - EPROTONOSUPPORT, - "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"); - RETURN_FALSE; - } -#endif - hcc->phc = new HttpClient(ZEND_THIS, std::string(host, host_len), port, ssl); + hcc->client = new Client(ZEND_THIS, std::string(host, host_len), port, ssl); } static PHP_METHOD(swoole_http_client_coro, __destruct) {} static PHP_METHOD(swoole_http_client_coro, set) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); zval *zset; ZEND_PARSE_PARAMETERS_START(1, 1) @@ -1749,14 +1783,14 @@ static PHP_METHOD(swoole_http_client_coro, set) { } static PHP_METHOD(swoole_http_client_coro, getDefer) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); RETURN_BOOL(phc->defer); } static PHP_METHOD(swoole_http_client_coro, setDefer) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); - zend_bool defer = 1; + Client *phc = http_client_coro_get_client(ZEND_THIS); + zend_bool defer = true; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL @@ -1796,7 +1830,7 @@ static PHP_METHOD(swoole_http_client_coro, setHeaders) { } static PHP_METHOD(swoole_http_client_coro, setBasicAuth) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *username, *password; size_t username_len, password_len; @@ -1950,7 +1984,7 @@ static PHP_METHOD(swoole_http_client_coro, addData) { } static PHP_METHOD(swoole_http_client_coro, execute) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; @@ -1962,7 +1996,7 @@ static PHP_METHOD(swoole_http_client_coro, execute) { } static PHP_METHOD(swoole_http_client_coro, get) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; @@ -1976,7 +2010,7 @@ static PHP_METHOD(swoole_http_client_coro, get) { } static PHP_METHOD(swoole_http_client_coro, post) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; zval *post_data; @@ -1993,7 +2027,7 @@ static PHP_METHOD(swoole_http_client_coro, post) { } static PHP_METHOD(swoole_http_client_coro, download) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *path; size_t path_len; zval *download_file; @@ -2013,7 +2047,7 @@ static PHP_METHOD(swoole_http_client_coro, download) { } static PHP_METHOD(swoole_http_client_coro, upgrade) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); char *path = nullptr; size_t path_len = 0; @@ -2025,7 +2059,11 @@ static PHP_METHOD(swoole_http_client_coro, upgrade) { } static PHP_METHOD(swoole_http_client_coro, push) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); + if (!phc->is_available()) { + RETURN_FALSE; + } + zval *zdata; zend_long opcode = WebSocket::OPCODE_TEXT; zval *zflags = nullptr; @@ -2041,12 +2079,16 @@ static PHP_METHOD(swoole_http_client_coro, push) { if (zflags != nullptr) { flags = zval_get_long(zflags); } - + SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); RETURN_BOOL(phc->push(zdata, opcode, flags & WebSocket::FLAGS_ALL)); } static PHP_METHOD(swoole_http_client_coro, recv) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); + if (!phc->is_available()) { + RETURN_FALSE; + } + double timeout = 0; ZEND_PARSE_PARAMETERS_START(0, 1) @@ -2054,17 +2096,18 @@ static PHP_METHOD(swoole_http_client_coro, recv) { Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); + if (phc->websocket) { - phc->recv(return_value, timeout); - return; + phc->recv_websocket_frame(return_value, timeout); } else { - RETURN_BOOL(phc->recv(timeout)); + RETURN_BOOL(phc->recv_response(timeout)); } } static PHP_METHOD(swoole_http_client_coro, close) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); - + Client *phc = http_client_coro_get_client(ZEND_THIS); + SW_CLIENT_PRESERVE_SOCKET(&phc->zsocket); RETURN_BOOL(phc->close()); } @@ -2085,23 +2128,61 @@ static PHP_METHOD(swoole_http_client_coro, getStatusCode) { } static PHP_METHOD(swoole_http_client_coro, getHeaderOut) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); phc->get_header_out(return_value); } static PHP_METHOD(swoole_http_client_coro, getsockname) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getsockname(return_value); } static PHP_METHOD(swoole_http_client_coro, getpeername) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getpeername(return_value); } -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_http_client_coro, getPeerCert) { - HttpClient *phc = php_swoole_get_phc(ZEND_THIS); + Client *phc = http_client_coro_get_client(ZEND_THIS); phc->getpeercert(return_value); } -#endif + +static PHP_METHOD(swoole_http_client_coro, ping) { + Client *phc = http_client_coro_get_client(ZEND_THIS); + if (!phc->is_available() || !phc->websocket) { + RETURN_FALSE; + } + + zend_string *zdata = zend_empty_string; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR(zdata) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + zval zpayload = {}; + ZVAL_STR(&zpayload, zdata); + RETURN_BOOL(phc->push(&zpayload, WebSocket::OPCODE_PING, WebSocket::FLAG_FIN)); +} + +static PHP_METHOD(swoole_http_client_coro, disconnect) { + Client *phc = http_client_coro_get_client(ZEND_THIS); + if (!phc->is_available() || !phc->websocket) { + RETURN_FALSE; + } + + zend_long code = WebSocket::CLOSE_NORMAL; + zend_string *reason = zend_empty_string; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(code) + Z_PARAM_STR(reason) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + zval zpayload = {}; + ZVAL_STR(&zpayload, reason); + + RETVAL_BOOL(phc->push(&zpayload, WebSocket::OPCODE_CLOSE, WebSocket::FLAG_FIN, code)); + phc->close(); // Regardless of the outcome, the connection will be closed. +} diff --git a/ext-src/swoole_http_cookie.cc b/ext-src/swoole_http_cookie.cc new file mode 100644 index 0000000000..82c9e45c44 --- /dev/null +++ b/ext-src/swoole_http_cookie.cc @@ -0,0 +1,440 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ +#include "php_swoole_http_server.h" + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_http_cookie_arginfo.h" +END_EXTERN_C() + +using HttpCookie = swoole::http::Cookie; + +#define ILLEGAL_COOKIE_CHARACTER_PRINT "\",\", \";\", \" \", \"\\t\", \"\\r\", \"\\n\", \"\\013\", or \"\\014\"" +#define ILLEGAL_COOKIE_CHARACTER ",; \t\r\n\013\014" + +static constexpr zend_long maxValidSeconds = 253402300800; + +zend_class_entry *swoole_http_cookie_ce; +static zend_object_handlers swoole_http_cookie_handlers; + +struct HttpCookieObject { + HttpCookie *cookie; + zend_object std; +}; + +static sw_inline HttpCookieObject *php_swoole_http_cookie_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_http_cookie_handlers.offset); +} + +static HttpCookie *php_swoole_http_get_cookie(const zval *zobject) { + return php_swoole_http_cookie_fetch_object(Z_OBJ_P(zobject))->cookie; +} + +HttpCookie *php_swoole_http_get_cooke_safety(const zval *zobject) { + HttpCookie *cookie = php_swoole_http_get_cookie(zobject); + if (!cookie) { + swoole_set_last_error(SW_ERROR_HTTP_COOKIE_UNAVAILABLE); + return nullptr; + } + return cookie; +} + +void php_swoole_http_response_set_cookie(const zval *zobject, HttpCookie *cookie) { + php_swoole_http_cookie_fetch_object(Z_OBJ_P(zobject))->cookie = cookie; +} + +static zend_object *php_swoole_http_cookie_create_object(zend_class_entry *ce) { + auto *httpCookieObject = static_cast(zend_object_alloc(sizeof(HttpCookieObject), ce)); + zend_object_std_init(&httpCookieObject->std, ce); + object_properties_init(&httpCookieObject->std, ce); + httpCookieObject->std.handlers = &swoole_http_cookie_handlers; + return &httpCookieObject->std; +} + +static void php_swoole_http_cookie_free_object(zend_object *object) { + auto *httpCookieObject = php_swoole_http_cookie_fetch_object(object); + delete httpCookieObject->cookie; +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_http_cookie, __construct); +static PHP_METHOD(swoole_http_cookie, withName); +static PHP_METHOD(swoole_http_cookie, withValue); +static PHP_METHOD(swoole_http_cookie, withExpires); +static PHP_METHOD(swoole_http_cookie, withPath); +static PHP_METHOD(swoole_http_cookie, withDomain); +static PHP_METHOD(swoole_http_cookie, withSecure); +static PHP_METHOD(swoole_http_cookie, withHttpOnly); +static PHP_METHOD(swoole_http_cookie, withSameSite); +static PHP_METHOD(swoole_http_cookie, withPriority); +static PHP_METHOD(swoole_http_cookie, withPartitioned); +static PHP_METHOD(swoole_http_cookie, toArray); +static PHP_METHOD(swoole_http_cookie, toString); +static PHP_METHOD(swoole_http_cookie, reset); +SW_EXTERN_C_END + +// clang-format off +const zend_function_entry swoole_http_cookie_methods[] = +{ + PHP_ME(swoole_http_cookie, __construct, arginfo_class_Swoole_Http_Cookie___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withName, arginfo_class_Swoole_Http_Cookie_withName, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withValue, arginfo_class_Swoole_Http_Cookie_withValue, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withExpires, arginfo_class_Swoole_Http_Cookie_withExpires, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withPath, arginfo_class_Swoole_Http_Cookie_withPath, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withDomain, arginfo_class_Swoole_Http_Cookie_withDomain, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withSecure, arginfo_class_Swoole_Http_Cookie_withSecure, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withHttpOnly, arginfo_class_Swoole_Http_Cookie_withHttpOnly, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withSameSite, arginfo_class_Swoole_Http_Cookie_withSameSite, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withPriority, arginfo_class_Swoole_Http_Cookie_withPriority, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, withPartitioned, arginfo_class_Swoole_Http_Cookie_withPartitioned, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, toString, arginfo_class_Swoole_Http_Cookie_toString, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, toArray, arginfo_class_Swoole_Http_Cookie_toArray, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_cookie, reset, arginfo_class_Swoole_Http_Cookie_reset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_http_cookie_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_http_cookie, "Swoole\\Http\\Cookie", nullptr, swoole_http_cookie_methods); + SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_cookie); + SW_SET_CLASS_CLONEABLE(swoole_http_cookie, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_cookie, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_cookie, + php_swoole_http_cookie_create_object, + php_swoole_http_cookie_free_object, + HttpCookieObject, + std); +} + +#define HTTP_COOKIE_WITH_STR(field) \ + if (field) { \ + zend_string_release(field); \ + } \ + if (_##field && ZSTR_LEN(_##field) > 0) { \ + zend_string_addref(_##field); \ + field = _##field; \ + } else { \ + field = nullptr; \ + } \ + return this; + +HttpCookie *HttpCookie::withName(zend_string *_name) { + HTTP_COOKIE_WITH_STR(name); +} + +HttpCookie *HttpCookie::withValue(zend_string *_value) { + HTTP_COOKIE_WITH_STR(value); +} + +HttpCookie *HttpCookie::withDomain(zend_string *_domain) { + HTTP_COOKIE_WITH_STR(domain); +} + +HttpCookie *HttpCookie::withPath(zend_string *_path) { + HTTP_COOKIE_WITH_STR(path); +} + +HttpCookie *HttpCookie::withSameSite(zend_string *_sameSite) { + HTTP_COOKIE_WITH_STR(sameSite); +} + +HttpCookie *HttpCookie::withPriority(zend_string *_priority) { + HTTP_COOKIE_WITH_STR(priority); +} + +HttpCookie *HttpCookie::withExpires(zend_long _expires) { + expires = _expires; + return this; +} + +HttpCookie *HttpCookie::withSecure(zend_bool _secure) { + secure = _secure; + return this; +} + +HttpCookie *HttpCookie::withHttpOnly(zend_bool _httpOnly) { + httpOnly = _httpOnly; + return this; +} + +HttpCookie *HttpCookie::withPartitioned(zend_bool _partitioned) { + partitioned = _partitioned; + return this; +} + +zend_string *HttpCookie::toString() { + zend_string *date = nullptr; + if (name == nullptr || ZSTR_LEN(name) == 0) { + php_swoole_error(E_WARNING, "The name cannot be empty"); + return nullptr; + } + + if (strpbrk(ZSTR_VAL(name), "=" ILLEGAL_COOKIE_CHARACTER) != nullptr) { + php_swoole_error(E_WARNING, "The name cannot contain \"=\", " ILLEGAL_COOKIE_CHARACTER_PRINT); + return nullptr; + } + + smart_str_append(&buffer_, name); + + if (!value) { + smart_str_appends(&buffer_, "=deleted; expires="); + + date = php_format_date(ZEND_STRL("D, d-M-Y H:i:s T"), 1, 0); + smart_str_append(&buffer_, date); + smart_str_appends(&buffer_, "; Max-Age=0"); + zend_string_free(date); + } else { + if (!encode_ && strpbrk(ZSTR_VAL(value), ILLEGAL_COOKIE_CHARACTER) != nullptr) { + php_swoole_error(E_WARNING, "The value cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); + return nullptr; + } + + smart_str_appendc(&buffer_, '='); + + if (encode_) { + zend_string *encoded_value = php_url_encode(ZSTR_VAL(value), ZSTR_LEN(value)); + smart_str_append(&buffer_, encoded_value); + zend_string_free(encoded_value); + } else { + smart_str_append(&buffer_, value); + } + + if (expires > 0) { + if (expires >= maxValidSeconds) { + php_swoole_error(E_WARNING, "The expires cannot have a year greater than 9999"); + return nullptr; + } + smart_str_appends(&buffer_, "; expires="); + date = php_format_date(ZEND_STRL("D, d-M-Y H:i:s T"), expires, 0); + smart_str_append(&buffer_, date); + smart_str_appends(&buffer_, "; Max-Age="); + + double diff = difftime(expires, php_time()); + smart_str_append_long(&buffer_, (zend_long) (diff >= 0 ? diff : 0)); + zend_string_free(date); + } + } + + if (path && ZSTR_LEN(path) > 0) { + if (strpbrk(ZSTR_VAL(path), ILLEGAL_COOKIE_CHARACTER) != nullptr) { + php_swoole_error(E_WARNING, "The path option cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); + return nullptr; + } + smart_str_appends(&buffer_, "; path="); + smart_str_append(&buffer_, path); + } + + if (domain && ZSTR_LEN(domain) > 0) { + if (strpbrk(ZSTR_VAL(domain), ILLEGAL_COOKIE_CHARACTER) != nullptr) { + php_swoole_error(E_WARNING, "The domain option cannot contain " ILLEGAL_COOKIE_CHARACTER_PRINT); + return nullptr; + } + smart_str_appends(&buffer_, "; domain="); + smart_str_append(&buffer_, domain); + } + + if (secure) { + smart_str_appends(&buffer_, "; secure"); + } + + if (httpOnly) { + smart_str_appends(&buffer_, "; HttpOnly"); + } + + if (sameSite && ZSTR_LEN(sameSite) > 0) { + smart_str_appends(&buffer_, "; SameSite="); + smart_str_append(&buffer_, sameSite); + } + + if (priority && ZSTR_LEN(priority) > 0) { + smart_str_appends(&buffer_, "; Priority="); + smart_str_append(&buffer_, priority); + } + + if (partitioned) { + smart_str_appends(&buffer_, "; Partitioned"); + } + + return smart_str_extract(&buffer_); +} + +void HttpCookie::reset() { + expires = 0; + secure = false; + httpOnly = false; + partitioned = false; + encode_ = true; + + if (name) { + zend_string_release(name); + name = nullptr; + } + + if (value) { + zend_string_release(value); + value = nullptr; + } + + if (path) { + zend_string_release(path); + path = nullptr; + } + + if (domain) { + zend_string_release(domain); + domain = nullptr; + } + + if (sameSite) { + zend_string_release(sameSite); + sameSite = nullptr; + } + + if (priority) { + zend_string_release(priority); + priority = nullptr; + } + + smart_str_free_ex(&buffer_, false); +} + +#define HTTP_COOKIE_ADD_STR_TO_ARRAY(field) \ + if (field) { \ + add_assoc_str(return_value, #field, field); \ + } else { \ + add_assoc_string(return_value, #field, ""); \ + } + +void HttpCookie::toArray(zval *return_value) const { + array_init(return_value); + + HTTP_COOKIE_ADD_STR_TO_ARRAY(name); + HTTP_COOKIE_ADD_STR_TO_ARRAY(value); + HTTP_COOKIE_ADD_STR_TO_ARRAY(path); + HTTP_COOKIE_ADD_STR_TO_ARRAY(domain); + HTTP_COOKIE_ADD_STR_TO_ARRAY(sameSite); + HTTP_COOKIE_ADD_STR_TO_ARRAY(priority); + + add_assoc_bool(return_value, "encode", encode_); + add_assoc_long(return_value, "expires", expires); + add_assoc_bool(return_value, "secure", secure); + add_assoc_bool(return_value, "httpOnly", httpOnly); + add_assoc_bool(return_value, "partitioned", partitioned); +} + +HttpCookie::~Cookie() { + reset(); +} + +static PHP_METHOD(swoole_http_cookie, __construct) { + zend_bool encode = true; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(encode) + ZEND_PARSE_PARAMETERS_END(); + + php_swoole_http_response_set_cookie(ZEND_THIS, new HttpCookie(encode)); +} + +#define PHP_METHOD_HTTP_COOKIE_WITH_STR(field) \ + zend_string *field; \ + HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); \ + \ + ZEND_PARSE_PARAMETERS_START(1, 1) \ + Z_PARAM_STR(field) \ + ZEND_PARSE_PARAMETERS_END(); \ + \ + cookie->with##field(field); \ + RETURN_ZVAL(ZEND_THIS, 1, 0); + +#define PHP_METHOD_HTTP_COOKIE_WITH_BOOL(field) \ + zend_bool field = false; \ + HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); \ + \ + ZEND_PARSE_PARAMETERS_START(0, 1) \ + Z_PARAM_OPTIONAL \ + Z_PARAM_BOOL(field) \ + ZEND_PARSE_PARAMETERS_END(); \ + \ + cookie->with##field(field); \ + RETURN_ZVAL(ZEND_THIS, 1, 0); + +static PHP_METHOD(swoole_http_cookie, withName) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(Name); +} + +static PHP_METHOD(swoole_http_cookie, withValue) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(Value); +} + +static PHP_METHOD(swoole_http_cookie, withExpires) { + zend_long expires = 0; + HttpCookie *cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(expires) + ZEND_PARSE_PARAMETERS_END(); + + cookie->withExpires(expires); + RETURN_ZVAL(ZEND_THIS, 1, 0); +} + +static PHP_METHOD(swoole_http_cookie, withPath) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(Path); +} + +static PHP_METHOD(swoole_http_cookie, withDomain) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(Domain); +} + +static PHP_METHOD(swoole_http_cookie, withSecure) { + PHP_METHOD_HTTP_COOKIE_WITH_BOOL(Secure); +} + +static PHP_METHOD(swoole_http_cookie, withHttpOnly) { + PHP_METHOD_HTTP_COOKIE_WITH_BOOL(HttpOnly); +} + +static PHP_METHOD(swoole_http_cookie, withSameSite) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(SameSite); +} + +static PHP_METHOD(swoole_http_cookie, withPriority) { + PHP_METHOD_HTTP_COOKIE_WITH_STR(Priority); +} + +static PHP_METHOD(swoole_http_cookie, withPartitioned) { + PHP_METHOD_HTTP_COOKIE_WITH_BOOL(Partitioned); +} + +static PHP_METHOD(swoole_http_cookie, toString) { + auto cookie = php_swoole_http_get_cooke_safety(ZEND_THIS); + auto cookie_str = cookie->toString(); + if (!cookie_str) { + cookie->reset(); + RETURN_FALSE; + } + ZVAL_STR(return_value, cookie_str); +} + +static PHP_METHOD(swoole_http_cookie, toArray) { + php_swoole_http_get_cooke_safety(ZEND_THIS)->toArray(return_value); +} + +static PHP_METHOD(swoole_http_cookie, reset) { + php_swoole_http_get_cooke_safety(ZEND_THIS)->reset(); +} diff --git a/ext-src/swoole_http_request.cc b/ext-src/swoole_http_request.cc index b191b67b72..80e62a34b1 100644 --- a/ext-src/swoole_http_request.cc +++ b/ext-src/swoole_http_request.cc @@ -19,18 +19,9 @@ SW_EXTERN_C_BEGIN #include "ext/standard/url.h" #include "stubs/php_swoole_http_request_arginfo.h" +#include "thirdparty/php/main/SAPI.h" SW_EXTERN_C_END -#include "main/php_variables.h" - -#ifdef SW_HAVE_ZLIB -#include -#endif - -#ifdef SW_HAVE_BROTLI -#include -#endif - enum http_upload_errno { HTTP_UPLOAD_ERR_OK = 0, HTTP_UPLOAD_ERR_INI_SIZE, @@ -50,13 +41,12 @@ using swoole::microtime; using swoole::Server; using swoole::http_server::ParseCookieCallback; -static int http_request_on_path(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_query_string(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_body(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_header_field(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_header_value(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_headers_complete(swoole_http_parser *parser); -static int http_request_message_complete(swoole_http_parser *parser); +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_headers_complete(llhttp_t *parser); +static int http_request_message_complete(llhttp_t *parser); static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length); @@ -64,11 +54,12 @@ static int multipart_body_on_data(multipart_parser *p, const char *at, size_t le static int multipart_body_on_header_complete(multipart_parser *p); static int multipart_body_on_data_end(multipart_parser *p); -static int http_request_on_path(swoole_http_parser *parser, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) parser->data; - ctx->request.path = estrndup(at, length); - ctx->request.path_len = length; - return 0; +static HttpContext *http_request_get_and_check_context(const zval *zobject) { + auto *ctx = php_swoole_http_request_get_context(zobject); + if (!ctx) { + swoole_set_last_error(SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); + } + return ctx; } static inline char *http_trim_double_quote(char *ptr, size_t *len) { @@ -98,26 +89,37 @@ static inline char *http_trim_double_quote(char *ptr, size_t *len) { return tmp; } -static sw_inline const char *http_get_method_name(enum swoole_http_method method) { - return swoole_http_method_str(method); -} - // clang-format off -static const swoole_http_parser_settings http_parser_settings = +static constexpr llhttp_settings_t http_parser_settings = { - nullptr, - http_request_on_path, - http_request_on_query_string, - nullptr, - nullptr, - http_request_on_header_field, - http_request_on_header_value, - http_request_on_headers_complete, - http_request_on_body, - http_request_message_complete + nullptr, // on_message_begin + nullptr, // on_protocol + http_request_on_url, // on_url + nullptr, // on_status + nullptr, // on_method + nullptr, // on_version + http_request_on_header_field, // on_header_field + http_request_on_header_value, // on_header_value + nullptr, // on_chunk_extension_name + nullptr, // on_chunk_extension_value + http_request_on_headers_complete, // on_headers_complete + http_request_on_body, // on_body + http_request_message_complete, // on_message_complete + nullptr, // on_protocol_complete + nullptr, // on_url_complete + nullptr, // on_status_complete + nullptr, // on_method_complete + nullptr, // on_version_complete + nullptr, // on_header_field_complete + nullptr, // on_header_value_complete + nullptr, // on_chunk_extension_name_complete + nullptr, // on_chunk_extension_value_complete + nullptr, // on_chunk_header + nullptr, // on_chunk_complete + nullptr, // on_reset }; -static const multipart_parser_settings mt_parser_settings = +static constexpr multipart_parser_settings mt_parser_settings = { multipart_body_on_header_field, multipart_body_on_header_value, @@ -130,21 +132,21 @@ static const multipart_parser_settings mt_parser_settings = // clang-format on size_t HttpContext::parse(const char *data, size_t length) { - return swoole_http_parser_execute(&parser, &http_parser_settings, data, length); + return swoole_llhttp_parser_execute(&parser, &http_parser_settings, data, length); } -bool HttpContext::parse_multipart_data(const char *at, size_t length) { +bool HttpContext::parse_multipart_data(const char *at, size_t length) const { ssize_t n = multipart_parser_execute(mt_parser, at, length); if (n < 0) { int l_error = multipart_parser_error_msg(mt_parser, sw_tg_buffer()->str, sw_tg_buffer()->size); - swoole_error_log(SW_LOG_WARNING, + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, reason: %.*s", l_error, sw_tg_buffer()->str); return false; } else if (n != (ssize_t) length) { - swoole_error_log(SW_LOG_WARNING, + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %lu/%zu bytes processed", n, @@ -157,44 +159,42 @@ bool HttpContext::parse_multipart_data(const char *at, size_t length) { zend_class_entry *swoole_http_request_ce; static zend_object_handlers swoole_http_request_handlers; -typedef struct { +struct HttpRequestObject { HttpContext *ctx; zend_object std; -} http_request_t; +}; -static sw_inline http_request_t *php_swoole_http_request_fetch_object(zend_object *obj) { - return (http_request_t *) ((char *) obj - swoole_http_request_handlers.offset); +static sw_inline HttpRequestObject *http_request_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_http_request_handlers.offset); } -HttpContext *php_swoole_http_request_get_context(zval *zobject) { - return php_swoole_http_request_fetch_object(Z_OBJ_P(zobject))->ctx; +HttpContext *php_swoole_http_request_get_context(const zval *zobject) { + return http_request_fetch_object(Z_OBJ_P(zobject))->ctx; } -void php_swoole_http_request_set_context(zval *zobject, HttpContext *ctx) { - php_swoole_http_request_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; +void php_swoole_http_request_set_context(const zval *zobject, HttpContext *ctx) { + http_request_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; } -static void php_swoole_http_request_free_object(zend_object *object) { - http_request_t *request = php_swoole_http_request_fetch_object(object); - HttpContext *ctx = request->ctx; - zval zobject, *ztmpfiles; +static void http_request_free_object(zend_object *object) { + auto *request = http_request_fetch_object(object); + auto *ctx = request->ctx; - ZVAL_OBJ(&zobject, object); - ztmpfiles = sw_zend_read_property_ex(swoole_http_request_ce, &zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_TMPFILES), 0); - if (ZVAL_IS_ARRAY(ztmpfiles)) { - zval *z_file_path; - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmpfiles), z_file_path) { - if (Z_TYPE_P(z_file_path) != IS_STRING) { - continue; - } - unlink(Z_STRVAL_P(z_file_path)); - if (SG(rfc1867_uploaded_files)) { - zend_hash_str_del(SG(rfc1867_uploaded_files), Z_STRVAL_P(z_file_path), Z_STRLEN_P(z_file_path)); + if (ctx) { + zval *ztmpfiles = ctx->request.ztmpfiles; + if (ztmpfiles && ZVAL_IS_ARRAY(ztmpfiles)) { + zval *z_file_path; + SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmpfiles), z_file_path) { + if (Z_TYPE_P(z_file_path) != IS_STRING) { + continue; + } + unlink(Z_STRVAL_P(z_file_path)); + if (SG(rfc1867_uploaded_files)) { + zend_hash_str_del(SG(rfc1867_uploaded_files), Z_STRVAL_P(z_file_path), Z_STRLEN_P(z_file_path)); + } } + SW_HASHTABLE_FOREACH_END(); } - SW_HASHTABLE_FOREACH_END(); - } - if (ctx) { ctx->request.zobject = nullptr; ctx->free(); } @@ -202,8 +202,8 @@ static void php_swoole_http_request_free_object(zend_object *object) { zend_object_std_dtor(&request->std); } -static zend_object *php_swoole_http_request_create_object(zend_class_entry *ce) { - http_request_t *request = (http_request_t *) zend_object_alloc(sizeof(http_request_t), ce); +static zend_object *http_request_create_object(zend_class_entry *ce) { + auto *request = static_cast(zend_object_alloc(sizeof(HttpRequestObject), ce)); zend_object_std_init(&request->std, ce); object_properties_init(&request->std, ce); request->std.handlers = &swoole_http_request_handlers; @@ -217,7 +217,6 @@ static PHP_METHOD(swoole_http_request, parse); static PHP_METHOD(swoole_http_request, isCompleted); static PHP_METHOD(swoole_http_request, getMethod); static PHP_METHOD(swoole_http_request, getContent); -static PHP_METHOD(swoole_http_request, __destruct); SW_EXTERN_C_END // clang-format off @@ -230,7 +229,6 @@ const zend_function_entry swoole_http_request_methods[] = PHP_ME(swoole_http_request, parse, arginfo_class_Swoole_Http_Request_parse, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, isCompleted, arginfo_class_Swoole_Http_Request_isCompleted, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_request, getMethod, arginfo_class_Swoole_Http_Request_getMethod, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_request, __destruct, arginfo_class_Swoole_Http_Request___destruct, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on @@ -240,11 +238,8 @@ void php_swoole_http_request_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_request); SW_SET_CLASS_CLONEABLE(swoole_http_request, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_request, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_request, - php_swoole_http_request_create_object, - php_swoole_http_request_free_object, - http_request_t, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_http_request, http_request_create_object, http_request_free_object, HttpRequestObject, std); zend_declare_property_long(swoole_http_request_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_http_request_ce, ZEND_STRL("streamId"), 0, ZEND_ACC_PUBLIC); @@ -257,20 +252,38 @@ void php_swoole_http_request_minit(int module_number) { zend_declare_property_null(swoole_http_request_ce, ZEND_STRL("tmpfiles"), ZEND_ACC_PUBLIC); } -static int http_request_on_query_string(swoole_http_parser *parser, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) parser->data; - add_assoc_stringl_ex(ctx->request.zserver, ZEND_STRL("query_string"), (char *) at, length); +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length) { + const char *query_start = (const char *) memchr(at, '?', length); + size_t path_len = query_start ? (size_t) (query_start - at) : length; + + auto *ctx = static_cast(parser->data); + ctx->request.path = estrndup(at, path_len); + ctx->request.path_len = path_len; + + if (!query_start || (length - path_len) <= 1) { + return 0; + } + + const char *query_str = query_start + 1; + size_t query_len = length - path_len - 1; + + zval tmp; + HashTable *ht = Z_ARR_P(ctx->request.zserver); + ZVAL_STRINGL(&tmp, (char *) query_str, query_len); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_QUERY_STRING), &tmp); + // parse url params - sapi_module.treat_data(PARSE_STRING, - estrndup(at, length), // it will be freed by treat_data - swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, ZEND_STRL("get"))); + swoole_php_treat_data( + PARSE_STRING, + estrndup((char *) query_str, query_len), // it will be freed by treat_data + swoole_http_init_and_read_property( + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zget, SW_ZSTR_KNOWN(SW_ZEND_STR_GET))); return 0; } -static int http_request_on_header_field(swoole_http_parser *parser, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) parser->data; - ctx->current_header_name = (char *) at; +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); + ctx->current_header_name = at; ctx->current_header_name_len = length; return 0; } @@ -291,78 +304,90 @@ bool HttpContext::get_multipart_boundary( if (!http_server::parse_multipart_boundary(at, length, offset, out_boundary_str, out_boundary_len)) { swoole_warning("boundary of multipart/form-data not found, fd:%ld", fd); /* make it same with protocol error */ - parser.state = s_dead; + parser.error = HPE_INVALID_HEADER_TOKEN; return false; } return true; } -void swoole_http_parse_cookie(zval *zarray, const char *at, size_t length) { +void swoole_http_parse_cookie(zval *zcookies, const char *at, size_t length) { if (length == 0) { return; } - zend_long count = 0; - ParseCookieCallback cb = [&count, zarray](char *key, size_t key_len, char *value, size_t value_len) { - if (++count > PG(max_input_vars)) { - swoole_warning("Input variables exceeded " ZEND_LONG_FMT - ". To increase the limit change max_input_vars in php.ini.", - PG(max_input_vars)); - return false; - } - if (value_len > 0) { - value_len = php_raw_url_decode(value, value_len); - } - add_assoc_stringl_ex(zarray, key, key_len, value, value_len); - return true; - }; - swoole::http_server::parse_cookie(at, length, cb); + + swoole_php_treat_data(PARSE_COOKIE, estrndup(at, length), zcookies); } static void http_request_add_upload_file(HttpContext *ctx, const char *file, size_t l_file) { zval *zfiles = swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.ztmpfiles, ZEND_STRL("tmpfiles")); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.ztmpfiles, SW_ZSTR_KNOWN(SW_ZEND_STR_TMPFILES)); add_next_index_stringl(zfiles, file, l_file); // support is_upload_file zend_hash_str_add_ptr(SG(rfc1867_uploaded_files), file, l_file, (char *) file); } -static int http_request_on_header_value(swoole_http_parser *parser, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) parser->data; +bool swoole_http_token_list_contains_value(const char *at, size_t length, const char *value) { + if (0 == length) { + return false; + } + if (SW_STRCASEEQ(at, length, value)) { + return true; + } + + char *var; + const char *separator = ",\0"; + char *strtok_buf = nullptr; + size_t var_len; + + char *_c = sw_tg_buffer()->str; + memcpy(_c, at, length); + _c[length] = '\0'; + + var = php_strtok_r(_c, separator, &strtok_buf); + while (var) { + var_len = swoole::ltrim(&var, strlen(var)); + var_len = swoole::rtrim(var, var_len); + if (swoole_strcaseeq(var, var_len, value, strlen(value))) { + return true; + } + var = php_strtok_r(nullptr, separator, &strtok_buf); + } + return false; +} + +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); zval *zheader = ctx->request.zheader; + const char *header_name = ctx->current_header_name; size_t header_len = ctx->current_header_name_len; - char *header_name = zend_str_tolower_dup(ctx->current_header_name, header_len); - if (ctx->parse_cookie && SW_STREQ(header_name, header_len, "cookie")) { + if (ctx->parse_cookie && SW_STRCASEEQ(header_name, header_len, "cookie")) { zval *zcookie = swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zcookie, ZEND_STRL("cookie")); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zcookie, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE)); swoole_http_parse_cookie(zcookie, at, length); - efree(header_name); return 0; - } else if (SW_STREQ(header_name, header_len, "upgrade") && SW_STRCASEEQ(at, length, "websocket")) { + } else if (SW_STRCASEEQ(header_name, header_len, "upgrade") && + swoole_http_token_list_contains_value(at, length, "websocket")) { ctx->websocket = 1; - if (ctx->co_socket) { - goto _add_header; - } - Server *serv = (Server *) ctx->private_data; - if (!serv) { + if (ctx->is_co_socket()) { goto _add_header; } + auto *serv = ctx->get_async_server(); Connection *conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED, "session[%ld] is closed", ctx->fd); - efree(header_name); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_CLOSED, "session[%ld] is closed", ctx->fd); return -1; } ListenPort *port = serv->get_port_by_server_fd(conn->server_fd); if (port->open_websocket_protocol) { conn->websocket_status = swoole::websocket::STATUS_CONNECTION; } - } else if ((parser->method == PHP_HTTP_POST || parser->method == PHP_HTTP_PUT || - parser->method == PHP_HTTP_DELETE || parser->method == PHP_HTTP_PATCH) && - SW_STREQ(header_name, header_len, "content-type")) { - if (SW_STRCASECT(at, length, "application/x-www-form-urlencoded")) { + } else if ((parser->method == HTTP_POST || parser->method == HTTP_PUT || parser->method == HTTP_DELETE || + parser->method == HTTP_PATCH) && + SW_STRCASEEQ(header_name, header_len, "content-type")) { + if (SW_STR_ISTARTS_WITH(at, length, "application/x-www-form-urlencoded")) { ctx->request.post_form_urlencoded = 1; - } else if (SW_STRCASECT(at, length, "multipart/form-data")) { + } else if (SW_STR_ISTARTS_WITH(at, length, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; @@ -374,25 +399,50 @@ static int http_request_on_header_value(swoole_http_parser *parser, const char * } } #ifdef SW_HAVE_COMPRESSION - else if (ctx->enable_compression && SW_STREQ(header_name, header_len, "accept-encoding")) { + else if (ctx->enable_compression && SW_STRCASEEQ(header_name, header_len, "accept-encoding")) { ctx->set_compression_method(at, length); } #endif - else if (SW_STREQ(header_name, header_len, "transfer-encoding") && SW_STRCASECT(at, length, "chunked")) { + else if (SW_STRCASEEQ(header_name, header_len, "transfer-encoding") && SW_STR_ISTARTS_WITH(at, length, "chunked")) { ctx->recv_chunked = 1; } _add_header: - add_assoc_stringl_ex(zheader, header_name, header_len, (char *) at, length); - efree(header_name); + zval tmp; + ZVAL_STRINGL(&tmp, (char *) at, length); + + /** + * some common request header key + */ + if (SW_STRCASEEQ(header_name, header_len, "host")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_HOST), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "user-agent")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_USER_AGENT), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "accept")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_ACCEPT), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "content-type")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONTENT_TYPE), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "content-length")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONTENT_LENGTH), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "authorization")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_AUTHORIZATION), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "connection")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_CONNECTION), &tmp); + } else if (SW_STRCASEEQ(header_name, header_len, "accept-encoding")) { + zend_hash_update(Z_ARR_P(zheader), SW_ZSTR_KNOWN(SW_ZEND_STR_ACCEPT_ENCODING), &tmp); + } else { + char *new_header_name = estrndup(header_name, header_len); + zend_str_tolower_copy(new_header_name, header_name, header_len); + zend::array_add_or_merge(zheader, new_header_name, header_len, &tmp); + efree(new_header_name); + } return 0; } -static int http_request_on_headers_complete(swoole_http_parser *parser) { - HttpContext *ctx = (HttpContext *) parser->data; +static int http_request_on_headers_complete(llhttp_t *parser) { + auto *ctx = static_cast(parser->data); const char *vpath = ctx->request.path, *end = vpath + ctx->request.path_len, *p = end; - zval *zserver = ctx->request.zserver; ctx->request.version = parser->http_major * 100 + parser->http_minor; ctx->request.ext = end; @@ -408,25 +458,31 @@ static int http_request_on_headers_complete(swoole_http_parser *parser) { } } - ctx->keepalive = swoole_http_should_keep_alive(parser); + HashTable *ht = Z_ARR_P(ctx->request.zserver); + http_server_add_server_array( + ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_METHOD2), llhttp_method_name((enum llhttp_method) parser->method)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_URI), ctx->request.path, ctx->request.path_len); - add_assoc_string(zserver, "request_method", http_get_method_name(parser->method)); - add_assoc_stringl_ex(zserver, ZEND_STRL("request_uri"), ctx->request.path, ctx->request.path_len); // path_info should be decoded - zend_string *zstr_path = zend_string_init(ctx->request.path, ctx->request.path_len, 0); + zend_string *zstr_path = zend_string_init(ctx->request.path, ctx->request.path_len, false); ZSTR_LEN(zstr_path) = php_url_decode(ZSTR_VAL(zstr_path), ZSTR_LEN(zstr_path)); - add_assoc_str_ex(zserver, ZEND_STRL("path_info"), zstr_path); - add_assoc_long_ex(zserver, ZEND_STRL("request_time"), time(nullptr)); - add_assoc_double_ex(zserver, ZEND_STRL("request_time_float"), microtime()); - add_assoc_string(zserver, "server_protocol", (char *) (ctx->request.version == 101 ? "HTTP/1.1" : "HTTP/1.0")); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_PATH_INFO), zstr_path); + + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); + http_server_add_server_array( + ht, + SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), + (ctx->request.version == 101 ? SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP11) : SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP10))); + ctx->keepalive = llhttp_should_keep_alive(parser); ctx->current_header_name = nullptr; return 0; } static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) p->data; + auto *ctx = static_cast(p->data); return http_request_on_header_field(&ctx->parser, at, length); } @@ -435,7 +491,7 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s size_t value_len; int ret = 0; - HttpContext *ctx = (HttpContext *) p->data; + auto *ctx = static_cast(p->data); /** * Hash collision attack */ @@ -450,7 +506,9 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s } size_t header_len = ctx->current_header_name_len; - char *header_name = zend_str_tolower_dup(ctx->current_header_name, header_len); + zend::CharPtr _header_name; + _header_name.assign_tolower(ctx->current_header_name, header_len); + char *header_name = _header_name.get(); if (SW_STRCASEEQ(header_name, header_len, "content-disposition")) { size_t offset = 0; @@ -460,7 +518,7 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s offset += sizeof("attachment;") - 1; } else { swoole_warning("Unsupported Content-Disposition [%.*s]", (int) length, at); - goto _end; + return ret; } zval tmp_array; @@ -469,13 +527,13 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s zval *zform_name; if (!(zform_name = zend_hash_str_find(Z_ARRVAL(tmp_array), ZEND_STRL("name")))) { - goto _end; + return ret; } if (Z_STRLEN_P(zform_name) >= SW_HTTP_FORM_KEYLEN) { swoole_warning("form_name[%s] is too large", Z_STRVAL_P(zform_name)); ret = -1; - goto _end; + return ret; } swoole_strlcpy(value_buf, Z_STRVAL_P(zform_name), sizeof(value_buf)); @@ -493,7 +551,7 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s if (Z_STRLEN_P(zfilename) >= SW_HTTP_FORM_KEYLEN) { swoole_warning("filename[%s] is too large", Z_STRVAL_P(zfilename)); ret = -1; - goto _end; + return ret; } ctx->current_input_name = estrndup(tmp, value_len); ctx->current_input_name_len = value_len; @@ -501,7 +559,12 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s zval *z_multipart_header = sw_malloc_zval(); array_init(z_multipart_header); - add_assoc_string(z_multipart_header, "type", (char *) ""); + if (ctx->tmp_content_type) { + add_assoc_stringl(z_multipart_header, "type", ctx->tmp_content_type, ctx->tmp_content_type_len); + ctx->tmp_content_type = nullptr; + } else { + add_assoc_string(z_multipart_header, "type", (char *) ""); + } add_assoc_string(z_multipart_header, "tmp_name", (char *) ""); add_assoc_long(z_multipart_header, "size", 0); @@ -518,29 +581,38 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s ctx->current_multipart_header = z_multipart_header; } zval_ptr_dtor(&tmp_array); - } else if (SW_STRCASEEQ(header_name, header_len, "content-type") && ctx->current_multipart_header) { - zval *z_multipart_header = ctx->current_multipart_header; - zval *zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); - if (zerr && Z_TYPE_P(zerr) == IS_LONG && Z_LVAL_P(zerr) == HTTP_UPLOAD_ERR_OK) { - add_assoc_stringl(z_multipart_header, "type", (char *) at, length); + } else if (SW_STRCASEEQ(header_name, header_len, "content-type")) { + if (ctx->current_multipart_header) { + zval *z_multipart_header = ctx->current_multipart_header; + zval *zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); + if (zerr && Z_TYPE_P(zerr) == IS_LONG && Z_LVAL_P(zerr) == HTTP_UPLOAD_ERR_OK) { + add_assoc_stringl(z_multipart_header, "type", (char *) at, length); + } + } else { + ctx->tmp_content_type = at; + ctx->tmp_content_type_len = length; } } else if (SW_STRCASEEQ(header_name, header_len, SW_HTTP_UPLOAD_FILE)) { + /** + * When the "SW_HTTP_UPLOAD_FILE" header appears in the request, it indicates that the uploaded file has been + * saved in a temporary file. The binary content in the message body will be replaced with the temporary + * filename. However, the Content-Length still reflects the original message size, causing llhttp to believe + * there is still data to be received. As a result, llhttp fails to trigger the message callback. Therefore, we + * need to set `ctx->completed = 1` to indicate that the message processing is complete. + */ + ctx->completed = 1; zval *z_multipart_header = ctx->current_multipart_header; std::string tmp_file(at, length); add_assoc_stringl(z_multipart_header, "tmp_name", at, length); add_assoc_long(z_multipart_header, "error", HTTP_UPLOAD_ERR_FILE_READY); - add_assoc_long(z_multipart_header, "size", swoole::file_get_size(tmp_file.c_str())); + add_assoc_long(z_multipart_header, "size", swoole::file_get_size(tmp_file)); http_request_add_upload_file(ctx, tmp_file.c_str(), tmp_file.length()); } - -_end: - efree(header_name); - return ret; } static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length) { - HttpContext *ctx = (HttpContext *) p->data; + auto *ctx = static_cast(p->data); if (ctx->current_form_data_name) { ctx->form_data_buffer->append(at, length); return 0; @@ -577,7 +649,7 @@ static void get_random_file_name(char *des, const char *src) #endif static int multipart_body_on_header_complete(multipart_parser *p) { - HttpContext *ctx = (HttpContext *) p->data; + auto *ctx = static_cast(p->data); if (!ctx->current_input_name) { return 0; } @@ -614,7 +686,7 @@ static int multipart_body_on_header_complete(multipart_parser *p) { } static int multipart_body_on_data_end(multipart_parser *p) { - HttpContext *ctx = (HttpContext *) p->data; + auto *ctx = static_cast(p->data); if (ctx->current_form_data_name) { php_register_variable_safe( @@ -622,7 +694,7 @@ static int multipart_body_on_data_end(multipart_parser *p) { ctx->form_data_buffer->str, ctx->form_data_buffer->length, swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post"))); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); efree(ctx->current_form_data_name); ctx->current_form_data_name = nullptr; @@ -653,7 +725,7 @@ static int multipart_body_on_data_end(multipart_parser *p) { } zval *zfiles = swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zfiles, ZEND_STRL("files")); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zfiles, SW_ZSTR_KNOWN(SW_ZEND_STR_FILES)); int input_path_pos = swoole_strnpos(ctx->current_input_name, ctx->current_input_name_len, ZEND_STRL("[")); if (ctx->parse_files && input_path_pos > 0) { @@ -667,7 +739,7 @@ static int multipart_body_on_data_end(multipart_parser *p) { zval *zname = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("name")); zval *ztype = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("type")); zval *zfile = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("tmp_name")); - zval *zerr = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); + zval *zerror = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("error")); zval *zsize = zend_hash_str_find(Z_ARRVAL_P(z_multipart_header), ZEND_STRL("size")); sw_snprintf(meta_path, meta_path_len, "[name]%s", input_path); @@ -680,7 +752,7 @@ static int multipart_body_on_data_end(multipart_parser *p) { php_register_variable_ex(meta_name, zfile, zfiles); sw_snprintf(meta_path, meta_path_len, "[error]%s", input_path); - php_register_variable_ex(meta_name, zerr, zfiles); + php_register_variable_ex(meta_name, zerror, zfiles); sw_snprintf(meta_path, meta_path_len, "[size]%s", input_path); php_register_variable_ex(meta_name, zsize, zfiles); @@ -697,12 +769,12 @@ static int multipart_body_on_data_end(multipart_parser *p) { return 0; } -static int http_request_on_body(swoole_http_parser *parser, const char *at, size_t length) { +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length) { if (length == 0) { return 0; } - HttpContext *ctx = (HttpContext *) parser->data; + auto *ctx = static_cast(parser->data); bool is_beginning = (ctx->request.chunked_body ? ctx->request.chunked_body->length : ctx->request.body_length) == 0; if (ctx->recv_chunked) { @@ -726,29 +798,31 @@ static int http_request_on_body(swoole_http_parser *parser, const char *at, size length--; } while (length != 0); } - ctx->parse_multipart_data(at, length); + if (!ctx->parse_multipart_data(at, length)) { + return -1; + } } - return 0; + return ctx->completed ? HPE_PAUSED : 0; } -static int http_request_message_complete(swoole_http_parser *parser) { - HttpContext *ctx = (HttpContext *) parser->data; +static int http_request_message_complete(llhttp_t *parser) { + auto *ctx = static_cast(parser->data); size_t content_length = ctx->request.chunked_body ? ctx->request.chunked_body->length : ctx->request.body_length; if (ctx->request.chunked_body != nullptr && ctx->parse_body && ctx->request.post_form_urlencoded) { /* parse dechunked content */ - sapi_module.treat_data( + swoole_php_treat_data( PARSE_STRING, estrndup(ctx->request.chunked_body->str, content_length), // do not free, it will be freed by treat_data swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post"))); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); } else if (!ctx->recv_chunked && ctx->parse_body && ctx->request.post_form_urlencoded && ctx->request.body_at) { - sapi_module.treat_data( + swoole_php_treat_data( PARSE_STRING, estrndup(ctx->request.body_at, ctx->request.body_length), // do not free, it will be freed by treat_data swoole_http_init_and_read_property( - swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, ZEND_STRL("post"))); + swoole_http_request_ce, ctx->request.zobject, &ctx->request.zpost, SW_ZSTR_KNOWN(SW_ZEND_STR_POST))); } if (ctx->mt_parser) { multipart_parser_free(ctx->mt_parser); @@ -763,7 +837,8 @@ static int http_request_message_complete(swoole_http_parser *parser) { swoole_trace_log(SW_TRACE_HTTP, "request body length=%ld", content_length); - return 1; /* return from execute */ + /* The analysis of the http protocol has been completed, no further processing is needed. */ + return HPE_PAUSED; } #ifdef SW_HAVE_COMPRESSION @@ -774,37 +849,52 @@ void HttpContext::set_compression_method(const char *accept_encoding, size_t len compression_method = HTTP_COMPRESS_BR; } else #endif +#ifdef SW_HAVE_ZLIB if (swoole_strnpos(accept_encoding, length, ZEND_STRL("gzip")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_GZIP; } else if (swoole_strnpos(accept_encoding, length, ZEND_STRL("deflate")) >= 0) { accept_compression = 1; compression_method = HTTP_COMPRESS_DEFLATE; - } else { + } else +#endif +#ifdef SW_HAVE_ZSTD + if (swoole_strnpos(accept_encoding, length, ZEND_STRL("zstd")) >= 0) { + accept_compression = 1; + compression_method = HTTP_COMPRESS_ZSTD; + } else +#endif + { accept_compression = 0; } } -const char *HttpContext::get_content_encoding() { +const char *HttpContext::get_content_encoding() const { +#ifdef SW_HAVE_ZLIB if (compression_method == HTTP_COMPRESS_GZIP) { return "gzip"; } else if (compression_method == HTTP_COMPRESS_DEFLATE) { return "deflate"; - } + } else +#endif #ifdef SW_HAVE_BROTLI - else if (compression_method == HTTP_COMPRESS_BR) { + if (compression_method == HTTP_COMPRESS_BR) { return "br"; - } + } else +#endif +#ifdef SW_HAVE_ZSTD + if (compression_method == HTTP_COMPRESS_ZSTD) { + return "zstd"; + } else #endif - else { + { return nullptr; } } - #endif static PHP_METHOD(swoole_http_request, getContent) { - HttpContext *ctx = php_swoole_http_request_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -815,7 +905,7 @@ static PHP_METHOD(swoole_http_request, getContent) { RETURN_STRINGL(Z_STRVAL_P(zdata) + Z_STRLEN_P(zdata) - req->body_length, req->body_length); } else if (req->chunked_body && req->chunked_body->length != 0) { RETURN_STRINGL(req->chunked_body->str, req->chunked_body->length); - } else if (req->h2_data_buffer && req->h2_data_buffer->length != 0) { + } else if (ctx->get_http2_data_length() > 0) { RETURN_STRINGL(req->h2_data_buffer->str, req->h2_data_buffer->length); } @@ -823,7 +913,7 @@ static PHP_METHOD(swoole_http_request, getContent) { } static PHP_METHOD(swoole_http_request, getData) { - HttpContext *ctx = php_swoole_http_request_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -848,7 +938,7 @@ static PHP_METHOD(swoole_http_request, create) { Z_PARAM_ARRAY(zoptions) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = new HttpContext(); + auto *ctx = new HttpContext(); object_init_ex(return_value, swoole_http_request_ce); zval *zrequest_object = &ctx->request._zobject; ctx->request.zobject = zrequest_object; @@ -898,9 +988,8 @@ static PHP_METHOD(swoole_http_request, create) { SW_HASHTABLE_FOREACH_END(); } - swoole_http_parser *parser = &ctx->parser; - parser->data = ctx; - swoole_http_parser_init(parser, PHP_HTTP_REQUEST); + llhttp_t *parser = &ctx->parser; + swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); swoole_http_init_and_read_property( swoole_http_request_ce, zrequest_object, &ctx->request.zserver, ZEND_STRL("server")); @@ -909,7 +998,7 @@ static PHP_METHOD(swoole_http_request, create) { } static PHP_METHOD(swoole_http_request, parse) { - HttpContext *ctx = php_swoole_http_request_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx) || ctx->completed) { RETURN_FALSE; } @@ -925,7 +1014,7 @@ static PHP_METHOD(swoole_http_request, parse) { ZVAL_STRINGL(&ctx->request.zdata, str, l_str); } else { size_t len = Z_STRLEN(ctx->request.zdata) + l_str; - zend_string *new_str = zend_string_alloc(len + 1, 0); + zend_string *new_str = zend_string_alloc(len + 1, false); memcpy(new_str->val, Z_STRVAL(ctx->request.zdata), Z_STRLEN(ctx->request.zdata)); memcpy(new_str->val + Z_STRLEN(ctx->request.zdata), str, l_str); new_str->val[len] = 0; @@ -938,20 +1027,23 @@ static PHP_METHOD(swoole_http_request, parse) { } static PHP_METHOD(swoole_http_request, getMethod) { - HttpContext *ctx = php_swoole_http_request_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } - const char *method = http_get_method_name((ctx->parser).method); - RETURN_STRING(method); + if (ctx->http2) { + zval *zmethod = zend_hash_str_find(Z_ARR_P(ctx->request.zserver), ZEND_STRL("request_method")); + RETURN_ZVAL(zmethod, 1, 0); + } else { + const char *method = llhttp_method_name((enum llhttp_method)(ctx->parser).method); + RETURN_STRING(method); + } } static PHP_METHOD(swoole_http_request, isCompleted) { - HttpContext *ctx = php_swoole_http_request_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_request_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } RETURN_BOOL(ctx->completed); } - -static PHP_METHOD(swoole_http_request, __destruct) {} diff --git a/ext-src/swoole_http_response.cc b/ext-src/swoole_http_response.cc index db5e573a51..e929eae409 100644 --- a/ext-src/swoole_http_response.cc +++ b/ext-src/swoole_http_response.cc @@ -15,17 +15,9 @@ */ #include "php_swoole_http_server.h" - +#include "php_swoole_websocket.h" #include "swoole_util.h" -#ifdef SW_HAVE_ZLIB -#include -#endif - -#ifdef SW_HAVE_BROTLI -#include -#endif - BEGIN_EXTERN_C() #include "stubs/php_swoole_http_response_arginfo.h" END_EXTERN_C() @@ -34,10 +26,12 @@ using swoole::Connection; using swoole::Server; using swoole::String; using swoole::substr_len; -using swoole::coroutine::Socket; +using swoole::WebSocketSettings; +using swoole::websocket::FrameObject; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; +using HttpCookie = swoole::http::Cookie; namespace WebSocket = swoole::websocket; namespace HttpServer = swoole::http_server; @@ -45,9 +39,14 @@ namespace HttpServer = swoole::http_server; zend_class_entry *swoole_http_response_ce; static zend_object_handlers swoole_http_response_handlers; -static inline void http_header_key_format(char *key, int length) { - int i, state = 0; - for (i = 0; i < length; i++) { +static SW_THREAD_LOCAL struct { + time_t time; + zend_string *date = nullptr; +} date_cache{}; + +static inline void http_response_header_key_format(char *key, int length) { + int state = 0; + for (int i = 0; i < length; i++) { if (state == 0) { if (key[i] >= 97 && key[i] <= 122) { key[i] -= 32; @@ -64,51 +63,57 @@ static inline void http_header_key_format(char *key, int length) { } String *HttpContext::get_write_buffer() { - if (co_socket) { - return ((Socket *) private_data)->get_write_buffer(); + if (is_co_socket()) { + return get_co_socket()->get_write_buffer(); } else { if (!write_buffer) { - write_buffer = std::make_shared(SW_BUFFER_SIZE_STD); + write_buffer = new String(SW_BUFFER_SIZE_STD, sw_php_allocator()); } - return write_buffer.get(); + return write_buffer; } } -typedef struct { +struct HttpResponseObject { HttpContext *ctx; zend_object std; -} http_response_t; +}; -static sw_inline http_response_t *php_swoole_http_response_fetch_object(zend_object *obj) { - return (http_response_t *) ((char *) obj - swoole_http_response_handlers.offset); +static HttpResponseObject *http_response_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_http_response_handlers.offset); } -HttpContext *php_swoole_http_response_get_context(zval *zobject) { - return php_swoole_http_response_fetch_object(Z_OBJ_P(zobject))->ctx; +HttpContext *php_swoole_http_response_get_context(const zval *zobject) { + return http_response_fetch_object(Z_OBJ_P(zobject))->ctx; } -void php_swoole_http_response_set_context(zval *zobject, HttpContext *ctx) { - php_swoole_http_response_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; +void php_swoole_http_response_set_context(const zval *zobject, HttpContext *ctx) { + http_response_fetch_object(Z_OBJ_P(zobject))->ctx = ctx; } -static void php_swoole_http_response_free_object(zend_object *object) { - http_response_t *response = php_swoole_http_response_fetch_object(object); +static HttpContext *http_response_get_and_check_context(const zval *zobject) { + auto *ctx = php_swoole_http_response_get_context(zobject); + if (!ctx || (ctx->end_ || ctx->detached)) { + swoole_set_last_error(SW_ERROR_HTTP_CONTEXT_UNAVAILABLE); + return nullptr; + } + return ctx; +} + +static void http_response_free_object(zend_object *object) { + HttpResponseObject *response = http_response_fetch_object(object); HttpContext *ctx = response->ctx; zval ztmp; /* bool, not required to release it */ if (ctx) { + if (ctx->onAfterResponse) { + ctx->onAfterResponse(ctx); + } if (!ctx->end_ && (ctx->send_chunked || !ctx->send_header_) && !ctx->detached && sw_reactor()) { if (ctx->response.status == 0) { ctx->response.status = SW_HTTP_INTERNAL_SERVER_ERROR; } - if (ctx->http2) { - if (ctx->stream) { - ctx->http2_end(nullptr, &ztmp); - } - } else { - if (ctx->is_available()) { - ctx->end(nullptr, &ztmp); - } + if (ctx->is_available()) { + ctx->end(nullptr, &ztmp); } } ctx->response.zobject = nullptr; @@ -118,8 +123,8 @@ static void php_swoole_http_response_free_object(zend_object *object) { zend_object_std_dtor(&response->std); } -static zend_object *php_swoole_http_response_create_object(zend_class_entry *ce) { - http_response_t *response = (http_response_t *) zend_object_alloc(sizeof(http_response_t), ce); +static zend_object *http_response_create_object(zend_class_entry *ce) { + auto *response = static_cast(zend_object_alloc(sizeof(HttpResponseObject), ce)); zend_object_std_init(&response->std, ce); object_properties_init(&response->std, ce); response->std.handlers = &swoole_http_response_handlers; @@ -149,30 +154,31 @@ static PHP_METHOD(swoole_http_response, trailer); static PHP_METHOD(swoole_http_response, ping); static PHP_METHOD(swoole_http_response, goaway); static PHP_METHOD(swoole_http_response, status); -static PHP_METHOD(swoole_http_response, __destruct); +static PHP_METHOD(swoole_http_response, disconnect); SW_EXTERN_C_END // clang-format off const zend_function_entry swoole_http_response_methods[] = { - PHP_ME(swoole_http_response, initHeader, arginfo_class_Swoole_Http_Response_initHeader, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, isWritable, arginfo_class_Swoole_Http_Response_isWritable, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_http_response, setCookie, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, rawcookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_http_response, setStatusCode, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_http_response, setHeader, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, trailer, arginfo_class_Swoole_Http_Response_trailer, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, ping, arginfo_class_Swoole_Http_Response_ping, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, goaway, arginfo_class_Swoole_Http_Response_goaway, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, write, arginfo_class_Swoole_Http_Response_write, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, end, arginfo_class_Swoole_Http_Response_end, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, sendfile, arginfo_class_Swoole_Http_Response_sendfile, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, redirect, arginfo_class_Swoole_Http_Response_redirect, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, detach, arginfo_class_Swoole_Http_Response_detach, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, create, arginfo_class_Swoole_Http_Response_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_http_response, initHeader, arginfo_class_Swoole_Http_Response_initHeader, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, isWritable, arginfo_class_Swoole_Http_Response_isWritable, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_http_response, setCookie, cookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, rawcookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_http_response, setRawCookie, rawcookie, arginfo_class_Swoole_Http_Response_cookie, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_http_response, setStatusCode, status, arginfo_class_Swoole_Http_Response_status, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) + PHP_MALIAS(swoole_http_response, setHeader, header, arginfo_class_Swoole_Http_Response_header, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, trailer, arginfo_class_Swoole_Http_Response_trailer, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, ping, arginfo_class_Swoole_Http_Response_ping, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, goaway, arginfo_class_Swoole_Http_Response_goaway, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, write, arginfo_class_Swoole_Http_Response_write, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, end, arginfo_class_Swoole_Http_Response_end, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, sendfile, arginfo_class_Swoole_Http_Response_sendfile, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, redirect, arginfo_class_Swoole_Http_Response_redirect, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, detach, arginfo_class_Swoole_Http_Response_detach, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, create, arginfo_class_Swoole_Http_Response_create, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) /** * WebSocket */ @@ -180,7 +186,7 @@ const zend_function_entry swoole_http_response_methods[] = PHP_ME(swoole_http_response, push, arginfo_class_Swoole_Http_Response_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, recv, arginfo_class_Swoole_Http_Response_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_response, close, arginfo_class_Swoole_Http_Response_close, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_response, __destruct, arginfo_class_Swoole_Http_Response___destruct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_http_response, disconnect, arginfo_class_Swoole_Http_Response_disconnect, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on @@ -190,11 +196,8 @@ void php_swoole_http_response_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_response); SW_SET_CLASS_CLONEABLE(swoole_http_response, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_response, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_response, - php_swoole_http_response_create_object, - php_swoole_http_response_free_object, - http_response_t, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_http_response, http_response_create_object, http_response_free_object, HttpResponseObject, std); zend_declare_property_long(swoole_http_response_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("socket"), ZEND_ACC_PUBLIC); @@ -203,72 +206,39 @@ void php_swoole_http_response_minit(int module_number) { zend_declare_property_null(swoole_http_response_ce, ZEND_STRL("trailer"), ZEND_ACC_PUBLIC); } -static PHP_METHOD(swoole_http_response, write) { - zval *zdata; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zdata) == FAILURE) { - RETURN_FALSE; - } - - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); - if (UNEXPECTED(!ctx)) { - RETURN_FALSE; - } - - if (ctx->http2) { - php_swoole_error(E_WARNING, "HTTP2 client does not support HTTP-CHUNK"); - RETURN_FALSE; +void php_swoole_http_response_rshutdown() { + if (date_cache.date) { + zend_string_release(date_cache.date); + date_cache = {}; } +} -#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); - if (!ctx->send(ctx, http_buffer->str, http_buffer->length)) { - ctx->send_chunked = 0; - ctx->send_header_ = 0; - RETURN_FALSE; - } - } +static PHP_METHOD(swoole_http_response, write) { + zend_string *sdata; - struct { - char *str; - size_t length; - } http_body; - size_t length = php_swoole_get_send_data(zdata, &http_body.str); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(sdata) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (length == 0) { - php_swoole_error(E_WARNING, "data to send is empty"); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); + if (UNEXPECTED(!ctx)) { RETURN_FALSE; - } else { - http_body.length = length; } +#ifdef SW_HAVE_COMPRESSION // Why not enable compression? // If both compression and chunked encoding are enabled, // then the content stream is first compressed, then chunked; // so the chunk encoding itself is not compressed, // **and the data in each chunk is not compressed individually.** // The remote endpoint then decodes the stream by concatenating the chunks and decompressing the result. - http_buffer->clear(); - char *hex_string = swoole_dec2hex(http_body.length, 16); - int hex_len = strlen(hex_string); - //"%.*s\r\n%.*s\r\n", hex_len, hex_string, body.length, body.str - http_buffer->append(hex_string, hex_len); - http_buffer->append(ZEND_STRL("\r\n")); - http_buffer->append(http_body.str, http_body.length); - http_buffer->append(ZEND_STRL("\r\n")); - sw_free(hex_string); + ctx->accept_compression = 0; +#endif - RETURN_BOOL(ctx->send(ctx, http_buffer->str, http_buffer->length)); + ctx->write(sdata, return_value); } -static int parse_header_name(const char *key, size_t keylen) { +static int http_response_parse_header_name(const char *key, size_t keylen) { if (SW_STRCASEEQ(key, keylen, "Server")) { return HTTP_HEADER_SERVER; } else if (SW_STRCASEEQ(key, keylen, "Connection")) { @@ -281,98 +251,129 @@ static int parse_header_name(const char *key, size_t keylen) { return HTTP_HEADER_CONTENT_TYPE; } else if (SW_STRCASEEQ(key, keylen, "Transfer-Encoding")) { return HTTP_HEADER_TRANSFER_ENCODING; + } else if (SW_STRCASEEQ(key, keylen, "Content-Encoding")) { + return HTTP_HEADER_CONTENT_ENCODING; } return 0; } -static void http_set_date_header(String *response) { - static struct { - time_t time; - size_t len; - char buf[64]; - } cache{}; - +zend_string *php_swoole_http_get_date() { 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 = sw_snprintf(cache.buf, sizeof(cache.buf), "Date: %s\r\n", date_str); - efree(date_str); - cache.time = now; + if (now != date_cache.time) { + if (date_cache.date) { + zend_string_release(date_cache.date); + } + date_cache.time = now; + date_cache.date = php_format_date((char *) ZEND_STRL(SW_HTTP_DATE_FORMAT), now, 0); } - response->append(cache.buf, cache.len); + return date_cache.date; } -void HttpContext::build_header(String *http_buffer, const char *body, size_t length) { - char *buf = sw_tg_buffer()->str; - size_t l_buf = sw_tg_buffer()->size; - size_t n; +static void http_response_set_date_header(String *response) { + auto date_str = php_swoole_http_get_date(); + response->append(ZEND_STRL("Date: ")); + response->append(ZSTR_VAL(date_str), ZSTR_LEN(date_str)); + response->append(ZEND_STRL("\r\n")); +} + +static void http_response_add_custom_header(String *http_buffer, const char *key, size_t l_key, zval *value) { + if (ZVAL_IS_NULL(value)) { + return; + } + + zend::String str_value(value); + str_value.rtrim(); + if (swoole_http_has_crlf(str_value.val(), str_value.len())) { + return; + } + http_buffer->append(key, l_key); + http_buffer->append(SW_STRL(": ")); + http_buffer->append(str_value.val(), str_value.len()); + http_buffer->append(SW_STRL("\r\n")); +} + +void HttpContext::build_header(String *http_buffer, const char *body, size_t length) { assert(send_header_ == 0); - /** - * http status line - */ - if (!response.reason) { - n = sw_snprintf(buf, l_buf, "HTTP/1.1 %s\r\n", HttpServer::get_status_message(response.status)); + // http status line + http_buffer->append(ZEND_STRL("HTTP/1.1 ")); + if (response.reason) { + http_buffer->append(response.status); + http_buffer->append(ZEND_STRL(" ")); + http_buffer->append(response.reason, strlen(response.reason)); } else { - n = sw_snprintf(buf, l_buf, "HTTP/1.1 %d %s\r\n", response.status, response.reason); + const char *status = HttpServer::get_status_message(response.status); + http_buffer->append((char *) status, strlen(status)); } - http_buffer->append(buf, n); + http_buffer->append(ZEND_STRL("\r\n")); + // http headers 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)) { - const char *key; - uint32_t keylen; - int type; +#ifdef SW_HAVE_COMPRESSION + zend_string *content_type = nullptr; +#endif zval *zvalue; + zend_string *string_key; + zend_ulong num_key; - auto add_header = [](String *response, const char *key, size_t l_key, zval *value) { - if (ZVAL_IS_NULL(value)) { - return; - } - zend::String str_value(value); - str_value.rtrim(); - if (swoole_http_has_crlf(str_value.val(), str_value.len())) { - return; + 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); } - response->append(key, l_key); - response->append(SW_STRL(": ")); - response->append(str_value.val(), str_value.len()); - response->append(SW_STRL("\r\n")); - }; + zend::String key(string_key, false); + + int key_header = http_response_parse_header_name(ZSTR_VAL(string_key), ZSTR_LEN(string_key)); - zend_string *content_type = nullptr; - SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(zheader), key, keylen, type, zvalue) { - // TODO: numeric key name neccessary? - if (UNEXPECTED(!key || ZVAL_IS_NULL(zvalue))) { - continue; - } - int key_header = parse_header_name(key, keylen); if (key_header > 0) { +#ifdef SW_HAVE_COMPRESSION if (key_header == HTTP_HEADER_CONTENT_TYPE && accept_compression && compression_types) { content_type = zval_get_string(zvalue); } + if (key_header == HTTP_HEADER_CONTENT_ENCODING && ZVAL_IS_STRING(zvalue)) { + accept_compression = 0; + } + // https://github.com/swoole/swoole-src/issues/4857 + // https://github.com/swoole/swoole-src/issues/5026 + if (key_header == HTTP_HEADER_CONTENT_LENGTH && accept_compression) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_HTTP_CONFLICT_HEADER, + "The client has set 'Accept-Encoding', 'Content-Length' will be ignored"); + continue; + } +#endif + // https://github.com/swoole/swoole-src/issues/4857 + // https://github.com/swoole/swoole-src/issues/5026 + if (key_header == HTTP_HEADER_CONTENT_LENGTH && send_chunked) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_HTTP_CONFLICT_HEADER, + "You have set 'Transfer-Encoding', 'Content-Length' will be ignored"); + continue; + } + header_flags |= key_header; + if (ZVAL_IS_STRING(zvalue) && Z_STRLEN_P(zvalue) == 0) { + continue; + } } if (ZVAL_IS_ARRAY(zvalue)) { zval *zvalue_2; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zvalue), zvalue_2) { - add_header(http_buffer, key, keylen, zvalue_2); + http_response_add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue_2); } SW_HASHTABLE_FOREACH_END(); } else { - add_header(http_buffer, key, keylen, zvalue); + http_response_add_custom_header(http_buffer, ZSTR_VAL(string_key), ZSTR_LEN(string_key), zvalue); } } - SW_HASHTABLE_FOREACH_END(); - (void) type; + ZEND_HASH_FOREACH_END(); +#ifdef SW_HAVE_COMPRESSION if (accept_compression && compression_types) { std::string str_content_type = content_type ? std::string(ZSTR_VAL(content_type), ZSTR_LEN(content_type)) : std::string(ZEND_STRL(SW_HTTP_DEFAULT_CONTENT_TYPE)); @@ -381,6 +382,7 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len zend_string_release(content_type); } } +#endif } // http cookies @@ -389,7 +391,7 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len if (ZVAL_IS_ARRAY(zcookie)) { zval *zvalue; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(zcookie), zvalue) { - if (Z_TYPE_P(zvalue) != IS_STRING) { + if (Z_TYPE_P(zvalue) != IS_STRING || swoole_http_has_crlf(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue))) { continue; } http_buffer->append(ZEND_STRL("Set-Cookie: ")); @@ -402,8 +404,9 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len if (!(header_flags & HTTP_HEADER_SERVER)) { http_buffer->append(ZEND_STRL("Server: " SW_HTTP_SERVER_SOFTWARE "\r\n")); } + if (!(header_flags & HTTP_HEADER_DATE)) { - http_set_date_header(http_buffer); + http_response_set_date_header(http_buffer); } // websocket protocol (subsequent header info is unnecessary) @@ -429,7 +432,7 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len } } // Content-Length - else if (length > 0 || parser.method != PHP_HTTP_HEAD) { + else if (length > 0 || parser.method != HTTP_HEAD) { #ifdef SW_HAVE_COMPRESSION if (compress(body, length)) { length = zlib_buffer->length; @@ -440,8 +443,12 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len } #endif if (!(header_flags & HTTP_HEADER_CONTENT_LENGTH)) { - n = sw_snprintf(buf, l_buf, "Content-Length: %zu\r\n", length); - http_buffer->append(buf, n); + http_buffer->append(ZEND_STRL("Content-Length: ")); + + char result[128]; + int convert_result = swoole_itoa(result, length); + http_buffer->append(result, convert_result); + http_buffer->append(ZEND_STRL("\r\n")); } } @@ -449,10 +456,9 @@ void HttpContext::build_header(String *http_buffer, const char *body, size_t len send_header_ = 1; } -ssize_t HttpContext::build_trailer(String *http_buffer) { +ssize_t HttpContext::build_trailer(String *http_buffer) const { char *buf = sw_tg_buffer()->str; size_t l_buf = sw_tg_buffer()->size; - int n; ssize_t ret = 0; zval *ztrailer = @@ -472,7 +478,7 @@ ssize_t HttpContext::build_trailer(String *http_buffer) { if (!ZVAL_IS_NULL(zvalue)) { zend::String str_value(zvalue); - n = sw_snprintf( + size_t n = sw_snprintf( buf, l_buf, "%.*s: %.*s\r\n", (int) keylen, key, (int) str_value.len(), str_value.val()); http_buffer->append(buf, n); ret += n; @@ -516,7 +522,7 @@ bool HttpContext::compress(const char *data, size_t length) { return false; } - if (0) { + if (false) { return false; } #ifdef SW_HAVE_ZLIB @@ -541,9 +547,9 @@ bool HttpContext::compress(const char *data, size_t length) { zlib_buffer = std::make_shared(memory_size); size_t input_size = length; - const uint8_t *input_buffer = (const uint8_t *) data; + const auto *input_buffer = reinterpret_cast(data); size_t encoded_size = zlib_buffer->size; - uint8_t *encoded_buffer = (uint8_t *) zlib_buffer->str; + auto *encoded_buffer = reinterpret_cast(zlib_buffer->str); if (BROTLI_TRUE != BrotliEncoderCompress(compression_level, BROTLI_DEFAULT_WINDOW, @@ -560,6 +566,30 @@ bool HttpContext::compress(const char *data, size_t length) { return true; } } +#endif +#ifdef SW_HAVE_ZSTD + else if (compression_method == HTTP_COMPRESS_ZSTD) { + int zstd_compress_level = compression_level; + int zstd_max_level = ZSTD_maxCLevel(); + int zstd_min_level = ZSTD_minCLevel(); + zstd_compress_level = (zstd_compress_level > zstd_max_level) + ? zstd_max_level + : (zstd_compress_level < zstd_min_level ? zstd_min_level : zstd_compress_level); + + size_t compress_size = ZSTD_compressBound(length); + zlib_buffer = std::make_shared(compress_size); + size_t zstd_compress_result = + ZSTD_compress((void *) zlib_buffer->str, compress_size, (void *) data, length, zstd_compress_level); + + if (ZSTD_isError(zstd_compress_result)) { + swoole_warning("ZSTD_compress() failed, Error: [%s]", ZSTD_getErrorName(zstd_compress_result)); + return false; + } + + zlib_buffer->length = zstd_compress_result; + content_compressed = 1; + return true; + } #endif else { swoole_warning("Unknown compression method"); @@ -578,12 +608,12 @@ bool HttpContext::compress(const char *data, size_t length) { zlib_buffer = std::make_shared(memory_size); z_stream zstream = {}; - int status; zstream.zalloc = php_zlib_alloc; zstream.zfree = php_zlib_free; - status = deflateInit2(&zstream, compression_level, Z_DEFLATED, encoding, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + int status = + deflateInit2(&zstream, compression_level, Z_DEFLATED, encoding, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (status != Z_OK) { swoole_warning("deflateInit2() failed, Error: [%d]", status); return false; @@ -610,7 +640,7 @@ bool HttpContext::compress(const char *data, size_t length) { #endif static PHP_METHOD(swoole_http_response, initHeader) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -633,16 +663,16 @@ static PHP_METHOD(swoole_http_response, isWritable) { } static PHP_METHOD(swoole_http_response, end) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } - zval *zdata = nullptr; + zend_string *sdata = nullptr; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL_EX(zdata, 1, 0) + Z_PARAM_STR_OR_NULL(sdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (ctx->onAfterResponse) { @@ -653,11 +683,7 @@ static PHP_METHOD(swoole_http_response, end) { swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE, ctx); } - if (ctx->http2) { - ctx->http2_end(zdata, return_value); - } else { - ctx->end(zdata, return_value); - } + ctx->end(sdata, return_value); } void HttpContext::send_trailer(zval *return_value) { @@ -674,11 +700,16 @@ void HttpContext::send_trailer(zval *return_value) { } } -bool HttpContext::send_file(const char *file, uint32_t l_file, off_t offset, size_t length) { +bool HttpContext::send_file(zend_string *file, off_t offset, size_t length) { + if (http2) { + return swoole_http2_server_send_file(this, file, offset, length); + } + zval *zheader = sw_zend_read_and_convert_property_array(swoole_http_response_ce, response.zobject, ZEND_STRL("header"), 0); if (!zend_hash_str_exists(Z_ARRVAL_P(zheader), ZEND_STRL("Content-Type"))) { - add_assoc_string(zheader, "Content-Type", (char *) swoole::mime_type::get(file).c_str()); + auto mime_type = swoole::mime_type::get({file->val, file->len}); + add_assoc_stringl(zheader, "Content-Type", mime_type.c_str(), mime_type.length()); } if (!send_header_) { @@ -696,7 +727,7 @@ bool HttpContext::send_file(const char *file, uint32_t l_file, off_t offset, siz } } - if (length > 0 && !sendfile(this, file, l_file, offset, length)) { + if (length > 0 && !sendfile(this, file, offset, length)) { close(this); return false; } @@ -709,19 +740,57 @@ bool HttpContext::send_file(const char *file, uint32_t l_file, off_t offset, siz return true; } -void HttpContext::end(zval *zdata, zval *return_value) { - struct { - char *str; - size_t length; - } http_body; - if (zdata) { - http_body.length = php_swoole_get_send_data(zdata, &http_body.str); - } else { - http_body.length = 0; - http_body.str = nullptr; +void HttpContext::write(zend_string *sdata, zval *return_value) { + if (http2) { + RETURN_BOOL(swoole_http2_server_write(this, sdata)); + } + + String *http_buffer = get_write_buffer(); + if (!send_header_) { + send_chunked = 1; + http_buffer->clear(); + build_header(http_buffer, nullptr, 0); + if (!send(this, http_buffer->str, http_buffer->length)) { + send_chunked = 0; + send_header_ = 0; + RETURN_FALSE; + } + } + + auto data = ZSTR_VAL(sdata); + size_t length = ZSTR_LEN(sdata); + + if (length == 0) { + php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); + RETURN_FALSE; + } + + http_buffer->clear(); + char *hex_string = swoole_dec2hex(length, 16); + int hex_len = strlen(hex_string); + //"%.*s\r\n%.*s\r\n", hex_len, hex_string, body.length, body.str + http_buffer->append(hex_string, hex_len); + http_buffer->append(ZEND_STRL("\r\n")); + http_buffer->append(data, length); + http_buffer->append(ZEND_STRL("\r\n")); + sw_free(hex_string); + + RETURN_BOOL(send(this, http_buffer->str, http_buffer->length)); +} + +void HttpContext::end(zend_string *sdata, zval *return_value) { + if (http2) { + RETURN_BOOL(swoole_http2_server_end(this, sdata)); } if (send_chunked) { + if (sdata && ZSTR_LEN(sdata) > 0) { + zval retval; + write(sdata, &retval); + if (ZVAL_IS_FALSE(&retval)) { + RETURN_FALSE; + } + } if (send_trailer_) { if (!send(this, ZEND_STRL("0\r\n"))) { RETURN_FALSE; @@ -735,6 +804,9 @@ void HttpContext::end(zval *zdata, zval *return_value) { } send_chunked = 0; } else { + const char *data = sdata ? ZSTR_VAL(sdata) : nullptr; + size_t length = sdata ? ZSTR_LEN(sdata) : 0; + String *http_buffer = get_write_buffer(); http_buffer->clear(); @@ -742,17 +814,18 @@ void HttpContext::end(zval *zdata, zval *return_value) { if (upgrade) { Server *serv = nullptr; Connection *conn = nullptr; - if (!co_socket) { - serv = (Server *) private_data; + if (!is_co_socket()) { + serv = get_async_server(); conn = serv->get_connection_verify(fd); } - bool enable_websocket_compression = co_socket ? websocket_compression : serv->websocket_compression; + bool enable_websocket_compression = + is_co_socket() ? websocket_settings.compression : serv->websocket_compression; bool accept_websocket_compression = false; zval *pData; if (enable_websocket_compression && request.zobject && - (pData = zend_hash_str_find(Z_ARRVAL_P(request.zheader), ZEND_STRL("sec-websocket-extensions"))) && + ((pData = zend_hash_str_find(Z_ARRVAL_P(request.zheader), ZEND_STRL("sec-websocket-extensions")))) && Z_TYPE_P(pData) == IS_STRING) { - std::string value(Z_STRVAL_P(pData), Z_STRLEN_P(pData)); + const std::string value(Z_STRVAL_P(pData), Z_STRLEN_P(pData)); if (value.substr(0, value.find_first_of(';')) == "permessage-deflate") { accept_websocket_compression = true; set_header(ZEND_STRL("Sec-Websocket-Extensions"), ZEND_STRL(SW_WEBSOCKET_EXTENSION_DEFLATE), false); @@ -765,39 +838,29 @@ void HttpContext::end(zval *zdata, zval *return_value) { } #endif - build_header(http_buffer, http_body.str, http_body.length); + build_header(http_buffer, data, length); - char *send_body_str; - size_t send_body_len; - - if (http_body.length > 0) { + if (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; + data = zlib_buffer->str; + length = zlib_buffer->length; } +#endif // 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 (length > SW_HTTP_MAX_APPEND_DATA) { if (!send(this, http_buffer->str, http_buffer->length)) { send_header_ = 0; RETURN_FALSE; } - if (!send(this, send_body_str, send_body_len)) { + if (!send(this, data, length)) { end_ = 1; close(this); RETURN_FALSE; } goto _skip_copy; + } else { + http_buffer->append(data, length); } } @@ -809,16 +872,16 @@ 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); + if (upgrade && !is_co_socket()) { + auto *serv = get_async_server(); + auto *conn = serv->get_connection_verify(fd); if (conn && conn->websocket_status == websocket::STATUS_HANDSHAKE) { if (response.status == 101) { conn->websocket_status = websocket::STATUS_ACTIVE; } else { /* connection should be closed when handshake failed */ - conn->websocket_status = websocket::STATUS_NONE; + conn->websocket_status = websocket::STATUS_HANDSHAKE_FAILED; keepalive = 0; } } @@ -831,21 +894,22 @@ void HttpContext::end(zval *zdata, zval *return_value) { } bool HttpContext::set_header(const char *k, size_t klen, const char *v, size_t vlen, bool format) { - zval ztmp; - ZVAL_STRINGL(&ztmp, v, vlen); - Z_ADDREF(ztmp); - return set_header(k, klen, &ztmp, format); + zend::Variable ztmp(v, vlen); + return set_header(k, klen, ztmp.ptr(), format); +} + +bool HttpContext::set_header(const char *k, size_t klen, const std::string &v, bool format) { + zend::Variable ztmp(v); + return set_header(k, klen, ztmp.ptr(), format); } bool HttpContext::set_header(const char *k, size_t klen, zval *zvalue, bool format) { if (UNEXPECTED(klen > SW_HTTP_HEADER_KEY_SIZE - 1)) { php_swoole_error(E_WARNING, "header key is too long"); - Z_TRY_DELREF_P(zvalue); return false; } if (swoole_http_has_crlf(k, klen)) { - Z_TRY_DELREF_P(zvalue); return false; } @@ -856,16 +920,16 @@ bool HttpContext::set_header(const char *k, size_t klen, zval *zvalue, bool form if (http2) { swoole_strtolower(sw_tg_buffer()->str, klen); } else { - http_header_key_format(sw_tg_buffer()->str, klen); + http_response_header_key_format(sw_tg_buffer()->str, klen); } k = sw_tg_buffer()->str; } - add_assoc_zval_ex(zheader, k, klen, zvalue); + zend::array_set(zheader, k, klen, zvalue); return true; } static PHP_METHOD(swoole_http_response, sendfile) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -875,27 +939,29 @@ static PHP_METHOD(swoole_http_response, sendfile) { RETURN_FALSE; } - char *file; - size_t l_file; + zend_string *file; zend_long offset = 0; zend_long length = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &file, &l_file, &offset, &length) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STR(file) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + Z_PARAM_LONG(length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (l_file == 0) { + if (ZSTR_LEN(file) == 0) { php_swoole_error(E_WARNING, "file name is empty"); RETURN_FALSE; } struct stat file_stat; - if (stat(file, &file_stat) < 0) { - php_swoole_sys_error(E_WARNING, "stat(%s) failed", file); + if (stat(file->val, &file_stat) < 0) { + php_swoole_sys_error(E_WARNING, "stat(%s) failed", file->val); RETURN_FALSE; } if (!S_ISREG(file_stat.st_mode)) { - php_swoole_error(E_WARNING, "parameter $file[%s] given is not a regular file", file); + php_swoole_error(E_WARNING, "parameter $file[%s] given is not a regular file", file->val); swoole_set_last_error(SW_ERROR_SERVER_IS_NOT_REGULAR_FILE); RETURN_FALSE; } @@ -917,120 +983,78 @@ static PHP_METHOD(swoole_http_response, sendfile) { if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE)) { swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_RESPONSE, ctx); } - if (ctx->http2) { - RETURN_BOOL(ctx->http2_send_file(file, l_file, offset, length)); - } else { - RETURN_BOOL(ctx->send_file(file, l_file, offset, length)); + RETURN_BOOL(ctx->send_file(file, offset, length)); +} + +static bool inline http_response_create_cookie(HttpCookie *cookie, zval *zobject) { + HttpContext *ctx = http_response_get_and_check_context(zobject); + + zend_string *cookie_str = cookie->toString(); + if (!cookie_str) { + cookie->reset(); + return false; } + + add_next_index_str( + swoole_http_init_and_read_property( + swoole_http_response_ce, ctx->response.zobject, &ctx->response.zcookie, SW_ZSTR_KNOWN(SW_ZEND_STR_COOKIE)), + cookie_str); + + return true; } -static void php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAMETERS, const bool url_encode) { - char *name = nullptr, *value = nullptr, *path = nullptr, *domain = nullptr, *samesite = nullptr, - *priority = nullptr; +static void http_response_set_cookie(INTERNAL_FUNCTION_PARAMETERS, const bool encode) { + zval *name_or_object; + zend_string *value = nullptr, *path = nullptr, *domain = nullptr, *sameSite = nullptr, *priority = nullptr; zend_long expires = 0; - size_t name_len, value_len = 0, path_len = 0, domain_len = 0, samesite_len = 0, priority_len = 0; - zend_bool secure = 0, httponly = 0; + zend_bool secure = false, httpOnly = false, partitioned = false; + bool result; - ZEND_PARSE_PARAMETERS_START(1, 9) - Z_PARAM_STRING(name, name_len) + ZEND_PARSE_PARAMETERS_START(1, 10) + Z_PARAM_ZVAL(name_or_object) Z_PARAM_OPTIONAL - Z_PARAM_STRING(value, value_len) + Z_PARAM_STR(value) Z_PARAM_LONG(expires) - Z_PARAM_STRING(path, path_len) - Z_PARAM_STRING(domain, domain_len) + Z_PARAM_STR(path) + Z_PARAM_STR(domain) Z_PARAM_BOOL(secure) - Z_PARAM_BOOL(httponly) - Z_PARAM_STRING(samesite, samesite_len) - Z_PARAM_STRING(priority, priority_len) + Z_PARAM_BOOL(httpOnly) + Z_PARAM_STR(sameSite) + Z_PARAM_STR(priority) + Z_PARAM_BOOL(partitioned) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); - if (UNEXPECTED(!ctx)) { - RETURN_FALSE; - } - - size_t cookie_size = name_len /* + value_len */ + path_len + domain_len + 100; - char *cookie = nullptr, *date = nullptr; - - if (name_len > 0 && strpbrk(name, "=,; \t\r\n\013\014") != nullptr) { - php_swoole_error(E_WARNING, "Cookie names can't contain any of the following '=,; \\t\\r\\n\\013\\014'"); - RETURN_FALSE; - } - - if (!url_encode && swoole_http_has_crlf(value, value_len)) { - RETURN_FALSE; - } - - 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); + if (ZVAL_IS_STRING(name_or_object)) { + HttpCookie cookie(encode); + (&cookie) + ->withName(Z_STR_P(name_or_object)) + ->withValue(value) + ->withExpires(expires) + ->withPath(path) + ->withDomain(domain) + ->withSecure(secure) + ->withHttpOnly(httpOnly) + ->withSameSite(sameSite) + ->withPriority(priority) + ->withPartitioned(partitioned); + result = http_response_create_cookie(&cookie, ZEND_THIS); + } else if (ZVAL_IS_OBJECT(name_or_object)) { + HttpCookie *cookie = php_swoole_http_get_cooke_safety(name_or_object); + result = http_response_create_cookie(cookie, ZEND_THIS); } else { - if (url_encode) { - char *encoded_value; - int encoded_value_len; - encoded_value = php_swoole_url_encode(value, value_len, &encoded_value_len); - cookie_size += encoded_value_len; - cookie = (char *) emalloc(cookie_size); - sw_snprintf(cookie, cookie_size, "%s=%s", name, encoded_value); - efree(encoded_value); - } else { - cookie_size += value_len; - cookie = (char *) emalloc(cookie_size); - sw_snprintf(cookie, cookie_size, "%s=%s", name, value); - } - 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)); - if (!p || *(p + 5) != ' ') { - php_swoole_error(E_WARNING, "Expiry date can't be a year greater than 9999"); - efree(date); - efree(cookie); - RETURN_FALSE; - } - strlcat(cookie, date, cookie_size); - efree(date); - } - } - if (path_len > 0) { - strlcat(cookie, "; path=", cookie_size); - strlcat(cookie, path, cookie_size); - } - if (domain_len > 0) { - strlcat(cookie, "; domain=", cookie_size); - strlcat(cookie, domain, cookie_size); - } - if (secure) { - strlcat(cookie, "; secure", cookie_size); + php_swoole_error(E_WARNING, "The first argument must be a string or an cookie object"); + result = false; } - if (httponly) { - strlcat(cookie, "; httponly", cookie_size); - } - if (samesite_len > 0) { - strlcat(cookie, "; samesite=", cookie_size); - strlcat(cookie, samesite, cookie_size); - } - if (priority_len > 0) { - strlcat(cookie, "; priority=", cookie_size); - strlcat(cookie, priority, cookie_size); - } - add_next_index_stringl( - swoole_http_init_and_read_property( - swoole_http_response_ce, ctx->response.zobject, &ctx->response.zcookie, ZEND_STRL("cookie")), - cookie, - strlen(cookie)); - efree(cookie); - RETURN_TRUE; + + RETURN_BOOL(result); } static PHP_METHOD(swoole_http_response, cookie) { - php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); + http_response_set_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_http_response, rawcookie) { - php_swoole_http_response_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); + http_response_set_cookie(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_http_response, status) { @@ -1044,7 +1068,7 @@ static PHP_METHOD(swoole_http_response, status) { Z_PARAM_STRING(reason, reason_len) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -1054,11 +1078,48 @@ static PHP_METHOD(swoole_http_response, status) { RETURN_TRUE; } +static PHP_METHOD(swoole_http_response, disconnect) { + HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); + if (UNEXPECTED(!ctx)) { + swoole_set_last_error(SW_ERROR_SESSION_CLOSED); + RETURN_FALSE; + } + if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { + php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); + RETURN_FALSE; + } + + zend_long code = WebSocket::CLOSE_NORMAL; + zend_string *reason = zend_empty_string; + zend_long flags = WebSocket::FLAG_FIN; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(code) + Z_PARAM_STR(reason) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + String *http_buffer = ctx->get_write_buffer(); + + zval zdata = {}; + ZVAL_STR(&zdata, reason); + + FrameObject frame(&zdata, WebSocket::OPCODE_CLOSE, flags, code); + + if (sw_unlikely(!frame.pack(http_buffer))) { + swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); + RETURN_FALSE; + } + + RETVAL_BOOL(ctx->send(ctx, http_buffer->str, http_buffer->length)); + ctx->close(ctx); +} + static PHP_METHOD(swoole_http_response, header) { char *k; size_t klen; zval *zvalue; - zend_bool format = 1; + zend_bool format = true; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_STRING(k, klen) @@ -1067,11 +1128,10 @@ static PHP_METHOD(swoole_http_response, header) { Z_PARAM_BOOL(format) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } - Z_TRY_ADDREF_P(zvalue); RETURN_BOOL(ctx->set_header(k, klen, zvalue, format)); } @@ -1085,7 +1145,7 @@ static PHP_METHOD(swoole_http_response, trailer) { Z_PARAM_STRING_EX(v, vlen, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (!ctx) { RETURN_FALSE; } @@ -1107,19 +1167,33 @@ static PHP_METHOD(swoole_http_response, trailer) { } static PHP_METHOD(swoole_http_response, ping) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } - if (UNEXPECTED(!ctx->http2)) { - php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a HTTP2 conncetion", ctx->fd); + + zend_string *zdata = zend_empty_string; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR(zdata) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (ctx->http2) { + RETURN_BOOL(swoole_http2_server_ping(ctx)); + } else if (ctx->websocket) { + String *buffer = ctx->get_write_buffer(); + buffer->clear(); + WebSocket::encode(buffer, ZSTR_VAL(zdata), ZSTR_LEN(zdata), WebSocket::OPCODE_PING, WebSocket::FLAG_FIN); + RETURN_BOOL(ctx->send(ctx, buffer->str, buffer->length)); + } else { + php_swoole_fatal_error(E_WARNING, "only supports websocket or http2 client"); RETURN_FALSE; } - SW_CHECK_RETURN(swoole_http2_server_ping(ctx)); } static PHP_METHOD(swoole_http_response, goaway) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -1127,23 +1201,25 @@ static PHP_METHOD(swoole_http_response, goaway) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a HTTP2 conncetion", ctx->fd); RETURN_FALSE; } + zend_long error_code = SW_HTTP2_ERROR_NO_ERROR; - char *debug_data = nullptr; - size_t debug_data_len = 0; + zend_string *debug_data = nullptr; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ls", &error_code, &debug_data, &debug_data_len) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(error_code) + Z_PARAM_STR_OR_NULL(debug_data) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - SW_CHECK_RETURN(swoole_http2_server_goaway(ctx, error_code, debug_data, debug_data_len)); + RETURN_BOOL(swoole_http2_server_goaway(ctx, error_code, debug_data)); } static PHP_METHOD(swoole_http_response, upgrade) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } - if (UNEXPECTED(!ctx->co_socket)) { + if (UNEXPECTED(!ctx->is_co_socket())) { php_swoole_fatal_error(E_WARNING, "async server dose not support protocol upgrade"); RETURN_FALSE; } @@ -1156,7 +1232,7 @@ static PHP_METHOD(swoole_http_response, push) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } - if (UNEXPECTED(!ctx->co_socket || !ctx->upgrade)) { + if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); RETURN_FALSE; } @@ -1178,17 +1254,19 @@ static PHP_METHOD(swoole_http_response, push) { } String *http_buffer = ctx->get_write_buffer(); - http_buffer->clear(); - if (php_swoole_websocket_frame_is_object(zdata)) { - if (php_swoole_websocket_frame_object_pack(http_buffer, zdata, 0, ctx->websocket_compression) < 0) { - RETURN_FALSE; - } - } else { - if (php_swoole_websocket_frame_pack( - http_buffer, zdata, opcode, flags & WebSocket::FLAGS_ALL, 0, ctx->websocket_compression) < 0) { - RETURN_FALSE; - } + + FrameObject frame(zdata, opcode, flags); + sw_unset_bit(frame.flags, WebSocket::FLAG_MASK); + + if (ctx->websocket_compression) { + sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); } + + if (sw_unlikely(!frame.pack(http_buffer))) { + swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); + RETURN_FALSE; + } + RETURN_BOOL(ctx->send(ctx, http_buffer->str, http_buffer->length)); } @@ -1201,13 +1279,170 @@ static PHP_METHOD(swoole_http_response, close) { RETURN_BOOL(ctx->close(ctx)); } +ssize_t WebSocket::send_frame(const swoole::WebSocketSettings &settings, + SocketImpl *sock, + uchar opcode, + uchar flags, + const char *payload, + size_t payload_length) { + if (settings.in_server) { + sw_unset_bit(flags, WebSocket::FLAG_MASK); + } else { + sw_set_bit(flags, WebSocket::FLAG_MASK); + } + auto wbuf = sock->get_write_buffer(); + wbuf->clear(); + WebSocket::encode(wbuf, payload, payload_length, opcode, flags); + return sock->send(wbuf->str, wbuf->length); +} + +/** + * return_value is object means success. + * return_value is false means socket.read() method returning -1. + * return_value is null means other error. + * return_value is empry string means socket is closed. + * the opcode is returned so the caller can decide when to release the continue_frame_buffer. + */ +void WebSocket::recv_frame(const WebSocketSettings &settings, + std::shared_ptr &continue_frame_buffer, + SocketImpl *sock, + zval *return_value, + double timeout) { + zval zpayload; + + do { + ssize_t retval = sock->recv_packet(timeout); + if (retval < 0) { + RETURN_FALSE; + } else if (retval == 0) { + RETURN_EMPTY_STRING(); + } + + auto buffer = sock->get_read_buffer(); + WebSocket::Frame frame{}; + + if (sw_unlikely(static_cast(buffer->length) < sizeof(frame.header))) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_NULL(); + } + + if (sw_unlikely(!WebSocket::decode(&frame, buffer))) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_NULL(); + } + + uchar opcode = frame.header.OPCODE; + bool should_respond = false; + if (opcode == WebSocket::OPCODE_PING) { + if (!settings.open_ping_frame) { + WebSocket::send_frame( + settings, sock, WebSocket::OPCODE_PONG, WebSocket::FLAG_FIN, frame.payload, frame.payload_length); + continue; + } + should_respond = true; + } + if (opcode == WebSocket::OPCODE_PONG) { + if (!settings.open_pong_frame) { + continue; + } + should_respond = true; + } + if (opcode == WebSocket::OPCODE_CLOSE) { + if (!settings.open_close_frame) { + WebSocket::send_frame( + settings, sock, WebSocket::OPCODE_CLOSE, WebSocket::FLAG_FIN, frame.payload, frame.payload_length); + continue; + } + should_respond = true; + } + + if (should_respond) { + ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); + WebSocket::construct_frame(return_value, opcode, &zpayload, WebSocket::FLAG_FIN); + zval_ptr_dtor(&zpayload); + zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); + return; + } + + if (opcode == WebSocket::OPCODE_CONTINUATION) { + if (sw_unlikely(!continue_frame_buffer)) { + swoole_warning("A continuation frame cannot stand alone and MUST be preceded by an initial frame whose " + "opcode indicates either text or binary data."); + RETURN_NULL(); + } + + if (sw_likely(frame.payload)) { + continue_frame_buffer->append(frame.payload, frame.payload_length); + } + + if (frame.header.FIN) { + uchar complete_opcode = 0; + uchar complete_flags = 0; + WebSocket::parse_ext_flags(continue_frame_buffer->offset, &complete_opcode, &complete_flags); + + if (complete_flags & WebSocket::FLAG_RSV1) { + if (sw_unlikely(!FrameObject::uncompress( + &zpayload, continue_frame_buffer->str, continue_frame_buffer->length))) { + continue_frame_buffer.reset(); + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_NULL(); + } + } else { + zend::assign_zend_string_by_val( + &zpayload, continue_frame_buffer->str, continue_frame_buffer->length); + Z_TRY_ADDREF(zpayload); + } + + sw_set_bit(complete_flags, WebSocket::FLAG_FIN); + WebSocket::construct_frame(return_value, complete_opcode, &zpayload, complete_flags); + zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); + zval_ptr_dtor(&zpayload); + /** + * The final frame of the continuous frame sequence has been received, + * and the complete message has been assembled. Memory can be released immediately. + */ + continue_frame_buffer.reset(); + return; + } + } else { + if (sw_unlikely(continue_frame_buffer)) { + swoole_warning("All fragments of a message, except for the initial frame, must use the continuation " + "frame opcode(0)."); + RETURN_NULL(); + } + if (frame.header.FIN) { + if (frame.compressed()) { + if (sw_unlikely(!FrameObject::uncompress(&zpayload, frame.payload, frame.payload_length))) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_NULL(); + } + } else { + ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); + } + WebSocket::construct_frame(return_value, frame.header.OPCODE, &zpayload, frame.get_flags()); + zend::object_set(return_value, ZEND_STRL("fd"), sock->get_fd()); + zval_ptr_dtor(&zpayload); + return; + } else { + continue_frame_buffer = std::make_shared( + (frame.payload_length > 0 ? frame.payload_length : SW_WEBSOCKET_DEFAULT_BUFFER), + sw_zend_string_allocator()); + continue_frame_buffer->offset = WebSocket::get_ext_flags(frame.header.OPCODE, frame.get_flags()); + if (sw_likely(frame.payload)) { + continue_frame_buffer->append(frame.payload, frame.payload_length); + } + } + } + } while (true); +} + static PHP_METHOD(swoole_http_response, recv) { HttpContext *ctx = php_swoole_http_response_get_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { swoole_set_last_error(SW_ERROR_SESSION_CLOSED); RETURN_FALSE; } - if (UNEXPECTED(!ctx->co_socket || !ctx->upgrade)) { + if (UNEXPECTED(!ctx->is_co_socket() || !ctx->upgrade)) { php_swoole_fatal_error(E_WARNING, "fd[%ld] is not a websocket conncetion", ctx->fd); RETURN_FALSE; } @@ -1219,31 +1454,19 @@ static PHP_METHOD(swoole_http_response, recv) { Z_PARAM_DOUBLE(timeout) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - Socket *sock = (Socket *) ctx->private_data; - ssize_t retval = sock->recv_packet(timeout); - String _tmp; - - if (retval < 0) { - swoole_set_last_error(sock->errCode); - RETURN_FALSE; - } else if (retval == 0) { - RETURN_EMPTY_STRING(); - } else { - _tmp.str = sock->get_read_buffer()->str; - _tmp.length = retval; - -#ifdef SW_HAVE_ZLIB - php_swoole_websocket_frame_unpack_ex(&_tmp, return_value, ctx->websocket_compression); -#else - php_swoole_websocket_frame_unpack(&_tmp, return_value); -#endif - zend_update_property_long( - swoole_websocket_frame_ce, SW_Z8_OBJ_P(return_value), ZEND_STRL("fd"), sock->get_fd()); + WebSocket::recv_frame( + ctx->websocket_settings, ctx->continue_frame_buffer, ctx->get_co_socket(), return_value, timeout); + if (ZVAL_IS_EMPTY_STRING(return_value)) { + ctx->close(ctx); + return; + } + if (sw_unlikely(ZVAL_IS_NULL(return_value))) { + ZVAL_FALSE(return_value); } } static PHP_METHOD(swoole_http_response, detach) { - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (!ctx) { RETURN_FALSE; } @@ -1254,9 +1477,9 @@ static PHP_METHOD(swoole_http_response, detach) { static PHP_METHOD(swoole_http_response, create) { zval *zobject = nullptr; zval *zrequest = nullptr; + zval *zsocket = nullptr; zend_long fd = -1; Server *serv = nullptr; - Socket *sock = nullptr; HttpContext *ctx = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) @@ -1270,12 +1493,12 @@ static PHP_METHOD(swoole_http_response, create) { if (instanceof_function(Z_OBJCE_P(zobject), swoole_server_ce)) { serv = php_swoole_server_get_and_check_server(zobject); if (serv->get_connection_verify(fd) == nullptr) { - php_swoole_fatal_error(E_WARNING, "parameter $2 must be valid connection session id"); + php_swoole_fatal_error(E_WARNING, "parameter $2 (%ld) must be valid connection session_id", (long) fd); RETURN_FALSE; } - } else if (instanceof_function(Z_OBJCE_P(zobject), swoole_socket_coro_ce)) { - sock = php_swoole_get_socket(zobject); - fd = sock->get_fd(); + } else if (sw_zval_is_co_socket(zobject)) { + zsocket = zobject; + fd = php_swoole_get_socket(zobject)->get_fd(); } else { _bad_type: php_swoole_fatal_error(E_WARNING, "parameter $1 must be instanceof Server or Coroutine\\Socket"); @@ -1310,10 +1533,9 @@ static PHP_METHOD(swoole_http_response, create) { if (serv) { ctx->init(serv); - } else if (sock) { - ctx->init(sock); - ctx->parser.data = ctx; - swoole_http_parser_init(&ctx->parser, PHP_HTTP_REQUEST); + } else if (zsocket) { + ctx->init(zsocket); + swoole_llhttp_parser_init(&ctx->parser, HTTP_REQUEST, (void *) ctx); } else { delete ctx; assert(0); @@ -1322,8 +1544,8 @@ static PHP_METHOD(swoole_http_response, create) { } else { if (serv) { ctx->bind(serv); - } else if (sock) { - ctx->bind(sock); + } else if (zsocket) { + ctx->bind(zsocket); } else { assert(0); RETURN_FALSE; @@ -1336,7 +1558,7 @@ static PHP_METHOD(swoole_http_response, create) { ctx->response.zobject = return_value; sw_copy_to_stack(ctx->response.zobject, ctx->response._zobject); zend_update_property_long(swoole_http_response_ce, SW_Z8_OBJ_P(return_value), ZEND_STRL("fd"), fd); - if (ctx->co_socket) { + if (ctx->is_co_socket()) { zend_update_property_ex( swoole_http_response_ce, SW_Z8_OBJ_P(ctx->response.zobject), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), zobject); } @@ -1355,7 +1577,7 @@ static PHP_METHOD(swoole_http_response, redirect) { Z_PARAM_ZVAL_EX(zhttp_code, 1, 0) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - HttpContext *ctx = php_swoole_http_response_get_and_check_context(ZEND_THIS); + HttpContext *ctx = http_response_get_and_check_context(ZEND_THIS); if (UNEXPECTED(!ctx)) { RETURN_FALSE; } @@ -1366,15 +1588,7 @@ static PHP_METHOD(swoole_http_response, redirect) { } else { ctx->response.status = 302; } - - zval zkey; - ZVAL_STRINGL(&zkey, "Location", 8); - sw_zend_call_method_with_2_params(ZEND_THIS, nullptr, nullptr, "header", return_value, &zkey, zurl); - zval_ptr_dtor(&zkey); - if (!Z_BVAL_P(return_value)) { - return; - } + // header + ctx->set_header(ZEND_STRL("Location"), zurl, false); ctx->end(nullptr, return_value); } - -static PHP_METHOD(swoole_http_response, __destruct) {} diff --git a/ext-src/swoole_http_server.cc b/ext-src/swoole_http_server.cc index aa50a914aa..117aa6ca49 100644 --- a/ext-src/swoole_http_server.cc +++ b/ext-src/swoole_http_server.cc @@ -17,29 +17,38 @@ #include "php_swoole_http_server.h" #include "swoole_process_pool.h" -using namespace swoole; -using swoole::coroutine::Socket; +BEGIN_EXTERN_C() +#include "rfc1867.h" +END_EXTERN_C() using HttpRequest = swoole::http::Request; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; +using swoole::Connection; +using swoole::DataHead; +using swoole::RecvData; +using swoole::Server; +using swoole::SessionId; + namespace WebSocket = swoole::websocket; zend_class_entry *swoole_http_server_ce; zend_object_handlers swoole_http_server_handlers; -static std::queue queued_http_contexts; +static SW_THREAD_LOCAL std::queue queued_http_contexts; +static SW_THREAD_LOCAL std::unordered_map server_ips; +static SW_THREAD_LOCAL std::unordered_map client_ips; static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length); -static bool http_context_sendfile(HttpContext *ctx, const char *file, uint32_t l_file, off_t offset, size_t length); +static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length); static bool http_context_disconnect(HttpContext *ctx); -static void http_server_process_request(Server *serv, zend_fcall_info_cache *fci_cache, HttpContext *ctx) { +static void http_server_process_request(const Server *serv, zend::Callable *cb, HttpContext *ctx) { zval args[2]; args[0] = *ctx->request.zobject; args[1] = *ctx->response.zobject; - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onRequest handler error", ZSTR_VAL(swoole_http_server_ce->name)); #ifdef SW_HTTP_SERVICE_UNAVAILABLE_PACKET ctx->send(ctx, SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET)); @@ -49,23 +58,23 @@ static void http_server_process_request(Server *serv, zend_fcall_info_cache *fci } int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { - SessionId session_id = req->info.fd; - int server_fd = req->info.server_fd; + auto session_id = req->info.fd; + auto server_fd = req->info.server_fd; Connection *conn = serv->get_connection_verify_no_ssl(session_id); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); return SW_ERR; } - ListenPort *port = serv->get_port_by_server_fd(server_fd); + auto *port = serv->get_port_by_server_fd(server_fd); // other server port if (!(port->open_http_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onRequest)) && !(port->open_websocket_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onMessage))) { return php_swoole_server_onReceive(serv, req); } // websocket client - if (conn->websocket_status == WebSocket::STATUS_ACTIVE) { + if (conn->websocket_status >= WebSocket::STATUS_HANDSHAKE) { return swoole_websocket_onMessage(serv, req); } @@ -75,6 +84,7 @@ int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { HttpContext *ctx = swoole_http_context_new(session_id); ctx->init(serv); + ctx->onBeforeRequest = swoole_http_server_onBeforeRequest; zval *zdata = &ctx->request.zdata; php_swoole_get_recv_data(serv, zdata, req); @@ -89,41 +99,41 @@ int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { zval *zrequest_object = ctx->request.zobject; zval *zresponse_object = ctx->response.zobject; - swoole_http_parser *parser = &ctx->parser; - parser->data = ctx; - swoole_http_parser_init(parser, PHP_HTTP_REQUEST); + llhttp_t *parser = &ctx->parser; + swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); size_t parsed_n = ctx->parse(Z_STRVAL_P(zdata), Z_STRLEN_P(zdata)); - if (ctx->parser.state == s_dead) { + if (sw_unlikely(ctx->parser.error != HPE_OK || !ctx->completed)) { ctx->send(ctx, SW_STRL(SW_HTTP_BAD_REQUEST_PACKET)); ctx->close(ctx); - swoole_notice("request is illegal and it has been discarded, %ld bytes unprocessed", - Z_STRLEN_P(zdata) - parsed_n); + if (ctx->parser.error != HPE_OK) { + swoole_notice("Invalid HTTP request discarded: %ld bytes unprocessed. Reason: %s", + Z_STRLEN_P(zdata) - parsed_n, + llhttp_get_error_reason(&ctx->parser)); + } else { + swoole_notice("Incomplete HTTP request: parsed successfully but missing required components"); + } goto _dtor_and_return; } do { zval *zserver = ctx->request.zserver; - Connection *serv_sock = serv->get_connection(conn->server_fd); - if (serv_sock) { - add_assoc_long(zserver, "server_port", serv_sock->info.get_port()); - } - add_assoc_long(zserver, "remote_port", conn->info.get_port()); - add_assoc_string(zserver, "remote_addr", (char *) conn->info.get_ip()); - add_assoc_long(zserver, "master_time", (int) conn->last_recv_time); - } while (0); + HashTable *ht = Z_ARR_P(zserver); + swoole_http_server_populate_ip_and_port(serv, ht, conn, session_id, ctx->keepalive); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_MASTER_TIME), (zend_long) conn->last_recv_time); + } while (false); - if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_REQUEST)) { - swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_REQUEST, ctx); + if (swoole_isset_hook(static_cast(PHP_SWOOLE_HOOK_BEFORE_REQUEST))) { + swoole_call_hook(static_cast(PHP_SWOOLE_HOOK_BEFORE_REQUEST), ctx); } - // begin to check and call registerd callback + // begin to check and call registered callback do { - zend_fcall_info_cache *fci_cache = nullptr; + zend::Callable *cb = nullptr; if (conn->websocket_status == WebSocket::STATUS_CONNECTION) { - fci_cache = php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onHandShake); - if (fci_cache == nullptr) { + cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onHandshake); + if (cb == nullptr) { swoole_websocket_onHandshake(serv, port, ctx); goto _dtor_and_return; } else { @@ -131,18 +141,18 @@ int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { ctx->upgrade = 1; } } else { - fci_cache = php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onRequest); - if (fci_cache == nullptr) { + cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onRequest); + if (cb == nullptr) { swoole_websocket_onRequest(ctx); goto _dtor_and_return; } } - ctx->private_data_2 = fci_cache; + ctx->private_data_2 = cb; if (ctx->onBeforeRequest && !ctx->onBeforeRequest(ctx)) { return SW_OK; } - http_server_process_request(serv, fci_cache, ctx); - } while (0); + http_server_process_request(serv, cb, ctx); + } while (false); _dtor_and_return: zval_ptr_dtor(zrequest_object); @@ -151,6 +161,12 @@ int php_swoole_http_server_onReceive(Server *serv, RecvData *req) { return SW_OK; } +void php_swoole_http_server_onClose(Server *serv, DataHead *info) { + server_ips.erase(info->fd); + client_ips.erase(info->fd); + php_swoole_server_onClose(serv, info); +} + void php_swoole_http_server_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX(swoole_http_server, "Swoole\\Http\\Server", nullptr, nullptr, swoole_server); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_http_server); @@ -166,26 +182,48 @@ void php_swoole_http_server_rinit() { } } +void php_swoole_http_server_rshutdown() { + if (SG(rfc1867_uploaded_files)) { + destroy_uploaded_files_hash(); + SG(rfc1867_uploaded_files) = nullptr; + } + + server_ips.clear(); + client_ips.clear(); + while (!queued_http_contexts.empty()) { + HttpContext *ctx = queued_http_contexts.front(); + queued_http_contexts.pop(); + ctx->end_ = 1; + ctx->onAfterResponse = nullptr; + zval_ptr_dtor(ctx->request.zobject); + zval_ptr_dtor(ctx->response.zobject); + } +} + HttpContext *swoole_http_context_new(SessionId fd) { - HttpContext *ctx = new HttpContext(); + auto *ctx = new HttpContext(); zval *zrequest_object = &ctx->request._zobject; ctx->request.zobject = zrequest_object; - object_init_ex(zrequest_object, swoole_http_request_ce); + ZVAL_OBJ(zrequest_object, swoole_http_request_ce->create_object(swoole_http_request_ce)); php_swoole_http_request_set_context(zrequest_object, ctx); zval *zresponse_object = &ctx->response._zobject; ctx->response.zobject = zresponse_object; - object_init_ex(zresponse_object, swoole_http_response_ce); + ZVAL_OBJ(zresponse_object, swoole_http_response_ce->create_object(swoole_http_response_ce)); php_swoole_http_response_set_context(zresponse_object, ctx); - zend_update_property_long(swoole_http_request_ce, SW_Z8_OBJ_P(zrequest_object), ZEND_STRL("fd"), fd); - zend_update_property_long(swoole_http_response_ce, SW_Z8_OBJ_P(zresponse_object), ZEND_STRL("fd"), fd); + http_server_set_object_fd_property(SW_Z8_OBJ_P(zrequest_object), swoole_http_request_ce, fd); + http_server_set_object_fd_property(SW_Z8_OBJ_P(zresponse_object), swoole_http_response_ce, fd); + swoole_http_init_and_read_property(swoole_http_request_ce, + zrequest_object, + &ctx->request.zserver, + SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER), + HT_MIN_SIZE << 1); swoole_http_init_and_read_property( - swoole_http_request_ce, zrequest_object, &ctx->request.zserver, ZEND_STRL("server")); - swoole_http_init_and_read_property( - swoole_http_request_ce, zrequest_object, &ctx->request.zheader, ZEND_STRL("header")); + swoole_http_request_ce, zrequest_object, &ctx->request.zheader, SW_ZSTR_KNOWN(SW_ZEND_STR_HEADER)); + ctx->fd = fd; return ctx; @@ -210,11 +248,10 @@ void HttpContext::bind(Server *serv) { send = http_context_send_data; sendfile = http_context_sendfile; close = http_context_disconnect; - onBeforeRequest = swoole_http_server_onBeforeRequest; - onAfterResponse = swoole_http_server_onAfterResponse; + ZVAL_NULL(&zsocket); } -void HttpContext::copy(HttpContext *ctx) { +void HttpContext::copy(const HttpContext *ctx) { parse_cookie = ctx->parse_cookie; parse_body = ctx->parse_body; parse_files = ctx->parse_files; @@ -224,7 +261,8 @@ void HttpContext::copy(HttpContext *ctx) { compression_min_length = ctx->compression_min_length; compression_types = ctx->compression_types; #endif - co_socket = ctx->co_socket; + zsocket = ctx->zsocket; + Z_TRY_ADDREF(zsocket); private_data = ctx->private_data; upload_tmp_dir = ctx->upload_tmp_dir; send = ctx->send; @@ -234,23 +272,15 @@ void HttpContext::copy(HttpContext *ctx) { onAfterResponse = ctx->onAfterResponse; } -bool HttpContext::is_available() { +bool HttpContext::is_available() const { if (!response.zobject) { return false; } - if (co_socket) { - zval rv; - zval *zconn = zend_read_property_ex( - swoole_http_response_ce, SW_Z8_OBJ_P(response.zobject), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), 1, &rv); - if (!zconn || ZVAL_IS_NULL(zconn)) { - return false; - } - if (php_swoole_socket_is_closed(zconn)) { - return false; - } + if (is_co_socket()) { + return !php_swoole_socket_is_closed(&zsocket); } else { - Server *serv = (Server *) private_data; - Connection *conn = serv->get_connection_by_session_id(fd); + auto *serv = get_async_server(); + auto *conn = serv->get_connection_by_session_id(fd); if (!conn || conn->closed || conn->peer_closed) { return false; } @@ -259,11 +289,9 @@ bool HttpContext::is_available() { } void HttpContext::free() { - /* http context can only be free'd after request and response were free'd */ - if (request.zobject || response.zobject) { - return; - } - if (stream) { + // http context can only be freed after request and response were freed + // If is an HTTP2 request, must wait for the stream to release before releasing the HTTP context + if (request.zobject || response.zobject || stream_id > 0) { return; } @@ -275,12 +303,10 @@ void HttpContext::free() { if (Z_TYPE(req->zdata) == IS_STRING) { zend_string_release(Z_STR(req->zdata)); } - if (req->chunked_body) { - delete req->chunked_body; - } - if (req->h2_data_buffer) { - delete req->h2_data_buffer; - } + + delete req->chunked_body; + delete req->h2_data_buffer; + if (res->reason) { efree(res->reason); } @@ -292,54 +318,51 @@ void HttpContext::free() { delete form_data_buffer; form_data_buffer = nullptr; } - delete this; -} -HttpContext *php_swoole_http_request_get_and_check_context(zval *zobject) { - HttpContext *ctx = php_swoole_http_request_get_context(zobject); - if (!ctx) { - php_swoole_fatal_error(E_WARNING, "http request is unavailable (maybe it has been ended)"); + if (is_co_socket()) { + zval_ptr_dtor(&zsocket); } - return ctx; -} -HttpContext *php_swoole_http_response_get_and_check_context(zval *zobject) { - HttpContext *ctx = php_swoole_http_response_get_context(zobject); - if (!ctx || (ctx->end_ || ctx->detached)) { - php_swoole_fatal_error(E_WARNING, "http response is unavailable (maybe it has been ended or detached)"); - return nullptr; - } - return ctx; + delete write_buffer; + delete this; } bool http_context_send_data(HttpContext *ctx, const char *data, size_t length) { - Server *serv = (Server *) ctx->private_data; - bool retval = serv->send(ctx->fd, (void *) data, length); + auto *serv = ctx->get_async_server(); + bool retval = serv->send(ctx->fd, data, length); if (!retval && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { - zval yield_data, return_value; - ZVAL_STRINGL(&yield_data, data, length); - php_swoole_server_send_yield(serv, ctx->fd, &yield_data, &return_value); - return Z_BVAL_P(&return_value); + auto sdata = zend_string_init(data, length, false); + auto rv = php_swoole_server_send_yield(serv, ctx->fd, sdata); + zend_string_release(sdata); + return rv; } return retval; } -static bool http_context_sendfile(HttpContext *ctx, const char *file, uint32_t l_file, off_t offset, size_t length) { - Server *serv = (Server *) ctx->private_data; - return serv->sendfile(ctx->fd, file, l_file, offset, length); +static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { + auto *serv = ctx->get_async_server(); + return serv->sendfile(ctx->fd, file->val, file->len, offset, length); } static bool http_context_disconnect(HttpContext *ctx) { - Server *serv = (Server *) ctx->private_data; - return serv->close(ctx->fd, 0); + auto *serv = ctx->get_async_server(); + return serv->close(ctx->fd, false); } bool swoole_http_server_onBeforeRequest(HttpContext *ctx) { - Server *serv = (Server *) ctx->private_data; - SwooleWG.worker->concurrency++; - sw_atomic_add_fetch(&serv->gs->concurrency, 1); + ctx->onBeforeRequest = nullptr; + ctx->onAfterResponse = swoole_http_server_onAfterResponse; + if (!sw_server() || !sw_worker() || sw_worker()->is_shutdown()) { + return false; + } + + auto serv = ctx->get_async_server(); + auto worker = sw_worker(); + swoole_trace("serv->gs->concurrency=%u, max_concurrency=%u", serv->gs->concurrency, serv->gs->max_concurrency); - if (SwooleWG.worker->concurrency > serv->worker_max_concurrency) { + sw_atomic_add_fetch(&serv->gs->concurrency, 1); + worker->concurrency++; + if (worker->concurrency > serv->worker_max_concurrency) { swoole_trace_log(SW_TRACE_COROUTINE, "exceed worker_max_concurrency[%u] limit, request[%p] queued", serv->worker_max_concurrency, @@ -353,25 +376,93 @@ bool swoole_http_server_onBeforeRequest(HttpContext *ctx) { void swoole_http_server_onAfterResponse(HttpContext *ctx) { ctx->onAfterResponse = nullptr; - Server *serv = (Server *) ctx->private_data; - SwooleWG.worker->concurrency--; - sw_atomic_sub_fetch(&serv->gs->concurrency, 1); + + if (sw_unlikely(!sw_server() || !sw_worker())) { + return; + } + + if (sw_unlikely(sw_worker()->is_shutdown())) { + while (!queued_http_contexts.empty()) { + HttpContext *_ctx = queued_http_contexts.front(); + queued_http_contexts.pop(); + _ctx->send(_ctx, SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET)); + _ctx->close(_ctx); + } + return; + } + + auto serv = ctx->get_async_server(); + auto worker = sw_worker(); swoole_trace("serv->gs->concurrency=%u, max_concurrency=%u", serv->gs->concurrency, serv->gs->max_concurrency); + sw_atomic_sub_fetch(&serv->gs->concurrency, 1); + worker->concurrency--; + if (!queued_http_contexts.empty()) { - HttpContext *ctx = queued_http_contexts.front(); - swoole_trace( - "[POP 1] concurrency=%u, ctx=%p, request=%p", SwooleWG.worker->concurrency, ctx, ctx->request.zobject); + HttpContext *_ctx = queued_http_contexts.front(); + swoole_trace("[POP 1] concurrency=%u, ctx=%p, request=%p", worker->concurrency, _ctx, _ctx->request.zobject); queued_http_contexts.pop(); swoole_event_defer( [](void *private_data) { - HttpContext *ctx = (HttpContext *) private_data; - Server *serv = (Server *) ctx->private_data; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ctx->private_data_2; + auto *ctx = static_cast(private_data); + auto *serv = ctx->get_async_server(); + auto *cb = static_cast(ctx->private_data_2); swoole_trace("[POP 2] ctx=%p, request=%p", ctx, ctx->request.zobject); - http_server_process_request(serv, fci_cache, ctx); + http_server_process_request(serv, cb, ctx); zval_ptr_dtor(ctx->request.zobject); zval_ptr_dtor(ctx->response.zobject); }, - ctx); + _ctx); + } +} + +/** + * When calculating the server-side IP and client-side IP, since these two calculations + * share the same memory block, they cannot be performed simultaneously; otherwise, + * both results would be identical. Therefore, it is necessary to first calculate the client-side IP, + * write it to the cache, and then proceed to calculate the server-side IP. + */ +static void http_server_session_track_ip(swoole::Server *server, + HashTable *ht, + swoole::Connection *conn, + swoole::SessionId session_id, + zend_string *known_string, + std::unordered_map &ips) { + auto iter = ips.find(session_id); + if (iter == ips.end()) { + const char *address = nullptr; + if (known_string == SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR)) { + address = server->get_local_addr(conn); + } else { + address = conn->info.get_addr(); + } + + auto rs = ips.emplace(session_id, address); + iter = rs.first; + } + + iter->second.add_ref(); + http_server_add_server_array(ht, known_string, iter->second.ptr()); +} + +void swoole_http_server_populate_ip_and_port( + Server *server, HashTable *ht, Connection *conn, SessionId session_id, bool keepalive) { + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PORT), (zend_long) conn->local_port); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_PORT), (zend_long) conn->info.get_port()); + + if (conn->info.is_loopback_addr()) { + auto key = conn->info.type == SW_SOCK_TCP6 ? SW_ZEND_STR_ADDR_LOOPBACK_V6 : SW_ZEND_STR_ADDR_LOOPBACK_V4; + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), SW_ZSTR_KNOWN(key)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), SW_ZSTR_KNOWN(key)); + } else { + if (keepalive && (server->is_base_mode() || + (server->is_process_mode() && server->dispatch_mode == Server::DISPATCH_FDMOD))) { + http_server_session_track_ip( + server, ht, conn, session_id, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server_ips); + http_server_session_track_ip( + server, ht, conn, session_id, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), client_ips); + } else { + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server->get_local_addr(conn)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), server->get_remote_addr(conn)); + } } } diff --git a/ext-src/swoole_http_server_coro.cc b/ext-src/swoole_http_server_coro.cc index b5ff84f120..9d192a1b71 100644 --- a/ext-src/swoole_http_server_coro.cc +++ b/ext-src/swoole_http_server_coro.cc @@ -15,49 +15,49 @@ */ #include "php_swoole_http_server.h" +#include "php_swoole_websocket.h" #include -#include BEGIN_EXTERN_C() #include "stubs/php_swoole_http_server_coro_arginfo.h" END_EXTERN_C() +using swoole::Coroutine; using swoole::microtime; using swoole::PHPCoroutine; using swoole::Server; using swoole::String; -using swoole::coroutine::Socket; using swoole::coroutine::System; +namespace WebSocket = swoole::websocket; + using HttpRequest = swoole::http::Request; using HttpResponse = swoole::http::Response; using HttpContext = swoole::http::Context; - -namespace http2 = swoole::http2; -using Http2Stream = http2::Stream; -using Http2Session = http2::Session; +using Http2Stream = swoole::http2::Stream; +using Http2Session = swoole::http2::Session; static zend_class_entry *swoole_http_server_coro_ce; static zend_object_handlers swoole_http_server_coro_handlers; static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length); -static bool http_context_sendfile(HttpContext *ctx, const char *file, uint32_t l_file, off_t offset, size_t length); +static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length); static bool http_context_disconnect(HttpContext *ctx); -static void http2_server_onRequest(Http2Session *session, Http2Stream *stream); +static void http2_server_onRequest(const std::shared_ptr &session, + const std::shared_ptr &stream); namespace swoole { namespace coroutine { class HttpServer { public: - Socket *socket; - zend_fcall_info_cache *default_handler; - std::map handlers; - zval zcallbacks; + SocketImpl *socket; + zend::Callable *default_handler; + std::unordered_map handlers; bool running; - std::list clients; + zval zclients; /* options */ bool parse_cookie; @@ -66,9 +66,8 @@ class HttpServer { #ifdef SW_HAVE_COMPRESSION bool compression; #endif -#ifdef SW_HAVE_ZLIB - bool websocket_compression; -#endif + WebSocketSettings websocket_settings; + char *upload_tmp_dir; #ifdef SW_HAVE_COMPRESSION uint8_t compression_level; @@ -76,10 +75,10 @@ class HttpServer { std::shared_ptr> compression_types = nullptr; #endif - HttpServer(enum swSocketType type) { - socket = new Socket(type); + explicit HttpServer(SocketType type) { + socket = new SocketImpl(type); default_handler = nullptr; - array_init(&zcallbacks); + array_init(&zclients); running = true; parse_cookie = true; @@ -91,37 +90,49 @@ class HttpServer { compression_min_length = SW_COMPRESSION_MIN_LENGTH_DEFAULT; #endif #ifdef SW_HAVE_ZLIB - websocket_compression = false; + websocket_settings.compression = false; #endif upload_tmp_dir = sw_strdup("/tmp"); } ~HttpServer() { sw_free(upload_tmp_dir); + zval_ptr_dtor(&zclients); + for (const auto &handler : handlers) { + sw_callable_free(handler.second); + } + delete socket; } - void set_handler(std::string pattern, zval *zcallback, const zend_fcall_info_cache *fci_cache) { - handlers[pattern] = *fci_cache; + bool set_handler(const std::string &pattern, zval *zfn) { + auto cb = sw_callable_create(zfn); + if (!cb) { + return false; + } + if (handlers.find(pattern) != handlers.end()) { + sw_callable_free(handlers[pattern]); + } + handlers[pattern] = cb; if (pattern == "/") { - default_handler = &handlers[pattern]; + default_handler = cb; } - Z_ADDREF_P(zcallback); - add_assoc_zval_ex(&zcallbacks, pattern.c_str(), pattern.length(), zcallback); + return true; } - zend_fcall_info_cache *get_handler(HttpContext *ctx) { - for (auto i = handlers.begin(); i != handlers.end(); i++) { - if (&i->second == default_handler) { + zend::Callable *get_handler(const HttpContext *ctx) const { + for (auto &handler : handlers) { + if (handler.second == default_handler) { continue; } - if (swoole_strcasect(ctx->request.path, ctx->request.path_len, i->first.c_str(), i->first.length())) { - return &i->second; + if (swoole_str_istarts_with( + ctx->request.path, ctx->request.path_len, handler.first.c_str(), handler.first.length())) { + return handler.second; } } return default_handler; } - HttpContext *create_context(Socket *conn, zval *zconn) { + HttpContext *create_context(SocketImpl *conn, zval *zconn) const { HttpContext *ctx = swoole_http_context_new(conn->get_fd()); ctx->parse_body = parse_post; ctx->parse_cookie = parse_cookie; @@ -132,16 +143,13 @@ class HttpServer { ctx->compression_min_length = compression_min_length; ctx->compression_types = compression_types; #endif -#ifdef SW_HAVE_ZLIB - ctx->websocket_compression = websocket_compression; -#endif + ctx->websocket_settings = websocket_settings; ctx->upload_tmp_dir = upload_tmp_dir; - ctx->bind(conn); + ctx->bind(zconn); - swoole_http_parser *parser = &ctx->parser; - parser->data = ctx; - swoole_http_parser_init(parser, PHP_HTTP_REQUEST); + llhttp_t *parser = &ctx->parser; + swoole_llhttp_parser_init(parser, HTTP_REQUEST, (void *) ctx); zend_update_property_ex( swoole_http_response_ce, SW_Z8_OBJ_P(ctx->response.zobject), SW_ZSTR_KNOWN(SW_ZEND_STR_SOCKET), zconn); @@ -150,7 +158,7 @@ class HttpServer { } void recv_http2_frame(HttpContext *ctx) { - Socket *sock = (Socket *) ctx->private_data; + auto *sock = ctx->get_co_socket(); http2::send_setting_frame(&sock->protocol, sock->get_socket()); sock->open_length_check = true; @@ -159,11 +167,12 @@ class HttpServer { sock->protocol.package_body_offset = 0; sock->protocol.get_package_length = http2::get_frame_length; - Http2Session session(ctx->fd); - session.default_ctx = ctx; - session.handle = http2_server_onRequest; - session.private_data = this; - session.is_coro = true; + auto session = swoole_http2_server_session_new(ctx->fd); + session->max_body_size = sock->protocol.package_max_length; + session->default_ctx = ctx; + session->handle = http2_server_onRequest; + session->private_data = this; + session->is_coro = true; while (true) { auto buffer = sock->get_read_buffer(); @@ -171,11 +180,12 @@ class HttpServer { if (sw_unlikely(retval <= 0)) { break; } - swoole_http2_server_parse(&session, buffer->str); + if (swoole_http2_server_parse(session, buffer->str) == SW_ERR) { + break; + } } - /* default_ctx does not blong to session object */ - session.default_ctx = nullptr; + swoole_http2_server_session_free(ctx->fd); ctx->detached = 1; zval_dtor(ctx->request.zobject); @@ -199,14 +209,12 @@ static PHP_METHOD(swoole_http_server_coro, handle); static PHP_METHOD(swoole_http_server_coro, start); static PHP_METHOD(swoole_http_server_coro, shutdown); static PHP_METHOD(swoole_http_server_coro, onAccept); -static PHP_METHOD(swoole_http_server_coro, __destruct); SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_http_server_coro_methods[] = { PHP_ME(swoole_http_server_coro, __construct, arginfo_class_Swoole_Coroutine_Http_Server___construct, ZEND_ACC_PUBLIC) - PHP_ME(swoole_http_server_coro, __destruct, arginfo_class_Swoole_Coroutine_Http_Server___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, set, arginfo_class_Swoole_Coroutine_Http_Server_set, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, handle, arginfo_class_Swoole_Coroutine_Http_Server_handle, ZEND_ACC_PUBLIC) PHP_ME(swoole_http_server_coro, onAccept, arginfo_class_Swoole_Coroutine_Http_Server_onAccept, ZEND_ACC_PRIVATE) @@ -216,53 +224,50 @@ static const zend_function_entry swoole_http_server_coro_methods[] = }; // clang-format on -static zend_object *php_swoole_http_server_coro_create_object(zend_class_entry *ce) { - HttpServerObject *hsc = (HttpServerObject *) zend_object_alloc(sizeof(HttpServerObject), ce); +static zend_object *http_server_coro_create_object(zend_class_entry *ce) { + auto *hsc = static_cast(zend_object_alloc(sizeof(HttpServerObject), ce)); zend_object_std_init(&hsc->std, ce); object_properties_init(&hsc->std, ce); hsc->std.handlers = &swoole_http_server_coro_handlers; return &hsc->std; } -static sw_inline HttpServerObject *php_swoole_http_server_coro_fetch_object(zend_object *obj) { - return (HttpServerObject *) ((char *) obj - swoole_http_server_coro_handlers.offset); +static HttpServerObject *http_server_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - + swoole_http_server_coro_handlers.offset); } -static sw_inline HttpServer *http_server_get_object(zend_object *obj) { - return php_swoole_http_server_coro_fetch_object(obj)->server; +static HttpServer *http_server_coro_get_object(zend_object *obj) { + return http_server_coro_fetch_object(obj)->server; } -static inline void http_server_set_error(zval *zobject, Socket *sock) { +static void http_server_coro_set_error(const zval *zobject, const SocketImpl *sock) { zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sock->errCode); zend_update_property_string(swoole_http_server_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), sock->errMsg); } static bool http_context_send_data(HttpContext *ctx, const char *data, size_t length) { - Socket *sock = (Socket *) ctx->private_data; - return sock->send_all(data, length) == (ssize_t) length; + return ctx->get_co_socket()->send_all(data, length) == (ssize_t) length; } -static bool http_context_sendfile(HttpContext *ctx, const char *file, uint32_t l_file, off_t offset, size_t length) { - Socket *sock = (Socket *) ctx->private_data; - return sock->sendfile(file, offset, length); +static bool http_context_sendfile(HttpContext *ctx, zend_string *file, off_t offset, size_t length) { + return ctx->get_co_socket()->sendfile(file->val, offset, length); } static bool http_context_disconnect(HttpContext *ctx) { - Socket *sock = (Socket *) ctx->private_data; - return sock->close(); + return ctx->get_co_socket()->close(); } -static void php_swoole_http_server_coro_free_object(zend_object *object) { - HttpServerObject *hsc = php_swoole_http_server_coro_fetch_object(object); +static void http_server_coro_free_object(zend_object *object) { + HttpServerObject *hsc = http_server_coro_fetch_object(object); if (hsc->server) { HttpServer *hs = hsc->server; - zval_ptr_dtor(&hs->zcallbacks); delete hs; } zend_object_std_dtor(&hsc->std); } -void HttpContext::init(Socket *sock) { +void HttpContext::init(zval *zsock) { parse_cookie = 1; parse_body = 1; parse_files = 1; @@ -274,12 +279,13 @@ void HttpContext::init(Socket *sock) { websocket_compression = 0; #endif upload_tmp_dir = "/tmp"; - bind(sock); + bind(zsock); } -void HttpContext::bind(Socket *sock) { - private_data = sock; - co_socket = 1; +void HttpContext::bind(zval *zsock) { + zsocket = *zsock; + Z_TRY_ADDREF(zsocket); + private_data = php_swoole_get_socket(zsock); send = http_context_send_data; sendfile = http_context_sendfile; close = http_context_disconnect; @@ -294,18 +300,9 @@ void php_swoole_http_server_coro_minit(int module_number) { SW_SET_CLASS_CLONEABLE(swoole_http_server_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_http_server_coro, sw_zend_class_unset_property_deny); SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_http_server_coro); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_http_server_coro, - php_swoole_http_server_coro_create_object, - php_swoole_http_server_coro_free_object, - HttpServerObject, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_http_server_coro, http_server_coro_create_object, http_server_coro_free_object, HttpServerObject, std); swoole_http_server_coro_ce->ce_flags |= ZEND_ACC_FINAL; - swoole_http_server_coro_handlers.get_gc = [](sw_zend7_object *object, zval **gc_data, int *gc_count) { - HttpServerObject *hs = php_swoole_http_server_coro_fetch_object(SW_Z7_OBJ_P(object)); - *gc_data = &hs->server->zcallbacks; - *gc_count = 1; - return zend_std_get_properties(object); - }; zend_declare_property_long(swoole_http_server_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_http_server_coro_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); @@ -320,8 +317,8 @@ static PHP_METHOD(swoole_http_server_coro, __construct) { char *host; size_t l_host; zend_long port = 0; - zend_bool ssl = 0; - zend_bool reuse_port = 0; + zend_bool ssl = false; + zend_bool reuse_port = false; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_STRING(host, l_host) @@ -340,62 +337,52 @@ static PHP_METHOD(swoole_http_server_coro, __construct) { RETURN_FALSE; } - HttpServerObject *hsc = php_swoole_http_server_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); + HttpServerObject *hsc = http_server_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); std::string host_str(host, l_host); hsc->server = new HttpServer(swoole::network::Socket::convert_to_type(host_str)); - Socket *sock = hsc->server->socket; + SocketImpl *sock = hsc->server->socket; if (reuse_port) { sock->get_socket()->set_reuse_port(); } if (!sock->bind(host_str, port)) { - http_server_set_error(ZEND_THIS, sock); + http_server_coro_set_error(ZEND_THIS, sock); zend_throw_exception_ex(swoole_exception_ce, sock->errCode, "bind(%s:%d) failed", host, (int) port); RETURN_FALSE; } // check ssl if (ssl) { -#ifndef SW_USE_OPENSSL - zend_throw_exception_ex( - swoole_exception_ce, - EPROTONOSUPPORT, - "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"); - RETURN_FALSE; -#else /* we have to call ssl_check_context after user setProtocols */ zval *zsettings = sw_zend_read_and_convert_property_array(swoole_http_server_coro_ce, ZEND_THIS, ZEND_STRL("settings"), 0); add_assoc_bool(zsettings, "open_ssl", 1); sock->enable_ssl_encrypt(); -#endif } if (!sock->listen()) { - http_server_set_error(ZEND_THIS, sock); + http_server_coro_set_error(ZEND_THIS, sock); zend_throw_exception_ex(swoole_exception_ce, sock->errCode, "listen() failed"); RETURN_FALSE; } zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("fd"), sock->get_fd()); - zend_update_property_long( - swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), sock->get_bind_port()); + zend_update_property_long(swoole_http_server_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("port"), sock->get_port()); } static PHP_METHOD(swoole_http_server_coro, handle) { char *pattern; size_t pattern_len; + zval *zfn; - HttpServer *hs = http_server_get_object(Z_OBJ_P(ZEND_THIS)); - zend_fcall_info fci; - zend_fcall_info_cache fci_cache; + HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_STRING(pattern, pattern_len) - Z_PARAM_FUNC(fci, fci_cache) + Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END(); std::string key(pattern, pattern_len); - hs->set_handler(key, ZEND_CALL_ARG(execute_data, 2), &fci_cache); + RETURN_BOOL(hs->set_handler(key, zfn)); } static PHP_METHOD(swoole_http_server_coro, set) { @@ -416,16 +403,15 @@ static PHP_METHOD(swoole_http_server_coro, set) { } static PHP_METHOD(swoole_http_server_coro, start) { - HttpServer *hs = http_server_get_object(Z_OBJ_P(ZEND_THIS)); - Socket *sock = hs->socket; + HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); + SocketImpl *sock = hs->socket; /* get callback fci cache */ char *func_name = nullptr; zend_fcall_info_cache fci_cache; - zval zcallback; - ZVAL_STRING(&zcallback, "onAccept"); + zend::Variable zcallback("onAccept"); if (!sw_zend_is_callable_at_frame( - &zcallback, ZEND_THIS, execute_data, 0, &func_name, nullptr, &fci_cache, nullptr)) { + zcallback.ptr(), ZEND_THIS, execute_data, 0, &func_name, nullptr, &fci_cache, nullptr)) { php_swoole_fatal_error(E_CORE_ERROR, "function '%s' is not callable", func_name); return; } @@ -486,11 +472,7 @@ static PHP_METHOD(swoole_http_server_coro, start) { } } #endif -#ifdef SW_HAVE_ZLIB - if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) { - hs->websocket_compression = zval_is_true(ztmp); - } -#endif + WebSocket::apply_setting(hs->websocket_settings, vht, true); // temporary directory for HTTP uploaded file. if (php_swoole_array_get_value(vht, "upload_tmp_dir", ztmp)) { zend::String str_v(ztmp); @@ -504,12 +486,14 @@ static PHP_METHOD(swoole_http_server_coro, start) { hs->upload_tmp_dir = str_v.dup(); } + hs->running = true; + while (hs->running) { auto conn = sock->accept(); if (conn) { zval zsocket; php_swoole_init_socket_object(&zsocket, conn); - long cid = PHPCoroutine::create(&fci_cache, 1, &zsocket); + long cid = PHPCoroutine::create(&fci_cache, 1, &zsocket, zcallback.ptr()); zval_dtor(&zsocket); if (cid < 0) { goto _wait_1s; @@ -524,50 +508,50 @@ static PHP_METHOD(swoole_http_server_coro, start) { } else if (sock->errCode == ETIMEDOUT || sock->errCode == SW_ERROR_SSL_BAD_CLIENT) { continue; } else if (sock->errCode == ECANCELED) { - http_server_set_error(ZEND_THIS, sock); + http_server_coro_set_error(ZEND_THIS, sock); break; } else { - http_server_set_error(ZEND_THIS, sock); + http_server_coro_set_error(ZEND_THIS, sock); php_swoole_fatal_error(E_WARNING, "accept failed, Error: %s[%d]", sock->errMsg, sock->errCode); break; } } } - zval_dtor(&zcallback); - RETURN_TRUE; } -static PHP_METHOD(swoole_http_server_coro, __destruct) {} - static PHP_METHOD(swoole_http_server_coro, onAccept) { - HttpServer *hs = http_server_get_object(Z_OBJ_P(ZEND_THIS)); + HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); zval *zconn; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) Z_PARAM_OBJECT(zconn) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + ZEND_PARSE_PARAMETERS_END_EX(RETURN_NULL()); - Socket *sock = php_swoole_get_socket(zconn); + Coroutine *co = Coroutine::get_current(); + SocketImpl *sock = php_swoole_get_socket(zconn); sock->set_buffer_allocator(sw_zend_string_allocator()); String *buffer = sock->get_read_buffer(); HttpContext *ctx = nullptr; bool header_completed = false; off_t header_crlf_offset = 0; - size_t total_length; + size_t total_length = 0; - hs->clients.push_front(sock); - auto client_iterator = hs->clients.begin(); - -#ifdef SW_USE_OPENSSL if (sock->ssl_is_enable() && !sock->ssl_handshake()) { - goto _handshake_failed; + RETURN_NULL(); } -#endif + zend::array_set(&hs->zclients, co->get_cid(), zconn); + auto remote_addr = zend::Variable(sock->get_addr()); + int remote_port = sock->get_port(); + + sock->get_socket()->get_name(); + auto server_addr = zend::Variable(sock->get_socket()->get_addr()); + int server_port = hs->socket->get_port(); while (true) { _recv_request : { + sock->get_socket()->recv_wait = 1; ssize_t retval = sock->recv(buffer->str + buffer->length, buffer->size - buffer->length); if (sw_unlikely(retval <= 0)) { break; @@ -610,9 +594,8 @@ static PHP_METHOD(swoole_http_server_coro, onAccept) { ctx->response.status = SW_HTTP_REQUEST_ENTITY_TOO_LARGE; break; } - if (total_length > buffer->size && !buffer->extend(total_length)) { - ctx->response.status = SW_HTTP_SERVICE_UNAVAILABLE; - break; + if (total_length > buffer->size) { + buffer->extend(total_length); } } @@ -636,13 +619,28 @@ static PHP_METHOD(swoole_http_server_coro, onAccept) { (intmax_t) buffer->offset, ctx->completed); - if (ctx->parser.state == s_dead) { + if (ctx->parser.error != HPE_OK && ctx->parser.error != HPE_PAUSED_H2_UPGRADE) { ctx->response.status = SW_HTTP_BAD_REQUEST; break; } } - if (ctx->parser.method == PHP_HTTP_NOT_IMPLEMENTED && buffer->length >= (sizeof(SW_HTTP2_PRI_STRING) - 1) && + zval *zserver = ctx->request.zserver; + HashTable *ht = Z_ARRVAL_P(zserver); + + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME), (zend_long) time(nullptr)); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REQUEST_TIME_FLOAT), microtime()); + + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_ADDR), remote_addr.ptr()); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_REMOTE_PORT), (zend_long) remote_port); + + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_ADDR), server_addr.ptr()); + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PORT), (zend_long) server_port); + + remote_addr.add_ref(); + server_addr.add_ref(); + + if (ctx->parser.error == HPE_PAUSED_H2_UPGRADE && buffer->length >= (sizeof(SW_HTTP2_PRI_STRING) - 1) && memcmp(buffer->str, SW_HTTP2_PRI_STRING, sizeof(SW_HTTP2_PRI_STRING) - 1) == 0) { buffer->offset = (sizeof(SW_HTTP2_PRI_STRING) - 1); hs->recv_http2_frame(ctx); @@ -651,19 +649,16 @@ static PHP_METHOD(swoole_http_server_coro, onAccept) { break; } + http_server_add_server_array(ht, SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP11)); zend::assign_zend_string_by_val(&ctx->request.zdata, buffer->pop(SW_BUFFER_SIZE_BIG), total_length); - zval *zserver = ctx->request.zserver; - add_assoc_long(zserver, "server_port", hs->socket->get_bind_port()); - add_assoc_long(zserver, "remote_port", (zend_long) sock->get_port()); - add_assoc_string(zserver, "remote_addr", (char *) sock->get_ip()); - - zend_fcall_info_cache *fci_cache = hs->get_handler(ctx); + zend::Callable *cb = hs->get_handler(ctx); zval args[2] = {*ctx->request.zobject, *ctx->response.zobject}; - bool keep_alive = swoole_http_should_keep_alive(&ctx->parser) && !ctx->websocket; + bool keep_alive = !!llhttp_should_keep_alive(&ctx->parser) && !ctx->websocket; + sock->get_socket()->recv_wait = 0; - if (fci_cache) { - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, 0))) { + if (cb) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, 0))) { php_swoole_error(E_WARNING, "handler error"); } } else { @@ -690,45 +685,49 @@ static PHP_METHOD(swoole_http_server_coro, onAccept) { zval_dtor(ctx->request.zobject); zval_dtor(ctx->response.zobject); } - -#ifdef SW_USE_OPENSSL -_handshake_failed: -#endif - /* notice: do not erase the element when server is shutting down */ - if (hs->running) { - hs->clients.erase(client_iterator); - } + zend_hash_index_del(Z_ARRVAL_P(&hs->zclients), co->get_cid()); } static PHP_METHOD(swoole_http_server_coro, shutdown) { - HttpServer *hs = http_server_get_object(Z_OBJ_P(ZEND_THIS)); + HttpServer *hs = http_server_coro_get_object(Z_OBJ_P(ZEND_THIS)); hs->running = false; hs->socket->cancel(SW_EVENT_READ); - /* accept has been canceled, we only need to traverse once */ - for (auto client : hs->clients) { - client->close(); + + zend_ulong index; + zval *zconn; + ZEND_HASH_FOREACH_NUM_KEY_VAL(Z_ARRVAL_P(&hs->zclients), index, zconn) { + SocketImpl *sock = php_swoole_get_socket(zconn); + if (sock->get_socket()->recv_wait) { + sock->cancel(SW_EVENT_READ); + zend_hash_index_del(Z_ARRVAL_P(&hs->zclients), index); + } } - hs->clients.clear(); + ZEND_HASH_FOREACH_END(); } -static void http2_server_onRequest(Http2Session *session, Http2Stream *stream) { +static void http2_server_onRequest(const std::shared_ptr &session, + const std::shared_ptr &stream) { HttpContext *ctx = stream->ctx; - HttpServer *hs = (HttpServer *) session->private_data; - Socket *sock = (Socket *) ctx->private_data; - zval *zserver = ctx->request.zserver; - - add_assoc_long(zserver, "request_time", time(nullptr)); - add_assoc_double(zserver, "request_time_float", microtime()); - add_assoc_long(zserver, "server_port", hs->socket->get_bind_port()); - add_assoc_long(zserver, "remote_port", sock->get_port()); - add_assoc_string(zserver, "remote_addr", (char *) sock->get_ip()); - add_assoc_string(zserver, "server_protocol", (char *) "HTTP/2"); - - zend_fcall_info_cache *fci_cache = hs->get_handler(ctx); + zval *zserver_http2 = ctx->request.zserver; + zval *zserver_http1 = session->default_ctx->request.zserver; + + zend_string *key; + zval *zvalue; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zserver_http1), key, zvalue) { + http_server_add_server_array(Z_ARRVAL_P(zserver_http2), key, zvalue); + if (ZVAL_IS_STRING(zvalue)) { + Z_TRY_ADDREF_P(zvalue); + } + } ZEND_HASH_FOREACH_END(); + + http_server_add_server_array(Z_ARRVAL_P(zserver_http2), SW_ZSTR_KNOWN(SW_ZEND_STR_SERVER_PROTOCOL), SW_ZSTR_KNOWN(SW_ZEND_STR_HTTP2)); + + const auto *hs = static_cast(session->private_data); + zend::Callable *cb = hs->get_handler(ctx); zval args[2] = {*ctx->request.zobject, *ctx->response.zobject}; - if (fci_cache) { - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, true))) { + if (cb) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, true))) { stream->reset(SW_HTTP2_ERROR_INTERNAL_ERROR); php_swoole_error(E_WARNING, "%s->onRequest[v2] handler error", ZSTR_VAL(swoole_http_server_ce->name)); } diff --git a/ext-src/swoole_lock.cc b/ext-src/swoole_lock.cc index 2c8961448c..369f4e901b 100644 --- a/ext-src/swoole_lock.cc +++ b/ext-src/swoole_lock.cc @@ -17,6 +17,10 @@ #include "php_swoole_private.h" #include "swoole_memory.h" #include "swoole_lock.h" +#include "swoole_util.h" +#include "swoole_timer.h" + +#include BEGIN_EXTERN_C() #include "stubs/php_swoole_lock_arginfo.h" @@ -39,36 +43,32 @@ struct LockObject { zend_object std; }; -static sw_inline LockObject *php_swoole_lock_fetch_object(zend_object *obj) { - return (LockObject *) ((char *) obj - swoole_lock_handlers.offset); +static sw_inline LockObject *lock_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_lock_handlers.offset); } -static Lock *php_swoole_lock_get_ptr(zval *zobject) { - return php_swoole_lock_fetch_object(Z_OBJ_P(zobject))->lock; +static Lock *lock_get_ptr(const zval *zobject) { + return lock_fetch_object(Z_OBJ_P(zobject))->lock; } -static Lock *php_swoole_lock_get_and_check_ptr(zval *zobject) { - Lock *lock = php_swoole_lock_get_ptr(zobject); - if (!lock) { - php_swoole_fatal_error(E_ERROR, "you must call Lock constructor first"); +static Lock *lock_get_and_check_ptr(const zval *zobject) { + Lock *lock = lock_get_ptr(zobject); + if (UNEXPECTED(!lock)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return lock; } -void php_swoole_lock_set_ptr(zval *zobject, Lock *ptr) { - php_swoole_lock_fetch_object(Z_OBJ_P(zobject))->lock = ptr; +static void lock_set_ptr(const zval *zobject, Lock *ptr) { + lock_fetch_object(Z_OBJ_P(zobject))->lock = ptr; } -static void php_swoole_lock_free_object(zend_object *object) { - LockObject *o = php_swoole_lock_fetch_object(object); - if (o->lock) { - delete o->lock; - } +static void lock_free_object(zend_object *object) { zend_object_std_dtor(object); } -static zend_object *php_swoole_lock_create_object(zend_class_entry *ce) { - LockObject *lock = (LockObject *) zend_object_alloc(sizeof(LockObject), ce); +static zend_object *lock_create_object(zend_class_entry *ce) { + auto *lock = static_cast(zend_object_alloc(sizeof(LockObject), ce)); zend_object_std_init(&lock->std, ce); object_properties_init(&lock->std, ce); lock->std.handlers = &swoole_lock_handlers; @@ -77,28 +77,16 @@ static zend_object *php_swoole_lock_create_object(zend_class_entry *ce) { SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_lock, __construct); -static PHP_METHOD(swoole_lock, __destruct); static PHP_METHOD(swoole_lock, lock); -static PHP_METHOD(swoole_lock, lockwait); -static PHP_METHOD(swoole_lock, trylock); -static PHP_METHOD(swoole_lock, lock_read); -static PHP_METHOD(swoole_lock, trylock_read); static PHP_METHOD(swoole_lock, unlock); -static PHP_METHOD(swoole_lock, destroy); SW_EXTERN_C_END // clang-format off -static const zend_function_entry swoole_lock_methods[] = +static constexpr zend_function_entry swoole_lock_methods[] = { PHP_ME(swoole_lock, __construct, arginfo_class_Swoole_Lock___construct, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, __destruct, arginfo_class_Swoole_Lock___destruct, ZEND_ACC_PUBLIC) PHP_ME(swoole_lock, lock, arginfo_class_Swoole_Lock_lock, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, lockwait, arginfo_class_Swoole_Lock_locakwait, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, trylock, arginfo_class_Swoole_Lock_trylock, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, lock_read, arginfo_class_Swoole_Lock_lock_read, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, trylock_read, arginfo_class_Swoole_Lock_trylock_read, ZEND_ACC_PUBLIC) PHP_ME(swoole_lock, unlock, arginfo_class_Swoole_Lock_unlock, ZEND_ACC_PUBLIC) - PHP_ME(swoole_lock, destroy, arginfo_class_Swoole_Lock_destroy, ZEND_ACC_PUBLIC) PHP_FE_END }; // clang-format on @@ -108,12 +96,9 @@ void php_swoole_lock_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_lock); SW_SET_CLASS_CLONEABLE(swoole_lock, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_lock, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_lock, php_swoole_lock_create_object, php_swoole_lock_free_object, LockObject, std); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_lock, lock_create_object, lock_free_object, LockObject, std); - zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("FILELOCK"), Lock::FILE_LOCK); zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("MUTEX"), Lock::MUTEX); - zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("SEM"), Lock::SEM); #ifdef HAVE_RWLOCK zend_declare_class_constant_long(swoole_lock_ce, ZEND_STRL("RWLOCK"), Lock::RW_LOCK); #endif @@ -122,9 +107,7 @@ void php_swoole_lock_minit(int module_number) { #endif zend_declare_property_long(swoole_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); - SW_REGISTER_LONG_CONSTANT("SWOOLE_FILELOCK", Lock::FILE_LOCK); SW_REGISTER_LONG_CONSTANT("SWOOLE_MUTEX", Lock::MUTEX); - SW_REGISTER_LONG_CONSTANT("SWOOLE_SEM", Lock::SEM); #ifdef HAVE_RWLOCK SW_REGISTER_LONG_CONSTANT("SWOOLE_RWLOCK", Lock::RW_LOCK); #endif @@ -134,94 +117,57 @@ void php_swoole_lock_minit(int module_number) { } static PHP_METHOD(swoole_lock, __construct) { - Lock *lock = php_swoole_lock_get_ptr(ZEND_THIS); + Lock *lock = lock_get_ptr(ZEND_THIS); if (lock != nullptr) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend_long type = Lock::MUTEX; - char *filelock; - size_t filelock_len = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ls", &type, &filelock, &filelock_len) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); switch (type) { - case Lock::FILE_LOCK: - case Lock::SEM: - zend_throw_exception( - swoole_exception_ce, "FileLock and SemLock is no longer supported, please use mutex lock", errno); - RETURN_FALSE; - break; #ifdef HAVE_SPINLOCK case Lock::SPIN_LOCK: - lock = new SpinLock(1); + lock = new SpinLock(true); break; #endif #ifdef HAVE_RWLOCK case Lock::RW_LOCK: - lock = new RWLock(1); + lock = new RWLock(true); break; #endif case Lock::MUTEX: + lock = new Mutex(true); + break; default: - lock = new Mutex(Mutex::PROCESS_SHARED); + zend_throw_exception(swoole_exception_ce, "lock type[%d] is not support", type); + RETURN_FALSE; break; } - php_swoole_lock_set_ptr(ZEND_THIS, lock); + lock_set_ptr(ZEND_THIS, lock); RETURN_TRUE; } -static PHP_METHOD(swoole_lock, __destruct) {} - static PHP_METHOD(swoole_lock, lock) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - SW_LOCK_CHECK_RETURN(lock->lock()); -} + zend_long operation = LOCK_EX; + double timeout = -1; -static PHP_METHOD(swoole_lock, lockwait) { - double timeout = 1.0; + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(operation) + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &timeout) == FAILURE) { - RETURN_FALSE; - } - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - if (lock->get_type() != Lock::MUTEX) { - zend_throw_exception(swoole_exception_ce, "only mutex supports lockwait", -2); - RETURN_FALSE; - } - Mutex *mutex = dynamic_cast(lock); - if (mutex == nullptr) { - zend_throw_exception(swoole_exception_ce, "wrong lock type", -3); - RETURN_FALSE; - } - SW_LOCK_CHECK_RETURN(mutex->lock_wait((int) timeout * 1000)); + Lock *lock = lock_get_and_check_ptr(ZEND_THIS); + SW_LOCK_CHECK_RETURN(lock->lock(operation, swoole::sec2msec(timeout))); } static PHP_METHOD(swoole_lock, unlock) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); + Lock *lock = lock_get_and_check_ptr(ZEND_THIS); SW_LOCK_CHECK_RETURN(lock->unlock()); } - -static PHP_METHOD(swoole_lock, trylock) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - SW_LOCK_CHECK_RETURN(lock->trylock()); -} - -static PHP_METHOD(swoole_lock, trylock_read) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - SW_LOCK_CHECK_RETURN(lock->trylock_rd()); -} - -static PHP_METHOD(swoole_lock, lock_read) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - SW_LOCK_CHECK_RETURN(lock->lock_rd()); -} - -static PHP_METHOD(swoole_lock, destroy) { - Lock *lock = php_swoole_lock_get_and_check_ptr(ZEND_THIS); - delete lock; - php_swoole_lock_set_ptr(ZEND_THIS, nullptr); -} diff --git a/ext-src/swoole_mysql_coro.cc b/ext-src/swoole_mysql_coro.cc deleted file mode 100644 index e6d50d1c66..0000000000 --- a/ext-src/swoole_mysql_coro.cc +++ /dev/null @@ -1,2223 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Swoole | - +----------------------------------------------------------------------+ - | Copyright (c) 2012-2018 The Swoole Group | - +----------------------------------------------------------------------+ - | 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: Twosee | - | Author: Tianfeng Han | - +----------------------------------------------------------------------+ - */ - -#include "php_swoole_cxx.h" -#include "php_swoole_mysql_proto.h" - -#include "swoole_string.h" - -// see mysqlnd 'L64' macro redefined -#undef L64 - -SW_EXTERN_C_BEGIN -#include "ext/hash/php_hash.h" -#include "ext/hash/php_hash_sha.h" -#include "ext/standard/php_math.h" -#ifdef SW_USE_MYSQLND -#include "ext/mysqlnd/mysqlnd.h" -#include "ext/mysqlnd/mysqlnd_charset.h" -#endif -SW_EXTERN_C_END - -#include - -/* keep same with pdo and mysqli */ -#define MYSQLND_UNKNOWN_SQLSTATE "HY000" -#define MYSQLND_SERVER_GONE "MySQL server has gone away" -#define MYSQLND_CR_UNKNOWN_ERROR 2000 -#define MYSQLND_CR_CONNECTION_ERROR 2002 -#define MYSQLND_CR_SERVER_GONE_ERROR 2006 -#define MYSQLND_CR_OUT_OF_MEMORY 2008 -#define MYSQLND_CR_SERVER_LOST 2013 -#define MYSQLND_CR_COMMANDS_OUT_OF_SYNC 2014 -#define MYSQLND_CR_CANT_FIND_CHARSET 2019 -#define MYSQLND_CR_MALFORMED_PACKET 2027 -#define MYSQLND_CR_NOT_IMPLEMENTED 2054 -#define MYSQLND_CR_NO_PREPARE_STMT 2030 -#define MYSQLND_CR_PARAMS_NOT_BOUND 2031 -#define MYSQLND_CR_INVALID_PARAMETER_NO 2034 -#define MYSQLND_CR_INVALID_BUFFER_USE 2035 - -using swoole::coroutine::Socket; -using namespace swoole; - -namespace swoole { -class mysql_statement; -class mysql_client { - public: - /* session related {{{ */ - Socket *socket = nullptr; - Socket::timeout_controller *tc = nullptr; - - enum sw_mysql_state state = SW_MYSQL_STATE_CLOSED; - bool quit = false; - mysql::result_info result; - - std::unordered_map statements; - mysql_statement *statement = nullptr; - /* }}} */ - - std::string host = SW_MYSQL_DEFAULT_HOST; - uint16_t port = SW_MYSQL_DEFAULT_PORT; - bool ssl = false; - - std::string user = "root"; - std::string password = "root"; - std::string database = "test"; - char charset = SW_MYSQL_DEFAULT_CHARSET; - - double connect_timeout = network::Socket::default_connect_timeout; - bool strict_type = false; - - inline int get_error_code() { - return error_code; - } - - inline const char *get_error_msg() { - return error_msg.c_str(); - } - - inline void non_sql_error(int code, const char *msg) { - error_code = code; - error_msg = std_string::format("SQLSTATE[" MYSQLND_UNKNOWN_SQLSTATE "] [%d] %s", code, msg); - } - - template - inline void non_sql_error(int code, const char *format, Args... args) { - error_code = code; - error_msg = std_string::format( - "SQLSTATE[" MYSQLND_UNKNOWN_SQLSTATE "] [%d] %s", code, std_string::format(format, args...).c_str()); - } - - void io_error() { - if (state == SW_MYSQL_STATE_CLOSED) { - non_sql_error(MYSQLND_CR_CONNECTION_ERROR, socket->errMsg); - } else { - non_sql_error(MYSQLND_CR_SERVER_GONE_ERROR, - MYSQLND_SERVER_GONE "%s%s", - socket->errCode ? " due to " : "", - socket->errCode ? socket->errMsg : ""); - } - /* don't send QUIT after IO error */ - quit = true; - close(); - } - - void proto_error(const char *data, const enum sw_mysql_packet_types expected_type) { - mysql::server_packet packet(data); - non_sql_error(MYSQLND_CR_MALFORMED_PACKET, - "Unexpected mysql packet length=%u, number=%u, type=%u, expected_type=%u", - packet.header.length, - packet.header.number, - (uint8_t) data[SW_MYSQL_PACKET_HEADER_SIZE], - expected_type); - close(); - } - - void server_error(const char *data) { - mysql::err_packet err_packet(data); - error_code = err_packet.code; - error_msg = - std_string::format("SQLSTATE[%s] [%d] %s", err_packet.sql_state, err_packet.code, err_packet.msg.c_str()); - state = SW_MYSQL_STATE_IDLE; - } - - inline bool get_fetch_mode() { - return fetch_mode; - } - - inline bool set_fetch_mode(bool v) { - if (sw_unlikely(socket && v)) { - non_sql_error(ENOTSUP, "Can not use fetch mode after the connection is established"); - return false; - } - fetch_mode = v; - return true; - } - - inline bool get_defer() { - return defer; - } - - inline bool set_defer(bool v) { - // if (sw_unlikely(fetch_mode && v)) - // { - // non_sql_error(ENOTSUP, "Can not use defer mode when fetch mode is on"); - // return false; - // } - defer = v; - return true; - } - - void add_timeout_controller(double timeout, const enum Socket::TimeoutType type) { - if (sw_unlikely(!socket)) { - return; - } - // Notice: `timeout > 0` is wrong, maybe -1 - if (timeout != 0) { - SW_ASSERT(!tc); - tc = new Socket::timeout_controller(socket, timeout, type); - } - } - - inline bool has_timedout(enum Socket::TimeoutType type) { - return tc && tc->has_timedout(type); - } - - void del_timeout_controller() { - if (tc) { - delete tc; - tc = nullptr; - } - } - - bool connect(std::string host, uint16_t port, bool ssl); - - inline bool connect() { - return connect(host, port, ssl); - } - - inline bool is_connected() { - return socket && socket->is_connected(); - } - - inline int get_fd() { - return socket ? socket->get_fd() : -1; - } - - inline bool check_connection() { - if (sw_unlikely(!is_connected())) { - non_sql_error(MYSQLND_CR_CONNECTION_ERROR, "%s or %s", strerror(ECONNRESET), strerror(ENOTCONN)); - return false; - } - return true; - } - - inline bool check_liveness() { - if (sw_unlikely(!check_connection())) { - return false; - } - if (sw_unlikely(!socket->check_liveness())) { - non_sql_error(MYSQLND_CR_SERVER_GONE_ERROR, MYSQLND_SERVER_GONE); - close(); - return false; - } - return true; - } - - inline bool is_writable() { - return is_connected() && !socket->has_bound(SW_EVENT_WRITE); - } - - bool is_available_for_new_request() { - if (sw_unlikely(state != SW_MYSQL_STATE_IDLE && state != SW_MYSQL_STATE_CLOSED)) { - if (socket) { - socket->check_bound_co(SW_EVENT_RDWR); - } - non_sql_error(EINPROGRESS, - "MySQL client is busy now on state#%d, " - "please use recv/fetchAll/nextResult to get all unread data " - "and wait for response then try again", - state); - return false; - } - if (sw_unlikely(!check_liveness())) { - return false; - } else { - /* without unread data */ - String *buffer = socket->get_read_buffer(); - SW_ASSERT(buffer->length == (size_t) buffer->offset); - buffer->clear(); - return true; - } - } - - const char *recv_packet(); - - inline const char *recv_none_error_packet() { - const char *data = recv_packet(); - if (sw_unlikely(data && mysql::server_packet::is_err(data))) { - server_error(data); - return nullptr; - } - return data; - } - - inline const char *recv_eof_packet() { - const char *data = recv_packet(); - if (sw_unlikely(data && !mysql::server_packet::is_eof(data))) { - proto_error(data, SW_MYSQL_PACKET_EOF); - return nullptr; - } -#ifdef SW_LOG_TRACE_OPEN - mysql::eof_packet eof_packet(data); -#endif - return data; - } - - inline bool send_raw(const char *data, size_t length) { - if (sw_unlikely(!check_connection())) { - return false; - } else { - if (sw_unlikely(has_timedout(Socket::TIMEOUT_WRITE))) { - io_error(); - return false; - } - if (sw_unlikely(socket->send_all(data, length) != (ssize_t) length)) { - io_error(); - return false; - } - return true; - } - } - - bool send_packet(mysql::client_packet *packet); - bool send_command(enum sw_mysql_command command, const char *sql = nullptr, size_t length = 0); - // just for internal - void send_command_without_check(enum sw_mysql_command command, const char *sql = nullptr, size_t length = 0); - - void query(zval *return_value, const char *statement, size_t statement_length); - void send_query_request(zval *return_value, const char *statement, size_t statement_length); - void recv_query_response(zval *return_value); - const char *handle_row_data_size(mysql::row_data *row_data, uint8_t size); - bool handle_row_data_lcb(mysql::row_data *row_data); - void handle_row_data_text(zval *return_value, mysql::row_data *row_data, mysql::field_packet *field); - void handle_strict_type(zval *ztext, mysql::field_packet *field); - void fetch(zval *return_value); - void fetch_all(zval *return_value); - void next_result(zval *return_value); - bool recv(); - - bool send_prepare_request(const char *statement, size_t statement_length); - mysql_statement *recv_prepare_response(); - - void close(); - - ~mysql_client() { - SW_ASSERT(statements.empty()); - close(); - } - - private: - int error_code = 0; - std::string error_msg = ""; - - /* unable to support both features at the same time, so we have to set them by method {{{ */ - bool fetch_mode = false; - bool defer = false; - /* }}} */ - - // recv data of specified length - const char *recv_length(size_t need_length, const bool try_to_recycle = false); - // usually mysql->connect = connect(TCP) + handshake - bool handshake(); -}; - -class mysql_statement { - public: - std::string statement; - mysql::statement info; - mysql::result_info result; - - mysql_statement(mysql_client *client, const char *statement, size_t statement_length) : client(client) { - this->statement = std::string(statement, statement_length); - } - - inline mysql_client *get_client() { - return client; - } - - inline int get_error_code() { - return sw_likely(client) ? client->get_error_code() : error_code; - } - - inline const char *get_error_msg() { - return sw_likely(client) ? client->get_error_msg() : error_msg.c_str(); - } - - inline bool is_available() { - if (sw_unlikely(!client)) { - error_code = ECONNRESET; - error_msg = "statement must to be recompiled after the connection is broken"; - return false; - } - return true; - } - - inline bool is_available_for_new_request() { - if (sw_unlikely(!is_available())) { - return false; - } - if (sw_unlikely(!client->is_available_for_new_request())) { - return false; - } - return true; - } - - inline void add_timeout_controller(double timeout, const enum Socket::TimeoutType type) { - if (sw_likely(client)) { - client->add_timeout_controller(timeout, type); - } - } - - inline void del_timeout_controller() { - if (sw_likely(client)) { - client->del_timeout_controller(); - } - } - - // [notify = false]: mysql_client actively close - inline void close(const bool notify = true) { - if (client) { - // if client point exists, socket is always available - if (notify) { - if (sw_likely(client->is_writable())) { - char id[4]; - sw_mysql_int4store(id, info.id); - client->send_command_without_check(SW_MYSQL_COM_STMT_CLOSE, id, sizeof(id)); - } - client->statements.erase(info.id); - } else { - error_code = client->get_error_code(); - error_msg = client->get_error_msg(); - } - client = nullptr; - } - } - - ~mysql_statement() { - close(); - } - - bool send_prepare_request(); - bool recv_prepare_response(); - - void execute(zval *return_value, zval *params); - void send_execute_request(zval *return_value, zval *params); - void recv_execute_response(zval *return_value); - - void fetch(zval *return_value); - void fetch_all(zval *return_value); - void next_result(zval *return_value); - - private: - mysql_client *client = nullptr; - int error_code = 0; - std::string error_msg; -}; -} // namespace swoole - -using swoole::mysql_client; -using swoole::mysql_statement; - -static zend_class_entry *swoole_mysql_coro_ce; -static zend_object_handlers swoole_mysql_coro_handlers; - -static zend_class_entry *swoole_mysql_coro_exception_ce; -static zend_object_handlers swoole_mysql_coro_exception_handlers; - -static zend_class_entry *swoole_mysql_coro_statement_ce; -static zend_object_handlers swoole_mysql_coro_statement_handlers; - -struct mysql_coro_t { - mysql_client *client; - zend_object std; -}; - -struct mysql_coro_statement_t { - mysql_statement *statement; - zend_object *zclient; - zend_object std; -}; - -SW_EXTERN_C_BEGIN -static PHP_METHOD(swoole_mysql_coro, __construct); -static PHP_METHOD(swoole_mysql_coro, __destruct); -static PHP_METHOD(swoole_mysql_coro, connect); -static PHP_METHOD(swoole_mysql_coro, getDefer); -static PHP_METHOD(swoole_mysql_coro, setDefer); -static PHP_METHOD(swoole_mysql_coro, query); -static PHP_METHOD(swoole_mysql_coro, fetch); -static PHP_METHOD(swoole_mysql_coro, fetchAll); -static PHP_METHOD(swoole_mysql_coro, nextResult); -static PHP_METHOD(swoole_mysql_coro, prepare); -static PHP_METHOD(swoole_mysql_coro, recv); -static PHP_METHOD(swoole_mysql_coro, begin); -static PHP_METHOD(swoole_mysql_coro, commit); -static PHP_METHOD(swoole_mysql_coro, rollback); -#ifdef SW_USE_MYSQLND -static PHP_METHOD(swoole_mysql_coro, escape); -#endif -static PHP_METHOD(swoole_mysql_coro, close); - -static PHP_METHOD(swoole_mysql_coro_statement, execute); -static PHP_METHOD(swoole_mysql_coro_statement, fetch); -static PHP_METHOD(swoole_mysql_coro_statement, fetchAll); -static PHP_METHOD(swoole_mysql_coro_statement, nextResult); -static PHP_METHOD(swoole_mysql_coro_statement, recv); -static PHP_METHOD(swoole_mysql_coro_statement, close); -SW_EXTERN_C_END - -// clang-format off -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_optional_timeout, 0, 0, 0) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_connect, 0, 0, 0) - ZEND_ARG_ARRAY_INFO(0, server_config, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_query, 0, 0, 1) - ZEND_ARG_INFO(0, sql) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_prepare, 0, 0, 1) - ZEND_ARG_INFO(0, query) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_setDefer, 0, 0, 0) - ZEND_ARG_INFO(0, defer) -ZEND_END_ARG_INFO() - -#ifdef SW_USE_MYSQLND -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_escape, 0, 0, 1) - ZEND_ARG_INFO(0, string) - ZEND_ARG_INFO(0, flags) -ZEND_END_ARG_INFO() -#endif - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_coro_statement_execute, 0, 0, 0) - ZEND_ARG_INFO(0, params) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -static const zend_function_entry swoole_mysql_coro_methods[] = -{ - PHP_ME(swoole_mysql_coro, __construct, arginfo_swoole_void, ZEND_ACC_PUBLIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_mysql_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, getDefer, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, setDefer, arginfo_swoole_mysql_coro_setDefer, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, connect, arginfo_swoole_mysql_coro_connect, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, query, arginfo_swoole_mysql_coro_query, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, fetch, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, fetchAll, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, nextResult, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, prepare, arginfo_swoole_mysql_coro_prepare, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, recv, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, begin, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, commit, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro, rollback, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) -#ifdef SW_USE_MYSQLND - PHP_ME(swoole_mysql_coro, escape, arginfo_swoole_mysql_coro_escape, ZEND_ACC_PUBLIC) -#endif - PHP_ME(swoole_mysql_coro, close, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_FE_END -}; - -static const zend_function_entry swoole_mysql_coro_statement_methods[] = -{ - PHP_ME(swoole_mysql_coro_statement, execute, arginfo_swoole_mysql_coro_statement_execute, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro_statement, fetch, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro_statement, fetchAll, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro_statement, nextResult, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro_statement, recv, arginfo_swoole_optional_timeout, ZEND_ACC_PUBLIC) - PHP_ME(swoole_mysql_coro_statement, close, arginfo_swoole_void, ZEND_ACC_PUBLIC) - PHP_FE_END -}; -// clang-format on - -void php_swoole_sha256(const char *str, int len, unsigned char *digest) { - PHP_SHA256_CTX context; - PHP_SHA256Init(&context); - PHP_SHA256Update(&context, (unsigned char *) str, len); - PHP_SHA256Final(digest, &context); -} - -bool mysql_client::connect(std::string host, uint16_t port, bool ssl) { - if (socket && (host != this->host || port != this->port || ssl != this->ssl)) { - close(); - } - if (!socket) { - if (host.compare(0, 6, "unix:/", 0, 6) == 0) { - host = host.substr(sizeof("unix:") - 1); - host.erase(0, host.find_first_not_of('/') - 1); - socket = new Socket(SW_SOCK_UNIX_STREAM); - } else if (host.find(':') != std::string::npos) { - socket = new Socket(SW_SOCK_TCP6); - } else { - socket = new Socket(SW_SOCK_TCP); - } - if (sw_unlikely(socket->get_fd() < 0)) { - php_swoole_fatal_error(E_WARNING, "new Socket() failed. Error: %s [%d]", strerror(errno), errno); - non_sql_error(MYSQLND_CR_CONNECTION_ERROR, strerror(errno)); - delete socket; - socket = nullptr; - return false; - } - socket->set_zero_copy(true); -#ifdef SW_USE_OPENSSL - if (ssl) { - socket->enable_ssl_encrypt(); - } -#endif - socket->set_timeout(connect_timeout, Socket::TIMEOUT_CONNECT); - add_timeout_controller(connect_timeout, Socket::TIMEOUT_ALL); - if (!socket->connect(host, port)) { - io_error(); - return false; - } - this->host = host; - this->port = port; -#ifdef SW_USE_OPENSSL - this->ssl = ssl; -#endif - if (!handshake()) { - close(); - return false; - } - state = SW_MYSQL_STATE_IDLE; - quit = false; - del_timeout_controller(); - } - return true; -} - -const char *mysql_client::recv_length(size_t need_length, const bool try_to_recycle) { - if (sw_likely(check_connection())) { - ssize_t retval; - String *buffer = socket->get_read_buffer(); - off_t offset = buffer->offset; // save offset instead of buffer point (due to realloc) - size_t read_n = buffer->length - buffer->offset; // readable bytes - if (try_to_recycle && read_n == 0) { - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "mysql buffer will be recycled, length=%zu, offset=%jd", - buffer->length, - (intmax_t) offset); - buffer->clear(); - offset = 0; - } - while (read_n < need_length) { - if (sw_unlikely(has_timedout(Socket::TIMEOUT_READ))) { - io_error(); - return nullptr; - } - if (sw_unlikely(buffer->length == buffer->size)) { - /* offset + need_length = new size (min) */ - if (!buffer->extend(SW_MEM_ALIGNED_SIZE_EX(offset + need_length, swoole_pagesize()))) { - non_sql_error(MYSQLND_CR_OUT_OF_MEMORY, strerror(ENOMEM)); - return nullptr; - } else { - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "mysql buffer extend to %zu", buffer->size); - } - } - retval = socket->recv(buffer->str + buffer->length, buffer->size - buffer->length); - if (sw_unlikely(retval <= 0)) { - io_error(); - return nullptr; - } - read_n += retval; - buffer->length += retval; - } - buffer->offset += need_length; - return buffer->str + offset; - } - return nullptr; -} - -const char *mysql_client::recv_packet() { - const char *p; - uint32_t length; - p = recv_length(SW_MYSQL_PACKET_HEADER_SIZE, true); - if (sw_unlikely(!p)) { - return nullptr; - } - length = mysql::packet::get_length(p); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "recv packet length=%u, number=%u", length, mysql::packet::get_number(p)); - p = recv_length(length); - if (sw_unlikely(!p)) { - return nullptr; - } - /* Notice: why we do this? because buffer maybe reallocated when recv data */ - return p - SW_MYSQL_PACKET_HEADER_SIZE; -} - -bool mysql_client::send_packet(mysql::client_packet *packet) { - const char *data = packet->get_data(); - uint32_t length = SW_MYSQL_PACKET_HEADER_SIZE + packet->get_length(); - if (sw_likely(send_raw(data, length))) { - return true; - } - return false; -} - -bool mysql_client::send_command(enum sw_mysql_command command, const char *sql, size_t length) { - if (sw_likely(SW_MYSQL_PACKET_HEADER_SIZE + 1 + length <= swoole_pagesize())) { - mysql::command_packet command_packet(command, sql, length); - return send_raw(command_packet.get_data(), command_packet.get_data_length()); - } else { - /* if the data is larger than page_size, copy memory to the kernel buffer multiple times is much faster */ - size_t send_s = SW_MIN(length, SW_MYSQL_MAX_PACKET_BODY_SIZE - 1), send_n = send_s, number = 0; - mysql::command_packet command_packet(command); - command_packet.set_header(1 + send_s, number++); - - if (sw_unlikely(!send_raw(command_packet.get_data(), SW_MYSQL_PACKET_HEADER_SIZE + 1)) || - !send_raw(sql, send_s)) { - return false; - } - /* MySQL single packet size is 16M, we must subpackage */ - while (send_n < length) { - send_s = length - send_n; - send_s = SW_MIN(send_s, SW_MYSQL_MAX_PACKET_BODY_SIZE); - command_packet.set_header(send_s, number++); - if (sw_unlikely(!send_raw(command_packet.get_data(), SW_MYSQL_PACKET_HEADER_SIZE)) || - !send_raw(sql + send_n, send_s)) { - return false; - } - send_n += send_s; - } - return true; - } -} - -void mysql_client::send_command_without_check(enum sw_mysql_command command, const char *sql, size_t length) { - mysql::command_packet command_packet(command, sql, length); - (void) (socket && socket->send(command_packet.get_data(), command_packet.get_data_length())); -} - -bool mysql_client::handshake() { - const char *data; - // recv greeting pakcet - if (sw_unlikely(!(data = recv_none_error_packet()))) { - return false; - } - mysql::greeting_packet greeting_packet(data); - // generate login packet - do { - mysql::login_packet login_packet(&greeting_packet, user, password, database, charset); - if (sw_unlikely(!send_packet(&login_packet))) { - return false; - } - } while (0); - // recv auth switch request packet, 4 possible packet types - switch (mysql::server_packet::parse_type(data = recv_packet())) { - case SW_MYSQL_PACKET_AUTH_SWITCH_REQUEST: { - mysql::auth_switch_request_packet request(data); - mysql::auth_switch_response_packet response(&request, password); - if (sw_unlikely(!send_packet(&response))) { - return false; - } - break; - } - case SW_MYSQL_PACKET_AUTH_SIGNATURE_REQUEST: { - mysql::auth_signature_request_packet request(data); - if (sw_unlikely(!request.is_vaild())) { - goto _proto_error; - } - if (sw_likely(!request.is_full_auth_required())) { - break; - } - // no cache, need full auth with rsa key (openssl required) -#ifdef SW_MYSQL_RSA_SUPPORT - // tell the server we are prepared - do { - mysql::auth_signature_prepared_packet prepared(request.header.number + 1); - if (sw_unlikely(!send_packet(&prepared))) { - return false; - } - } while (0); - // recv rsa key and encode the password - do { - if (sw_unlikely(!(data = recv_none_error_packet()))) { - return false; - } - mysql::raw_data_packet raw_data_packet(data); - mysql::auth_signature_response_packet response( - &raw_data_packet, password, greeting_packet.auth_plugin_data); - if (sw_unlikely(!send_packet(&response))) { - return false; - } - } while (0); - break; -#else - error_code = EPROTONOSUPPORT; - error_msg = SW_MYSQL_NO_RSA_ERROR; - return false; -#endif - } - case SW_MYSQL_PACKET_OK: { -#ifdef SW_LOG_TRACE_OPEN - mysql::ok_packet ok_packet(data); -#endif - return true; - } - case SW_MYSQL_PACKET_ERR: - server_error(data); - return false; - case SW_MYSQL_PACKET_NULL: - // io_error - return false; - default: - _proto_error: - proto_error(data, SW_MYSQL_PACKET_AUTH_SWITCH_REQUEST); - return false; - } - // maybe ok packet or err packet - if (sw_unlikely(!(data = recv_none_error_packet()))) { - return false; - } -#ifdef SW_LOG_TRACE_OPEN - mysql::ok_packet ok_packet(data); -#endif - return true; -} - -void mysql_client::query(zval *return_value, const char *statement, size_t statement_length) { - send_query_request(return_value, statement, statement_length); - if (EXPECTED(!defer && Z_TYPE_P(return_value) == IS_TRUE)) { - recv_query_response(return_value); - } -} - -void mysql_client::send_query_request(zval *return_value, const char *statement, size_t statement_length) { - if (sw_unlikely(!is_available_for_new_request())) { - RETURN_FALSE; - } - if (sw_unlikely(!send_command(SW_MYSQL_COM_QUERY, statement, statement_length))) { - RETURN_FALSE; - } - state = SW_MYSQL_STATE_QUERY; - RETURN_TRUE; -}; - -void mysql_client::recv_query_response(zval *return_value) { - const char *data; - if (sw_unlikely(!(data = recv_none_error_packet()))) { - RETURN_FALSE; - } - if (mysql::server_packet::is_ok(data)) { - mysql::ok_packet ok_packet(data); - result.ok = ok_packet; - state = ok_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_QUERY_MORE_RESULTS : SW_MYSQL_STATE_IDLE; - RETURN_TRUE; - } - do { - mysql::lcb_packet lcb_packet(data); - if (sw_unlikely(lcb_packet.length == 0)) { - // is it possible? - proto_error(data, SW_MYSQL_PACKET_FIELD); - RETURN_FALSE; - } - result.alloc_fields(lcb_packet.length); - for (uint32_t i = 0; i < lcb_packet.length; i++) { - if (sw_unlikely(!(data = recv_packet()))) { - RETURN_FALSE; - } - result.set_field(i, data); - } - } while (0); - // expect eof - if (sw_unlikely(!(data = recv_eof_packet()))) { - RETURN_FALSE; - } - state = SW_MYSQL_STATE_QUERY_FETCH; - if (get_fetch_mode()) { - RETURN_TRUE; - } - fetch_all(return_value); -} - -const char *mysql_client::handle_row_data_size(mysql::row_data *row_data, uint8_t size) { - const char *p, *data; - SW_ASSERT(size < sizeof(row_data->stack_buffer)); - if (sw_unlikely(!(p = row_data->read(size)))) { - uint8_t received = row_data->recv(row_data->stack_buffer, size); - if (sw_unlikely(!(data = recv_packet()))) { - return nullptr; - } - row_data->next_packet(data); - received += row_data->recv(row_data->stack_buffer + received, size - received); - if (sw_unlikely(received != size)) { - proto_error(data, SW_MYSQL_PACKET_ROW_DATA); - return nullptr; - } - p = row_data->stack_buffer; - } - return p; -} - -bool mysql_client::handle_row_data_lcb(mysql::row_data *row_data) { - const char *p, *data; - // recv 1 byte to get binary code size - if (sw_unlikely(row_data->eof())) { - if (sw_unlikely(!(data = recv_packet()))) { - return false; - } - row_data->next_packet(data); - if (sw_unlikely(row_data->eof())) { - proto_error(data, SW_MYSQL_PACKET_ROW_DATA); - return false; - } - } - // decode lcb (use 0 to prevent read_ptr from moving) - // recv "size" bytes to get binary code length - p = handle_row_data_size(row_data, mysql::read_lcb_size(row_data->read(0))); - if (sw_unlikely(!p)) { - return false; - } - mysql::read_lcb(p, &row_data->text.length, &row_data->text.nul); - return true; -} - -void mysql_client::handle_row_data_text(zval *return_value, mysql::row_data *row_data, mysql::field_packet *field) { - const char *p, *data; - if (sw_unlikely(!handle_row_data_lcb(row_data))) { - RETURN_FALSE; - } - if (sw_unlikely(!(p = row_data->read(row_data->text.length)))) { - size_t received = 0, required = row_data->text.length; - if (required < sizeof(row_data->stack_buffer)) { - p = handle_row_data_size(row_data, required); - if (sw_unlikely(!p)) { - RETURN_FALSE; - } - } else { - zend_string *zstring = zend_string_alloc(required, 0); - do { - received += row_data->recv(ZSTR_VAL(zstring) + received, required - received); - if (received == required) { - break; - } - if (row_data->eof()) { - if (sw_unlikely(!(data = recv_packet()))) { - RETURN_FALSE; - } - row_data->next_packet(data); - } - } while (true); - ZSTR_VAL(zstring)[ZSTR_LEN(zstring)] = '\0'; - RETVAL_STR(zstring); - goto _return; - } - } - if (row_data->text.nul || field->type == SW_MYSQL_TYPE_NULL) { - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s is null", field->name_length, field->name); - RETURN_NULL(); - } else { - RETVAL_STRINGL(p, row_data->text.length); - _return: - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "%.*s=[%lu]%.*s%s", - field->name_length, - field->name, - Z_STRLEN_P(return_value), - (int) SW_MIN(32, Z_STRLEN_P(return_value)), - Z_STRVAL_P(return_value), - (Z_STRLEN_P(return_value) > 32 ? "..." : "")); - } -} - -void mysql_client::handle_strict_type(zval *ztext, mysql::field_packet *field) { - if (sw_likely(Z_TYPE_P(ztext) == IS_STRING)) { - char *error; - switch (field->type) { - /* String */ - case SW_MYSQL_TYPE_TINY_BLOB: - case SW_MYSQL_TYPE_MEDIUM_BLOB: - case SW_MYSQL_TYPE_LONG_BLOB: - case SW_MYSQL_TYPE_BLOB: - case SW_MYSQL_TYPE_DECIMAL: - case SW_MYSQL_TYPE_NEWDECIMAL: - case SW_MYSQL_TYPE_BIT: - case SW_MYSQL_TYPE_STRING: - case SW_MYSQL_TYPE_VAR_STRING: - case SW_MYSQL_TYPE_VARCHAR: - case SW_MYSQL_TYPE_NEWDATE: - case SW_MYSQL_TYPE_GEOMETRY: - /* Date Time */ - case SW_MYSQL_TYPE_TIME: - case SW_MYSQL_TYPE_YEAR: - case SW_MYSQL_TYPE_TIMESTAMP: - case SW_MYSQL_TYPE_DATETIME: - case SW_MYSQL_TYPE_DATE: - case SW_MYSQL_TYPE_JSON: - return; - /* Integer */ - case SW_MYSQL_TYPE_TINY: - case SW_MYSQL_TYPE_SHORT: - case SW_MYSQL_TYPE_INT24: - case SW_MYSQL_TYPE_LONG: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - ulong_t uint = strtoul(Z_STRVAL_P(ztext), &error, 10); - if (sw_likely(*error == '\0')) { - zend_string_release(Z_STR_P(ztext)); - ZVAL_LONG(ztext, uint); - } - } else { - long sint = strtol(Z_STRVAL_P(ztext), &error, 10); - if (sw_likely(*error == '\0')) { - zend_string_release(Z_STR_P(ztext)); - ZVAL_LONG(ztext, sint); - } - } - break; - case SW_MYSQL_TYPE_LONGLONG: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - unsigned long long ubigint = strtoull(Z_STRVAL_P(ztext), &error, 10); - if (sw_likely(*error == '\0' && ubigint <= ZEND_LONG_MAX)) { - zend_string_release(Z_STR_P(ztext)); - ZVAL_LONG(ztext, ubigint); - } - } else { - long long sbigint = strtoll(Z_STRVAL_P(ztext), &error, 10); - if (sw_likely(*error == '\0')) { - zend_string_release(Z_STR_P(ztext)); - ZVAL_LONG(ztext, sbigint); - } - } - break; - case SW_MYSQL_TYPE_FLOAT: - case SW_MYSQL_TYPE_DOUBLE: { - double mdouble = strtod(Z_STRVAL_P(ztext), &error); - if (sw_likely(*error == '\0')) { - zend_string_release(Z_STR_P(ztext)); - ZVAL_DOUBLE(ztext, mdouble); - } - break; - } - default: { - swoole_warning("unknown type[%d] for field [%.*s].", field->type, field->name_length, field->name); - break; - } - } - } -} - -void mysql_client::fetch(zval *return_value) { - if (sw_unlikely(!is_connected())) { - RETURN_FALSE; - } - if (sw_unlikely(state != SW_MYSQL_STATE_QUERY_FETCH)) { - RETURN_NULL(); - } - const char *data; - if (sw_unlikely(!(data = recv_packet()))) { - RETURN_FALSE; - } - if (mysql::server_packet::is_eof(data)) { - mysql::eof_packet eof_packet(data); - state = - eof_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_QUERY_MORE_RESULTS : SW_MYSQL_STATE_IDLE; - RETURN_NULL(); - } - do { - mysql::row_data row_data(data); - array_init_size(return_value, result.get_fields_length()); - for (uint32_t i = 0; i < result.get_fields_length(); i++) { - mysql::field_packet *field = result.get_field(i); - zval ztext; - handle_row_data_text(&ztext, &row_data, field); - if (sw_unlikely(Z_TYPE_P(&ztext) == IS_FALSE)) { - zval_ptr_dtor(return_value); - RETURN_FALSE; - } - if (strict_type) { - handle_strict_type(&ztext, field); - } - add_assoc_zval_ex(return_value, field->name, field->name_length, &ztext); - } - } while (0); -} - -void mysql_client::fetch_all(zval *return_value) { - array_init(return_value); - while (true) { - zval zrow; - fetch(&zrow); - if (sw_unlikely(ZVAL_IS_NULL(&zrow))) { - // eof - return; - } - if (sw_unlikely(Z_TYPE_P(&zrow) == IS_FALSE)) { - // error - zval_ptr_dtor(return_value); - RETURN_FALSE; - } - (void) add_next_index_zval(return_value, &zrow); - } -} - -void mysql_client::next_result(zval *return_value) { - if (sw_unlikely(state == SW_MYSQL_STATE_QUERY_FETCH)) { - // skip unread data - fetch_all(return_value); - zval_ptr_dtor(return_value); - next_result(return_value); - } else if (sw_likely(state == SW_MYSQL_STATE_QUERY_MORE_RESULTS)) { - recv_query_response(return_value); - } else if (state == SW_MYSQL_STATE_IDLE) { - RETURN_NULL(); - } else { - RETURN_FALSE; - } -} - -bool mysql_client::send_prepare_request(const char *statement, size_t statement_length) { - this->statement = new mysql_statement(this, statement, statement_length); - if (sw_unlikely(!this->statement->send_prepare_request())) { - delete this->statement; - this->statement = nullptr; - return false; - } - return true; -} - -mysql_statement *mysql_client::recv_prepare_response() { - if (sw_likely(state == SW_MYSQL_STATE_PREPARE)) { - mysql_statement *statement = this->statement; - SW_ASSERT(statement != nullptr); - this->statement = nullptr; - if (sw_unlikely(!statement->recv_prepare_response())) { - delete statement; - return nullptr; - } - statements[statement->info.id] = statement; - return statement; - } - return nullptr; -} - -void mysql_client::close() { - state = SW_MYSQL_STATE_CLOSED; - Socket *socket = this->socket; - if (socket) { - del_timeout_controller(); - if (!quit && is_writable()) { - send_command_without_check(SW_MYSQL_COM_QUIT); - quit = true; - } - // make statements non-available - while (!statements.empty()) { - auto i = statements.begin(); - i->second->close(false); - statements.erase(i); - } - if (sw_likely(!socket->has_bound())) { - this->socket = nullptr; - } - if (sw_likely(socket->close())) { - delete socket; - } - } -} - -bool mysql_statement::send_prepare_request() { - if (sw_unlikely(!is_available_for_new_request())) { - return false; - } - if (sw_unlikely(!client->send_command(SW_MYSQL_COM_STMT_PREPARE, statement.c_str(), statement.length()))) { - return false; - } - client->state = SW_MYSQL_STATE_PREPARE; - return true; -} - -bool mysql_statement::recv_prepare_response() { - if (sw_unlikely(!is_available())) { - return false; - } else { - client->state = SW_MYSQL_STATE_IDLE; - } - const char *data; - if (sw_unlikely(!(data = client->recv_none_error_packet()))) { - return false; - } - info = mysql::statement(data); - if (sw_likely(info.param_count != 0)) { - for (uint16_t i = info.param_count; i--;) { - if (sw_unlikely(!(data = client->recv_packet()))) { - return false; - } -#ifdef SW_LOG_TRACE_OPEN - mysql::param_packet param_packet(data); -#endif - } - if (sw_unlikely(!(data = client->recv_eof_packet()))) { - return false; - } - } - if (info.field_count != 0) { - result.alloc_fields(info.field_count); - for (uint16_t i = 0; i < info.field_count; i++) { - if (sw_unlikely(!(data = client->recv_packet()))) { - return false; - } - result.set_field(i, data); - } - if (sw_unlikely(!(data = client->recv_eof_packet()))) { - return false; - } - } - return true; -} - -void mysql_statement::execute(zval *return_value, zval *params) { - send_execute_request(return_value, params); - /* Notice: must check return_value first */ - if (EXPECTED(Z_TYPE_P(return_value) == IS_TRUE && !client->get_defer())) { - recv_execute_response(return_value); - } -} - -void mysql_statement::send_execute_request(zval *return_value, zval *params) { - if (sw_unlikely(!is_available_for_new_request())) { - RETURN_FALSE; - } - - uint32_t param_count = params ? php_swoole_array_length(params) : 0; - - if (sw_unlikely(param_count != info.param_count)) { - client->non_sql_error(MYSQLND_CR_INVALID_PARAMETER_NO, - "Statement#%u expects %u parameter, %u given.", - info.id, - info.param_count, - param_count); - RETURN_FALSE; - } - - String *buffer = client->socket->get_write_buffer(); - char *p = buffer->str; - - memset(p, 0, 5); - // command - buffer->str[4] = SW_MYSQL_COM_STMT_EXECUTE; - buffer->length = 5; - p += 5; - - // stmt.id - sw_mysql_int4store(p, info.id); - p += 4; - // flags = CURSOR_TYPE_NO_CURSOR - sw_mysql_int1store(p, 0); - p += 1; - // iteration_count - sw_mysql_int4store(p, 1); - p += 4; - buffer->length += 9; - - // TODO: support more types - if (param_count != 0) { - // null bitmap - size_t null_start_offset = p - buffer->str; - unsigned int map_size = (param_count + 7) / 8; - memset(p, 0, map_size); - p += map_size; - buffer->length += map_size; - - // rebind - sw_mysql_int1store(p, 1); - p += 1; - buffer->length += 1; - - size_t type_start_offset = p - buffer->str; - p += param_count * 2; - buffer->length += param_count * 2; - - char stack_buffer[10]; - zend_ulong index = 0; - zval *value; - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(params), value) { - switch (client->strict_type ? Z_TYPE_P(value) : (IS_NULL == Z_TYPE_P(value) ? IS_NULL : IS_STRING)) { - case IS_NULL: - *((buffer->str + null_start_offset) + (index / 8)) |= (1UL << (index % 8)); - sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_NULL); - break; - case IS_TRUE: - case IS_FALSE: - case IS_LONG: - sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_LONGLONG); - sw_mysql_int8store(stack_buffer, zval_get_long(value)); - if (buffer->append(stack_buffer, mysql::get_static_type_size(SW_MYSQL_TYPE_LONGLONG)) < 0) { - RETURN_FALSE; - } - break; - case IS_DOUBLE: - sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_DOUBLE); - sw_mysql_doublestore(stack_buffer, zval_get_double(value)); - if (buffer->append(stack_buffer, mysql::get_static_type_size(SW_MYSQL_TYPE_DOUBLE)) < 0) { - RETURN_FALSE; - } - break; - default: - zend::String str_value(value); - uint8_t lcb_size = mysql::write_lcb(stack_buffer, str_value.len()); - sw_mysql_int2store((buffer->str + type_start_offset) + (index * 2), SW_MYSQL_TYPE_VAR_STRING); - if (buffer->append(stack_buffer, lcb_size) < 0) { - RETURN_FALSE; - } - if (buffer->append(str_value.val(), str_value.len()) < 0) { - RETURN_FALSE; - } - } - index++; - } - ZEND_HASH_FOREACH_END(); - } - do { - size_t length = buffer->length - SW_MYSQL_PACKET_HEADER_SIZE; - size_t send_s = SW_MIN(length, SW_MYSQL_MAX_PACKET_BODY_SIZE); - mysql::packet::set_header(buffer->str, send_s, 0); - if (sw_unlikely(!client->send_raw(buffer->str, SW_MYSQL_PACKET_HEADER_SIZE + send_s))) { - RETURN_FALSE; - } - if (sw_unlikely(length > SW_MYSQL_MAX_PACKET_BODY_SIZE)) { - size_t send_n = SW_MYSQL_MAX_PACKET_BODY_SIZE, number = 1; - /* MySQL single packet size is 16M, we must subpackage */ - while (send_n < length) { - send_s = length - send_n; - send_s = SW_MIN(send_s, SW_MYSQL_MAX_PACKET_BODY_SIZE); - mysql::packet::set_header(buffer->str, send_s, number++); - if (sw_unlikely(!client->send_raw(buffer->str, SW_MYSQL_PACKET_HEADER_SIZE)) || - !client->send_raw(buffer->str + SW_MYSQL_PACKET_HEADER_SIZE + send_n, send_s)) { - RETURN_FALSE; - } - send_n += send_s; - } - } - } while (0); - client->state = SW_MYSQL_STATE_EXECUTE; - RETURN_TRUE; -} - -void mysql_statement::recv_execute_response(zval *return_value) { - if (sw_unlikely(!is_available())) { - RETURN_FALSE; - } - const char *data; - if (sw_unlikely(!(data = client->recv_none_error_packet()))) { - RETURN_FALSE; - } - if (mysql::server_packet::is_ok(data)) { - mysql::ok_packet ok_packet(data); - result.ok = ok_packet; - client->state = - ok_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_EXECUTE_MORE_RESULTS : SW_MYSQL_STATE_IDLE; - RETURN_TRUE; - } - do { - mysql::lcb_packet lcb_packet(data); - if (sw_unlikely(lcb_packet.length == 0)) { - // is it possible? - client->proto_error(data, SW_MYSQL_PACKET_FIELD); - RETURN_FALSE; - } - // although we have already known the field data when we prepared the statement, - // we don't know if the data is always reliable, such as when we using stored procedure... - // so we should not optimize here for the time being for stability - result.alloc_fields(lcb_packet.length); - for (size_t i = 0; i < result.get_fields_length(); i++) { - if (sw_unlikely(!(data = client->recv_packet()))) { - RETURN_FALSE; - } - result.set_field(i, data); - } - } while (0); - // expect eof - if (sw_unlikely(!(data = client->recv_eof_packet()))) { - RETURN_FALSE; - } - client->state = SW_MYSQL_STATE_EXECUTE_FETCH; - if (client->get_fetch_mode()) { - RETURN_TRUE; - } - fetch_all(return_value); -} - -void mysql_statement::fetch(zval *return_value) { - if (sw_unlikely(!is_available())) { - RETURN_FALSE; - } - if (sw_unlikely(client->state != SW_MYSQL_STATE_EXECUTE_FETCH)) { - RETURN_NULL(); - } - const char *data; - if (sw_unlikely(!(data = client->recv_packet()))) { - RETURN_FALSE; - } - if (mysql::server_packet::is_eof(data)) { - mysql::eof_packet eof_packet(data); - client->state = - eof_packet.server_status.more_results_exists() ? SW_MYSQL_STATE_EXECUTE_MORE_RESULTS : SW_MYSQL_STATE_IDLE; - RETURN_NULL(); - } - do { - mysql::row_data row_data(data); - uint32_t null_bitmap_size = mysql::null_bitmap::get_size(result.get_fields_length()); - mysql::null_bitmap null_bitmap(row_data.read(null_bitmap_size), null_bitmap_size); - - array_init_size(return_value, result.get_fields_length()); - for (uint32_t i = 0; i < result.get_fields_length(); i++) { - mysql::field_packet *field = result.get_field(i); - - /* to check Null-Bitmap @see https://dev.mysql.com/doc/internals/en/null-bitmap.html */ - if (null_bitmap.is_null(i) || field->type == SW_MYSQL_TYPE_NULL) { - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s is null", field->name_length, field->name); - add_assoc_null_ex(return_value, field->name, field->name_length); - continue; - } - - switch (field->type) { - /* String */ - case SW_MYSQL_TYPE_TINY_BLOB: - case SW_MYSQL_TYPE_MEDIUM_BLOB: - case SW_MYSQL_TYPE_LONG_BLOB: - case SW_MYSQL_TYPE_BLOB: - case SW_MYSQL_TYPE_DECIMAL: - case SW_MYSQL_TYPE_NEWDECIMAL: - case SW_MYSQL_TYPE_BIT: - case SW_MYSQL_TYPE_JSON: - case SW_MYSQL_TYPE_STRING: - case SW_MYSQL_TYPE_VAR_STRING: - case SW_MYSQL_TYPE_VARCHAR: - case SW_MYSQL_TYPE_NEWDATE: - case SW_MYSQL_TYPE_GEOMETRY: { - _add_string: - zval ztext; - client->handle_row_data_text(&ztext, &row_data, field); - if (sw_unlikely(Z_TYPE_P(&ztext) == IS_FALSE)) { - zval_ptr_dtor(return_value); - RETURN_FALSE; - } - add_assoc_zval_ex(return_value, field->name, field->name_length, &ztext); - break; - } - default: { - const char *p = nullptr; - uint8_t lcb = mysql::get_static_type_size(field->type); - if (lcb == 0) { - client->handle_row_data_lcb(&row_data); - lcb = row_data.text.length; - } - p = client->handle_row_data_size(&row_data, lcb); - if (sw_unlikely(!p)) { - zval_ptr_dtor(return_value); - RETURN_FALSE; - } - /* Date Time */ - switch (field->type) { - case SW_MYSQL_TYPE_TIMESTAMP: - case SW_MYSQL_TYPE_DATETIME: { - std::string datetime = mysql::datetime(p, row_data.text.length, field->decimals); - add_assoc_stringl_ex( - return_value, field->name, field->name_length, (char *) datetime.c_str(), datetime.length()); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, datetime.c_str()); - break; - } - case SW_MYSQL_TYPE_TIME: { - std::string time = mysql::time(p, row_data.text.length, field->decimals); - add_assoc_stringl_ex( - return_value, field->name, field->name_length, (char *) time.c_str(), time.length()); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, time.c_str()); - break; - } - case SW_MYSQL_TYPE_DATE: { - std::string date = mysql::date(p, row_data.text.length); - add_assoc_stringl_ex( - return_value, field->name, field->name_length, (char *) date.c_str(), date.length()); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%s", field->name_length, field->name, date.c_str()); - break; - } - case SW_MYSQL_TYPE_YEAR: { -#if PHP_VERSION_ID >= 80100 - std::string year = mysql::year(p, row_data.text.length); - add_assoc_stringl_ex( - return_value, field->name, field->name_length, (char *) year.c_str(), year.length()); -#else - add_assoc_long_ex(return_value, field->name, field->name_length, sw_mysql_uint2korr2korr(p)); -#endif - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, sw_mysql_uint2korr2korr(p)); - break; - } - /* Number */ - case SW_MYSQL_TYPE_TINY: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - add_assoc_long_ex(return_value, field->name, field->name_length, *(uint8_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint8_t *) p); - } else { - add_assoc_long_ex(return_value, field->name, field->name_length, *(int8_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int8_t *) p); - } - break; - case SW_MYSQL_TYPE_SHORT: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - add_assoc_long_ex(return_value, field->name, field->name_length, *(uint16_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint16_t *) p); - } else { - add_assoc_long_ex(return_value, field->name, field->name_length, *(int16_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int16_t *) p); - } - break; - case SW_MYSQL_TYPE_INT24: - case SW_MYSQL_TYPE_LONG: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - add_assoc_long_ex(return_value, field->name, field->name_length, *(uint32_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%u", field->name_length, field->name, *(uint32_t *) p); - } else { - add_assoc_long_ex(return_value, field->name, field->name_length, *(int32_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%d", field->name_length, field->name, *(int32_t *) p); - } - break; - case SW_MYSQL_TYPE_LONGLONG: - if (field->flags & SW_MYSQL_UNSIGNED_FLAG) { - add_assoc_ulong_safe_ex(return_value, field->name, field->name_length, *(uint64_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%" PRIu64, field->name_length, field->name, *(uint64_t *) p); - } else { - add_assoc_long_ex(return_value, field->name, field->name_length, *(int64_t *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%" PRId64, field->name_length, field->name, *(int64_t *) p); - } - break; - case SW_MYSQL_TYPE_FLOAT: { - double dv = sw_php_math_round(*(float *) p, 7, PHP_ROUND_HALF_DOWN); - add_assoc_double_ex(return_value, field->name, field->name_length, dv); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "%.*s=%.7f", field->name_length, field->name, dv); - } break; - case SW_MYSQL_TYPE_DOUBLE: { - add_assoc_double_ex(return_value, field->name, field->name_length, *(double *) p); - swoole_trace_log( - SW_TRACE_MYSQL_CLIENT, "%.*s=%.16f", field->name_length, field->name, *(double *) p); - } break; - default: - swoole_warning("unknown type[%d] for field [%.*s].", field->type, field->name_length, field->name); - goto _add_string; - } - } - } - } - } while (0); -} - -void mysql_statement::fetch_all(zval *return_value) { - if (sw_unlikely(!is_available())) { - RETURN_FALSE; - } - - zval zrow; - array_init(return_value); - while (true) { - fetch(&zrow); - if (sw_unlikely(ZVAL_IS_NULL(&zrow))) { - // eof - return; - } - if (sw_unlikely(Z_TYPE_P(&zrow) == IS_FALSE)) { - // error - zval_ptr_dtor(return_value); - RETURN_FALSE; - } - (void) add_next_index_zval(return_value, &zrow); - } -} - -void mysql_statement::next_result(zval *return_value) { - if (sw_unlikely(!is_available())) { - RETURN_FALSE; - } - if (sw_unlikely(client->state == SW_MYSQL_STATE_EXECUTE_FETCH)) { - // skip unread data - fetch_all(return_value); - zval_ptr_dtor(return_value); - next_result(return_value); - } else if (sw_likely(client->state == SW_MYSQL_STATE_EXECUTE_MORE_RESULTS)) { - recv_execute_response(return_value); - } else if (client->state == SW_MYSQL_STATE_IDLE) { - RETURN_NULL(); - } else { - RETURN_FALSE; - } -} - -static sw_inline mysql_coro_t *php_swoole_mysql_coro_fetch_object(zend_object *obj) { - return (mysql_coro_t *) ((char *) obj - swoole_mysql_coro_handlers.offset); -} - -static sw_inline mysql_client *php_swoole_get_mysql_client(zval *zobject) { - return php_swoole_mysql_coro_fetch_object(Z_OBJ_P(zobject))->client; -} - -static void php_swoole_mysql_coro_free_object(zend_object *object) { - mysql_coro_t *zmc = php_swoole_mysql_coro_fetch_object(object); - delete zmc->client; - zend_object_std_dtor(&zmc->std); -} - -static zend_object *php_swoole_mysql_coro_create_object(zend_class_entry *ce) { - mysql_coro_t *zmc = (mysql_coro_t *) zend_object_alloc(sizeof(mysql_coro_t), ce); - zend_object_std_init(&zmc->std, ce); - object_properties_init(&zmc->std, ce); - zmc->std.handlers = &swoole_mysql_coro_handlers; - zmc->client = new mysql_client; - return &zmc->std; -} - -static sw_inline mysql_coro_statement_t *php_swoole_mysql_coro_statement_fetch_object(zend_object *obj) { - return (mysql_coro_statement_t *) ((char *) obj - swoole_mysql_coro_statement_handlers.offset); -} - -static sw_inline mysql_statement *php_swoole_get_mysql_statement(zval *zobject) { - return php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject))->statement; -} - -static void php_swoole_mysql_coro_statement_free_object(zend_object *object) { - mysql_coro_statement_t *zms = php_swoole_mysql_coro_statement_fetch_object(object); - delete zms->statement; - OBJ_RELEASE(zms->zclient); - zend_object_std_dtor(&zms->std); -} - -static sw_inline zend_object *php_swoole_mysql_coro_statement_create_object(zend_class_entry *ce, - mysql_statement *statement, - zend_object *client) { - zval zobject; - mysql_coro_statement_t *zms = (mysql_coro_statement_t *) zend_object_alloc(sizeof(mysql_coro_statement_t), ce); - zend_object_std_init(&zms->std, ce); - object_properties_init(&zms->std, ce); - zms->std.handlers = &swoole_mysql_coro_statement_handlers; - ZVAL_OBJ(&zobject, &zms->std); - zend_update_property_long(ce, SW_Z8_OBJ_P(&zobject), ZEND_STRL("id"), statement->info.id); - zms->statement = statement; - zms->zclient = client; - GC_ADDREF(client); - return &zms->std; -} - -static sw_inline zend_object *php_swoole_mysql_coro_statement_create_object(mysql_statement *statement, - zend_object *client) { - return php_swoole_mysql_coro_statement_create_object(swoole_mysql_coro_statement_ce, statement, client); -} - -static zend_object *php_swoole_mysql_coro_statement_create_object(zend_class_entry *ce) { - php_swoole_fatal_error(E_ERROR, "you must create mysql statement object by prepare method"); - return nullptr; -} - -static sw_inline void swoole_mysql_coro_sync_error_properties(zval *zobject, - int error_code, - const char *error_msg, - const bool connected = true) { - SW_ASSERT(instanceof_function(Z_OBJCE_P(zobject), swoole_mysql_coro_ce) || - instanceof_function(Z_OBJCE_P(zobject), swoole_mysql_coro_statement_ce)); - zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errno"), error_code); - zend_update_property_string(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("error"), error_msg); - if (!connected) { - zend_update_property_bool(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), connected); - } -} - -static sw_inline void swoole_mysql_coro_sync_query_result_properties(zval *zobject, - mysql_client *mc, - zval *return_value) { - switch (Z_TYPE_P(return_value)) { - case IS_TRUE: { - mysql::ok_packet *ok_packet = &mc->result.ok; - zend_update_property_long( - Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("affected_rows"), ok_packet->affected_rows); - zend_update_property_long( - Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("insert_id"), ok_packet->last_insert_id); - break; - } - case IS_FALSE: { - swoole_mysql_coro_sync_error_properties(zobject, mc->get_error_code(), mc->get_error_msg()); - break; - } - default: - break; - } -} - -static sw_inline void swoole_mysql_coro_sync_execute_error_properties(zval *zobject, - int error_code, - const char *error_msg, - const bool connected = true) { - swoole_mysql_coro_sync_error_properties(zobject, error_code, error_msg, connected); - - /* backward compatibility (sync error info to client) */ - zval zclient; - ZVAL_OBJ(&zclient, php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject))->zclient); - swoole_mysql_coro_sync_error_properties(&zclient, error_code, error_msg, connected); -} - -static sw_inline void swoole_mysql_coro_sync_execute_result_properties(zval *zobject, zval *return_value) { - mysql_coro_statement_t *zms = php_swoole_mysql_coro_statement_fetch_object(Z_OBJ_P(zobject)); - mysql_statement *ms = zms->statement; - - switch (Z_TYPE_P(return_value)) { - case IS_TRUE: { - mysql::ok_packet *ok_packet = &ms->result.ok; - zend_update_property_long( - Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("affected_rows"), ok_packet->affected_rows); - zend_update_property_long( - Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("insert_id"), ok_packet->last_insert_id); - - /* backward compatibility (sync result info to client) */ - zval zclient; - ZVAL_OBJ(&zclient, zms->zclient); - zend_update_property_long( - Z_OBJCE_P(&zclient), SW_Z8_OBJ_P(&zclient), ZEND_STRL("affected_rows"), ok_packet->affected_rows); - zend_update_property_long( - Z_OBJCE_P(&zclient), SW_Z8_OBJ_P(&zclient), ZEND_STRL("insert_id"), ok_packet->last_insert_id); - break; - } - case IS_FALSE: { - swoole_mysql_coro_sync_execute_error_properties(zobject, ms->get_error_code(), ms->get_error_msg()); - break; - } - default: - break; - } -} - -void php_swoole_mysql_coro_minit(int module_number) { - SW_INIT_CLASS_ENTRY(swoole_mysql_coro, "Swoole\\Coroutine\\MySQL", "Co\\MySQL", swoole_mysql_coro_methods); - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro); - SW_SET_CLASS_CLONEABLE(swoole_mysql_coro, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_mysql_coro, php_swoole_mysql_coro_create_object, php_swoole_mysql_coro_free_object, mysql_coro_t, std); - - SW_INIT_CLASS_ENTRY(swoole_mysql_coro_statement, - "Swoole\\Coroutine\\MySQL\\Statement", - "Co\\MySQL\\Statement", - swoole_mysql_coro_statement_methods); - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro_statement); - SW_SET_CLASS_CLONEABLE(swoole_mysql_coro_statement, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro_statement, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_mysql_coro_statement, - php_swoole_mysql_coro_statement_create_object, - php_swoole_mysql_coro_statement_free_object, - mysql_coro_statement_t, - std); - - SW_INIT_CLASS_ENTRY_EX(swoole_mysql_coro_exception, - "Swoole\\Coroutine\\MySQL\\Exception", - "Co\\MySQL\\Exception", - nullptr, - swoole_exception); - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_mysql_coro_exception); - SW_SET_CLASS_CLONEABLE(swoole_mysql_coro_exception, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_mysql_coro_exception, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_mysql_coro_exception); - - zend_declare_property_null(swoole_mysql_coro_ce, ZEND_STRL("serverInfo"), ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); - zend_declare_property_bool(swoole_mysql_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("connect_errno"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_string(swoole_mysql_coro_ce, ZEND_STRL("connect_error"), "", ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("affected_rows"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("insert_id"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_string(swoole_mysql_coro_ce, ZEND_STRL("error"), "", ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_ce, ZEND_STRL("errno"), 0, ZEND_ACC_PUBLIC); - - zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("affected_rows"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("insert_id"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_string(swoole_mysql_coro_statement_ce, ZEND_STRL("error"), "", ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_mysql_coro_statement_ce, ZEND_STRL("errno"), 0, ZEND_ACC_PUBLIC); - - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_UNKNOWN_ERROR", MYSQLND_CR_UNKNOWN_ERROR); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_CONNECTION_ERROR", MYSQLND_CR_CONNECTION_ERROR); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR", MYSQLND_CR_SERVER_GONE_ERROR); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_OUT_OF_MEMORY", MYSQLND_CR_OUT_OF_MEMORY); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_SERVER_LOST", MYSQLND_CR_SERVER_LOST); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_COMMANDS_OUT_OF_SYNC", MYSQLND_CR_COMMANDS_OUT_OF_SYNC); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_CANT_FIND_CHARSET", MYSQLND_CR_CANT_FIND_CHARSET); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_MALFORMED_PACKET", MYSQLND_CR_MALFORMED_PACKET); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_NOT_IMPLEMENTED", MYSQLND_CR_NOT_IMPLEMENTED); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_NO_PREPARE_STMT", MYSQLND_CR_NO_PREPARE_STMT); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_PARAMS_NOT_BOUND", MYSQLND_CR_PARAMS_NOT_BOUND); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_INVALID_PARAMETER_NO", MYSQLND_CR_INVALID_PARAMETER_NO); - SW_REGISTER_LONG_CONSTANT("SWOOLE_MYSQLND_CR_INVALID_BUFFER_USE", MYSQLND_CR_INVALID_BUFFER_USE); -} - -static PHP_METHOD(swoole_mysql_coro, __construct) {} -static PHP_METHOD(swoole_mysql_coro, __destruct) {} - -static PHP_METHOD(swoole_mysql_coro, connect) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - zval *zserver_info = nullptr; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_ARRAY_EX(zserver_info, 1, 0) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - if (zserver_info) { - HashTable *ht = Z_ARRVAL_P(zserver_info); - zval *ztmp; - - if (php_swoole_array_get_value(ht, "host", ztmp)) { - mc->host = std::string(zend::String(ztmp).val()); - } else { - zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [host] is required", EINVAL); - RETURN_FALSE; - } - if (php_swoole_array_get_value(ht, "port", ztmp)) { - mc->port = zval_get_long(ztmp); - } - if (php_swoole_array_get_value(ht, "ssl", ztmp)) { - mc->ssl = zval_is_true(ztmp); -#ifndef SW_USE_OPENSSL - if (sw_unlikely(mc->ssl)) { - zend_throw_exception_ex( - swoole_mysql_coro_exception_ce, - EPROTONOSUPPORT, - "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"); - RETURN_FALSE; - } -#endif - } - if (php_swoole_array_get_value(ht, "user", ztmp)) { - mc->user = std::string(zend::String(ztmp).val()); - } else { - zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [user] is required", EINVAL); - RETURN_FALSE; - } - if (php_swoole_array_get_value(ht, "password", ztmp)) { - mc->password = std::string(zend::String(ztmp).val()); - } else { - zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [password] is required", EINVAL); - RETURN_FALSE; - } - if (php_swoole_array_get_value(ht, "database", ztmp)) { - mc->database = std::string(zend::String(ztmp).val()); - } else { - zend_throw_exception(swoole_mysql_coro_exception_ce, "Parameter [database] is required", EINVAL); - RETURN_FALSE; - } - if (php_swoole_array_get_value(ht, "timeout", ztmp)) { - mc->connect_timeout = zval_get_double(ztmp); - } - if (php_swoole_array_get_value(ht, "charset", ztmp)) { - zend::String zstr_charset(ztmp); - char charset = mysql::get_charset(zstr_charset.val()); - if (UNEXPECTED(charset < 0)) { - zend_throw_exception_ex( - swoole_mysql_coro_exception_ce, EINVAL, "Unknown charset [%s]", zstr_charset.val()); - RETURN_FALSE; - } - mc->charset = charset; - } - if (php_swoole_array_get_value(ht, "strict_type", ztmp)) { - mc->strict_type = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(ht, "fetch_mode", ztmp)) { - if (UNEXPECTED(!mc->set_fetch_mode(zval_is_true(ztmp)))) { - zend_throw_exception_ex( - swoole_mysql_coro_exception_ce, mc->get_error_code(), "%s", mc->get_error_msg()); - RETURN_FALSE; - } - } - } - if (!mc->connect()) { - zend_update_property_long( - swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connect_errno"), mc->get_error_code()); - zend_update_property_string( - swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connect_error"), mc->get_error_msg()); - RETURN_FALSE; - } - if (zserver_info && php_swoole_array_length(zserver_info) > 0) { - php_array_merge(Z_ARRVAL_P(sw_zend_read_and_convert_property_array( - swoole_mysql_coro_ce, ZEND_THIS, ZEND_STRL("serverInfo"), 0)), - Z_ARRVAL_P(zserver_info)); - } - zend_update_property_long(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("sock"), mc->get_fd()); - zend_update_property_bool(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 1); - RETURN_TRUE; -} - -static PHP_METHOD(swoole_mysql_coro, getDefer) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - RETURN_BOOL(mc->get_defer()); -} - -static PHP_METHOD(swoole_mysql_coro, setDefer) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - zend_bool defer = 1; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_BOOL(defer) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - bool ret = mc->set_defer(defer); - if (UNEXPECTED(!ret)) { - zend_throw_exception_ex(swoole_mysql_coro_exception_ce, mc->get_error_code(), "%s", mc->get_error_msg()); - } - RETURN_BOOL(ret); -} - -static PHP_METHOD(swoole_mysql_coro, query) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - char *sql; - size_t sql_length; - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_STRING(sql, sql_length) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - mc->query(return_value, sql, sql_length); - mc->del_timeout_controller(); - swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value); -} - -static PHP_METHOD(swoole_mysql_coro, fetch) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - mc->fetch(return_value); - mc->del_timeout_controller(); - if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) { - swoole_mysql_coro_sync_error_properties( - ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected()); - } -} - -static PHP_METHOD(swoole_mysql_coro, fetchAll) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - mc->fetch_all(return_value); - mc->del_timeout_controller(); - if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) { - swoole_mysql_coro_sync_error_properties( - ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected()); - } -} - -static PHP_METHOD(swoole_mysql_coro, nextResult) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - mc->next_result(return_value); - mc->del_timeout_controller(); - swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value); - if (Z_TYPE_P(return_value) == IS_TRUE) { - if (mc->state == SW_MYSQL_STATE_IDLE) { - // the end of procedure - Z_TYPE_INFO_P(return_value) = mc->get_fetch_mode() ? IS_FALSE : IS_NULL; - } - } -} - -static PHP_METHOD(swoole_mysql_coro, prepare) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - char *statement; - size_t statement_length; - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_STRING(statement, statement_length) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - if (UNEXPECTED(!mc->send_prepare_request(statement, statement_length))) { - _failed: - swoole_mysql_coro_sync_error_properties( - ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), mc->is_connected()); - RETVAL_FALSE; - } else if (UNEXPECTED(mc->get_defer())) { - RETVAL_TRUE; - } else { - mysql_statement *statement = mc->recv_prepare_response(); - if (UNEXPECTED(!statement)) { - goto _failed; - } - RETVAL_OBJ(php_swoole_mysql_coro_statement_create_object(statement, Z_OBJ_P(ZEND_THIS))); - } - mc->del_timeout_controller(); -} - -static PHP_METHOD(swoole_mysql_coro, recv) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - if (UNEXPECTED(!mc->check_connection())) { - swoole_mysql_coro_sync_error_properties(ZEND_THIS, mc->get_error_code(), mc->get_error_msg(), false); - RETURN_FALSE; - } - mc->add_timeout_controller(timeout, Socket::TIMEOUT_READ); - switch (mc->state) { - case SW_MYSQL_STATE_IDLE: - swoole_mysql_coro_sync_error_properties(ZEND_THIS, ENOMSG, "no message to receive"); - RETVAL_FALSE; - break; - case SW_MYSQL_STATE_QUERY: - mc->recv_query_response(return_value); - break; - case SW_MYSQL_STATE_PREPARE: { - mysql_statement *statement = mc->recv_prepare_response(); - if (UNEXPECTED(!statement)) { - RETVAL_FALSE; - } else { - RETVAL_OBJ(php_swoole_mysql_coro_statement_create_object(statement, Z_OBJ_P(ZEND_THIS))); - } - break; - } - default: - if (UNEXPECTED(mc->state & SW_MYSQL_COMMAND_FLAG_EXECUTE)) { - swoole_mysql_coro_sync_error_properties(ZEND_THIS, EPERM, "please use statement to receive data"); - } else { - swoole_mysql_coro_sync_error_properties( - ZEND_THIS, EPERM, "please use fetch/fetchAll/nextResult to get result"); - } - RETVAL_FALSE; - } - mc->del_timeout_controller(); -} - -static void swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAMETERS, - const char *command, - size_t command_length) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - if (UNEXPECTED(mc->get_defer())) { - zend_throw_exception_ex( - swoole_mysql_coro_exception_ce, - EPERM, - "you should not query transaction when defer mode is on, if you want, please use `query('%s')` instead", - command); - RETURN_FALSE; - } - - mc->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - mc->query(return_value, command, command_length); - mc->del_timeout_controller(); - swoole_mysql_coro_sync_query_result_properties(ZEND_THIS, mc, return_value); -} - -static PHP_METHOD(swoole_mysql_coro, begin) { - swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("BEGIN")); -} - -static PHP_METHOD(swoole_mysql_coro, commit) { - swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("COMMIT")); -} - -static PHP_METHOD(swoole_mysql_coro, rollback) { - swoole_mysql_coro_query_transcation(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ROLLBACK")); -} - -#ifdef SW_USE_MYSQLND -static PHP_METHOD(swoole_mysql_coro, escape) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - char *str; - size_t str_length; - zend_long flags = 0; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_STRING(str, str_length) - Z_PARAM_OPTIONAL - Z_PARAM_LONG(flags) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - char *newstr = (char *) safe_emalloc(2, str_length + 1, 1); - const MYSQLND_CHARSET *cset = mysqlnd_find_charset_nr(mc->charset); - if (!cset) { - php_swoole_fatal_error(E_ERROR, "unknown mysql charset[%d]", mc->charset); - RETURN_FALSE; - } - zend_ulong newstr_len = mysqlnd_cset_escape_slashes(cset, newstr, str, str_length); - if (newstr_len == (zend_ulong) ~0) { - php_swoole_fatal_error(E_ERROR, "mysqlnd_cset_escape_slashes() failed"); - RETURN_FALSE; - } - RETVAL_STRINGL(newstr, newstr_len); - efree(newstr); - return; -} -#endif - -static PHP_METHOD(swoole_mysql_coro, close) { - mysql_client *mc = php_swoole_get_mysql_client(ZEND_THIS); - mc->close(); - zend_update_property_bool(swoole_mysql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("connected"), 0); - RETURN_TRUE; -} - -static PHP_METHOD(swoole_mysql_coro_statement, execute) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - zval *params = nullptr; - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 2) - Z_PARAM_OPTIONAL - Z_PARAM_ARRAY_EX(params, 1, 0) - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - ms->execute(return_value, params); - ms->del_timeout_controller(); - swoole_mysql_coro_sync_execute_result_properties(ZEND_THIS, return_value); -} - -static PHP_METHOD(swoole_mysql_coro_statement, fetch) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - ms->fetch(return_value); - ms->del_timeout_controller(); - if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) { - swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg()); - } -} - -static PHP_METHOD(swoole_mysql_coro_statement, fetchAll) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - ms->fetch_all(return_value); - ms->del_timeout_controller(); - if (sw_unlikely(Z_TYPE_P(return_value) == IS_FALSE)) { - swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg()); - } -} - -static PHP_METHOD(swoole_mysql_coro_statement, nextResult) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - double timeout = 0; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - ms->add_timeout_controller(timeout, Socket::TIMEOUT_RDWR); - ms->next_result(return_value); - ms->del_timeout_controller(); - swoole_mysql_coro_sync_execute_result_properties(ZEND_THIS, return_value); - if (Z_TYPE_P(return_value) == IS_TRUE) { - mysql_client *mc = ms->get_client(); - if (mc->state == SW_MYSQL_STATE_IDLE) { - // the end of procedure - Z_TYPE_INFO_P(return_value) = mc->get_fetch_mode() ? IS_FALSE : IS_NULL; - } - } -} - -static PHP_METHOD(swoole_mysql_coro_statement, recv) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - double timeout = 0; - enum sw_mysql_state state; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - if (UNEXPECTED(!ms->is_available())) { - swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ms->get_error_code(), ms->get_error_msg(), false); - RETURN_FALSE; - } - ms->add_timeout_controller(timeout, Socket::TIMEOUT_READ); - switch ((state = ms->get_client()->state)) { - case SW_MYSQL_STATE_IDLE: - swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, ENOMSG, "no message to receive"); - RETVAL_FALSE; - break; - case SW_MYSQL_STATE_EXECUTE: - ms->recv_execute_response(return_value); - break; - default: - if (UNEXPECTED(state & SW_MYSQL_COMMAND_FLAG_QUERY)) { - swoole_mysql_coro_sync_execute_error_properties(ZEND_THIS, EPERM, "please use client to receive data"); - } else { - swoole_mysql_coro_sync_execute_error_properties( - ZEND_THIS, EPERM, "please use fetch/fetchAll/nextResult to get result"); - } - RETVAL_FALSE; - } - ms->del_timeout_controller(); -} - -static PHP_METHOD(swoole_mysql_coro_statement, close) { - mysql_statement *ms = php_swoole_get_mysql_statement(ZEND_THIS); - ms->close(); - RETURN_TRUE; -} diff --git a/ext-src/swoole_mysql_proto.cc b/ext-src/swoole_mysql_proto.cc deleted file mode 100644 index 7e4342faa0..0000000000 --- a/ext-src/swoole_mysql_proto.cc +++ /dev/null @@ -1,744 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | Swoole | - +----------------------------------------------------------------------+ - | Copyright (c) 2012-2015 The Swoole Group | - +----------------------------------------------------------------------+ - | 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: Twosee | - | Author: Tianfeng Han | - +----------------------------------------------------------------------+ - */ - -#include "php_swoole_mysql_proto.h" - -using namespace swoole::mysql; - -namespace swoole { -namespace mysql { -struct charset_t { - uint nr; - const char *name; - const char *collation; -}; - -char get_charset(const char *name) { - static const charset_t charsets[] = { - {1, "big5", "big5_chinese_ci"}, - {3, "dec8", "dec8_swedish_ci"}, - {4, "cp850", "cp850_general_ci"}, - {6, "hp8", "hp8_english_ci"}, - {7, "koi8r", "koi8r_general_ci"}, - {8, "latin1", "latin1_swedish_ci"}, - {5, "latin1", "latin1_german1_ci"}, - {9, "latin2", "latin2_general_ci"}, - {2, "latin2", "latin2_czech_cs"}, - {10, "swe7", "swe7_swedish_ci"}, - {11, "ascii", "ascii_general_ci"}, - {12, "ujis", "ujis_japanese_ci"}, - {13, "sjis", "sjis_japanese_ci"}, - {16, "hebrew", "hebrew_general_ci"}, - {17, "filename", "filename"}, - {18, "tis620", "tis620_thai_ci"}, - {19, "euckr", "euckr_korean_ci"}, - {21, "latin2", "latin2_hungarian_ci"}, - {27, "latin2", "latin2_croatian_ci"}, - {22, "koi8u", "koi8u_general_ci"}, - {24, "gb2312", "gb2312_chinese_ci"}, - {25, "greek", "greek_general_ci"}, - {26, "cp1250", "cp1250_general_ci"}, - {28, "gbk", "gbk_chinese_ci"}, - {30, "latin5", "latin5_turkish_ci"}, - {31, "latin1", "latin1_german2_ci"}, - {15, "latin1", "latin1_danish_ci"}, - {32, "armscii8", "armscii8_general_ci"}, - {33, "utf8", "utf8_general_ci"}, - {35, "ucs2", "ucs2_general_ci"}, - {36, "cp866", "cp866_general_ci"}, - {37, "keybcs2", "keybcs2_general_ci"}, - {38, "macce", "macce_general_ci"}, - {39, "macroman", "macroman_general_ci"}, - {40, "cp852", "cp852_general_ci"}, - {41, "latin7", "latin7_general_ci"}, - {20, "latin7", "latin7_estonian_cs"}, - {57, "cp1256", "cp1256_general_ci"}, - {59, "cp1257", "cp1257_general_ci"}, - {63, "binary", "binary"}, - {97, "eucjpms", "eucjpms_japanese_ci"}, - {29, "cp1257", "cp1257_lithuanian_ci"}, - {31, "latin1", "latin1_german2_ci"}, - {34, "cp1250", "cp1250_czech_cs"}, - {42, "latin7", "latin7_general_cs"}, - {43, "macce", "macce_bin"}, - {44, "cp1250", "cp1250_croatian_ci"}, - {45, "utf8mb4", "utf8mb4_general_ci"}, - {46, "utf8mb4", "utf8mb4_bin"}, - {47, "latin1", "latin1_bin"}, - {48, "latin1", "latin1_general_ci"}, - {49, "latin1", "latin1_general_cs"}, - {51, "cp1251", "cp1251_general_ci"}, - {14, "cp1251", "cp1251_bulgarian_ci"}, - {23, "cp1251", "cp1251_ukrainian_ci"}, - {50, "cp1251", "cp1251_bin"}, - {52, "cp1251", "cp1251_general_cs"}, - {53, "macroman", "macroman_bin"}, - {54, "utf16", "utf16_general_ci"}, - {55, "utf16", "utf16_bin"}, - {56, "utf16le", "utf16le_general_ci"}, - {58, "cp1257", "cp1257_bin"}, - {60, "utf32", "utf32_general_ci"}, - {61, "utf32", "utf32_bin"}, - {62, "utf16le", "utf16le_bin"}, - {64, "armscii8", "armscii8_bin"}, - {65, "ascii", "ascii_bin"}, - {66, "cp1250", "cp1250_bin"}, - {67, "cp1256", "cp1256_bin"}, - {68, "cp866", "cp866_bin"}, - {69, "dec8", "dec8_bin"}, - {70, "greek", "greek_bin"}, - {71, "hebrew", "hebrew_bin"}, - {72, "hp8", "hp8_bin"}, - {73, "keybcs2", "keybcs2_bin"}, - {74, "koi8r", "koi8r_bin"}, - {75, "koi8u", "koi8u_bin"}, - {77, "latin2", "latin2_bin"}, - {78, "latin5", "latin5_bin"}, - {79, "latin7", "latin7_bin"}, - {80, "cp850", "cp850_bin"}, - {81, "cp852", "cp852_bin"}, - {82, "swe7", "swe7_bin"}, - {83, "utf8", "utf8_bin"}, - {84, "big5", "big5_bin"}, - {85, "euckr", "euckr_bin"}, - {86, "gb2312", "gb2312_bin"}, - {87, "gbk", "gbk_bin"}, - {88, "sjis", "sjis_bin"}, - {89, "tis620", "tis620_bin"}, - {90, "ucs2", "ucs2_bin"}, - {91, "ujis", "ujis_bin"}, - {92, "geostd8", "geostd8_general_ci"}, - {93, "geostd8", "geostd8_bin"}, - {94, "latin1", "latin1_spanish_ci"}, - {95, "cp932", "cp932_japanese_ci"}, - {96, "cp932", "cp932_bin"}, - {97, "eucjpms", "eucjpms_japanese_ci"}, - {98, "eucjpms", "eucjpms_bin"}, - {99, "cp1250", "cp1250_polish_ci"}, - {128, "ucs2", "ucs2_unicode_ci"}, - {129, "ucs2", "ucs2_icelandic_ci"}, - {130, "ucs2", "ucs2_latvian_ci"}, - {131, "ucs2", "ucs2_romanian_ci"}, - {132, "ucs2", "ucs2_slovenian_ci"}, - {133, "ucs2", "ucs2_polish_ci"}, - {134, "ucs2", "ucs2_estonian_ci"}, - {135, "ucs2", "ucs2_spanish_ci"}, - {136, "ucs2", "ucs2_swedish_ci"}, - {137, "ucs2", "ucs2_turkish_ci"}, - {138, "ucs2", "ucs2_czech_ci"}, - {139, "ucs2", "ucs2_danish_ci"}, - {140, "ucs2", "ucs2_lithuanian_ci"}, - {141, "ucs2", "ucs2_slovak_ci"}, - {142, "ucs2", "ucs2_spanish2_ci"}, - {143, "ucs2", "ucs2_roman_ci"}, - {144, "ucs2", "ucs2_persian_ci"}, - {145, "ucs2", "ucs2_esperanto_ci"}, - {146, "ucs2", "ucs2_hungarian_ci"}, - {147, "ucs2", "ucs2_sinhala_ci"}, - {148, "ucs2", "ucs2_german2_ci"}, - {149, "ucs2", "ucs2_croatian_ci"}, - {150, "ucs2", "ucs2_unicode_520_ci"}, - {151, "ucs2", "ucs2_vietnamese_ci"}, - {160, "utf32", "utf32_unicode_ci"}, - {161, "utf32", "utf32_icelandic_ci"}, - {162, "utf32", "utf32_latvian_ci"}, - {163, "utf32", "utf32_romanian_ci"}, - {164, "utf32", "utf32_slovenian_ci"}, - {165, "utf32", "utf32_polish_ci"}, - {166, "utf32", "utf32_estonian_ci"}, - {167, "utf32", "utf32_spanish_ci"}, - {168, "utf32", "utf32_swedish_ci"}, - {169, "utf32", "utf32_turkish_ci"}, - {170, "utf32", "utf32_czech_ci"}, - {171, "utf32", "utf32_danish_ci"}, - {172, "utf32", "utf32_lithuanian_ci"}, - {173, "utf32", "utf32_slovak_ci"}, - {174, "utf32", "utf32_spanish2_ci"}, - {175, "utf32", "utf32_roman_ci"}, - {176, "utf32", "utf32_persian_ci"}, - {177, "utf32", "utf32_esperanto_ci"}, - {178, "utf32", "utf32_hungarian_ci"}, - {179, "utf32", "utf32_sinhala_ci"}, - {180, "utf32", "utf32_german2_ci"}, - {181, "utf32", "utf32_croatian_ci"}, - {182, "utf32", "utf32_unicode_520_ci"}, - {183, "utf32", "utf32_vietnamese_ci"}, - {192, "utf8", "utf8_unicode_ci"}, - {193, "utf8", "utf8_icelandic_ci"}, - {194, "utf8", "utf8_latvian_ci"}, - {195, "utf8", "utf8_romanian_ci"}, - {196, "utf8", "utf8_slovenian_ci"}, - {197, "utf8", "utf8_polish_ci"}, - {198, "utf8", "utf8_estonian_ci"}, - {199, "utf8", "utf8_spanish_ci"}, - {200, "utf8", "utf8_swedish_ci"}, - {201, "utf8", "utf8_turkish_ci"}, - {202, "utf8", "utf8_czech_ci"}, - {203, "utf8", "utf8_danish_ci"}, - {204, "utf8", "utf8_lithuanian_ci"}, - {205, "utf8", "utf8_slovak_ci"}, - {206, "utf8", "utf8_spanish2_ci"}, - {207, "utf8", "utf8_roman_ci"}, - {208, "utf8", "utf8_persian_ci"}, - {209, "utf8", "utf8_esperanto_ci"}, - {210, "utf8", "utf8_hungarian_ci"}, - {211, "utf8", "utf8_sinhala_ci"}, - {212, "utf8", "utf8_german2_ci"}, - {213, "utf8", "utf8_croatian_ci"}, - {214, "utf8", "utf8_unicode_520_ci"}, - {215, "utf8", "utf8_vietnamese_ci"}, - {224, "utf8mb4", "utf8mb4_unicode_ci"}, - {225, "utf8mb4", "utf8mb4_icelandic_ci"}, - {226, "utf8mb4", "utf8mb4_latvian_ci"}, - {227, "utf8mb4", "utf8mb4_romanian_ci"}, - {228, "utf8mb4", "utf8mb4_slovenian_ci"}, - {229, "utf8mb4", "utf8mb4_polish_ci"}, - {230, "utf8mb4", "utf8mb4_estonian_ci"}, - {231, "utf8mb4", "utf8mb4_spanish_ci"}, - {232, "utf8mb4", "utf8mb4_swedish_ci"}, - {233, "utf8mb4", "utf8mb4_turkish_ci"}, - {234, "utf8mb4", "utf8mb4_czech_ci"}, - {235, "utf8mb4", "utf8mb4_danish_ci"}, - {236, "utf8mb4", "utf8mb4_lithuanian_ci"}, - {237, "utf8mb4", "utf8mb4_slovak_ci"}, - {238, "utf8mb4", "utf8mb4_spanish2_ci"}, - {239, "utf8mb4", "utf8mb4_roman_ci"}, - {240, "utf8mb4", "utf8mb4_persian_ci"}, - {241, "utf8mb4", "utf8mb4_esperanto_ci"}, - {242, "utf8mb4", "utf8mb4_hungarian_ci"}, - {243, "utf8mb4", "utf8mb4_sinhala_ci"}, - {244, "utf8mb4", "utf8mb4_german2_ci"}, - {245, "utf8mb4", "utf8mb4_croatian_ci"}, - {246, "utf8mb4", "utf8mb4_unicode_520_ci"}, - {247, "utf8mb4", "utf8mb4_vietnamese_ci"}, - {248, "gb18030", "gb18030_chinese_ci"}, - {249, "gb18030", "gb18030_bin"}, - {254, "utf8", "utf8_general_cs"}, - {0, nullptr, nullptr}, - }; - const charset_t *c = charsets; - while (c[0].nr) { - if (!strcasecmp(c->name, name)) { - return c->nr; - } - ++c; - } - return -1; -} - -// clang-format off -uint8_t get_static_type_size(uint8_t type) -{ - static const uint8_t map[] = - { - 0, // SW_MYSQL_TYPE_DECIMAL 0 - sizeof(int8_t), // SW_MYSQL_TYPE_TINY 1 - sizeof(int16_t), // SW_MYSQL_TYPE_SHORT 2 - sizeof(int32_t), // SW_MYSQL_TYPE_LONG 3 - sizeof(float), // SW_MYSQL_TYPE_FLOAT 4 - sizeof(double), // SW_MYSQL_TYPE_DOUBLE 5 - 0, // SW_MYSQL_TYPE_NULL 6 - 0, // SW_MYSQL_TYPE_TIMESTAMP 7 - sizeof(int64_t), // SW_MYSQL_TYPE_LONGLONG 8 - sizeof(int32_t), // SW_MYSQL_TYPE_INT24 9 - 0, // SW_MYSQL_TYPE_DATE 10 - 0, // SW_MYSQL_TYPE_TIME 11 - 0, // SW_MYSQL_TYPE_DATETIME 12 - sizeof(int16_t), // SW_MYSQL_TYPE_YEAR 13 - 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0 - }; - SW_ASSERT(sizeof(map) == UINT8_MAX + 1); - return map[type]; -} -// clang-format on - -static uint32_t sha1_password_with_nonce(char *buf, const char *nonce, const char *password) { - char hash_0[20] = {}; - php_swoole_sha1(password, strlen(password), (uchar *) hash_0); - - char hash_1[20] = {}; - php_swoole_sha1(hash_0, sizeof(hash_0), (uchar *) hash_1); - - char str[40]; - memcpy(str, nonce, 20); - memcpy(str + 20, hash_1, 20); - - char hash_2[20]; - php_swoole_sha1(str, sizeof(str), (uchar *) hash_2); - - char hash_3[20]; - - int *a = (int *) hash_2; - int *b = (int *) hash_0; - int *c = (int *) hash_3; - - int i; - for (i = 0; i < 5; i++) { - c[i] = a[i] ^ b[i]; - } - memcpy(buf, hash_3, 20); - return 20; -} - -static uint32_t sha256_password_with_nonce(char *buf, const char *nonce, const char *password) { - // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) - char hashed[32], double_hashed[32]; - php_swoole_sha256(password, strlen(password), (unsigned char *) hashed); - php_swoole_sha256(hashed, 32, (unsigned char *) double_hashed); - char combined[32 + SW_MYSQL_NONCE_LENGTH]; // double-hashed + nonce - memcpy(combined, double_hashed, 32); - memcpy(combined + 32, nonce, SW_MYSQL_NONCE_LENGTH); - char xor_bytes[32]; - php_swoole_sha256(combined, 32 + SW_MYSQL_NONCE_LENGTH, (unsigned char *) xor_bytes); - int i; - for (i = 0; i < 32; i++) { - hashed[i] ^= xor_bytes[i]; - } - memcpy(buf, hashed, 32); - return 32; -} - -/** @return: password length */ -static sw_inline uint32_t mysql_auth_encrypt_dispatch(char *buf, - const std::string auth_plugin_name, - const char *nonce, - const char *password) { - if (auth_plugin_name.length() == 0 || auth_plugin_name == "mysql_native_password") { - // mysql_native_password is default - return sha1_password_with_nonce(buf, nonce, password); - } else if (auth_plugin_name == "caching_sha2_password") { - return sha256_password_with_nonce(buf, nonce, password); - } else { - swoole_warning("Unknown auth plugin: %s", auth_plugin_name.c_str()); - return 0; - } -} - -eof_packet::eof_packet(const char *data) : server_packet(data) { - swMysqlPacketDump(header.length, header.number, data, "EOF_Packet"); - // EOF_Packet = Packet header (4 bytes) + 0xFE + warning(2byte) + status(2byte) - data += SW_MYSQL_PACKET_HEADER_SIZE; - // int<1> header [fe] EOF header - data += 1; - // int<2> warnings number of warnings - warning_count = sw_mysql_uint2korr2korr(data); - data += 2; - // int<2> status_flags Status Flags - server_status = sw_mysql_uint2korr2korr(data); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "EOF_Packet, warnings=%u, status_code=%u", warning_count, server_status.status); -} - -ok_packet::ok_packet(const char *data) : server_packet(data) { - swMysqlPacketDump(header.length, header.number, data, "OK_Packet"); - bool nul; - data += SW_MYSQL_PACKET_HEADER_SIZE; - // int<1> header [00] or [fe] the OK packet header - data += 1; - // int affected_rows affected rows - data += read_lcb(data, &affected_rows, &nul); - // int last_insert_id last insert id - data += read_lcb(data, &last_insert_id, &nul); - // int<2> status_flags status Flags - server_status = sw_mysql_uint2korr2korr(data); - data += 2; - // int<2> warnings number of warnings - warning_count = sw_mysql_uint2korr2korr(data); - // p += 2; - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "OK_Packet, affected_rows=%" PRIu64 ", insert_id=%" PRIu64 ", status_flags=0x%08x, warnings=%u", - affected_rows, - last_insert_id, - server_status.status, - warning_count); -} - -err_packet::err_packet(const char *data) : server_packet(data) { - swMysqlPacketDump(header.length, header.number, data, "ERR_Packet"); - // ERR Packet = Packet header (4 bytes) + ERR Payload - data += SW_MYSQL_PACKET_HEADER_SIZE; - // int<1> header [ff] header of the ERR packet - data += 1; - // int<2> error_code error-code - code = sw_mysql_uint2korr2korr(data); - data += 2; - // string[1] sql_state_marker # marker of the SQL State - data += 1; - // string[5] sql_state SQL State - memcpy(sql_state, data, 5); - sql_state[5] = '\0'; - data += 5; - // string error_message human readable error message - msg = std::string(data, header.length - 9); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "ERR_Packet, error_code=%u, sql_state=%s, status_msg=[%s]", - code, - sql_state, - msg.c_str()); -}; - -greeting_packet::greeting_packet(const char *data) : server_packet(data) { - swMysqlPacketDump(header.length, header.number, data, "Protocol::HandshakeGreeting"); - /** - 1 [0a] protocol version - string[NUL] server version - 4 connection id - string[8] auth-plugin-data-part-1 - 1 [00] filler - 2 capability flags (lower 2 bytes) - if more data in the packet: - 1 character set - 2 status flags - 2 capability flags (upper 2 bytes) - if capabilities & CLIENT_PLUGIN_AUTH { - 1 length of auth-plugin-data - } else { - 1 [00] - } - string[10] reserved (all [00]) - if capabilities & CLIENT_SECURE_CONNECTION { - string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8)) - if capabilities & CLIENT_PLUGIN_AUTH { - string[NUL] auth-plugin name - } - */ - const char *p = data + SW_MYSQL_PACKET_HEADER_SIZE; - // 1 [0a] protocol version - protocol_version = *p; - p++; - // x server version - server_version = std::string(p); - p += server_version.length() + 1; - // 4 connection id - connection_id = *((int *) p); - p += 4; - // string[8] auth-plugin-data-part-1 - memcpy(auth_plugin_data, p, 8); - p += 8; - // 1 [00] filler - filler = *p; - p += 1; - // 2 capability flags (lower 2 bytes) - memcpy(((char *) (&capability_flags)), p, 2); - p += 2; - - if (p < data + header.length) { - // 1 character set - charset = *p; - p += 1; - // 2 status flags - memcpy(&status_flags, p, 2); - p += 2; - // 2 capability flags (upper 2 bytes) - memcpy(((char *) (&capability_flags) + 2), p, 2); - p += 2; - // 1 auth plugin data length - auth_plugin_data_length = (uint8_t) *p; - p += 1; - // x reserved - memcpy(&reserved, p, sizeof(reserved)); - p += sizeof(reserved); - if (capability_flags & SW_MYSQL_CLIENT_SECURE_CONNECTION) { - uint8_t len = SW_MAX(13, auth_plugin_data_length - 8); - memcpy(auth_plugin_data + 8, p, len); - p += len; - } - if (capability_flags & SW_MYSQL_CLIENT_PLUGIN_AUTH) { - auth_plugin_name = std::string(p, strlen(p)); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "use %s auth plugin", auth_plugin_name.c_str()); - } - } - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "Server protocol=%d, version=%s, connection_id=%d, capabilites=0x%08x, status=%u, auth_plugin_name=%s, " - "auth_plugin_data=L%u[%s]", - protocol_version, - server_version.c_str(), - connection_id, - capability_flags, - status_flags.status, - auth_plugin_name.c_str(), - auth_plugin_data_length, - auth_plugin_data); -}; - -login_packet::login_packet(greeting_packet *greeting_packet, - const std::string &user, - const std::string &password, - std::string database, - char charset) { - char *p = data.body; - uint32_t tint; - // capability flags, CLIENT_PROTOCOL_41 always set - tint = SW_MYSQL_CLIENT_LONG_PASSWORD | SW_MYSQL_CLIENT_PROTOCOL_41 | SW_MYSQL_CLIENT_SECURE_CONNECTION | - SW_MYSQL_CLIENT_CONNECT_WITH_DB | SW_MYSQL_CLIENT_PLUGIN_AUTH | SW_MYSQL_CLIENT_MULTI_RESULTS; - memcpy(p, &tint, sizeof(tint)); - p += sizeof(tint); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "Client capabilites=0x%08x", tint); - // max-packet size - tint = 300; - memcpy(p, &tint, sizeof(tint)); - p += sizeof(tint); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "Client max packet=%u", tint); - // use the server character_set when the character_set is not set. - *p = charset ? charset : greeting_packet->charset; - p += 1; - // string[23] reserved (all [0]) - p += 23; - // string[NUL] username - strcpy(p, user.c_str()); - p += (user.length() + 1); - // string[NUL] password - if (password.length() > 0) { - *p = mysql_auth_encrypt_dispatch( - p + 1, greeting_packet->auth_plugin_name, greeting_packet->auth_plugin_data, password.c_str()); - } else { - *p = 0; - } - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "Client charset=%u, user=%s, password=%s, hased=L%d[%.*s], database=%s, auth_plugin_name=%s", - charset, - user.c_str(), - password.c_str(), - (int) *p, - (int) *p, - p + 1, - database.c_str(), - greeting_packet->auth_plugin_name.c_str()); - p += (((uint32_t) *p) + 1); - // string[NUL] database - strcpy(p, database.c_str()); - p += (database.length() + 1); - // string[NUL] auth plugin name - strcpy(p, greeting_packet->auth_plugin_name.c_str()); - p += (greeting_packet->auth_plugin_name.length() + 1); - // packet header - set_header(p - data.body, greeting_packet->header.number + 1); - swMysqlPacketDump(get_length(), get_number(), get_data(), "Protocol::HandshakeLogin"); -} - -auth_switch_request_packet::auth_switch_request_packet(const char *data) : server_packet(data) { - swMysqlPacketDump(header.length, header.number, data, "Protocol::AuthSwitchRequest"); - // 4 header - data += SW_MYSQL_PACKET_HEADER_SIZE; - // 1 type - data += 1; - // string[NUL] auth_method_name - auth_method_name = std::string(data); - data += (auth_method_name.length() + 1); - // string[NUL] auth_method_data - strcpy(auth_method_data, data); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "auth switch plugin name=%s", auth_method_name.c_str()); -} - -auth_switch_response_packet::auth_switch_response_packet(auth_switch_request_packet *req, const std::string &password) { - // if auth switch is triggered, password can't be empty - // create auth switch response packet - set_header(mysql_auth_encrypt_dispatch(data.body, req->auth_method_name, req->auth_method_data, password.c_str()), - req->header.number + 1); - swMysqlPacketDump(get_length(), get_number(), get_data(), "Protocol::AuthSignatureResponse"); -} - -// Caching sha2 authentication. Public key request and send encrypted password -// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse -auth_signature_response_packet::auth_signature_response_packet(raw_data_packet *raw_data_pakcet, - const std::string &password, - const char *auth_plugin_data) { -#ifndef SW_MYSQL_RSA_SUPPORT - { - swoole_warning(SW_MYSQL_NO_RSA_ERROR); -#else - if (0) { - _error: -#endif - data.body[0] = SW_MYSQL_AUTH_SIGNATURE_ERROR; - set_header(1, raw_data_pakcet->header.number + 1); - return; - } -#ifdef SW_MYSQL_RSA_SUPPORT - const char *tmp = raw_data_pakcet->body; - uint32_t rsa_public_key_length = raw_data_pakcet->header.length; - while (tmp[0] != 0x2d) { - tmp++; // ltrim - rsa_public_key_length--; - } - char rsa_public_key[rsa_public_key_length + 1]; // rsa + '\0' - memcpy((char *) rsa_public_key, tmp, rsa_public_key_length); - rsa_public_key[rsa_public_key_length] = '\0'; - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "rsa_public_key_length=%d;\nrsa_public_key=[%.*s]", - rsa_public_key_length, - rsa_public_key_length, - rsa_public_key); - - size_t password_bytes_length = password.length() + 1; - unsigned char password_bytes[password_bytes_length]; - // copy NUL terminator to password to stack - strcpy((char *) password_bytes, password.c_str()); - // XOR the password bytes with the challenge - for (size_t i = 0; i < password_bytes_length; i++) // include '\0' byte - { - password_bytes[i] ^= auth_plugin_data[i % SW_MYSQL_NONCE_LENGTH]; - } - - // prepare RSA public key - BIO *bio = nullptr; - RSA *public_rsa = nullptr; - if (sw_unlikely((bio = BIO_new_mem_buf((void *) rsa_public_key, -1)) == nullptr)) { - swoole_warning("BIO_new_mem_buf publicKey error!"); - goto _error; - } - // PEM_read_bio_RSA_PUBKEY - ERR_clear_error(); - if (sw_unlikely((public_rsa = PEM_read_bio_RSA_PUBKEY(bio, nullptr, nullptr, nullptr)) == nullptr)) { - char err_buf[512]; - ERR_load_crypto_strings(); - ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); - swoole_warning("[PEM_read_bio_RSA_PUBKEY ERROR]: %s", err_buf); - goto _error; - } - BIO_free_all(bio); - // encrypt with RSA public key - int rsa_len = RSA_size(public_rsa); - unsigned char encrypt_msg[rsa_len]; - // RSA_public_encrypt - ERR_clear_error(); - size_t flen = rsa_len - 42; - flen = password_bytes_length > flen ? flen : password_bytes_length; - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, "rsa_len=%d", rsa_len); - if (sw_unlikely(RSA_public_encrypt(flen, - (const unsigned char *) password_bytes, - (unsigned char *) encrypt_msg, - public_rsa, - RSA_PKCS1_OAEP_PADDING) < 0)) { - char err_buf[512]; - ERR_load_crypto_strings(); - ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); - swoole_warning("[RSA_public_encrypt ERROR]: %s", err_buf); - goto _error; - } - RSA_free(public_rsa); - memcpy(data.body, (char *) encrypt_msg, rsa_len); // copy rsa to buf - set_header(rsa_len, raw_data_pakcet->header.number + 1); - swMysqlPacketDump(get_length(), get_number(), get_data(), "Protocol::AuthSignatureResponse"); -#endif -} - -void field_packet::parse(const char *data) { - server_packet::parse(data); - bool nul = false; - char *p = body = new char[header.length]; - memcpy(body, data + SW_MYSQL_PACKET_HEADER_SIZE, header.length); - // catalog - p += read_lcb(p, &catalog_length, &nul); - catalog = p; - p += catalog_length; - // database - p += read_lcb(p, &database_length, &nul); - database = p; - p += database_length; - // table - p += read_lcb(p, &table_length, &nul); - table = p; - p += table_length; - // origin table - p += read_lcb(p, &org_table_length, &nul); - org_table = p; - p += org_table_length; - // name - p += read_lcb(p, &name_length, &nul); - name = p; - p += name_length; - // origin table - p += read_lcb(p, &org_name_length, &nul); - org_name = p; - p += org_name_length; - // filler - p += 1; - // charset - charset = sw_mysql_uint2korr2korr(p); - p += 2; - // binary length - length = sw_mysql_uint2korr4korr(p); - p += 4; - // field type - type = (uint8_t) *p; - p += 1; - // flags - flags = sw_mysql_uint2korr2korr(p); - p += 2; - /* decimals */ - decimals = *p; - p += 1; - /* filler */ - p += 2; - /* default - a priori facultatif */ - if (p < body + header.length) { - p += read_lcb(p, &def_length, &nul); - def = p; - p += def_length; - } - swMysqlPacketDump(header.length, header.number, data, (*name == '?' ? "Protocol::Param" : "Protocol::Field")); - swoole_trace_log(SW_TRACE_MYSQL_CLIENT, - "catalog=%.*s, database=%.*s, table=%.*s, org_table=%.*s, name=%.*s, org_name=%.*s," - "charset=%u, binary_length=%" PRIu64 ", type=%u, flags=0x%08x, decimals=%u, def=[%.*s]", - catalog_length, - catalog, - database_length, - database, - table_length, - table, - org_table_length, - org_table, - name_length, - name, - org_name_length, - org_name, - charset, - length, - type, - flags, - decimals, - def_length, - def); -} -} // namespace mysql -} // namespace swoole diff --git a/ext-src/swoole_name_resolver.cc b/ext-src/swoole_name_resolver.cc index 2a9e1584e2..e959b5edc1 100644 --- a/ext-src/swoole_name_resolver.cc +++ b/ext-src/swoole_name_resolver.cc @@ -35,24 +35,26 @@ struct ContextObject { }; static zend_always_inline NameResolver::Context *swoole_name_resolver_context_get_handle(zend_object *object) { - return ((ContextObject *) ((char *) object - swoole_name_resolver_context_handlers.offset))->context; + return reinterpret_cast(reinterpret_cast(object) - + swoole_name_resolver_context_handlers.offset) + ->context; } static zend_always_inline ContextObject *swoole_name_resolver_context_get_object(zend_object *object) { - return (ContextObject *) ((char *) object - swoole_name_resolver_context_handlers.offset); + return reinterpret_cast(reinterpret_cast(object) - + swoole_name_resolver_context_handlers.offset); } static zend_always_inline ContextObject *swoole_name_resolver_context_get_object_safe(zend_object *object) { NameResolver::Context *name_resolver_context = swoole_name_resolver_context_get_handle(object); - if (!name_resolver_context) { - php_swoole_fatal_error(E_ERROR, "must call name_resolver_context constructor first"); + if (UNEXPECTED(!name_resolver_context)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return swoole_name_resolver_context_get_object(object); } static zend_object *swoole_name_resolver_context_create_object(zend_class_entry *ce) { - ContextObject *name_resolver_context_object = - (ContextObject *) zend_object_alloc(sizeof(*name_resolver_context_object), ce); + auto *name_resolver_context_object = static_cast(zend_object_alloc(sizeof(ContextObject), ce)); zend_object_std_init(&name_resolver_context_object->std, ce); object_properties_init(&name_resolver_context_object->std, ce); @@ -181,7 +183,7 @@ std::string php_swoole_name_resolver_lookup(const std::string &name, NameResolve _lookup: zval zname; ZVAL_STRINGL(&zname, name.c_str(), name.length()); - zend_call_method_with_1_params(SW_Z8_OBJ_P(zresolver), NULL, NULL, "lookup", &retval, &zname); + zend_call_method_with_1_params(SW_Z8_OBJ_P(zresolver), nullptr, nullptr, "lookup", &retval, &zname); zval_dtor(&zname); if (Z_TYPE(retval) == IS_OBJECT) { ctx->private_data = zcluster_object = (zval *) ecalloc(1, sizeof(zval)); @@ -196,7 +198,7 @@ std::string php_swoole_name_resolver_lookup(const std::string &name, NameResolve } else if (Z_TYPE(retval) == IS_STRING) { ctx->final_ = true; ctx->cluster_ = false; - return std::string(Z_STRVAL(retval), Z_STRLEN(retval)); + return {Z_STRVAL(retval), Z_STRLEN(retval)}; } else { ctx->final_ = false; ctx->cluster_ = false; @@ -205,7 +207,7 @@ std::string php_swoole_name_resolver_lookup(const std::string &name, NameResolve } else { zcluster_object = (zval *) ctx->private_data; // no available node, resolve again - sw_zend_call_method_with_0_params(zcluster_object, NULL, NULL, "count", &retval); + sw_zend_call_method_with_0_params(zcluster_object, nullptr, nullptr, "count", &retval); if (zval_get_long(&retval) == 0) { ctx->dtor(ctx); ctx->private_data = nullptr; @@ -213,7 +215,7 @@ std::string php_swoole_name_resolver_lookup(const std::string &name, NameResolve } } - sw_zend_call_method_with_0_params(zcluster_object, NULL, NULL, "pop", &retval); + sw_zend_call_method_with_0_params(zcluster_object, nullptr, nullptr, "pop", &retval); if (!ZVAL_IS_ARRAY(&retval)) { return ""; } diff --git a/ext-src/swoole_odbc.cc b/ext-src/swoole_odbc.cc new file mode 100644 index 0000000000..0826bea00b --- /dev/null +++ b/ext-src/swoole_odbc.cc @@ -0,0 +1,267 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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_odbc.h" +#include "php_swoole_cxx.h" +#include "php_swoole_private.h" +#include "php_swoole_cxx.h" + +#include "swoole_coroutine_system.h" + +#ifdef SW_USE_ODBC + +static bool swoole_odbc_blocking = true; + +#ifdef SQL_ATTR_CONNECTION_POOLING +zend_ulong pdo_odbc_pool_on = SQL_CP_OFF; +zend_ulong pdo_odbc_pool_mode = SQL_CP_ONE_PER_HENV; +#endif + +void swoole_odbc_set_blocking(bool blocking) { + swoole_odbc_blocking = blocking; +} + +RETCODE swoole_odbc_SQLConnect(SQLHDBC ConnectionHandle, + SQLCHAR *ServerName, + SQLSMALLINT NameLength1, + SQLCHAR *UserName, + SQLSMALLINT NameLength2, + SQLCHAR *Authentication, + SQLSMALLINT NameLength3) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLConnect(server=%s)", ServerName); + php_swoole_async(swoole_odbc_blocking, [&]() { + rc = SQLConnect(ConnectionHandle, ServerName, NameLength1, UserName, NameLength2, Authentication, NameLength3); + }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLDriverConnect(SQLHDBC hdbc, + SQLHWND hwnd, + SQLCHAR *szConnStrIn, + SQLSMALLINT cbConnStrIn, + SQLCHAR *szConnStrOut, + SQLSMALLINT cbConnStrOutMax, + SQLSMALLINT *pcbConnStrOut, + SQLUSMALLINT fDriverCompletion) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLDriverConnect"); + php_swoole_async(swoole_odbc_blocking, [&]() { + rc = SQLDriverConnect( + hdbc, hwnd, szConnStrIn, cbConnStrIn, szConnStrOut, cbConnStrOutMax, pcbConnStrOut, fDriverCompletion); + }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLExecDirect"); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLExecDirect(StatementHandle, StatementText, TextLength); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLGetInfo(SQLHDBC ConnectionHandle, + SQLUSMALLINT InfoType, + SQLPOINTER InfoValue, + SQLSMALLINT BufferLength, + SQLSMALLINT *StringLength) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLGetInfo"); + rc = SQLGetInfo(ConnectionHandle, InfoType, InfoValue, BufferLength, StringLength); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLGetDiagRec(SQLSMALLINT HandleType, + SQLHANDLE Handle, + SQLSMALLINT RecNumber, + SQLCHAR *Sqlstate, + SQLINTEGER *NativeError, + SQLCHAR *MessageText, + SQLSMALLINT BufferLength, + SQLSMALLINT *TextLength) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLGetInfo"); + rc = SQLGetDiagRec(HandleType, Handle, RecNumber, Sqlstate, NativeError, MessageText, BufferLength, TextLength); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLPrepare(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPrepare(StatementText=%s)", StatementText); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLPrepare(StatementHandle, StatementText, TextLength); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLExecute(SQLHSTMT StatementHandle) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLExecute"); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLExecute(StatementHandle); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLCloseCursor(SQLHSTMT StatementHandle) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLCloseCursor"); + rc = SQLCloseCursor(StatementHandle); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLPutData(SQLHSTMT StatementHandle, SQLPOINTER Data, SQLLEN StrLen_or_Ind) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPutData"); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLPutData(StatementHandle, Data, StrLen_or_Ind); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLGetData(SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, + SQLSMALLINT TargetType, + SQLPOINTER TargetValue, + SQLLEN BufferLength, + SQLLEN *StrLen_or_Ind) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLPutData"); + php_swoole_async(swoole_odbc_blocking, [&]() { + rc = SQLGetData(StatementHandle, ColumnNumber, TargetType, TargetValue, BufferLength, StrLen_or_Ind); + }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLMoreResults(SQLHSTMT hstmt) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLMoreResults"); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLMoreResults(hstmt); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLDescribeCol(SQLHSTMT StatementHandle, + SQLUSMALLINT ColumnNumber, + SQLCHAR *ColumnName, + SQLSMALLINT BufferLength, + SQLSMALLINT *NameLength, + SQLSMALLINT *DataType, + SQLULEN *ColumnSize, + SQLSMALLINT *DecimalDigits, + SQLSMALLINT *Nullable) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLMoreResults"); + php_swoole_async(swoole_odbc_blocking, [&]() { + rc = SQLDescribeCol(StatementHandle, + ColumnNumber, + ColumnName, + BufferLength, + NameLength, + DataType, + ColumnSize, + DecimalDigits, + Nullable); + }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLRowCount(SQLHSTMT StatementHandle, SQLLEN *RowCount) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLRowCount"); + rc = SQLRowCount(StatementHandle, RowCount); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLFreeHandle"); + rc = SQLFreeHandle(HandleType, Handle); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLEndTran(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT CompletionType) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLEndTran(CompletionType=%d)", CompletionType); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLEndTran(HandleType, Handle, CompletionType); }); + return rc; +} + +SQLRETURN SQL_API swoole_odbc_SQLDisconnect(SQLHDBC ConnectionHandle) { + RETCODE rc; + swoole_trace_log(SW_TRACE_CO_ODBC, "SQLDisconnect"); + php_swoole_async(swoole_odbc_blocking, [&]() { rc = SQLDisconnect(ConnectionHandle); }); + return rc; +} + +int php_swoole_odbc_minit(int module_id) { + if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("ODBC_ATTR_USE_CURSOR_LIBRARY")) == + nullptr) { +#ifdef SQL_ATTR_CONNECTION_POOLING + const char *pooling_val = NULL; +#endif + +#ifdef SQL_ATTR_CONNECTION_POOLING + /* ugh, we don't really like .ini stuff in PDO, but since ODBC connection + * pooling is process wide, we can't set it from within the scope of a + * request without affecting others, which goes against our isolated request + * policy. So, we use cfg_get_string here to check it this once. + * */ + if (FAILURE == cfg_get_string("pdo_odbc.connection_pooling", (char **) &pooling_val) || pooling_val == NULL) { + pooling_val = "strict"; + } + if (strcasecmp(pooling_val, "strict") == 0 || strcmp(pooling_val, "1") == 0) { + pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; + pdo_odbc_pool_mode = SQL_CP_STRICT_MATCH; + } else if (strcasecmp(pooling_val, "relaxed") == 0) { + pdo_odbc_pool_on = SQL_CP_ONE_PER_HENV; + pdo_odbc_pool_mode = SQL_CP_RELAXED_MATCH; + } else if (*pooling_val == '\0' || strcasecmp(pooling_val, "off") == 0) { + pdo_odbc_pool_on = SQL_CP_OFF; + } else { + php_error_docref(NULL, + E_CORE_ERROR, + "Error in pdo_odbc.connection_pooling configuration. Value must be one of \"strict\", " + "\"relaxed\", or \"off\""); + return FAILURE; + } + + if (pdo_odbc_pool_on != SQL_CP_OFF) { + SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void *) pdo_odbc_pool_on, 0); + } +#endif + +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); +#else + REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); + REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); +#endif + } + + php_pdo_unregister_driver(&swoole_pdo_odbc_driver); + php_pdo_register_driver(&swoole_pdo_odbc_driver); + + return SUCCESS; +} + +void php_swoole_odbc_mshutdown(void) { + php_pdo_unregister_driver(&swoole_pdo_odbc_driver); +} + +#endif diff --git a/ext-src/swoole_oracle.cc b/ext-src/swoole_oracle.cc new file mode 100644 index 0000000000..e9d665422f --- /dev/null +++ b/ext-src/swoole_oracle.cc @@ -0,0 +1,161 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ +#include "php_swoole_private.h" +#include "php_swoole_cxx.h" +#include "swoole_coroutine.h" +#include "php_swoole_oracle.h" + +#ifdef SW_USE_ORACLE + +static bool swoole_oracle_blocking = true; + +void swoole_oracle_set_blocking(bool blocking) { + swoole_oracle_blocking = blocking; +} + +sword swoole_oci_session_begin(OCISvcCtx *svchp, OCIError *errhp, OCISession *usrhp, ub4 credt, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_session_begin"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCISessionBegin(svchp, errhp, usrhp, credt, mode); }); + + return result; +} + +sword swoole_oci_server_detach(OCIServer *srvhp, OCIError *errhp, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_server_detach"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIServerDetach(srvhp, errhp, mode); }); + + return result; +} + +sword swoole_oci_stmt_prepare( + OCIStmt *stmtp, OCIError *errhp, const OraText *stmt, ub4 stmt_len, ub4 language, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_prepare"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, + [&]() { result = OCIStmtPrepare(stmtp, errhp, stmt, stmt_len, language, mode); }); + + return result; +} + +sword swoole_oci_stmt_execute(OCISvcCtx *svchp, + OCIStmt *stmtp, + OCIError *errhp, + ub4 iters, + ub4 rowoff, + const OCISnapshot *snap_in, + OCISnapshot *snap_out, + ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_execute"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, + [&]() { result = OCIStmtExecute(svchp, stmtp, errhp, iters, rowoff, snap_in, snap_out, mode); }); + + return result; +} + +sword swoole_oci_stmt_fetch(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_fetch"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIStmtFetch(stmtp, errhp, nrows, orientation, mode); }); + + return result; +} + +sword swoole_oci_stmt_fetch2(OCIStmt *stmtp, OCIError *errhp, ub4 nrows, ub2 orientation, sb4 scrollOffset, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_stmt_fetch2"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, + [&]() { result = OCIStmtFetch2(stmtp, errhp, nrows, orientation, scrollOffset, mode); }); + + return result; +} + +sword swoole_oci_trans_commit(OCISvcCtx *svchp, OCIError *errhp, ub4 flags) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_trans_commit"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCITransCommit(svchp, errhp, flags); }); + + return result; +} + +sword swoole_oci_trans_rollback(OCISvcCtx *svchp, OCIError *errhp, ub4 flags) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_trans_rollback"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCITransRollback(svchp, errhp, flags); }); + + return result; +} + +sword swoole_oci_ping(OCISvcCtx *svchp, OCIError *errhp, ub4 mode) { + swoole_trace_log(SW_TRACE_CO_ORACLE, "oci_ping"); + sword result = 0; + php_swoole_async(swoole_oracle_blocking, [&]() { result = OCIPing(svchp, errhp, mode); }); + + return result; +} + +const ub4 SWOOLE_PDO_OCI_INIT_MODE = OCI_DEFAULT | OCI_THREADED +#ifdef OCI_OBJECT + | OCI_OBJECT +#endif + ; + +OCIEnv *swoole_pdo_oci_Env = NULL; + +void php_swoole_oracle_rinit() { + if (!swoole_pdo_oci_Env) { +#ifdef HAVE_OCIENVCREATE + OCIEnvCreate(&swoole_pdo_oci_Env, SWOOLE_PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL, 0, NULL); +#else + OCIInitialize(SWOOLE_PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL); + OCIEnvInit(&swoole_pdo_oci_Env, OCI_DEFAULT, 0, NULL); +#endif + } +} + +void php_swoole_oracle_minit(int module_id) { + if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("OCI_ATTR_ACTION")) == nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); +#else + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); +#endif + } + + php_pdo_unregister_driver(&swoole_pdo_oci_driver); + php_pdo_register_driver(&swoole_pdo_oci_driver); +} + +void php_swoole_oracle_mshutdown(void) { + php_pdo_unregister_driver(&swoole_pdo_oci_driver); + + if (!swoole_pdo_oci_Env) { + OCIHandleFree((dvoid *) swoole_pdo_oci_Env, OCI_HTYPE_ENV); + } +} +#endif diff --git a/ext-src/swoole_pgsql.cc b/ext-src/swoole_pgsql.cc new file mode 100644 index 0000000000..298c236130 --- /dev/null +++ b/ext-src/swoole_pgsql.cc @@ -0,0 +1,266 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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_pgsql.h" +#include "php_swoole_private.h" +#include "php_swoole_coroutine.h" + +#include "swoole_socket_impl.h" + +#ifdef SW_USE_PGSQL + +using swoole::Coroutine; +using swoole::EventType; +using swoole::Reactor; +using swoole::translate_events_to_poll; + +static bool swoole_pgsql_blocking = true; +/** + * In macOS, under special circumstances, the poll function will not return a readable or writable signal. It is + * necessary to set a relatively short poll timeout value to allow it to fail quickly. Then, re-execute PQconnectPoll, + * and let the libpq underlying code determine whether the connection is ready or if an error has occurred. + */ +static const double swoole_pgsql_poll_timeout = 0.1; + +void swoole_libpq_version(char *buf, size_t len) +{ + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int swoole_pgsql_socket_poll(PGconn *conn, EventType event, bool check_nonblock = false) { + if (swoole_pgsql_blocking) { + struct pollfd fds[1]; + fds[0].fd = PQsocket(conn); + fds[0].events |= translate_events_to_poll(event); + + int result = 0; + do { + result = poll(fds, 1, swoole_pgsql_poll_timeout * 1000); + } while (result < 0 && errno == EINTR); + + return result; + } + + SocketImpl sock(PQsocket(conn), SW_SOCK_RAW); + sock.get_socket()->nonblock = 1; + + bool retval = sock.poll(event, swoole_pgsql_poll_timeout); + while (check_nonblock && event == SW_EVENT_READ) { + if (PQconsumeInput(conn) == 0) { + retval = false; + break; + } + if (PQisBusy(conn) == 0) { + break; + } + retval = sock.poll(event, swoole_pgsql_poll_timeout); + } + + sock.move_fd(); + return retval ? 1 : sock.errCode == ETIMEDOUT ? 0 : -1; +} + +static int swoole_pgsql_flush(PGconn *conn) { + int flush_ret; + + do { + int ret = swoole_pgsql_socket_poll(conn, SW_EVENT_WRITE); + if (sw_unlikely(ret < 0)) { + return -1; + } + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQflush(conn=%p)", conn); + flush_ret = PQflush(conn); + } while (flush_ret == 1); + + return flush_ret; +} + +static PGresult *swoole_pgsql_get_result(PGconn *conn) { + PGresult *result, *last_result = nullptr; + // PQgetResult will block the process; it is necessary to forcibly check if the data is ready. + int poll_ret = swoole_pgsql_socket_poll(conn, SW_EVENT_READ, true); + if (sw_unlikely(poll_ret < 0)) { + return nullptr; + } + + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQgetResult(conn=%p)", conn); + while ((result = PQgetResult(conn))) { + PQclear(last_result); + last_result = result; + } + + return last_result; +} + +PGconn *swoole_pgsql_connectdb(const char *conninfo) { + PGconn *conn = PQconnectStart(conninfo); + if (conn == nullptr) { + return nullptr; + } + + int fd = PQsocket(conn); + if (sw_unlikely(fd < 0)) { + return conn; + } + + if (!swoole_pgsql_blocking && Coroutine::get_current()) { + PQsetnonblocking(conn, 1); + } else { + PQsetnonblocking(conn, 0); + } + + SW_LOOP { + int r = PQconnectPoll(conn); + if (r == PGRES_POLLING_OK || r == PGRES_POLLING_FAILED) { + break; + } + EventType event; + + switch (r) { + case PGRES_POLLING_READING: + event = SW_EVENT_READ; + break; + case PGRES_POLLING_WRITING: + event = SW_EVENT_WRITE; + break; + default: + // should not be here including PGRES_POLLING_ACTIVE + abort(); + break; + } + + if (swoole_pgsql_socket_poll(conn, event) < 0) { + break; + } + } + + return conn; +} + +PGresult *swoole_pgsql_prepare( + PGconn *conn, const char *stmt_name, const char *query, int n_params, const Oid *param_types) { + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendPrepare(conn=%p, stmt_name='%s')", conn, stmt_name); + int ret = PQsendPrepare(conn, stmt_name, query, n_params, param_types); + if (ret == 0) { + return nullptr; + } + + if (swoole_pgsql_flush(conn) == -1) { + return nullptr; + } + + return swoole_pgsql_get_result(conn); +} + +PGresult *swoole_pgsql_exec_prepared(PGconn *conn, + const char *stmt_name, + int n_params, + const char *const *param_values, + const int *param_lengths, + const int *param_formats, + int result_format) { + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQueryPrepared(conn=%p, stmt_name='%s')", conn, stmt_name); + int ret = PQsendQueryPrepared(conn, stmt_name, n_params, param_values, param_lengths, param_formats, result_format); + if (ret == 0) { + return nullptr; + } + + if (swoole_pgsql_flush(conn) == -1) { + return nullptr; + } + + return swoole_pgsql_get_result(conn); +} + +PGresult *swoole_pgsql_exec(PGconn *conn, const char *query) { + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQuery(conn=%p, query='%s')", conn, query); + int ret = PQsendQuery(conn, query); + if (ret == 0) { + return nullptr; + } + + if (swoole_pgsql_flush(conn) == -1) { + return nullptr; + } + + return swoole_pgsql_get_result(conn); +} + +PGresult *swoole_pgsql_exec_params(PGconn *conn, + const char *command, + int n_params, + const Oid *param_types, + const char *const *param_values, + const int *param_lengths, + const int *param_formats, + int result_format) { + swoole_trace_log(SW_TRACE_CO_PGSQL, "PQsendQueryParams(conn=%p, command='%s')", conn, command); + int ret = PQsendQueryParams( + conn, command, n_params, param_types, param_values, param_lengths, param_formats, result_format); + if (ret == 0) { + return nullptr; + } + + if (swoole_pgsql_flush(conn) == -1) { + return nullptr; + } + + return swoole_pgsql_get_result(conn); +} + +void swoole_pgsql_set_blocking(bool blocking) { + swoole_pgsql_blocking = blocking; +} + +void php_swoole_pgsql_minit(int module_id) { + if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("PGSQL_ATTR_DISABLE_PREPARES")) == + nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); +#else + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); +#endif + } + php_pdo_unregister_driver(&swoole_pdo_pgsql_driver); + php_pdo_register_driver(&swoole_pdo_pgsql_driver); +} + +void php_swoole_pgsql_mshutdown(void) { + php_pdo_unregister_driver(&swoole_pdo_pgsql_driver); +} + +#endif diff --git a/ext-src/swoole_postgresql_coro.cc b/ext-src/swoole_postgresql_coro.cc deleted file mode 100644 index 062e977e7d..0000000000 --- a/ext-src/swoole_postgresql_coro.cc +++ /dev/null @@ -1,1660 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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: Zhenyu Wu <936321732@qq.com> | - | Tianfeng Han | - +----------------------------------------------------------------------+ - */ - -#include "php_swoole_cxx.h" -#include "swoole_reactor.h" -#include "swoole_socket.h" - -#ifdef SW_USE_PGSQL - -#include - -BEGIN_EXTERN_C() -#include "stubs/php_swoole_postgresql_coro_arginfo.h" -END_EXTERN_C() - -namespace swoole { -namespace postgresql { - -enum QueryType { NORMAL_QUERY, META_DATA, PREPARE }; - -class Statement; - -class Object { - public: - PGconn *conn; - network::Socket *socket; - Coroutine *co; - PGresult *result; - zval *return_value; - zval *object; - zval _object; - ConnStatusType status; - Statement *statement; - std::list statements; - enum QueryType request_type; - int row; - bool connected; - bool ignore_notices; - bool log_notices; - size_t stmt_counter; - - bool yield(zval *_return_value, EventType event, double timeout); - bool wait_write_ready(); -}; - -class Statement { - public: - zval *object; - zval _object; - Object *pg_object; - PGresult *result; - char* name; - char* query; -}; -} // namespace postgresql -} // namespace swoole - -#define PGSQL_ASSOC 1 << 0 -#define PGSQL_NUM 1 << 1 -#define PGSQL_BOTH (PGSQL_ASSOC | PGSQL_NUM) - -/* from postgresql/src/include/catalog/pg_type.h */ -#define BOOLOID 16 -#define BYTEAOID 17 -#define INT2OID 21 -#define INT4OID 23 -#define INT8OID 20 -#define TEXTOID 25 -#define OIDOID 26 -#define FLOAT4OID 700 -#define FLOAT8OID 701 - -// extension part - -using swoole::Coroutine; -using swoole::Event; -using swoole::Reactor; -using swoole::coroutine::System; -using swoole::network::Socket; -using PGObject = swoole::postgresql::Object; -using PGStatement = swoole::postgresql::Statement; -using PGQueryType = swoole::postgresql::QueryType; - -static zend_class_entry *swoole_postgresql_coro_ce, *swoole_postgresql_coro_statement_ce; -static zend_object_handlers swoole_postgresql_coro_handlers, swoole_postgresql_coro_statement_handlers; - -struct PostgreSQLObject { - PGObject *object; - zend_object std; -}; - -static sw_inline PostgreSQLObject *php_swoole_postgresql_coro_fetch_object(zend_object *obj) { - return (PostgreSQLObject *) ((char *) obj - swoole_postgresql_coro_handlers.offset); -} - -static sw_inline PGObject *php_swoole_postgresql_coro_get_object(zval *zobject) { - return php_swoole_postgresql_coro_fetch_object(Z_OBJ_P(zobject))->object; -} - -static sw_inline zend_object *php_swoole_postgresql_coro_get_zend_object(PostgreSQLObject *obj) { - return (zend_object *) ((char *) obj + swoole_postgresql_coro_handlers.offset); -} - -struct PostgreSQLStatementObject { - PGStatement *object; - zend_object std; -}; - -static sw_inline PostgreSQLStatementObject *php_swoole_postgresql_coro_statement_fetch_object(zend_object *obj) { - return (PostgreSQLStatementObject *) ((char *) obj - swoole_postgresql_coro_statement_handlers.offset); -} - -static sw_inline PGStatement *php_swoole_postgresql_coro_statement_get_object(zval *zobject) { - return php_swoole_postgresql_coro_statement_fetch_object(Z_OBJ_P(zobject))->object; -} - -static int swoole_postgresql_coro_close(zval *zobject); - -static void php_swoole_postgresql_coro_free_object(zend_object *object) { - PostgreSQLObject *postgresql_coro = php_swoole_postgresql_coro_fetch_object(object); - if (postgresql_coro->object->conn) { - zval zobject; - ZVAL_OBJ(&zobject, object); - swoole_postgresql_coro_close(&zobject); - } - delete postgresql_coro->object; - zend_object_std_dtor(&postgresql_coro->std); -} - -static zend_object *php_swoole_postgresql_coro_create_object(zend_class_entry *ce) { - PostgreSQLObject *postgresql_coro = (PostgreSQLObject *) zend_object_alloc(sizeof(*postgresql_coro), ce); - zend_object_std_init(&postgresql_coro->std, ce); - object_properties_init(&postgresql_coro->std, ce); - postgresql_coro->std.handlers = &swoole_postgresql_coro_handlers; - - Coroutine::get_current_safe(); - - do { - postgresql_coro->object = new PGObject(); - PGObject *object = postgresql_coro->object; - object->object = &object->_object; - ZVAL_OBJ(object->object, &postgresql_coro->std); - } while (0); - - return &postgresql_coro->std; -} - -static void php_swoole_postgresql_coro_statement_dtor_object(zend_object *object) { - PGresult *pgsql_result; - PostgreSQLStatementObject *postgresql_coro_statement = php_swoole_postgresql_coro_statement_fetch_object(object); - PGStatement *statement = postgresql_coro_statement->object; - if (statement->result) { - PQclear(statement->result); - statement->result = nullptr; - } - - if (swoole_coroutine_is_in() && statement->pg_object->conn && statement->pg_object->connected && statement->name) { - while ((pgsql_result = PQgetResult(statement->pg_object->conn))) { - PQclear(pgsql_result); - } - - statement->pg_object->request_type = PGQueryType::NORMAL_QUERY; - if (0 == PQsendQuery(statement->pg_object->conn, swoole::std_string::format("DEALLOCATE %s", statement->name).c_str())) { - char *err_msg = PQerrorMessage(statement->pg_object->conn); - swoole_warning("error:[%s]", err_msg); - } - zval zv; - if (statement->pg_object->wait_write_ready() && statement->pg_object->yield(&zv, SW_EVENT_READ, Socket::default_read_timeout) && statement->pg_object->result) { - PQclear(statement->pg_object->result); - statement->pg_object->result = nullptr; - } - } -} - -static void php_swoole_postgresql_coro_statement_free_object(zend_object *object) { - PostgreSQLStatementObject *postgresql_coro_statement = php_swoole_postgresql_coro_statement_fetch_object(object); - PGStatement *statement = postgresql_coro_statement->object; - - if (statement->name) { - efree(statement->name); - statement->name = nullptr; - } - if (statement->query) { - efree(statement->query); - statement->query = nullptr; - } - statement->pg_object->statements.remove(statement); - OBJ_RELEASE(SW_Z8_OBJ_P(statement->pg_object->object)); - delete statement; - zend_object_std_dtor(&postgresql_coro_statement->std); -} - -static zend_object *php_swoole_postgresql_coro_statement_create_object(zend_class_entry *ce) { - php_swoole_fatal_error(E_ERROR, "you must create postgresql statement object by prepare method"); - return nullptr; -} - -static zend_object *php_swoole_postgresql_coro_statement_create_object(PGObject *pg_object) { - PostgreSQLStatementObject *postgresql_coro_statement = (PostgreSQLStatementObject *) zend_object_alloc(sizeof(*postgresql_coro_statement), swoole_postgresql_coro_statement_ce); - zend_object_std_init(&postgresql_coro_statement->std, swoole_postgresql_coro_statement_ce); - object_properties_init(&postgresql_coro_statement->std, swoole_postgresql_coro_statement_ce); - postgresql_coro_statement->std.handlers = &swoole_postgresql_coro_statement_handlers; - - Coroutine::get_current_safe(); - - do { - postgresql_coro_statement->object = new PGStatement(); - PGStatement *object = postgresql_coro_statement->object; - object->pg_object = pg_object; - object->object = &object->_object; - ZVAL_OBJ(object->object, &postgresql_coro_statement->std); - pg_object->statements.push_back(object); - } while (0); - - GC_ADDREF(SW_Z8_OBJ_P(pg_object->object)); - return &postgresql_coro_statement->std; -} - -static zend_object *php_swoole_postgresql_coro_statement_create_object(PGObject *pg_object, - const char* query) { - zend_object *zobject = php_swoole_postgresql_coro_statement_create_object(pg_object); - PGStatement *stmt = php_swoole_postgresql_coro_statement_fetch_object(zobject)->object; - stmt->query = estrdup(query); - stmt->result = stmt->pg_object->result; - return zobject; -} - -static zend_object *php_swoole_postgresql_coro_statement_create_object(PGObject *pg_object, - const char* stmtname, - const char* query) { - zend_object *zobject = php_swoole_postgresql_coro_statement_create_object(pg_object); - PGStatement *stmt = php_swoole_postgresql_coro_statement_fetch_object(zobject)->object; - stmt->name = estrdup(stmtname); - stmt->query = estrdup(query); - return zobject; -} - -static PHP_METHOD(swoole_postgresql_coro, __construct); -static PHP_METHOD(swoole_postgresql_coro, __destruct); -static PHP_METHOD(swoole_postgresql_coro, connect); -static PHP_METHOD(swoole_postgresql_coro, escape); -static PHP_METHOD(swoole_postgresql_coro, escapeLiteral); -static PHP_METHOD(swoole_postgresql_coro, escapeIdentifier); -static PHP_METHOD(swoole_postgresql_coro, query); -static PHP_METHOD(swoole_postgresql_coro, prepare); -static PHP_METHOD(swoole_postgresql_coro, metaData); - -static PHP_METHOD(swoole_postgresql_coro_statement, execute); -static PHP_METHOD(swoole_postgresql_coro_statement, fetchAll); -static PHP_METHOD(swoole_postgresql_coro_statement, affectedRows); -static PHP_METHOD(swoole_postgresql_coro_statement, numRows); -static PHP_METHOD(swoole_postgresql_coro_statement, fieldCount); -static PHP_METHOD(swoole_postgresql_coro_statement, fetchObject); -static PHP_METHOD(swoole_postgresql_coro_statement, fetchAssoc); -static PHP_METHOD(swoole_postgresql_coro_statement, fetchArray); -static PHP_METHOD(swoole_postgresql_coro_statement, fetchRow); - -static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_type, int into_object); - -static int swoole_pgsql_coro_onReadable(Reactor *reactor, Event *event); -static int swoole_pgsql_coro_onWritable(Reactor *reactor, Event *event); -static int swoole_pgsql_coro_onError(Reactor *reactor, Event *event); -static int swoole_postgresql_coro_close(zval *zobject); -static int query_result_parse(PGObject *object); -static int prepare_result_parse(PGObject *object); -static int meta_data_result_parse(PGObject *object); -static void _php_pgsql_free_params(char **params, int num_params); - -void swoole_pgsql_result2array(PGresult *pg_result, zval *ret_array, long result_type); - -// clang-format off -static const zend_function_entry swoole_postgresql_coro_methods[] = -{ - PHP_ME(swoole_postgresql_coro, __construct, arginfo_class_Swoole_Coroutine_PostgreSQL___construct, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, connect, arginfo_class_Swoole_Coroutine_PostgreSQL_connect, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, query, arginfo_class_Swoole_Coroutine_PostgreSQL_query, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, prepare, arginfo_class_Swoole_Coroutine_PostgreSQL_prepare, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, metaData, arginfo_class_Swoole_Coroutine_PostgreSQL_metaData, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, escape, arginfo_class_Swoole_Coroutine_PostgreSQL_escape, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, escapeLiteral, arginfo_class_Swoole_Coroutine_PostgreSQL_escapeLiteral, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, escapeIdentifier, arginfo_class_Swoole_Coroutine_PostgreSQL_escapeIdentifier, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro, __destruct, arginfo_class_Swoole_Coroutine_PostgreSQL___destruct, ZEND_ACC_PUBLIC) - PHP_FE_END -}; -// clang-format on - -// clang-format off -static const zend_function_entry swoole_postgresql_coro_statement_methods[] = -{ - PHP_ME(swoole_postgresql_coro_statement, execute, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_execute, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fetchAll, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fetchAll, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, affectedRows, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_affectedRows, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, numRows, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_numRows, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fieldCount, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fieldCount, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fetchObject, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fetchObject, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fetchAssoc, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fetchAssoc, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fetchArray, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fetchArray, ZEND_ACC_PUBLIC) - PHP_ME(swoole_postgresql_coro_statement, fetchRow, arginfo_class_Swoole_Coroutine_PostgreSQLStatement_fetchRow, ZEND_ACC_PUBLIC) - PHP_FE_END -}; -// clang-format on - -void php_swoole_postgresql_coro_minit(int module_number) { - SW_INIT_CLASS_ENTRY( - swoole_postgresql_coro, "Swoole\\Coroutine\\PostgreSQL", "Co\\PostgreSQL", swoole_postgresql_coro_methods); -#ifdef SW_SET_CLASS_NOT_SERIALIZABLE - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_postgresql_coro); -#else - SW_SET_CLASS_SERIALIZABLE(swoole_postgresql_coro, zend_class_serialize_deny, zend_class_unserialize_deny); -#endif - SW_SET_CLASS_CLONEABLE(swoole_postgresql_coro, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_postgresql_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_postgresql_coro, - php_swoole_postgresql_coro_create_object, - php_swoole_postgresql_coro_free_object, - PostgreSQLObject, - std); - - zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("error"), ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_postgresql_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_postgresql_coro_ce, ZEND_STRL("resultStatus"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("resultDiag"), ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("notices"), ZEND_ACC_PUBLIC); - - SW_INIT_CLASS_ENTRY( - swoole_postgresql_coro_statement, "Swoole\\Coroutine\\PostgreSQLStatement", nullptr, swoole_postgresql_coro_statement_methods); -#ifdef SW_SET_CLASS_NOT_SERIALIZABLE - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_postgresql_coro_statement); -#else - SW_SET_CLASS_SERIALIZABLE(swoole_postgresql_coro_statement, zend_class_serialize_deny, zend_class_unserialize_deny); -#endif - SW_SET_CLASS_CLONEABLE(swoole_postgresql_coro_statement, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_postgresql_coro_statement, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_postgresql_coro_statement, - php_swoole_postgresql_coro_statement_create_object, - php_swoole_postgresql_coro_statement_free_object, - PostgreSQLStatementObject, - std); - SW_SET_CLASS_DTOR(swoole_postgresql_coro_statement, php_swoole_postgresql_coro_statement_dtor_object); - - zend_declare_property_null(swoole_postgresql_coro_statement_ce, ZEND_STRL("error"), ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_postgresql_coro_statement_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_postgresql_coro_statement_ce, ZEND_STRL("resultStatus"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_postgresql_coro_statement_ce, ZEND_STRL("resultDiag"), ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_postgresql_coro_statement_ce, ZEND_STRL("notices"), ZEND_ACC_PUBLIC); - - SW_REGISTER_LONG_CONSTANT("SW_PGSQL_ASSOC", PGSQL_ASSOC); - SW_REGISTER_LONG_CONSTANT("SW_PGSQL_NUM", PGSQL_NUM); - SW_REGISTER_LONG_CONSTANT("SW_PGSQL_BOTH", PGSQL_BOTH); -} - -static char *_php_pgsql_trim_message(const char *message, size_t *len) { - size_t i = strlen(message); - if (i > 2 && (message[i - 2] == '\r' || message[i - 2] == '\n') && message[i - 1] == '.') { - --i; - } - while (i > 1 && (message[i - 1] == '\r' || message[i - 1] == '\n')) { - --i; - } - if (len) { - *len = i; - } - return estrndup(message, i); -} - -static void _php_pgsql_notice_handler(void *resource_id, const char *message) { - zval *notices; - char *trimed_message; - size_t trimed_message_len; - PGObject *object = (PGObject *) resource_id; - - if (!object->ignore_notices) { - notices = sw_zend_read_and_convert_property_array( - swoole_postgresql_coro_ce, &object->_object, ZEND_STRL("notices"), 0); - - trimed_message = _php_pgsql_trim_message(message, &trimed_message_len); - if (object->log_notices) { - php_error_docref(nullptr, E_NOTICE, "%s", trimed_message); - } - add_next_index_stringl(notices, trimed_message, trimed_message_len); - efree(trimed_message); - } -} - -static PHP_METHOD(swoole_postgresql_coro, __construct) {} - -static PHP_METHOD(swoole_postgresql_coro, connect) { - zval *conninfo; - double timeout = Socket::default_connect_timeout; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_ZVAL(conninfo) - Z_PARAM_OPTIONAL - Z_PARAM_DOUBLE(timeout) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (object->conn) { - RETURN_FALSE; - } - - zend::String dsn(conninfo); - char *p = dsn.val(); - for (size_t i = 0; i < dsn.len(); i++) { - if (*p == ';') { - *p = ' '; - } - p++; - } - - PGconn *pgsql = PQconnectStart(dsn.val()); - if (!pgsql) { - RETURN_FALSE; - } - - int fd = PQsocket(pgsql); - if (sw_unlikely(fd < 0)) { - RETURN_FALSE; - } - - php_swoole_check_reactor(); - - if (!swoole_event_isset_handler(PHP_SWOOLE_FD_POSTGRESQL)) { - swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_READ, swoole_pgsql_coro_onReadable); - swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_WRITE, swoole_pgsql_coro_onWritable); - swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_ERROR, swoole_pgsql_coro_onError); - } - - object->socket = swoole::make_socket(fd, (enum swFdType) PHP_SWOOLE_FD_POSTGRESQL); - object->socket->object = object; - object->conn = pgsql; - object->status = CONNECTION_STARTED; - object->connected = false; - - ON_SCOPE_EXIT { - if (!object->connected) { - object->conn = nullptr; - } - }; - - PQsetnonblocking(pgsql, 1); - PQsetNoticeProcessor(pgsql, _php_pgsql_notice_handler, object); - - if (pgsql == nullptr || PQstatus(pgsql) == CONNECTION_BAD) { - swoole_warning("Unable to connect to PostgreSQL server: [%s]", PQhost(pgsql)); - if (pgsql) { - PQfinish(pgsql); - } - RETURN_FALSE; - } - - if (!object->yield(return_value, SW_EVENT_WRITE, timeout)) { - const char *feedback; - - switch (PQstatus(pgsql)) { - case CONNECTION_STARTED: - feedback = "connection time out...please make sure your host,dbname,user and password is correct "; - break; - case CONNECTION_MADE: - feedback = "Connected to server.."; - break; - default: - feedback = " time out.."; - break; - } - - char *err_msg = PQerrorMessage(object->conn); - if (pgsql == nullptr || PQstatus(pgsql) == CONNECTION_STARTED) { - swoole_warning(" [%s, %s] ", feedback, err_msg); - } else if (PQstatus(pgsql) == CONNECTION_MADE) { - PQfinish(pgsql); - } - zend_update_property_string(swoole_postgresql_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("error"), - swoole_strerror(swoole_get_last_error())); - RETURN_FALSE; - } - - ZVAL_BOOL(return_value, object->connected); -} - -static void connect_callback(PGObject *object, Reactor *reactor, Event *event) { - PGconn *conn = object->conn; - ConnStatusType status = PQstatus(conn); - int events = 0; - char *err_msg; - - swoole_event_del(object->socket); - - if (status != CONNECTION_OK) { - PostgresPollingStatusType flag = PQconnectPoll(conn); - switch (flag) { - case PGRES_POLLING_READING: - events = SW_EVENT_READ; - break; - case PGRES_POLLING_WRITING: - events = SW_EVENT_WRITE; - break; - case PGRES_POLLING_OK: - object->connected = true; - events = 0; - break; - case PGRES_POLLING_FAILED: - events = 0; - err_msg = PQerrorMessage(conn); - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); - if (object->statement) { - zend_update_property_string( - swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error"), err_msg); - } - break; - default: - swoole_warning("PQconnectPoll unexpected status"); - break; - } - - if (events) { - event->socket->fd = PQsocket(conn); - swoole_event_add(event->socket, events); - return; - } - } - - if (object->connected == 1) { - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); - if (object->statement) { - zend_update_property_null(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); - } - } - object->co->resume(); -} - -static int swoole_pgsql_coro_onWritable(Reactor *reactor, Event *event) { - PGObject *object = (PGObject *) event->socket->object; - - if (!object->connected) { - connect_callback(object, reactor, event); - return SW_OK; - } - - if (object->co) { - object->co->resume(); - return SW_OK; - } else { - return reactor->default_write_handler(reactor, event); - } -} - -static int swoole_pgsql_coro_onReadable(Reactor *reactor, Event *event) { - PGObject *object = (PGObject *) (event->socket->object); - - if (!object->connected) { - connect_callback(object, reactor, event); - return SW_OK; - } - - switch (object->request_type) { - case PGQueryType::NORMAL_QUERY: - query_result_parse(object); - break; - case PGQueryType::META_DATA: - meta_data_result_parse(object); - break; - case PGQueryType::PREPARE: - prepare_result_parse(object); - break; - } - - return SW_OK; -} - -static int meta_data_result_parse(PGObject *object) { - int i, num_rows; - zval elem; - PGresult *pg_result; - zend_bool extended = 0; - pg_result = PQgetResult(object->conn); - - if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) { - php_swoole_fatal_error(E_WARNING, "Table doesn't exists"); - return 0; - } - - array_init(object->return_value); - - object->result = pg_result; - for (i = 0; i < num_rows; i++) { - char *name; - array_init(&elem); - /* pg_attribute.attnum */ - add_assoc_long_ex(&elem, "num", sizeof("num") - 1, atoi(PQgetvalue(pg_result, i, 1))); - /* pg_type.typname */ - add_assoc_string_ex(&elem, "type", sizeof("type") - 1, PQgetvalue(pg_result, i, 2)); - /* pg_attribute.attlen */ - add_assoc_long_ex(&elem, "len", sizeof("len") - 1, atoi(PQgetvalue(pg_result, i, 3))); - /* pg_attribute.attnonull */ - add_assoc_bool_ex(&elem, "not null", sizeof("not null") - 1, !strcmp(PQgetvalue(pg_result, i, 4), "t")); - /* pg_attribute.atthasdef */ - add_assoc_bool_ex(&elem, "has default", sizeof("has default") - 1, !strcmp(PQgetvalue(pg_result, i, 5), "t")); - /* pg_attribute.attndims */ - add_assoc_long_ex(&elem, "array dims", sizeof("array dims") - 1, atoi(PQgetvalue(pg_result, i, 6))); - /* pg_type.typtype */ - add_assoc_bool_ex(&elem, "is enum", sizeof("is enum") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "e")); - if (extended) { - /* pg_type.typtype */ - add_assoc_bool_ex(&elem, "is base", sizeof("is base") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "b")); - add_assoc_bool_ex( - &elem, "is composite", sizeof("is composite") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "c")); - add_assoc_bool_ex(&elem, "is pesudo", sizeof("is pesudo") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "p")); - /* pg_description.description */ - add_assoc_string_ex(&elem, "description", sizeof("description") - 1, PQgetvalue(pg_result, i, 8)); - } - /* pg_attribute.attname */ - name = PQgetvalue(pg_result, i, 0); - add_assoc_zval(object->return_value, name, &elem); - } - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); - if (object->statement) { - zend_update_property_null(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error")); - zend_update_property_null(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("resultDiag")); - } - object->co->resume(); - return SW_OK; -} - -static void set_error_diag(const PGObject *object, const PGresult *pgsql_result) { - const unsigned int error_codes[] = {PG_DIAG_SEVERITY, - PG_DIAG_SQLSTATE, - PG_DIAG_MESSAGE_PRIMARY, - PG_DIAG_MESSAGE_DETAIL, - PG_DIAG_MESSAGE_HINT, - PG_DIAG_STATEMENT_POSITION, - PG_DIAG_INTERNAL_POSITION, - PG_DIAG_INTERNAL_QUERY, - PG_DIAG_CONTEXT, - PG_DIAG_SCHEMA_NAME, - PG_DIAG_TABLE_NAME, - PG_DIAG_COLUMN_NAME, - PG_DIAG_DATATYPE_NAME, - PG_DIAG_CONSTRAINT_NAME, - PG_DIAG_SOURCE_FILE, - PG_DIAG_SOURCE_LINE, - PG_DIAG_SOURCE_FUNCTION}; - - const char *error_names[] = {"severity", - "sqlstate", - "message_primary", - "message_detail", - "message_hint", - "statement_position", - "internal_position", - "internal_query", - "content", - "schema_name", - "table_name", - "column_name", - "datatype_name", - "constraint_name", - "source_file", - "source_line", - "source_function"}; - - long unsigned int i; - char *error_result; - - zval result_diag; - array_init_size(&result_diag, sizeof(error_codes) / sizeof(int)); - - for (i = 0; i < sizeof(error_codes) / sizeof(int); i++) { - error_result = PQresultErrorField(pgsql_result, error_codes[i]); - - if (error_result != nullptr) { - add_assoc_string(&result_diag, error_names[i], error_result); - } else { - add_assoc_null(&result_diag, error_names[i]); - } - } - - zend_update_property(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag"), &result_diag); - zval_dtor(&result_diag); -} - -static int query_result_parse(PGObject *object) { - PGresult *pgsql_result; - ExecStatusType status; - - int error = 0; - char *err_msg; - int res; - - pgsql_result = PQgetResult(object->conn); - status = PQresultStatus(pgsql_result); - - zend_update_property_long( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultStatus"), status); - if (object->statement) { - zend_update_property_long( - swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("resultStatus"), status); - } - - switch (status) { - case PGRES_EMPTY_QUERY: - case PGRES_BAD_RESPONSE: - case PGRES_NONFATAL_ERROR: - case PGRES_FATAL_ERROR: - err_msg = PQerrorMessage(object->conn); - set_error_diag(object, pgsql_result); - PQclear(pgsql_result); - ZVAL_FALSE(object->return_value); - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); - if (object->statement) { - zend_update_property_string( - swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error"), err_msg); - } - object->co->resume(); - break; - case PGRES_COMMAND_OK: /* successful command that did not return rows */ - default: - object->result = pgsql_result; - object->row = 0; - /* Wait to finish sending buffer */ - res = PQflush(object->conn); - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); - if (object->statement) { - zend_update_property_null( - swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error")); - zend_update_property_null( - swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("resultDiag")); - } - object->co->resume(); - if (error != 0) { - php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); - } - break; - } - (void) res; - - return SW_OK; -} - -static int prepare_result_parse(PGObject *object) { - int error = 0; - char *err_msg; - int res; - - PGresult *pgsql_result = PQgetResult(object->conn); - ExecStatusType status = PQresultStatus(pgsql_result); - - zend_update_property_long( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultStatus"), status); - if (object->statement) { - zend_update_property_long(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("resultStatus"), status); - } - - switch (status) { - case PGRES_EMPTY_QUERY: - case PGRES_BAD_RESPONSE: - case PGRES_NONFATAL_ERROR: - case PGRES_FATAL_ERROR: - err_msg = PQerrorMessage(object->conn); - set_error_diag(object, pgsql_result); - PQclear(pgsql_result); - ZVAL_FALSE(object->return_value); - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); - if (object->statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error"), err_msg); - } - object->co->resume(); - if (error != 0) { - php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); - } - break; - case PGRES_COMMAND_OK: /* successful command that did not return rows */ - /* Wait to finish sending buffer */ - PQclear(pgsql_result); - ZVAL_TRUE(object->return_value); - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); - zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); - if (object->statement) { - zend_update_property_null(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error")); - zend_update_property_null(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("resultDiag")); - } - object->co->resume(); - if (error != 0) { - php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); - } - break; - default: - PQclear(pgsql_result); - ZVAL_FALSE(object->return_value); - zend_update_property_string(swoole_postgresql_coro_ce, - SW_Z8_OBJ_P(object->object), - ZEND_STRL("error"), - "Bad result returned to prepare"); - if (object->statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, - SW_Z8_OBJ_P(object->statement->object), - ZEND_STRL("error"), - "Bad result returned to prepare"); - } - object->co->resume(); - if (error != 0) { - php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); - } - break; - } - (void) res; - - return SW_OK; -} - -bool PGObject::wait_write_ready() { - int retval = 0; - while ((retval = PQflush(conn)) == 1) { - zval return_value; - if (!yield(&return_value, SW_EVENT_WRITE, Socket::default_write_timeout)) { - return false; - } - } - - if (retval == -1) { - char *err_msg = PQerrorMessage(conn); - zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object), ZEND_STRL("error"), err_msg); - if (statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(statement->object), ZEND_STRL("error"), err_msg); - } - return false; - } - - return true; -} - -bool PGObject::yield(zval *_return_value, EventType event, double timeout) { - co = swoole::Coroutine::get_current_safe(); - if (swoole_event_add(socket, event) < 0) { - php_swoole_fatal_error(E_WARNING, "swoole_event_add failed"); - RETVAL_FALSE; - return false; - } - - ON_SCOPE_EXIT { - co = nullptr; - if (!socket->removed && swoole_event_del(socket) < 0) { - php_swoole_fatal_error(E_WARNING, "swoole_event_del failed"); - } - }; - - return_value = _return_value; - - if (!co->yield_ex(timeout)) { - ZVAL_FALSE(_return_value); - - if (co->is_canceled()) { - zend_update_property_string(swoole_postgresql_coro_ce, - SW_Z8_OBJ_P(object), - ZEND_STRL("error"), - swoole_strerror(SW_ERROR_CO_CANCELED)); - if (statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, - SW_Z8_OBJ_P(statement->object), - ZEND_STRL("error"), - swoole_strerror(SW_ERROR_CO_CANCELED)); - } - } else if (co->is_timedout()) { - zend_update_property_string(swoole_postgresql_coro_ce, - SW_Z8_OBJ_P(object), - ZEND_STRL("error"), - swoole_strerror(SW_ERROR_CO_TIMEDOUT)); - if (statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, - SW_Z8_OBJ_P(statement->object), - ZEND_STRL("error"), - swoole_strerror(SW_ERROR_CO_TIMEDOUT)); - } - } - - return false; - } - - return true; -} - -static PHP_METHOD(swoole_postgresql_coro, query) { - zval *zquery; - PGconn *pgsql; - PGresult *pgsql_result; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ZVAL(zquery) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - object->request_type = PGQueryType::NORMAL_QUERY; - pgsql = object->conn; - - while ((pgsql_result = PQgetResult(pgsql))) { - PQclear(pgsql_result); - } - - zend::String query = zquery; - if (PQsendQuery(pgsql, query.val()) == 0) { - char *err_msg = PQerrorMessage(pgsql); - zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), err_msg); - RETURN_FALSE; - } - - if (!object->wait_write_ready()) { - RETURN_FALSE; - } - - if (object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout)) { - RETVAL_OBJ(php_swoole_postgresql_coro_statement_create_object(object, query.val())); - } -} - -static PHP_METHOD(swoole_postgresql_coro, prepare) { - zval *zquery; - PGconn *pgsql; - int is_non_blocking; - PGresult *pgsql_result; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ZVAL(zquery) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - object->request_type = PGQueryType::PREPARE; - pgsql = object->conn; - - is_non_blocking = PQisnonblocking(pgsql); - - if (is_non_blocking == 0 && PQsetnonblocking(pgsql, 1) == -1) { - php_swoole_fatal_error(E_NOTICE, "Cannot set connection to nonblocking mode"); - RETURN_FALSE; - } - - while ((pgsql_result = PQgetResult(pgsql))) { - PQclear(pgsql_result); - } - - std::string stmtname = swoole::std_string::format("swoole_stmt_%ld", ++object->stmt_counter); - zend::String query = zquery; - if (!PQsendPrepare(pgsql, stmtname.c_str(), query.val(), 0, nullptr)) { - if (is_non_blocking) { - RETURN_FALSE; - } else { - /*if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { - PQreset(pgsql); - }*/ - if (!PQsendPrepare(pgsql, stmtname.c_str(), query.val(), 0, nullptr)) { - RETURN_FALSE; - } - } - } - - if (!object->wait_write_ready()) { - RETURN_FALSE; - } - - if (object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout)) { - RETVAL_OBJ(php_swoole_postgresql_coro_statement_create_object(object, stmtname.c_str(), query.val())); - } -} - -static PHP_METHOD(swoole_postgresql_coro_statement, execute) { - zval *pv_param_arr = nullptr, *tmp; - int num_params = 0; - char **params = nullptr; - PGconn *pgsql; - int is_non_blocking; - PGresult *pgsql_result; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(pv_param_arr) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGStatement *statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - PGObject *object = statement->pg_object; - if (!object || !object->conn) { - RETURN_FALSE; - } - object->statement = statement; - ON_SCOPE_EXIT { - object->statement = nullptr; - }; - object->request_type = PGQueryType::NORMAL_QUERY; - pgsql = object->conn; - - is_non_blocking = PQisnonblocking(pgsql); - - if (is_non_blocking == 0 && PQsetnonblocking(pgsql, 1) == -1) { - php_swoole_fatal_error(E_NOTICE, "Cannot set connection to nonblocking mode"); - RETURN_FALSE; - } - - while ((pgsql_result = PQgetResult(pgsql))) { - PQclear(pgsql_result); - } - - num_params = pv_param_arr ? zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)) : 0; - if (num_params > 0) { - int i = 0; - params = (char **) safe_emalloc(sizeof(char *), num_params, 0); - - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { - if (Z_TYPE_P(tmp) == IS_NULL) { - params[i] = nullptr; - } else { - zval tmp_val; - ZVAL_COPY(&tmp_val, tmp); - convert_to_string(&tmp_val); - if (Z_TYPE(tmp_val) != IS_STRING) { - php_swoole_fatal_error(E_WARNING, "Error converting parameter"); - zval_ptr_dtor(&tmp_val); - _php_pgsql_free_params(params, num_params); - RETURN_FALSE; - } - params[i] = estrndup(Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); - zval_ptr_dtor(&tmp_val); - } - i++; - } - ZEND_HASH_FOREACH_END(); - } - - if (PQsendQueryPrepared(pgsql, statement->name, num_params, (const char *const *) params, nullptr, nullptr, 0)) { - _php_pgsql_free_params(params, num_params); - } else if (is_non_blocking) { - _php_pgsql_free_params(params, num_params); - RETURN_FALSE; - } else { - /* - if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { - PQreset(pgsql); - } - */ - if (!PQsendQueryPrepared( - pgsql, statement->name, num_params, (const char *const *) params, nullptr, nullptr, 0)) { - _php_pgsql_free_params(params, num_params); - RETURN_FALSE; - } - } - if (!object->wait_write_ready()) { - RETURN_FALSE; - } - if (object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout)) { - statement->result = object->result; - RETURN_TRUE; - } -} - -static PHP_METHOD(swoole_postgresql_coro_statement, fetchAll) { - zend_long result_type = PGSQL_ASSOC; - - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_LONG(result_type) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGStatement *statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - if (!statement->result) { - RETURN_FALSE; - } - - array_init(return_value); - swoole_pgsql_result2array(statement->result, return_value, result_type); -} - -static PHP_METHOD(swoole_postgresql_coro_statement, affectedRows) { - PGStatement *statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - if (!statement->result) { - RETURN_FALSE; - } - - RETVAL_LONG(atoi(PQcmdTuples(statement->result))); -} - -// query's num -static PHP_METHOD(swoole_postgresql_coro_statement, numRows) { - PGStatement *statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - if (!statement->result) { - RETURN_FALSE; - } - - RETVAL_LONG(PQntuples(statement->result)); -} - -// query's field count -static PHP_METHOD(swoole_postgresql_coro_statement, fieldCount) { - PGStatement *statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - if (!statement->result) { - RETURN_FALSE; - } - - RETVAL_LONG(PQnfields(statement->result)); -} - -/* {{{ proto array fetchRow([, int row [, int result_type]]) - Get a row as an enumerated array */ -static PHP_METHOD(swoole_postgresql_coro_statement, fetchRow) { - php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_NUM, 0); -} -/* }}} */ - -/* {{{ proto array fetchAssoc([, int row]) - Fetch a row as an assoc array */ -static PHP_METHOD(swoole_postgresql_coro_statement, fetchAssoc) { - /* pg_fetch_assoc() is added from PHP 4.3.0. It should raise error, when - there is 3rd parameter */ - if (ZEND_NUM_ARGS() > 2) WRONG_PARAM_COUNT; - php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 0); -} -/* }}} */ - -/* {{{ proto array fetchArray([, int row [, int result_type]]) - Fetch a row as an array */ -static PHP_METHOD(swoole_postgresql_coro_statement, fetchArray) { - php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_BOTH, 0); -} -/* }}} */ - -/* {{{ proto object fetchObject([, int row [, string class_name [, NULL|array ctor_params]]]) - Fetch a row as an object */ -static PHP_METHOD(swoole_postgresql_coro_statement, fetchObject) { - /* fetchObject() allowed result_type used to be. 3rd parameter - must be allowed for compatibility */ - php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 1); -} - -static void _php_pgsql_free_params(char **params, int num_params) { - if (num_params > 0) { - for (int i = 0; i < num_params; i++) { - if (params[i]) { - efree(params[i]); - } - } - efree(params); - } -} - -/* {{{ void php_pgsql_get_field_value */ -static inline void php_pgsql_get_field_value( - zval *value, PGresult *pgsql_result, zend_long result_type, int row, int column) { - if (PQgetisnull(pgsql_result, row, column)) { - ZVAL_NULL(value); - } else { - char *element = PQgetvalue(pgsql_result, row, column); - if (element) { - const size_t element_len = PQgetlength(pgsql_result, row, column); - Oid pgsql_type = PQftype(pgsql_result, column); - - switch (pgsql_type) { - case BOOLOID: - ZVAL_BOOL(value, *element == 't'); - break; - case FLOAT4OID: - case FLOAT8OID: - if (element_len == sizeof("Infinity") - 1 && strcmp(element, "Infinity") == 0) { - ZVAL_DOUBLE(value, ZEND_INFINITY); - } else if (element_len == sizeof("-Infinity") - 1 && strcmp(element, "-Infinity") == 0) { - ZVAL_DOUBLE(value, -ZEND_INFINITY); - } else if (element_len == sizeof("NaN") - 1 && strcmp(element, "NaN") == 0) { - ZVAL_DOUBLE(value, ZEND_NAN); - } else { - ZVAL_DOUBLE(value, zend_strtod(element, nullptr)); - } - break; - case OIDOID: - case INT2OID: - case INT4OID: -#if SIZEOF_ZEND_LONG >= 8 - case INT8OID: -#endif - { - zend_long long_value; -#if PHP_VERSION_ID < 80100 - ZEND_ATOL(long_value, element); -#else - long_value = ZEND_ATOL(element); -#endif - ZVAL_LONG(value, long_value); - break; - } - case BYTEAOID: { - size_t tmp_len; - char *tmp_ptr = (char *) PQunescapeBytea((unsigned char *) element, &tmp_len); - if (!tmp_ptr) { - /* PQunescapeBytea returned an error */ - ZVAL_NULL(value); - } else { - ZVAL_STRINGL(value, tmp_ptr, tmp_len); - PQfreemem(tmp_ptr); - } - break; - } - default: - ZVAL_STRINGL(value, element, element_len); - } - } else { - ZVAL_NULL(value); - } - } -} -/* }}} */ - -/* {{{ swoole_pgsql_result2array - */ -void swoole_pgsql_result2array(PGresult *pg_result, zval *ret_array, long result_type) { - zval row; - const char *field_name; - size_t num_fields, unknown_columns; - int pg_numrows, pg_row; - uint32_t i; - assert(Z_TYPE_P(ret_array) == IS_ARRAY); - - pg_numrows = PQntuples(pg_result); - for (pg_row = 0; pg_row < pg_numrows; pg_row++) { - array_init(&row); - unknown_columns = 0; - for (i = 0, num_fields = PQnfields(pg_result); i < num_fields; i++) { - if (result_type & PGSQL_ASSOC) { - zval value; - php_pgsql_get_field_value(&value, pg_result, result_type, pg_row, i); - field_name = PQfname(pg_result, i); - if (0 == strcmp("?column?", field_name)) { - if (unknown_columns > 0) { - field_name = (std::string(field_name) + std::to_string(unknown_columns)).c_str(); - } - ++unknown_columns; - } - add_assoc_zval(&row, field_name, &value); - } - if (result_type & PGSQL_NUM) { - zval value; - php_pgsql_get_field_value(&value, pg_result, result_type, pg_row, i); - add_next_index_zval(&row, &value); - } - } - add_index_zval(ret_array, pg_row, &row); - } -} -/* }}} */ - -static PHP_METHOD(swoole_postgresql_coro, metaData) { - char *table_name; - size_t table_name_len; - zend_bool extended = 0; - PGconn *pgsql; - - PGresult *pg_result; - char *src, *tmp_name, *tmp_name2 = nullptr; - char *escaped; - smart_str querystr = {0}; - size_t new_len; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STRING(table_name, table_name_len) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - object->request_type = PGQueryType::META_DATA; - pgsql = object->conn; - - while ((pg_result = PQgetResult(pgsql))) { - PQclear(pg_result); - } - - if (table_name_len == 0) { - php_swoole_fatal_error(E_WARNING, "The table name must be specified"); - RETURN_FALSE; - } - - src = estrdup(table_name); - tmp_name = php_strtok_r(src, ".", &tmp_name2); - if (!tmp_name) { - efree(src); - php_swoole_fatal_error(E_WARNING, "The table name must be specified"); - RETURN_FALSE; - } - if (!tmp_name2 || !*tmp_name2) { - /* Default schema */ - tmp_name2 = tmp_name; - tmp_name = (char *) "public"; - } - - if (extended) { - smart_str_appends( - &querystr, - "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotNULL, a.atthasdef, a.attndims, t.typtype, " - "d.description " - "FROM pg_class as c " - " JOIN pg_attribute a ON (a.attrelid = c.oid) " - " JOIN pg_type t ON (a.atttypid = t.oid) " - " JOIN pg_namespace n ON (c.relnamespace = n.oid) " - " LEFT JOIN pg_description d ON (d.objoid=a.attrelid AND d.objsubid=a.attnum AND c.oid=d.objoid) " - "WHERE a.attnum > 0 AND c.relname = '"); - } else { - smart_str_appends( - &querystr, - "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotnull, a.atthasdef, a.attndims, t.typtype " - "FROM pg_class as c " - " JOIN pg_attribute a ON (a.attrelid = c.oid) " - " JOIN pg_type t ON (a.atttypid = t.oid) " - " JOIN pg_namespace n ON (c.relnamespace = n.oid) " - "WHERE a.attnum > 0 AND c.relname = '"); - } - escaped = (char *) safe_emalloc(strlen(tmp_name2), 2, 1); - new_len = PQescapeStringConn(pgsql, escaped, tmp_name2, strlen(tmp_name2), nullptr); - if (new_len) { - smart_str_appendl(&querystr, escaped, new_len); - } - efree(escaped); - - smart_str_appends(&querystr, "' AND n.nspname = '"); - escaped = (char *) safe_emalloc(strlen(tmp_name), 2, 1); - new_len = PQescapeStringConn(pgsql, escaped, tmp_name, strlen(tmp_name), nullptr); - if (new_len) { - smart_str_appendl(&querystr, escaped, new_len); - } - efree(escaped); - - smart_str_appends(&querystr, "' ORDER BY a.attnum;"); - smart_str_0(&querystr); - efree(src); - - int ret = PQsendQuery(pgsql, ZSTR_VAL(querystr.s)); - if (ret == 0) { - char *err_msg = PQerrorMessage(pgsql); - swoole_warning("error:[%s]", err_msg); - } - smart_str_free(&querystr); - object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout); -} - -/* {{{ void php_pgsql_fetch_hash */ -static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_type, int into_object) { - zval *zrow = nullptr; - PGresult *pgsql_result; - PGObject *pg_result; - PGStatement *statement; - int i, num_fields, pgsql_row, use_row; - zend_long row = -1; - char *field_name; - zval *ctor_params = nullptr; - zend_class_entry *ce = nullptr; - - if (into_object) { - zend_string *class_name = nullptr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z!Sz", &zrow, &class_name, &ctor_params) == FAILURE) { - RETURN_FALSE; - } - if (!class_name) { - ce = zend_standard_class_def; - } else { - ce = zend_fetch_class(class_name, ZEND_FETCH_CLASS_AUTO); - } - if (!ce) { - php_swoole_fatal_error(E_WARNING, "Could not find class '%s'", ZSTR_VAL(class_name)); - return; - } - result_type = PGSQL_ASSOC; - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z!l", &zrow, &result_type) == FAILURE) { - RETURN_FALSE; - } - } - if (zrow == nullptr) { - row = -1; - } else { - row = zval_get_long(zrow); - if (row < 0) { - php_swoole_fatal_error(E_WARNING, "The row parameter must be greater or equal to zero"); - RETURN_FALSE; - } - } - use_row = ZEND_NUM_ARGS() > 1 && row != -1; - - if (!(result_type & PGSQL_BOTH)) { - php_swoole_fatal_error(E_WARNING, "Invalid result type"); - RETURN_FALSE; - } - - statement = php_swoole_postgresql_coro_statement_get_object(ZEND_THIS); - if (!statement || !statement->result) { - RETURN_FALSE; - } - pgsql_result = statement->result; - pg_result = statement->pg_object; - if (!pg_result || !pg_result->conn) { - RETURN_FALSE; - } - - if (use_row) { - if (row < 0 || row >= PQntuples(pgsql_result)) { - php_swoole_fatal_error(E_WARNING, - "Unable to jump to row " ZEND_LONG_FMT " on PostgreSQL result", - row); - RETURN_FALSE; - } - pgsql_row = (int) row; - pg_result->row = pgsql_row; - } else { - /* If 2nd param is nullptr, use internal row counter to access next row */ - pgsql_row = pg_result->row; - if (pgsql_row < 0 || pgsql_row >= PQntuples(pgsql_result)) { - RETURN_FALSE; - } - pg_result->row++; - } - - array_init(return_value); - for (i = 0, num_fields = PQnfields(pgsql_result); i < num_fields; i++) { - if (result_type & PGSQL_NUM) { - zval value; - php_pgsql_get_field_value(&value, pgsql_result, result_type, pgsql_row, i); - add_index_zval(return_value, i, &value); - } - - if (result_type & PGSQL_ASSOC) { - zval value; - php_pgsql_get_field_value(&value, pgsql_result, result_type, pgsql_row, i); - field_name = PQfname(pgsql_result, i); - add_assoc_zval(return_value, field_name, &value); - } - } - - if (into_object) { - zval dataset; - zend_fcall_info fci; - zend_fcall_info_cache fcc; - zval retval; - - ZVAL_COPY_VALUE(&dataset, return_value); - object_and_properties_init(return_value, ce, nullptr); - if (!ce->default_properties_count && !ce->__set) { - Z_OBJ_P(return_value)->properties = Z_ARR(dataset); - } else { - zend_merge_properties(return_value, Z_ARRVAL(dataset)); - zval_ptr_dtor(&dataset); - } - - if (ce->constructor) { - fci.size = sizeof(fci); - ZVAL_UNDEF(&fci.function_name); - fci.object = Z_OBJ_P(return_value); - fci.retval = &retval; - fci.params = nullptr; - fci.param_count = 0; - - if (ctor_params && Z_TYPE_P(ctor_params) != IS_NULL) { - if (zend_fcall_info_args(&fci, ctor_params) == FAILURE) { - /* Two problems why we throw exceptions here: PHP is typeless - * and hence passing one argument that's not an array could be - * by mistake and the other way round is possible, too. The - * single value is an array. Also we'd have to make that one - * argument passed by reference. - */ - zend_throw_exception(zend_ce_exception, "Parameter ctor_params must be an array", 0); - return; - } - } - - fcc.function_handler = ce->constructor; - fcc.calling_scope = zend_get_executed_scope(); - fcc.called_scope = Z_OBJCE_P(return_value); - fcc.object = Z_OBJ_P(return_value); - - if (zend_call_function(&fci, &fcc) == FAILURE) { - zend_throw_exception_ex(zend_ce_exception, - 0, - "Could not execute %s::%s()", - ZSTR_VAL(ce->name), - ZSTR_VAL(ce->constructor->common.function_name)); - } else { - zval_ptr_dtor(&retval); - } - if (fci.params) { - efree(fci.params); - } - } else if (ctor_params) { - zend_throw_exception_ex(zend_ce_exception, - 0, - "Class %s does not have a constructor hence you cannot use ctor_params", - ZSTR_VAL(ce->name)); - } - } -} -/* }}} */ - -static int swoole_pgsql_coro_onError(Reactor *reactor, Event *event) { - PGObject *object = (PGObject *) (event->socket->object); - - zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), "onerror"); - if (object->statement) { - zend_update_property_string(swoole_postgresql_coro_statement_ce, SW_Z8_OBJ_P(object->statement->object), ZEND_STRL("error"), "onerror"); - object->statement = nullptr; - } - object->connected = false; - ZVAL_FALSE(object->return_value); - object->co->resume(); - - return SW_OK; -} - -static PHP_METHOD(swoole_postgresql_coro, __destruct) {} - -static int swoole_postgresql_coro_close(zval *zobject) { - PGObject *object = php_swoole_postgresql_coro_get_object(zobject); - if (!object || !object->conn) { - php_swoole_fatal_error(E_WARNING, "object is not instanceof swoole_postgresql_coro"); - return FAILURE; - } - - if (sw_reactor()) { - Socket *_socket = object->socket; - if (!_socket->removed) { - sw_reactor()->del(_socket); - } - _socket->object = nullptr; - _socket->free(); - } - - PGresult *res; - if (object->connected) { - while ((res = PQgetResult(object->conn))) { - PQclear(res); - } - /** - * PQfinish will close fd - */ - PQfinish(object->conn); - /** - * fd marked -1, prevent double close - */ - object->socket->fd = -1; - object->conn = nullptr; - object->connected = false; - } - object->co = nullptr; - return SUCCESS; -} - -static PHP_METHOD(swoole_postgresql_coro, escape) { - char *str; - size_t l_str; - PGconn *pgsql; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STRING(str, l_str) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - pgsql = object->conn; - - zend_string *result = zend_string_alloc(l_str * 2, 0); - int error = 0; - size_t new_len = PQescapeStringConn(object->conn, result->val, str, l_str, &error); - - if (new_len == 0 || error) { - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); - zend_update_property_long(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), error); - zend_string_free(result); - RETURN_FALSE; - } else { - result->val[new_len] = 0; - result->len = new_len; - RETURN_STR(result); - } -} - -static PHP_METHOD(swoole_postgresql_coro, escapeLiteral) { - char *str, *tmp; - size_t l_str; - PGconn *pgsql; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STRING(str, l_str) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - pgsql = object->conn; - - tmp = PQescapeLiteral(pgsql, str, l_str); - if (tmp == nullptr) { - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); - - RETURN_FALSE; - } - - RETVAL_STRING(tmp); - PQfreemem(tmp); -} - -static PHP_METHOD(swoole_postgresql_coro, escapeIdentifier) { - char *str, *tmp; - size_t l_str; - PGconn *pgsql; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_STRING(str, l_str) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); - if (!object || !object->conn) { - RETURN_FALSE; - } - pgsql = object->conn; - - tmp = PQescapeIdentifier(pgsql, str, l_str); - if (tmp == nullptr) { - zend_update_property_string( - swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); - - RETURN_FALSE; - } - - RETVAL_STRING(tmp); - PQfreemem(tmp); -} - -#endif diff --git a/ext-src/swoole_process.cc b/ext-src/swoole_process.cc index 1904960928..c65100c512 100644 --- a/ext-src/swoole_process.cc +++ b/ext-src/swoole_process.cc @@ -33,60 +33,69 @@ using namespace swoole; zend_class_entry *swoole_process_ce; static zend_object_handlers swoole_process_handlers; -static uint32_t php_swoole_worker_round_id = 0; -static zend_fcall_info_cache *signal_fci_caches[SW_SIGNO_MAX] = {}; +static uint32_t round_process_id = 0; +static thread_local uint32_t server_user_worker_id = 0; +static zend::Callable *signal_fci_caches[SW_SIGNO_MAX] = {}; struct ProcessObject { Worker *worker; + zend_object *zsocket; + PipeType pipe_type; + bool enable_coroutine; + bool blocking; zend_object std; }; static sw_inline ProcessObject *php_swoole_process_fetch_object(zend_object *obj) { - return (ProcessObject *) ((char *) obj - swoole_process_handlers.offset); + return reinterpret_cast(reinterpret_cast(obj) - swoole_process_handlers.offset); } -Worker *php_swoole_process_get_worker(zval *zobject) { - return php_swoole_process_fetch_object(Z_OBJ_P(zobject))->worker; +static sw_inline ProcessObject *php_swoole_process_fetch_object(const zval *zobj) { + return php_swoole_process_fetch_object(Z_OBJ_P(zobj)); } -Worker *php_swoole_process_get_and_check_worker(zval *zobject) { +Worker *php_swoole_process_get_worker(const zval *zobject) { + return php_swoole_process_fetch_object(zobject)->worker; +} + +Worker *php_swoole_process_get_and_check_worker(const zval *zobject) { Worker *worker = php_swoole_process_get_worker(zobject); - if (!worker) { - php_swoole_fatal_error(E_ERROR, "you must call Process constructor first"); + if (UNEXPECTED(!worker)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return worker; } -void php_swoole_process_set_worker(zval *zobject, Worker *worker) { - php_swoole_process_fetch_object(Z_OBJ_P(zobject))->worker = worker; +void php_swoole_process_set_worker(const zval *zobject, Worker *worker, bool enable_coroutine, int pipe_type) { + auto po = php_swoole_process_fetch_object(zobject); + po->worker = worker; + po->pipe_type = static_cast(pipe_type); + po->enable_coroutine = enable_coroutine; + po->blocking = true; } static void php_swoole_process_free_object(zend_object *object) { - ProcessObject *process = php_swoole_process_fetch_object(object); - Worker *worker = process->worker; + ProcessObject *po = php_swoole_process_fetch_object(object); + Worker *worker = po->worker; if (worker) { UnixSocket *_pipe = worker->pipe_object; - if (_pipe) { + if (_pipe && !worker->shared) { delete _pipe; } + delete worker->queue; + delete worker; + } - if (worker->queue) { - delete worker->queue; - } - - zend::Process *proc = (zend::Process *) worker->ptr2; - if (proc) { - delete proc; - } - efree(worker); + if (po->zsocket) { + OBJ_RELEASE(po->zsocket); } zend_object_std_dtor(object); } static zend_object *php_swoole_process_create_object(zend_class_entry *ce) { - ProcessObject *process = (ProcessObject *) zend_object_alloc(sizeof(ProcessObject), ce); + auto *process = static_cast(zend_object_alloc(sizeof(ProcessObject), ce)); zend_object_std_init(&process->std, ce); object_properties_init(&process->std, ce); process->std.handlers = &swoole_process_handlers; @@ -108,6 +117,7 @@ static PHP_METHOD(swoole_process, wait); static PHP_METHOD(swoole_process, daemon); #ifdef HAVE_CPU_AFFINITY static PHP_METHOD(swoole_process, setAffinity); +static PHP_METHOD(swoole_process, getAffinity); #endif static PHP_METHOD(swoole_process, set); static PHP_METHOD(swoole_process, setTimeout); @@ -139,6 +149,7 @@ static const zend_function_entry swoole_process_methods[] = PHP_ME(swoole_process, daemon, arginfo_class_Swoole_Process_daemon, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #ifdef HAVE_CPU_AFFINITY PHP_ME(swoole_process, setAffinity, arginfo_class_Swoole_Process_setAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_process, getAffinity, arginfo_class_Swoole_Process_getAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) #endif PHP_ME(swoole_process, setPriority, arginfo_class_Swoole_Process_setPriority, ZEND_ACC_PUBLIC) PHP_ME(swoole_process, getPriority, arginfo_class_Swoole_Process_getPriority, ZEND_ACC_PUBLIC) @@ -175,6 +186,9 @@ void php_swoole_process_minit(int module_number) { zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_WORKER"), SW_PIPE_CLOSE_WORKER); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_READ"), SW_PIPE_CLOSE_READ); zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_WRITE"), SW_PIPE_CLOSE_WRITE); + zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_NONE"), PIPE_TYPE_NONE); + zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_STREAM"), PIPE_TYPE_STREAM); + zend_declare_class_constant_long(swoole_process_ce, ZEND_STRL("PIPE_TYPE_DGRAM"), PIPE_TYPE_DGRAM); zend_declare_property_null(swoole_process_ce, ZEND_STRL("pipe"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_ce, ZEND_STRL("msgQueueId"), ZEND_ACC_PUBLIC); @@ -188,80 +202,87 @@ void php_swoole_process_minit(int module_number) { * 31 signal constants */ if (!zend_hash_str_find(&module_registry, ZEND_STRL("pcntl"))) { - REGISTER_LONG_CONSTANT("SIGHUP", (zend_long) SIGHUP, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGINT", (zend_long) SIGINT, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGQUIT", (zend_long) SIGQUIT, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGILL", (zend_long) SIGILL, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTRAP", (zend_long) SIGTRAP, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGABRT", (zend_long) SIGABRT, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGBUS", (zend_long) SIGBUS, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGFPE", (zend_long) SIGFPE, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGKILL", (zend_long) SIGKILL, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGUSR1", (zend_long) SIGUSR1, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGSEGV", (zend_long) SIGSEGV, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGUSR2", (zend_long) SIGUSR2, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGPIPE", (zend_long) SIGPIPE, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGALRM", (zend_long) SIGALRM, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTERM", (zend_long) SIGTERM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGHUP", SIGHUP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGINT", SIGINT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGQUIT", SIGQUIT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGILL", SIGILL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTRAP", SIGTRAP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGABRT", SIGABRT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGBUS", SIGBUS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGFPE", SIGFPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGKILL", SIGKILL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGUSR1", SIGUSR1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSEGV", SIGSEGV, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGUSR2", SIGUSR2, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPIPE", SIGPIPE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGALRM", SIGALRM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTERM", SIGTERM, CONST_CS | CONST_PERSISTENT); #ifdef SIGSTKFLT - REGISTER_LONG_CONSTANT("SIGSTKFLT", (zend_long) SIGSTKFLT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSTKFLT", SIGSTKFLT, CONST_CS | CONST_PERSISTENT); #endif - REGISTER_LONG_CONSTANT("SIGCHLD", (zend_long) SIGCHLD, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGCONT", (zend_long) SIGCONT, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGSTOP", (zend_long) SIGSTOP, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTSTP", (zend_long) SIGTSTP, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTTIN", (zend_long) SIGTTIN, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGTTOU", (zend_long) SIGTTOU, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGURG", (zend_long) SIGURG, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGXCPU", (zend_long) SIGXCPU, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGXFSZ", (zend_long) SIGXFSZ, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGVTALRM", (zend_long) SIGVTALRM, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGPROF", (zend_long) SIGPROF, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGWINCH", (zend_long) SIGWINCH, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("SIGIO", (zend_long) SIGIO, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCHLD", SIGCHLD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGCONT", SIGCONT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSTOP", SIGSTOP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTSTP", SIGTSTP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTTIN", SIGTTIN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGTTOU", SIGTTOU, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGURG", SIGURG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGXCPU", SIGXCPU, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGXFSZ", SIGXFSZ, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGVTALRM", SIGVTALRM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPROF", SIGPROF, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGWINCH", SIGWINCH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGIO", SIGIO, CONST_CS | CONST_PERSISTENT); #ifdef SIGPWR - REGISTER_LONG_CONSTANT("SIGPWR", (zend_long) SIGPWR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGPWR", SIGPWR, CONST_CS | CONST_PERSISTENT); #endif #ifdef SIGSYS - REGISTER_LONG_CONSTANT("SIGSYS", (zend_long) SIGSYS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SIGSYS", SIGSYS, CONST_CS | CONST_PERSISTENT); #endif REGISTER_LONG_CONSTANT("SIG_IGN", (zend_long) SIG_IGN, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PRIO_PROCESS", (zend_long) PRIO_PROCESS, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PRIO_PGRP", (zend_long) PRIO_PGRP, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("PRIO_USER", (zend_long) PRIO_USER, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PRIO_PROCESS", PRIO_PROCESS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PRIO_PGRP", PRIO_PGRP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("PRIO_USER", PRIO_USER, CONST_CS | CONST_PERSISTENT); } + + SW_REGISTER_LONG_CONSTANT("SWOOLE_MSGQUEUE_ORIENT", SW_MSGQUEUE_ORIENT); + SW_REGISTER_LONG_CONSTANT("SWOOLE_MSGQUEUE_BALANCE", SW_MSGQUEUE_BALANCE); } static PHP_METHOD(swoole_process, __construct) { - Worker *process = php_swoole_process_get_worker(ZEND_THIS); + auto po = php_swoole_process_fetch_object(ZEND_THIS); + Server *server = sw_server(); - if (process) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + if (po->worker) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } // only cli env if (!SWOOLE_G(cli)) { - zend_throw_error(NULL, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + zend_throw_error(nullptr, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } - if (sw_server() && sw_server()->is_started() && sw_server()->is_master()) { - zend_throw_error(NULL, "%s can't be used in master process", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + if (server && server->is_started() && server->is_master()) { + zend_throw_error(nullptr, "%s can't be used in master process", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } if (SwooleTG.async_threads) { - zend_throw_error(NULL, "unable to create %s with async-io threads", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + zend_throw_error(nullptr, "unable to create %s with async-io threads", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } zend::Function func; - zend_bool redirect_stdin_and_stdout = 0; - zend_long pipe_type = zend::PIPE_TYPE_DGRAM; + zend_bool redirect_stdin_and_stdout = false; + zend_long pipe_type = PIPE_TYPE_DGRAM; zend_bool enable_coroutine = false; + po->worker = new Worker(); + Worker *process = po->worker; + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_FUNC(func.fci, func.fci_cache); Z_PARAM_OPTIONAL @@ -270,59 +291,72 @@ static PHP_METHOD(swoole_process, __construct) { Z_PARAM_BOOL(enable_coroutine) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - process = (Worker *) ecalloc(1, sizeof(Worker)); - - uint32_t base = 1; - if (sw_server() && sw_server()->is_started()) { - base = sw_server()->worker_num + sw_server()->task_worker_num + sw_server()->get_user_worker_num(); - } - if (php_swoole_worker_round_id == 0) { - php_swoole_worker_round_id = base; - } - process->id = php_swoole_worker_round_id++; - - if (redirect_stdin_and_stdout) { - process->redirect_stdin = true; - process->redirect_stdout = true; - process->redirect_stderr = true; - /** - * Forced to use stream pipe - */ - pipe_type = zend::PIPE_TYPE_STREAM; - } + if (server && server->is_worker_thread()) { + Worker *shared_worker; + if (server->is_user_worker()) { + shared_worker = server->get_worker(swoole_get_worker_id()); + } else { + shared_worker = server->get_worker((server_user_worker_id++) + server->get_core_worker_num()); + } + *process = *shared_worker; + process->shared = true; + if (server->is_user_worker()) { + process->pipe_current = process->pipe_worker; + } else { + process->pipe_current = process->pipe_master; + } + } else { + if (redirect_stdin_and_stdout) { + process->redirect_stdin = true; + process->redirect_stdout = true; + process->redirect_stderr = true; + /** + * Forced to use stream pipe + */ + pipe_type = PIPE_TYPE_STREAM; + } - if (pipe_type > 0) { - int socket_type = pipe_type == zend::PIPE_TYPE_STREAM ? SOCK_STREAM : SOCK_DGRAM; - UnixSocket *_pipe = new UnixSocket(true, socket_type); - if (!_pipe->ready()) { - zend_throw_exception(swoole_exception_ce, "failed to create unix soccket", errno); - delete _pipe; - efree(process); - RETURN_FALSE; + uint32_t base = 1; + if (server && server->is_started()) { + base = server->get_all_worker_num(); + } + if (round_process_id == 0) { + round_process_id = base; } + process->id = round_process_id++; + process->shared = false; + + if (pipe_type > 0) { + int socket_type = pipe_type == PIPE_TYPE_STREAM ? SOCK_STREAM : SOCK_DGRAM; + auto *_pipe = new UnixSocket(true, socket_type); + if (!_pipe->ready()) { + zend_throw_exception(swoole_exception_ce, "failed to create unix soccket", errno); + delete _pipe; + efree(process); + RETURN_FALSE; + } - process->pipe_master = _pipe->get_socket(true); - process->pipe_worker = _pipe->get_socket(false); + process->pipe_master = _pipe->get_socket(true); + process->pipe_worker = _pipe->get_socket(false); - process->pipe_object = _pipe; - process->pipe_current = process->pipe_master; + process->pipe_object = _pipe; + process->pipe_current = process->pipe_master; - zend_update_property_long( - swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("pipe"), process->pipe_master->fd); + zend_update_property_long( + swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("pipe"), process->pipe_master->fd); + } } - zend::Process *proc = new zend::Process((enum zend::PipeType) pipe_type, enable_coroutine); - process->ptr2 = proc; - + zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("id"), process->id); zend_update_property( swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("callback"), ZEND_CALL_ARG(execute_data, 1)); - php_swoole_process_set_worker(ZEND_THIS, process); + php_swoole_process_set_worker(ZEND_THIS, process, enable_coroutine, pipe_type); } static PHP_METHOD(swoole_process, __destruct) {} static PHP_METHOD(swoole_process, wait) { - zend_bool blocking = 1; + zend_bool blocking = true; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &blocking) == FAILURE) { RETURN_FALSE; @@ -341,7 +375,7 @@ static PHP_METHOD(swoole_process, wait) { static PHP_METHOD(swoole_process, useQueue) { long msgkey = 0; - long mode = 2; + long mode = SW_MSGQUEUE_BALANCE; long capacity = -1; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lll", &msgkey, &mode, &capacity) == FAILURE) { @@ -354,7 +388,7 @@ static PHP_METHOD(swoole_process, useQueue) { msgkey = ftok(zend_get_executed_filename(), 1); } - MsgQueue *queue = new MsgQueue(msgkey); + auto *queue = new MsgQueue(msgkey); if (!queue->ready()) { delete queue; RETURN_FALSE; @@ -367,7 +401,7 @@ static PHP_METHOD(swoole_process, useQueue) { queue->set_capacity(capacity); } process->queue = queue; - process->ipc_mode = mode; + process->msgqueue_mode = mode; zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("msgQueueId"), queue->get_id()); zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("msgQueueKey"), msgkey); RETURN_TRUE; @@ -421,9 +455,10 @@ static PHP_METHOD(swoole_process, kill) { } static PHP_METHOD(swoole_process, signal) { + SW_MUST_BE_MAIN_THREAD(); zend_long signo = 0; zval *zcallback = nullptr; - zend_fcall_info_cache *fci_cache = nullptr; + zend::Callable *fci_cache = nullptr; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(signo) @@ -451,10 +486,14 @@ static PHP_METHOD(swoole_process, signal) { if (zcallback == nullptr) { fci_cache = signal_fci_caches[signo]; if (fci_cache) { +#ifdef SW_USE_THREAD_CONTEXT + swoole_event_defer([signo](void *) { swoole_signal_set(signo, nullptr); }, nullptr); +#else swoole_signal_set(signo, nullptr); +#endif signal_fci_caches[signo] = nullptr; - swoole_event_defer(sw_zend_fci_cache_free, fci_cache); - SwooleTG.signal_listener_num--; + swoole_event_defer(sw_callable_free, fci_cache); + SwooleG.signal_listener_num--; RETURN_TRUE; } else { php_swoole_error(E_WARNING, "unable to find the callback of signal [" ZEND_LONG_FMT "]", signo); @@ -463,27 +502,25 @@ static PHP_METHOD(swoole_process, signal) { } else if (Z_TYPE_P(zcallback) == IS_LONG && Z_LVAL_P(zcallback) == (zend_long) SIG_IGN) { handler = nullptr; } else { - char *func_name; - fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(zcallback, nullptr, 0, &func_name, 0, fci_cache, nullptr)) { - php_swoole_error(E_WARNING, "function '%s' is not callable", func_name); - efree(func_name); - efree(fci_cache); + fci_cache = sw_callable_create(zcallback); + if (!fci_cache) { RETURN_FALSE; } - efree(func_name); - sw_zend_fci_cache_persist(fci_cache); handler = php_swoole_onSignal; } if (sw_server() && sw_server()->is_sync_process()) { if (signal_fci_caches[signo]) { - sw_zend_fci_cache_free(signal_fci_caches[signo]); + sw_callable_free(signal_fci_caches[signo]); } else { - SwooleTG.signal_listener_num++; + SwooleG.signal_listener_num++; } signal_fci_caches[signo] = fci_cache; - swoole_signal_set(signo, handler); +#ifdef SW_USE_THREAD_CONTEXT + swoole_event_defer([signo, handler](void *) { swoole_signal_set(signo, handler, true); }, nullptr); +#else + swoole_signal_set(signo, handler, true); +#endif RETURN_TRUE; } @@ -491,19 +528,23 @@ static PHP_METHOD(swoole_process, signal) { if (!SwooleTG.reactor->isset_exit_condition(Reactor::EXIT_CONDITION_SIGNAL_LISTENER)) { SwooleTG.reactor->set_exit_condition(Reactor::EXIT_CONDITION_SIGNAL_LISTENER, [](Reactor *reactor, size_t &event_num) -> bool { - return SwooleTG.signal_listener_num == 0 or !SwooleG.wait_signal; + return SwooleG.signal_listener_num == 0 or !SwooleG.wait_signal; }); } if (signal_fci_caches[signo]) { // free the old fci_cache - swoole_event_defer(sw_zend_fci_cache_free, signal_fci_caches[signo]); + swoole_event_defer(sw_callable_free, signal_fci_caches[signo]); } else { - SwooleTG.signal_listener_num++; + SwooleG.signal_listener_num++; } signal_fci_caches[signo] = fci_cache; - swoole_signal_set(signo, handler); +#ifdef SW_USE_THREAD_CONTEXT + swoole_event_defer([signo, handler](void *) { swoole_signal_set(signo, handler, true); }, nullptr); +#else + swoole_signal_set(signo, handler, true); +#endif RETURN_TRUE; } @@ -556,13 +597,13 @@ static PHP_METHOD(swoole_process, alarm) { * safe signal */ static void php_swoole_onSignal(int signo) { - zend_fcall_info_cache *fci_cache = signal_fci_caches[signo]; + auto fci_cache = signal_fci_caches[signo]; if (fci_cache) { zval argv[1]; ZVAL_LONG(&argv[0], signo); - if (UNEXPECTED(!zend::function::call(fci_cache, 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 1, argv, nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_fatal_error( E_WARNING, "%s: signal [%d] handler error", ZSTR_VAL(swoole_process_ce->name), signo); } @@ -572,37 +613,37 @@ static void php_swoole_onSignal(int signo) { zend_bool php_swoole_signal_isset_handler(int signo) { if (signo < 0 || signo >= SW_SIGNO_MAX) { php_swoole_fatal_error(E_WARNING, "invalid signal number [%d]", signo); - return 0; + return false; } return signal_fci_caches[signo] != nullptr; } void php_swoole_process_clean() { - for (int i = 0; i < SW_SIGNO_MAX; i++) { - zend_fcall_info_cache *fci_cache = signal_fci_caches[i]; + for (auto &signal_fci_cache : signal_fci_caches) { + const auto fci_cache = signal_fci_cache; if (fci_cache) { - sw_zend_fci_cache_discard(fci_cache); - efree(fci_cache); - signal_fci_caches[i] = nullptr; + sw_callable_free(fci_cache); + signal_fci_cache = nullptr; } } - - if (SwooleG.process_type != SW_PROCESS_USERWORKER) { - SwooleG.process_type = 0; +#ifndef SW_THREAD + if (swoole_get_worker_type() != SW_USER_WORKER) { + swoole_set_worker_type(0); } +#endif +} + +void php_swoole_process_rshutdown() { + php_swoole_process_clean(); } int php_swoole_process_start(Worker *process, zval *zobject) { zval *zcallback = sw_zend_read_property_ex(swoole_process_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_CALLBACK), 0); - zend_fcall_info_cache fci_cache; - - if (!sw_zend_is_callable_ex(zcallback, nullptr, 0, nullptr, 0, &fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "Illegal callback function of %s", SW_Z_OBJCE_NAME_VAL_P(zobject)); + auto fci_cache = sw_callable_create(zcallback); + if (!fci_cache) { return SW_ERR; } - zend::Process *proc = (zend::Process *) process->ptr2; - process->pipe_current = process->pipe_worker; process->pid = getpid(); @@ -625,7 +666,8 @@ int php_swoole_process_start(Worker *process, zval *zobject) { } php_swoole_process_clean(); - SwooleG.process_id = process->id; + swoole_set_worker_id(process->id); + swoole_set_worker_pid(getpid()); SwooleWG.worker = process; zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("pid"), process->pid); @@ -633,18 +675,21 @@ int php_swoole_process_start(Worker *process, zval *zobject) { zend_update_property_long( swoole_process_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("pipe"), process->pipe_current->fd); } + auto po = php_swoole_process_fetch_object(zobject); // eventloop create - if (proc->enable_coroutine && php_swoole_reactor_init() < 0) { + if (po->enable_coroutine && php_swoole_reactor_init() < 0) { + sw_callable_free(fci_cache); return SW_ERR; } // main function - if (UNEXPECTED(!zend::function::call(&fci_cache, 1, zobject, nullptr, proc->enable_coroutine))) { + if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 1, zobject, nullptr, po->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } // eventloop start - if (proc->enable_coroutine) { + if (po->enable_coroutine) { php_swoole_event_wait(); } + sw_callable_free(fci_cache); // equivalent to exit zend_bailout(); @@ -676,31 +721,30 @@ static PHP_METHOD(swoole_process, start) { } static PHP_METHOD(swoole_process, read) { - long buf_size = 8192; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &buf_size) == FAILURE) { - RETURN_FALSE; - } + zend_long buf_size = 8192; - if (buf_size > 65536) { - buf_size = 65536; - } - - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(buf_size) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + const Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); if (process->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot read from pipe"); RETURN_FALSE; } - zend_string *buf = zend_string_alloc(buf_size, 0); - ssize_t ret = process->pipe_current->read(buf->val, buf_size); + ssize_t ret; + zend_string *buf = zend_string_alloc(buf_size, false); + const auto po = php_swoole_process_fetch_object(ZEND_THIS); + if (po->blocking) { + ret = process->pipe_current->read_sync(buf->val, buf_size); + } else { + ret = process->pipe_current->read(buf->val, buf_size); + } if (ret < 0) { efree(buf); - if (errno != EINTR) { - php_swoole_sys_error(E_WARNING, "read() failed"); - } RETURN_FALSE; } buf->val[ret] = 0; @@ -716,6 +760,10 @@ static PHP_METHOD(swoole_process, write) { RETURN_FALSE; } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(data, data_len) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + if (data_len < 1) { php_swoole_fatal_error(E_WARNING, "the data to send is empty"); RETURN_FALSE; @@ -728,17 +776,11 @@ static PHP_METHOD(swoole_process, write) { } ssize_t ret; - - // async write - if (SwooleTG.reactor) { - if (process->pipe_current->nonblock) { - ret = swoole_event_write(process->pipe_current, data, (size_t) data_len); - } else { - goto _blocking_read; - } + const auto po = php_swoole_process_fetch_object(ZEND_THIS); + if (!po->blocking && swoole_event_is_available()) { + ret = swoole_event_write(process->pipe_current, data, data_len); } else { - _blocking_read: - ret = process->pipe_current->send_blocking(data, data_len); + ret = process->pipe_current->send_sync(data, data_len); } if (ret < 0) { @@ -752,22 +794,20 @@ static PHP_METHOD(swoole_process, write) { * export Swoole\Coroutine\Socket object */ static PHP_METHOD(swoole_process, exportSocket) { - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); - if (process->pipe_current == nullptr) { + auto po = php_swoole_process_fetch_object(ZEND_THIS); + if (!po->worker || po->worker->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot export stream"); RETURN_FALSE; } - zend::Process *proc = (zend::Process *) process->ptr2; - if (!proc->zsocket) { - proc->zsocket = - php_swoole_dup_socket(process->pipe_current->fd, - proc->pipe_type == zend::PIPE_TYPE_STREAM ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM); - if (!proc->zsocket) { + if (!po->zsocket) { + po->zsocket = php_swoole_dup_socket( + po->worker->pipe_current->fd, po->pipe_type == PIPE_TYPE_STREAM ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM); + if (!po->zsocket) { RETURN_FALSE; } } - GC_ADDREF(proc->zsocket); - RETURN_OBJ(proc->zsocket); + GC_ADDREF(po->zsocket); + RETURN_OBJ(po->zsocket); } static PHP_METHOD(swoole_process, push) { @@ -779,9 +819,9 @@ static PHP_METHOD(swoole_process, push) { char data[SW_MSGMAX]; } message; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &length) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(data, length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (length <= 0) { php_swoole_fatal_error(E_WARNING, "the data to push is empty"); @@ -808,11 +848,12 @@ static PHP_METHOD(swoole_process, push) { } static PHP_METHOD(swoole_process, pop) { - long maxsize = SW_MSGMAX; + zend_long maxsize = SW_MSGMAX; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &maxsize) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(maxsize) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (maxsize > SW_MSGMAX || maxsize <= 0) { maxsize = SW_MSGMAX; @@ -829,7 +870,7 @@ static PHP_METHOD(swoole_process, pop) { char data[SW_MSGMAX]; } message; - if (process->ipc_mode == 2) { + if (process->msgqueue_mode == SW_MSGQUEUE_BALANCE) { message.type = 0; } else { message.type = process->id + 1; @@ -863,12 +904,13 @@ static PHP_METHOD(swoole_process, exec) { exec_args[0] = sw_strdup(execfile); int i = 1; - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(args), value) - convert_to_string(value); - Z_TRY_ADDREF_P(value); - exec_args[i] = Z_STRVAL_P(value); - i++; - SW_HASHTABLE_FOREACH_END(); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args), value) { + auto arg_str = zval_get_string(value); + exec_args[i] = ZSTR_VAL(arg_str); + i++; + } + ZEND_HASH_FOREACH_END(); + exec_args[i] = nullptr; if (execv(execfile, exec_args) < 0) { @@ -880,8 +922,8 @@ static PHP_METHOD(swoole_process, exec) { } static PHP_METHOD(swoole_process, daemon) { - zend_bool nochdir = 1; - zend_bool noclose = 1; + zend_bool nochdir = true; + zend_bool noclose = true; zval *zpipes = nullptr; ZEND_PARSE_PARAMETERS_START(0, 3) @@ -891,10 +933,9 @@ static PHP_METHOD(swoole_process, daemon) { Z_PARAM_ARRAY(zpipes) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - zval *elem; - int fd = 0; - if (zpipes) { + int fd = 0; + zval *elem; ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zpipes), elem) { if (!ZVAL_IS_NULL(elem)) { int new_fd = php_swoole_convert_to_fd(elem); @@ -915,37 +956,67 @@ static PHP_METHOD(swoole_process, daemon) { } #ifdef HAVE_CPU_AFFINITY -static PHP_METHOD(swoole_process, setAffinity) { - zval *array; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &array) == FAILURE) { - RETURN_FALSE; - } +bool php_swoole_array_to_cpu_set(const zval *array, cpu_set_t *cpu_set) { if (php_swoole_array_length(array) == 0) { - RETURN_FALSE; + return false; } + if (php_swoole_array_length(array) > SW_CPU_NUM) { php_swoole_fatal_error(E_WARNING, "More than the number of CPU"); - RETURN_FALSE; + return false; } zval *value = nullptr; - cpu_set_t cpu_set; - CPU_ZERO(&cpu_set); + CPU_ZERO(cpu_set); SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(array), value) if (zval_get_long(value) >= SW_CPU_NUM) { php_swoole_fatal_error(E_WARNING, "invalid cpu id [%d]", (int) Z_LVAL_P(value)); - RETURN_FALSE; + return false; } - CPU_SET(Z_LVAL_P(value), &cpu_set); + CPU_SET(Z_LVAL_P(value), cpu_set); SW_HASHTABLE_FOREACH_END(); + return true; +} + +void php_swoole_cpu_set_to_array(zval *array, cpu_set_t *cpu_set) { + array_init(array); + + int cpu_n = SW_CPU_NUM; + SW_LOOP_N(cpu_n) { + if (CPU_ISSET(i, cpu_set)) { + add_next_index_long(array, i); + } + } +} + +static PHP_METHOD(swoole_process, setAffinity) { + zval *array; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + + cpu_set_t cpu_set; + if (!php_swoole_array_to_cpu_set(array, &cpu_set)) { + RETURN_FALSE; + } + if (swoole_set_cpu_affinity(&cpu_set) < 0) { php_swoole_sys_error(E_WARNING, "sched_setaffinity() failed"); RETURN_FALSE; } RETURN_TRUE; } + +static PHP_METHOD(swoole_process, getAffinity) { + cpu_set_t cpu_set; + if (swoole_get_cpu_affinity(&cpu_set) < 0) { + php_swoole_sys_error(E_WARNING, "sched_getaffinity() failed"); + RETURN_FALSE; + } + php_swoole_cpu_set_to_array(return_value, &cpu_set); +} #endif static PHP_METHOD(swoole_process, exit) { @@ -967,7 +1038,11 @@ static PHP_METHOD(swoole_process, exit) { ret_code = 1; } - exit(ret_code); + if (swoole_event_is_available()) { + swoole_event_free(); + } + + exit(static_cast(ret_code)); } static PHP_METHOD(swoole_process, close) { @@ -1017,12 +1092,12 @@ static PHP_METHOD(swoole_process, set) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); vht = Z_ARRVAL_P(zset); - - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); - zend::Process *proc = (zend::Process *) process->ptr2; - + auto po = php_swoole_process_fetch_object(ZEND_THIS); + if (UNEXPECTED(!po->worker)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); + } if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { - proc->enable_coroutine = zval_is_true(ztmp); + po->enable_coroutine = zval_is_true(ztmp); } } @@ -1037,7 +1112,8 @@ static PHP_METHOD(swoole_process, setTimeout) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot setTimeout the pipe"); RETURN_FALSE; } - RETURN_BOOL(process->pipe_current->set_timeout(seconds)); + process->pipe_current->set_timeout(seconds); + RETURN_BOOL(process->pipe_current->set_kernel_timeout(seconds)); } static PHP_METHOD(swoole_process, setBlocking) { @@ -1046,35 +1122,68 @@ static PHP_METHOD(swoole_process, setBlocking) { RETURN_FALSE; } - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); - if (process->pipe_current == nullptr) { + auto po = php_swoole_process_fetch_object(ZEND_THIS); + if (po->worker == nullptr || po->worker->pipe_current == nullptr) { php_swoole_fatal_error(E_WARNING, "no pipe, cannot setBlocking the pipe"); RETURN_FALSE; } + po->blocking = blocking; if (blocking) { - process->pipe_current->set_block(); + RETURN_BOOL(po->worker->pipe_current->set_block()); } else { - process->pipe_current->set_nonblock(); + RETURN_BOOL(po->worker->pipe_current->set_nonblock()); } } +#define SW_CHECK_PRIORITY_WHO() \ + if (who_is_null) { \ + if (which == PRIO_PROCESS) { \ + Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); \ + who = process->pid; \ + } else { \ + php_swoole_fatal_error(E_WARNING, "$who parameter must not be null"); \ + swoole_set_last_error(SW_ERROR_INVALID_PARAMS); \ + RETURN_FALSE; \ + } \ + } + static PHP_METHOD(swoole_process, setPriority) { - zend_long which, priority; - ZEND_PARSE_PARAMETERS_START(2, 2) + zend_long which, priority, who; + bool who_is_null = true; + + ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_LONG(which) Z_PARAM_LONG(priority) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(who, who_is_null) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); - RETURN_BOOL(setpriority(which, process->pid, priority) == 0); + SW_CHECK_PRIORITY_WHO(); + if (setpriority(which, who, priority) < 0) { + swoole_set_last_error(errno); + RETURN_FALSE; + } else { + RETURN_TRUE; + } } static PHP_METHOD(swoole_process, getPriority) { - zend_long which; - ZEND_PARSE_PARAMETERS_START(1, 1) + zend_long which, who; + bool who_is_null = true; + + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(which) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(who, who_is_null) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - Worker *process = php_swoole_process_get_and_check_worker(ZEND_THIS); - RETURN_LONG(getpriority(which, process->pid)); + SW_CHECK_PRIORITY_WHO(); + errno = 0; + int priority = getpriority(which, who); + if (priority == -1 && errno != 0) { + swoole_set_last_error(errno); + RETURN_FALSE; + } else { + RETURN_LONG(priority); + } } diff --git a/ext-src/swoole_process_pool.cc b/ext-src/swoole_process_pool.cc index 89462ba8d7..ae5be831f3 100644 --- a/ext-src/swoole_process_pool.cc +++ b/ext-src/swoole_process_pool.cc @@ -26,102 +26,84 @@ END_EXTERN_C() using namespace swoole; -struct ProcessPoolProperty { - zend_fcall_info_cache *onStart; - zend_fcall_info_cache *onWorkerStart; - zend_fcall_info_cache *onWorkerStop; - zend_fcall_info_cache *onMessage; - bool enable_coroutine; -}; - static zend_class_entry *swoole_process_pool_ce; static zend_object_handlers swoole_process_pool_handlers; -static ProcessPool *current_pool; +static ProcessPool *current_pool = nullptr; +static Worker *current_worker = nullptr; struct ProcessPoolObject { ProcessPool *pool; - ProcessPoolProperty *pp; + zend::Callable *onStart; + zend::Callable *onShutdown; + zend::Callable *onWorkerStart; + zend::Callable *onWorkerStop; + zend::Callable *onWorkerExit; + zend::Callable *onMessage; + zend_bool enable_coroutine; + zend_bool enable_message_bus; zend_object std; }; -static void pool_signal_handler(int sig); - -static sw_inline ProcessPoolObject *php_swoole_process_pool_fetch_object(zend_object *obj) { - return (ProcessPoolObject *) ((char *) obj - swoole_process_pool_handlers.offset); -} - -static sw_inline ProcessPool *php_swoole_process_pool_get_pool(zval *zobject) { - return php_swoole_process_pool_fetch_object(Z_OBJ_P(zobject))->pool; -} +static void process_pool_signal_handler(int signo); -static sw_inline ProcessPool *php_swoole_process_pool_get_and_check_pool(zval *zobject) { - ProcessPool *pool = php_swoole_process_pool_get_pool(zobject); - if (!pool) { - php_swoole_fatal_error(E_ERROR, "you must call Process\\Pool constructor first"); - } - return pool; +static sw_inline ProcessPoolObject *process_pool_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_process_pool_handlers.offset); } -static sw_inline void php_swoole_process_pool_set_pool(zval *zobject, ProcessPool *pool) { - php_swoole_process_pool_fetch_object(Z_OBJ_P(zobject))->pool = pool; +static sw_inline ProcessPoolObject *process_pool_fetch_object(const zval *zobject) { + return process_pool_fetch_object(Z_OBJ_P(zobject)); } -static sw_inline ProcessPoolProperty *php_swoole_process_pool_get_pp(zval *zobject) { - return php_swoole_process_pool_fetch_object(Z_OBJ_P(zobject))->pp; +static sw_inline ProcessPool *process_pool_get_pool(const zval *zobject) { + return process_pool_fetch_object(Z_OBJ_P(zobject))->pool; } -static sw_inline ProcessPoolProperty *php_swoole_process_pool_get_and_check_pp(zval *zobject) { - ProcessPoolProperty *pp = php_swoole_process_pool_get_pp(zobject); - if (!pp) { - php_swoole_fatal_error(E_ERROR, "you must call Process\\Pool constructor first"); +static sw_inline ProcessPool *process_pool_get_and_check_pool(const zval *zobject) { + ProcessPool *pool = process_pool_get_pool(zobject); + if (UNEXPECTED(!pool)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } - return pp; -} - -static sw_inline void php_swoole_process_pool_set_pp(zval *zobject, ProcessPoolProperty *pp) { - php_swoole_process_pool_fetch_object(Z_OBJ_P(zobject))->pp = pp; + return pool; } -static void php_swoole_process_pool_free_object(zend_object *object) { - ProcessPoolObject *process_pool = php_swoole_process_pool_fetch_object(object); +static void process_pool_free_object(zend_object *object) { + ProcessPoolObject *pp = process_pool_fetch_object(object); - ProcessPool *pool = process_pool->pool; + ProcessPool *pool = pp->pool; if (pool) { efree(pool->ptr); pool->destroy(); efree(pool); } - ProcessPoolProperty *pp = process_pool->pp; - if (pp) { - if (pp->onWorkerStart) { - sw_zend_fci_cache_discard(pp->onWorkerStart); - efree(pp->onWorkerStart); - } - if (pp->onMessage) { - sw_zend_fci_cache_discard(pp->onMessage); - efree(pp->onMessage); - } - if (pp->onWorkerStop) { - sw_zend_fci_cache_discard(pp->onWorkerStop); - efree(pp->onWorkerStop); - } - if (pp->onStart) { - sw_zend_fci_cache_discard(pp->onStart); - efree(pp->onStart); - } - efree(pp); + if (pp->onWorkerStart) { + sw_callable_free(pp->onWorkerStart); + } + if (pp->onMessage) { + sw_callable_free(pp->onMessage); + } + if (pp->onWorkerStop) { + sw_callable_free(pp->onWorkerStop); + } + if (pp->onStart) { + sw_callable_free(pp->onStart); + } + if (pp->onWorkerExit) { + sw_callable_free(pp->onWorkerExit); + } + if (pp->onShutdown) { + sw_callable_free(pp->onShutdown); } zend_object_std_dtor(object); } -static zend_object *php_swoole_process_pool_create_object(zend_class_entry *ce) { - ProcessPoolObject *process_pool = (ProcessPoolObject *) zend_object_alloc(sizeof(ProcessPoolObject), ce); - zend_object_std_init(&process_pool->std, ce); - object_properties_init(&process_pool->std, ce); - process_pool->std.handlers = &swoole_process_pool_handlers; - return &process_pool->std; +static zend_object *process_pool_create_object(zend_class_entry *ce) { + auto *pp = static_cast(zend_object_alloc(sizeof(ProcessPoolObject), ce)); + zend_object_std_init(&pp->std, ce); + object_properties_init(&pp->std, ce); + pp->std.handlers = &swoole_process_pool_handlers; + return &pp->std; } SW_EXTERN_C_BEGIN @@ -131,6 +113,7 @@ static PHP_METHOD(swoole_process_pool, set); static PHP_METHOD(swoole_process_pool, on); static PHP_METHOD(swoole_process_pool, listen); static PHP_METHOD(swoole_process_pool, write); +static PHP_METHOD(swoole_process_pool, sendMessage); static PHP_METHOD(swoole_process_pool, detach); static PHP_METHOD(swoole_process_pool, getProcess); static PHP_METHOD(swoole_process_pool, start); @@ -148,6 +131,7 @@ static const zend_function_entry swoole_process_pool_methods[] = PHP_ME(swoole_process_pool, getProcess, arginfo_class_Swoole_Process_Pool_getProcess, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, listen, arginfo_class_Swoole_Process_Pool_listen, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, write, arginfo_class_Swoole_Process_Pool_write, ZEND_ACC_PUBLIC) + PHP_ME(swoole_process_pool, sendMessage, arginfo_class_Swoole_Process_Pool_sendMessage, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, detach, arginfo_class_Swoole_Process_Pool_detach, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, start, arginfo_class_Swoole_Process_Pool_start, ZEND_ACC_PUBLIC) PHP_ME(swoole_process_pool, stop, arginfo_class_Swoole_Process_Pool_stop, ZEND_ACC_PUBLIC) @@ -161,136 +145,249 @@ void php_swoole_process_pool_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_process_pool); SW_SET_CLASS_CLONEABLE(swoole_process_pool, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_process_pool, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_process_pool, - php_swoole_process_pool_create_object, - php_swoole_process_pool_free_object, - ProcessPoolObject, - std); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_process_pool, process_pool_create_object, process_pool_free_object, ProcessPoolObject, std); zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("master_pid"), -1, ZEND_ACC_PUBLIC); + zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("workerPid"), -1, ZEND_ACC_PUBLIC); + zend_declare_property_long(swoole_process_pool_ce, ZEND_STRL("workerId"), -1, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_process_pool_ce, ZEND_STRL("workers"), ZEND_ACC_PUBLIC); + zend_declare_property_bool(swoole_process_pool_ce, ZEND_STRL("workerRunning"), -1, ZEND_ACC_PUBLIC); + zend_declare_property_bool(swoole_process_pool_ce, ZEND_STRL("running"), -1, ZEND_ACC_PUBLIC); } -static void pool_onWorkerStart(ProcessPool *pool, int worker_id) { - zval *zobject = (zval *) pool->ptr; - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(zobject); - +static void process_pool_onWorkerStart(ProcessPool *pool, Worker *worker) { + auto zobject = static_cast(pool->ptr); + auto pp = process_pool_fetch_object(zobject); php_swoole_process_clean(); - SwooleG.process_id = worker_id; + current_pool = pool; - // main function - if (!pp->onWorkerStart) { - return; + current_worker = worker; + + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), true); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), true); + zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerPid"), getpid()); + zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerId"), worker->id); + + swoole_set_worker_type(SW_WORKER); + SwooleG.enable_coroutine = pp->enable_coroutine; + + if (pp->onWorkerStart) { + zval args[2]; + args[0] = *zobject; + ZVAL_LONG(&args[1], worker->id); + if (UNEXPECTED(!zend::function::call(pp->onWorkerStart->ptr(), 2, args, nullptr, pp->enable_coroutine))) { + php_swoole_error(E_WARNING, "%s->onWorkerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + } } - // eventloop create - if (pp->enable_coroutine && php_swoole_reactor_init() < 0) { - return; + + if (!swoole_signal_isset(SIGTERM) && (pp->onMessage || pp->enable_coroutine)) { + swoole_signal_set(SIGTERM, process_pool_signal_handler); + } + if (!swoole_signal_isset(SIGWINCH)) { + swoole_signal_set(SIGWINCH, process_pool_signal_handler); } - if (!pp->enable_coroutine && pp->onMessage) { - swoole_signal_set(SIGTERM, pool_signal_handler); +#ifdef SIGRTMIN + if (!swoole_signal_isset(SIGRTMIN)) { + swoole_signal_set(SIGRTMIN, process_pool_signal_handler); } +#endif +} + +static void process_pool_onMessage(ProcessPool *pool, RecvData *msg) { + auto zobject = static_cast(pool->ptr); + auto pp = process_pool_fetch_object(zobject); zval args[2]; + args[0] = *zobject; - ZVAL_LONG(&args[1], worker_id); - if (UNEXPECTED(!zend::function::call(pp->onWorkerStart, 2, args, nullptr, pp->enable_coroutine))) { - php_swoole_error(E_WARNING, "%s->onWorkerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + const char *data = msg->data; + uint32_t length = msg->info.len; + if (length == 0) { + ZVAL_EMPTY_STRING(&args[1]); + } else { + if (msg->info.flags & SW_EVENT_DATA_OBJ_PTR) { + zend::assign_zend_string_by_val(&args[1], (char *) data, length); + pool->message_bus->move_packet(); + } else { + ZVAL_STRINGL(&args[1], data, length); + } } - // eventloop start - if (pp->enable_coroutine) { - php_swoole_event_wait(); + auto *worker = sw_worker(); + worker->set_status_to_busy(); + if (UNEXPECTED(!zend::function::call(pp->onMessage->ptr(), 2, args, nullptr, pp->enable_coroutine))) { + php_swoole_error(E_WARNING, "%s->onMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } + worker->add_request_count(); + worker->set_status_to_idle(); + zval_ptr_dtor(&args[1]); } -static void pool_onMessage(ProcessPool *pool, const char *data, uint32_t length) { - zval *zobject = (zval *) pool->ptr; - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(zobject); +static void process_pool_onWorkerStop(ProcessPool *pool, Worker *worker) { + auto zobject = static_cast(pool->ptr); + ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[2]; - args[0] = *zobject; - ZVAL_STRINGL(&args[1], data, length); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); - if (UNEXPECTED(!zend::function::call(pp->onMessage, 2, args, nullptr, false))) { - php_swoole_error(E_WARNING, "%s->onMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + if (pp->onWorkerStop == nullptr) { + return; } - zval_ptr_dtor(&args[1]); + args[0] = *zobject; + ZVAL_LONG(&args[1], worker->id); + + if (UNEXPECTED(!zend::function::call(pp->onWorkerStop->ptr(), 2, args, nullptr, false))) { + php_swoole_error(E_WARNING, "%s->onWorkerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + } } -static void pool_onWorkerStop(ProcessPool *pool, int worker_id) { +static void process_pool_onWorkerExit(ProcessPool *pool, Worker *worker) { zval *zobject = (zval *) pool->ptr; - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(zobject); + ProcessPoolObject *pp = process_pool_fetch_object(zobject); zval args[2]; - if (pp->onWorkerStop == nullptr) { + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); + + if (pp->onWorkerExit == nullptr) { return; } args[0] = *zobject; - ZVAL_LONG(&args[1], worker_id); + ZVAL_LONG(&args[1], worker->id); - if (UNEXPECTED(!zend::function::call(pp->onWorkerStop, 2, args, nullptr, false))) { - php_swoole_error(E_WARNING, "%s->onWorkerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + if (UNEXPECTED(!zend::function::call(pp->onWorkerExit->ptr(), 2, args, nullptr, false))) { + php_swoole_error(E_WARNING, "%s->onWorkerExit handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + } +} + +static void process_pool_onStart(ProcessPool *pool) { + zval *zobject = static_cast(pool->ptr); + ProcessPoolObject *pp = process_pool_fetch_object(zobject); + zval args[1]; + + zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("master_pid"), getpid()); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), true); + + swoole_set_worker_type(SW_MASTER); + SwooleG.enable_coroutine = false; + + if (pp->onStart == nullptr) { + return; + } + + args[0] = *zobject; + if (UNEXPECTED(!zend::function::call(pp->onStart->ptr(), 1, args, nullptr, false))) { + php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); } } -static void pool_signal_handler(int sig) { +static void process_pool_onShutdown(ProcessPool *pool) { + zval *zobject = static_cast(pool->ptr); + ProcessPoolObject *pp = process_pool_fetch_object(zobject); + zval args[1]; + + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("running"), false); + zend_update_property_bool(swoole_process_pool_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("workerRunning"), false); + + if (pp->onShutdown == nullptr) { + return; + } + + args[0] = *zobject; + + if (UNEXPECTED(!zend::function::call(pp->onShutdown->ptr(), 1, args, nullptr, false))) { + php_swoole_error(E_WARNING, "%s->onShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zobject)); + } +} + +static void process_pool_signal_handler(int signo) { if (!current_pool) { return; } - switch (sig) { + switch (signo) { case SIGTERM: current_pool->running = false; + if (current_worker) { + current_pool->stop(current_worker); + } break; case SIGUSR1: case SIGUSR2: current_pool->reload(); - current_pool->reload_init = false; break; case SIGIO: - current_pool->read_message = true; + current_pool->trigger_read_message_event(); + break; + case SIGWINCH: + current_pool->reopen_logger(); break; default: +#ifdef SIGRTMIN + if (signo == SIGRTMIN) { + current_pool->reopen_logger(); + } +#endif break; } } +ProcessPool *sw_process_pool() { + return current_pool; +} + static PHP_METHOD(swoole_process_pool, __construct) { zval *zobject = ZEND_THIS; zend_long worker_num; zend_long ipc_type = SW_IPC_NONE; zend_long msgq_key = 0; - zend_bool enable_coroutine = 0; + zend_bool enable_coroutine = false; // only cli env if (!SWOOLE_G(cli)) { - zend_throw_error(NULL, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(zobject)); + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_error(nullptr, "%s can only be used in PHP CLI mode", SW_Z_OBJCE_NAME_VAL_P(zobject)); RETURN_FALSE; } if (sw_server()) { - zend_throw_error(NULL, "%s cannot use in server process", SW_Z_OBJCE_NAME_VAL_P(zobject)); + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_error(nullptr, "cannot create server and process pool instances simultaneously"); + RETURN_FALSE; + } + + if (sw_process_pool()) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_error(nullptr, "A process pool instance has already been created and cannot be created again"); RETURN_FALSE; } +#ifdef SW_THREAD + if (!tsrm_is_main_thread()) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_exception_ex(swoole_exception_ce, -1, "This operation is only allowed in the main thread"); + RETURN_FALSE; + } +#endif + if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "l|llb", &worker_num, &ipc_type, &msgq_key, &enable_coroutine) == FAILURE) { RETURN_FALSE; } if (worker_num <= 0) { - zend_throw_exception_ex(swoole_exception_ce, errno, "invalid worker_num"); + zend_throw_exception_ex(swoole_exception_ce, errno, "the parameter $worker_num must be greater than 0"); RETURN_FALSE; } if (enable_coroutine && ipc_type > 0 && ipc_type != SW_IPC_UNIXSOCK) { ipc_type = SW_IPC_UNIXSOCK; - zend_throw_error(NULL, - "%s object's ipc_type will be reset to SWOOLE_IPC_UNIXSOCK after enable coroutine", - SW_Z_OBJCE_NAME_VAL_P(zobject)); + zend_throw_error(nullptr, "the parameter $ipc_type must be SWOOLE_IPC_UNIXSOCK when enable coroutine"); RETURN_FALSE; } - ProcessPool *pool = (ProcessPool *) emalloc(sizeof(*pool)); + auto *pool = static_cast(emalloc(sizeof(ProcessPool))); *pool = {}; if (pool->create(worker_num, (key_t) msgq_key, (swIPCMode) ipc_type) < 0) { zend_throw_exception_ex(swoole_exception_ce, errno, "failed to create process pool"); @@ -299,19 +396,11 @@ static PHP_METHOD(swoole_process_pool, __construct) { } pool->ptr = sw_zval_dup(zobject); + pool->async = enable_coroutine; - if (enable_coroutine) { - pool->main_loop = nullptr; - } else { - if (ipc_type > 0) { - pool->set_protocol(0, SW_INPUT_BUFFER_SIZE); - } - } - - ProcessPoolProperty *pp = (ProcessPoolProperty *) ecalloc(1, sizeof(ProcessPoolProperty)); + auto pp = process_pool_fetch_object(ZEND_THIS); pp->enable_coroutine = enable_coroutine; - php_swoole_process_pool_set_pp(zobject, pp); - php_swoole_process_pool_set_pool(zobject, pool); + pp->pool = pool; } static PHP_METHOD(swoole_process_pool, set) { @@ -325,30 +414,34 @@ static PHP_METHOD(swoole_process_pool, set) { vht = Z_ARRVAL_P(zset); - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(ZEND_THIS); + ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); + ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); php_swoole_set_global_option(vht); php_swoole_set_coroutine_option(vht); php_swoole_set_aio_option(vht); if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { - pp->enable_coroutine = zval_is_true(ztmp); + pool->async = pp->enable_coroutine = zval_is_true(ztmp); } - - ProcessPool *pool = php_swoole_process_pool_get_and_check_pool(ZEND_THIS); - if (pp->enable_coroutine) { - pool->main_loop = nullptr; + if (php_swoole_array_get_value(vht, "enable_message_bus", ztmp)) { + pp->enable_message_bus = zval_is_true(ztmp); + } + if (php_swoole_array_get_value(vht, "max_package_size", ztmp)) { + pool->set_max_packet_size(php_swoole_parse_to_size(ztmp)); + } + if (php_swoole_array_get_value(vht, "max_wait_time", ztmp)) { + zend_long v = zval_get_long(ztmp); + pool->max_wait_time = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } } static PHP_METHOD(swoole_process_pool, on) { char *name; size_t l_name; + zval *zfn; - zend_fcall_info fci; - zend_fcall_info_cache fci_cache; - - ProcessPool *pool = php_swoole_process_pool_get_and_check_pool(ZEND_THIS); + ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is started. unable to register event callback function"); @@ -357,63 +450,51 @@ static PHP_METHOD(swoole_process_pool, on) { ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2) Z_PARAM_STRING(name, l_name) - Z_PARAM_FUNC(fci, fci_cache); + Z_PARAM_ZVAL(zfn); ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(ZEND_THIS); + ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); if (SW_STRCASEEQ(name, l_name, "WorkerStart")) { if (pp->onWorkerStart) { - sw_zend_fci_cache_discard(pp->onWorkerStart); - efree(pp->onWorkerStart); - } else { - pp->onWorkerStart = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); + sw_callable_free(pp->onWorkerStart); } - *pp->onWorkerStart = fci_cache; - sw_zend_fci_cache_persist(pp->onWorkerStart); - RETURN_TRUE; + pp->onWorkerStart = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "Message")) { - if (pp->enable_coroutine) { - php_swoole_fatal_error(E_NOTICE, "cannot set onMessage event with enable_coroutine"); - RETURN_FALSE; - } if (pool->ipc_mode == SW_IPC_NONE) { - php_swoole_fatal_error(E_WARNING, "cannot set onMessage event with ipc_type=0"); + zend_throw_exception( + swoole_exception_ce, "cannot set `onMessage` event with ipc_type=0", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } if (pp->onMessage) { - sw_zend_fci_cache_discard(pp->onMessage); - efree(pp->onMessage); - } else { - pp->onMessage = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); + sw_callable_free(pp->onMessage); } - *pp->onMessage = fci_cache; - sw_zend_fci_cache_persist(pp->onMessage); - RETURN_TRUE; + pp->onMessage = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "WorkerStop")) { if (pp->onWorkerStop) { - sw_zend_fci_cache_discard(pp->onWorkerStop); - efree(pp->onWorkerStop); - } else { - pp->onWorkerStop = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); + sw_callable_free(pp->onWorkerStop); + } + pp->onWorkerStop = sw_callable_create(zfn); + } else if (SW_STRCASEEQ(name, l_name, "WorkerExit")) { + if (pp->onWorkerExit) { + sw_callable_free(pp->onWorkerExit); } - *pp->onWorkerStop = fci_cache; - sw_zend_fci_cache_persist(pp->onWorkerStop); - RETURN_TRUE; + pp->onWorkerExit = sw_callable_create(zfn); } else if (SW_STRCASEEQ(name, l_name, "Start")) { if (pp->onStart) { - sw_zend_fci_cache_discard(pp->onStart); - efree(pp->onStart); - } else { - pp->onStart = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); + sw_callable_free(pp->onStart); + } + pp->onStart = sw_callable_create(zfn); + } else if (SW_STRCASEEQ(name, l_name, "Shutdown")) { + if (pp->onShutdown) { + sw_callable_free(pp->onShutdown); } - *pp->onStart = fci_cache; - sw_zend_fci_cache_persist(pp->onStart); - RETURN_TRUE; + pp->onShutdown = sw_callable_create(zfn); } else { php_swoole_error(E_WARNING, "unknown event type[%s]", name); RETURN_FALSE; } + RETURN_TRUE; } static PHP_METHOD(swoole_process_pool, listen) { @@ -422,8 +503,7 @@ static PHP_METHOD(swoole_process_pool, listen) { zend_long port = 0; zend_long backlog = 2048; - ProcessPool *pool = php_swoole_process_pool_get_and_check_pool(ZEND_THIS); - + auto pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->started) { php_swoole_fatal_error(E_WARNING, "process pool is started. unable to listen"); RETURN_FALSE; @@ -440,7 +520,7 @@ static PHP_METHOD(swoole_process_pool, listen) { int ret; // unix socket - if (SW_STRCASECT(host, l_host, "unix:/")) { + if (SW_STR_ISTARTS_WITH(host, l_host, "unix:/")) { ret = pool->listen(host + 5, backlog); } else { ret = pool->listen(host, port, backlog); @@ -454,11 +534,11 @@ static PHP_METHOD(swoole_process_pool, write) { char *data; size_t length; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &data, &length) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(data, length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ProcessPool *pool = php_swoole_process_pool_get_and_check_pool(ZEND_THIS); + ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); if (pool->ipc_mode != SW_IPC_SOCKET) { php_swoole_fatal_error(E_WARNING, "unsupported ipc type[%d]", pool->ipc_mode); RETURN_FALSE; @@ -469,63 +549,103 @@ static PHP_METHOD(swoole_process_pool, write) { SW_CHECK_RETURN(pool->response(data, length)); } -static PHP_METHOD(swoole_process_pool, start) { - ProcessPool *pool = php_swoole_process_pool_get_and_check_pool(ZEND_THIS); - if (pool->started) { - php_swoole_fatal_error(E_WARNING, "process pool is started. unable to execute swoole_process_pool->start"); +static PHP_METHOD(swoole_process_pool, sendMessage) { + ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); + if (!pool->started) { + php_swoole_fatal_error(E_WARNING, "process pool is not started."); + RETURN_FALSE; + } + if (pool->ipc_mode != SW_IPC_UNIXSOCK) { + php_swoole_fatal_error(E_WARNING, "unsupported ipc type[%d]", pool->ipc_mode); RETURN_FALSE; } - swoole_event_free(); + char *message; + size_t l_message; + zend_long worker_id; - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(ZEND_THIS); + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(message, l_message) + Z_PARAM_LONG(worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - std::unordered_map ori_handlers; + RETURN_BOOL(pool->send_message(worker_id, message, l_message)); +} + +static PHP_METHOD(swoole_process_pool, start) { + ProcessPool *pool = process_pool_get_and_check_pool(ZEND_THIS); + if (pool->started) { + php_swoole_fatal_error(E_WARNING, "process pool is started"); + RETURN_FALSE; + } - ori_handlers[SIGTERM] = swoole_signal_set(SIGTERM, pool_signal_handler); - ori_handlers[SIGUSR1] = swoole_signal_set(SIGUSR1, pool_signal_handler); - ori_handlers[SIGUSR2] = swoole_signal_set(SIGUSR2, pool_signal_handler); - ori_handlers[SIGIO] = swoole_signal_set(SIGIO, pool_signal_handler); + ProcessPoolObject *pp = process_pool_fetch_object(ZEND_THIS); + std::unordered_map ori_handlers; - if (pool->ipc_mode == SW_IPC_NONE || pp->enable_coroutine) { - if (pp->onWorkerStart == nullptr) { - php_swoole_fatal_error(E_ERROR, "require onWorkerStart callback"); + // The reactor must be cleaned up before registering signal + swoole_event_free(); + ori_handlers[SIGTERM] = swoole_signal_set(SIGTERM, process_pool_signal_handler); + ori_handlers[SIGUSR1] = swoole_signal_set(SIGUSR1, process_pool_signal_handler); + ori_handlers[SIGUSR2] = swoole_signal_set(SIGUSR2, process_pool_signal_handler); + ori_handlers[SIGIO] = swoole_signal_set(SIGIO, process_pool_signal_handler); + ori_handlers[SIGWINCH] = swoole_signal_set(SIGWINCH, process_pool_signal_handler); +#ifdef SIGRTMIN + ori_handlers[SIGRTMIN] = swoole_signal_set(SIGRTMIN, process_pool_signal_handler); +#endif + + if (pp->enable_message_bus) { + if (pool->create_message_bus() != SW_OK) { RETURN_FALSE; } + pool->message_bus->set_allocator(sw_zend_string_allocator()); + pool->set_protocol(SW_PROTOCOL_MESSAGE); } else { - if (pp->onMessage == nullptr) { - php_swoole_fatal_error(E_ERROR, "require onMessage callback"); + pool->set_protocol(SW_PROTOCOL_STREAM); + } + + if (pp->onWorkerStart == nullptr && pp->onMessage == nullptr) { + if (pool->async) { + php_swoole_fatal_error(E_ERROR, "require 'onWorkerStart' callback"); + RETURN_FALSE; + } else if (pool->ipc_mode != SW_IPC_NONE && pp->onMessage == nullptr) { + php_swoole_fatal_error(E_ERROR, "require 'onMessage' callback"); RETURN_FALSE; } - pool->onMessage = pool_onMessage; } - pool->onWorkerStart = pool_onWorkerStart; - pool->onWorkerStop = pool_onWorkerStop; - - zend_update_property_long(swoole_process_pool_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("master_pid"), getpid()); - - if (pool->start() < 0) { + if (pp->onWorkerExit && !pp->enable_coroutine) { + zend_throw_exception( + swoole_exception_ce, "cannot set `onWorkerExit` without enable_coroutine", SW_ERROR_INVALID_PARAMS); RETURN_FALSE; } + if (pp->onMessage) { + pool->onMessage = process_pool_onMessage; + } else { + pool->main_loop = nullptr; + } + current_pool = pool; - if (pp->onStart) { - zval args[1]; - args[0] = *ZEND_THIS; - if (UNEXPECTED(!zend::function::call(pp->onStart, 1, args, nullptr, 0))) { - php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); - } + pool->onStart = process_pool_onStart; + pool->onShutdown = process_pool_onShutdown; + pool->onWorkerStart = process_pool_onWorkerStart; + pool->onWorkerStop = process_pool_onWorkerStop; + + if (pp->enable_coroutine && pp->onWorkerExit) { + pool->onWorkerExit = process_pool_onWorkerExit; + } + + if (pool->start() < 0) { + RETURN_FALSE; } pool->wait(); - pool->shutdown(); current_pool = nullptr; - for (auto iter = ori_handlers.begin(); iter != ori_handlers.end(); iter++) { - swoole_signal_set(iter->first, iter->second); + for (auto &ori_handler : ori_handlers) { + swoole_signal_set(ori_handler.first, ori_handler.second); } } @@ -551,7 +671,7 @@ static PHP_METHOD(swoole_process_pool, getProcess) { php_swoole_error(E_WARNING, "invalid worker_id[%ld]", worker_id); RETURN_FALSE; } else if (worker_id < 0) { - worker_id = SwooleG.process_id; + worker_id = swoole_get_worker_id(); } zval *zworkers = @@ -564,15 +684,15 @@ static PHP_METHOD(swoole_process_pool, getProcess) { /** * Separation from shared memory */ - Worker *worker = (Worker *) emalloc(sizeof(Worker)); + auto *worker = static_cast(emalloc(sizeof(Worker))); *worker = current_pool->workers[worker_id]; object_init_ex(zprocess, swoole_process_ce); - zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("id"), SwooleG.process_id); + zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("id"), swoole_get_worker_id()); zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("pid"), worker->pid); if (current_pool->ipc_mode == SW_IPC_UNIXSOCK) { // current process - if (worker->id == SwooleG.process_id) { + if (worker->id == swoole_get_worker_id()) { worker->pipe_current = worker->pipe_worker; } else { worker->pipe_current = worker->pipe_master; @@ -584,10 +704,21 @@ static PHP_METHOD(swoole_process_pool, getProcess) { zend_update_property_long( swoole_process_ce, SW_Z8_OBJ_P(zprocess), ZEND_STRL("pipe"), worker->pipe_current->fd); } - php_swoole_process_set_worker(zprocess, worker); - ProcessPoolProperty *pp = php_swoole_process_pool_get_and_check_pp(ZEND_THIS); - zend::Process *proc = new zend::Process(zend::PIPE_TYPE_STREAM, pp->enable_coroutine); - worker->ptr2 = proc; + /** + * The message bus is enabled and forbid to read/write/close the pipeline in the php layer + */ + if (current_pool->message_bus) { + worker->pipe_current = nullptr; + worker->pipe_object = nullptr; + } + /** + * The onMessage callback is not set, use getProcess()->push()/pop() to operate msgqueue + */ + if (current_pool->ipc_mode == SW_IPC_MSGQUEUE && current_pool->onMessage == nullptr) { + worker->queue = current_pool->queue; + worker->msgqueue_mode = SW_MSGQUEUE_BALANCE; + } + php_swoole_process_set_worker(zprocess, worker, PIPE_TYPE_STREAM, current_pool->async); (void) add_index_zval(zworkers, worker_id, zprocess); } else { auto _worker = php_swoole_process_get_worker(zprocess); @@ -603,14 +734,19 @@ static PHP_METHOD(swoole_process_pool, getProcess) { static PHP_METHOD(swoole_process_pool, stop) { if (current_pool) { current_pool->running = false; + if (current_worker) { + current_pool->stop(current_worker); + } } } static PHP_METHOD(swoole_process_pool, shutdown) { - zval *retval = - sw_zend_read_property_ex(swoole_process_pool_ce, ZEND_THIS, SW_ZSTR_KNOWN(SW_ZEND_STR_MASTER_PID), 0); - long pid = zval_get_long(retval); - RETURN_BOOL(swoole_kill(pid, SIGTERM) == 0); + if (current_pool) { + RETURN_BOOL(current_pool->shutdown()); + } else { + zend_throw_exception(swoole_exception_ce, "The process pool is not started", SW_ERROR_INVALID_PARAMS); + RETURN_FALSE; + } } static PHP_METHOD(swoole_process_pool, __destruct) {} diff --git a/ext-src/swoole_redis_coro.cc b/ext-src/swoole_redis_coro.cc deleted file mode 100644 index dd95b71601..0000000000 --- a/ext-src/swoole_redis_coro.cc +++ /dev/null @@ -1,5542 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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_cxx.h" - -#include "thirdparty/hiredis/hiredis.h" - -#include "ext/standard/php_var.h" - -using swoole::coroutine::Socket; -using namespace swoole; - -#define SW_REDIS_COMMAND_ALLOC_ARGS_ARR zval *z_args = (zval *) emalloc(argc * sizeof(zval)); -#define SW_REDIS_COMMAND_ARGS_TYPE(arg) Z_TYPE(arg) -#define SW_REDIS_COMMAND_ARGS_LVAL(arg) Z_LVAL(arg) -#define SW_REDIS_COMMAND_ARGS_DVAL(arg) Z_DVAL(arg) -#define SW_REDIS_COMMAND_ARGS_ARRVAL(arg) Z_ARRVAL(arg) -#define SW_REDIS_COMMAND_ARGS_STRVAL(arg) Z_STRVAL(arg) -#define SW_REDIS_COMMAND_ARGS_STRLEN(arg) Z_STRLEN(arg) -#define SW_REDIS_COMMAND_ARGS_REF(arg) &arg - -#define SW_REDIS_COMMAND_BUFFER_SIZE 64 -#define SW_BITOP_MIN_OFFSET 0 -#define SW_BITOP_MAX_OFFSET 4294967295 -#define SW_REDIS_TYPE_NOT_FOUND 0 -#define SW_REDIS_TYPE_STRING 1 -#define SW_REDIS_TYPE_SET 2 -#define SW_REDIS_TYPE_LIST 3 -#define SW_REDIS_TYPE_ZSET 4 -#define SW_REDIS_TYPE_HASH 5 - -/* The same errCode define with hiredis */ -enum swRedisError { - SW_REDIS_ERR_IO = 1, /* Error in read or write */ - SW_REDIS_ERR_OTHER = 2, /* Everything else... */ - SW_REDIS_ERR_EOF = 3, /* End of file */ - SW_REDIS_ERR_PROTOCOL = 4, /* Protocol error */ - SW_REDIS_ERR_OOM = 5, /* Out of memory */ - SW_REDIS_ERR_CLOSED = 6, /* Closed */ - SW_REDIS_ERR_NOAUTH = 7, /* Authentication required */ - SW_REDIS_ERR_ALLOC = 8, /* Alloc failed */ -}; - -/* Extended SET argument detection */ -// clang-format off -#define IS_EX_ARG(a) \ - ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') -#define IS_PX_ARG(a) \ - ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') -#define IS_NX_ARG(a) \ - ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') -#define IS_XX_ARG(a) \ - ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0') - -static zend_class_entry *swoole_redis_coro_ce; -static zend_object_handlers swoole_redis_coro_handlers; - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_construct, 0, 0, 0) - ZEND_ARG_INFO(0, config) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_connect, 0, 0, 1) - ZEND_ARG_INFO(0, host) - ZEND_ARG_INFO(0, port) - ZEND_ARG_INFO(0, serialize) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setOptions, 0, 0, 1) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setDefer, 0, 0, 1) - ZEND_ARG_INFO(0, defer) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_void, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_key, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_key_value, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_key_long, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, integer) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_request, 0, 0, 1) - ZEND_ARG_ARRAY_INFO(0, params, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_append, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_auth, 0, 0, 1) - ZEND_ARG_INFO(0, password) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bgSave, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bgrewriteaof, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bitcount, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bitop, 0, 0, 3) - ZEND_ARG_INFO(0, operation) - ZEND_ARG_INFO(0, ret_key) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_blPop, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timeout_or_key) - ZEND_ARG_INFO(0, extra_args) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_brPop, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timeout_or_key) - ZEND_ARG_INFO(0, extra_args) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_brpoplpush, 0, 0, 3) - ZEND_ARG_INFO(0, src) - ZEND_ARG_INFO(0, dst) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_close, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_dbSize, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_debug, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_decr, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_decrBy, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_dump, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_eval, 0, 0, 1) - ZEND_ARG_INFO(0, script) - ZEND_ARG_INFO(0, args) - ZEND_ARG_INFO(0, num_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_evalsha, 0, 0, 1) - ZEND_ARG_INFO(0, script_sha) - ZEND_ARG_INFO(0, args) - ZEND_ARG_INFO(0, num_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_exec, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_exists, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_expireAt, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timestamp) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_flushAll, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_flushDB, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_get, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_getBit, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, offset) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_getKeys, 0, 0, 1) - ZEND_ARG_INFO(0, pattern) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_getRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_getSet, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hDel, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, other_members) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hExists, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hGet, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hGetAll, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hIncrBy, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hIncrByFloat, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hKeys, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hLen, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hMget, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hMset, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, pairs) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hSet, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hSetNx, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_hVals, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_incr, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_incrBy, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_incrByFloat, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lGet, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, index) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lGetRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lInsert, 0, 0, 4) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, position) - ZEND_ARG_INFO(0, pivot) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lPop, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lPush, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lPushx, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lRemove, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lSet, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, index) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lSize, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lastSave, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_listTrim, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, stop) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_move, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, dbindex) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_mset, 0, 0, 1) - ZEND_ARG_INFO(0, pairs) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_msetnx, 0, 0, 1) - ZEND_ARG_INFO(0, pairs) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_multi, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_persist, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pexpire, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timestamp) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pexpireAt, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timestamp) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pfadd, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, elements) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pfcount, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pfmerge, 0, 0, 2) - ZEND_ARG_INFO(0, dstkey) - ZEND_ARG_INFO(0, keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_ping, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_psetex, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, expire) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_psubscribe, 0, 0, 1) - ZEND_ARG_INFO(0, patterns) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_punsubscribe, 0, 0, 1) - ZEND_ARG_INFO(0, patterns) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_pttl, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_publish, 0, 0, 2) - ZEND_ARG_INFO(0, channel) - ZEND_ARG_INFO(0, message) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_rPop, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_rPush, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_rPushx, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_randomKey, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_renameKey, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, newkey) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_renameNx, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, newkey) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_restore, 0, 0, 3) - ZEND_ARG_INFO(0, ttl) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_role, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_rpoplpush, 0, 0, 2) - ZEND_ARG_INFO(0, src) - ZEND_ARG_INFO(0, dst) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sAdd, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sContains, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sDiff, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sDiffStore, 0, 0, 2) - ZEND_ARG_INFO(0, dst) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sInter, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sInterStore, 0, 0, 2) - ZEND_ARG_INFO(0, dst) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sMembers, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sMove, 0, 0, 3) - ZEND_ARG_INFO(0, src) - ZEND_ARG_INFO(0, dst) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sPop, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sRandMember, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sRemove, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sSize, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sUnion, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_sUnionStore, 0, 0, 2) - ZEND_ARG_INFO(0, dst) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_save, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_script, 0, 0, 1) - ZEND_ARG_INFO(0, cmd) - ZEND_ARG_INFO(0, args) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_select, 0, 0, 1) - ZEND_ARG_INFO(0, dbindex) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_set, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) - ZEND_ARG_INFO(0, timeout) - ZEND_ARG_INFO(0, opt) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setBit, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, offset) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, offset) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setTimeout, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timeout) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setex, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, expire) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_setnx, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_strlen, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_subscribe, 0, 0, 1) - ZEND_ARG_INFO(0, channels) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_unsubscribe, 0, 0, 1) - ZEND_ARG_INFO(0, channels) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_time, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_ttl, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_type, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_unwatch, 0, 0, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_watch, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zAdd, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, score) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zPopMin, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zPopMax, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bzPopMin, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timeout_or_key) - ZEND_ARG_INFO(0, extra_args) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_bzPopMax, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, timeout_or_key) - ZEND_ARG_INFO(0, extra_args) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zCard, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zCount, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zDelete, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, other_members) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zDeleteRangeByRank, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zDeleteRangeByScore, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zIncrBy, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zInter, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, keys) - ZEND_ARG_INFO(0, weights) - ZEND_ARG_INFO(0, aggregate) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, scores) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRangeByLex, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) - ZEND_ARG_INFO(0, offset) - ZEND_ARG_INFO(0, limit) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRangeByScore, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRank, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRevRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, scores) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRevRangeByLex, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) - ZEND_ARG_INFO(0, offset) - ZEND_ARG_INFO(0, limit) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRevRangeByScore, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRevRank, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zScore, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zUnion, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, keys) - ZEND_ARG_INFO(0, weights) - ZEND_ARG_INFO(0, aggregate) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_del, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, other_keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lLen, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lrange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_lrem, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, value) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_ltrim, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, stop) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_mget, 0, 0, 1) - ZEND_ARG_INFO(0, keys) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_rename, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, newkey) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_scard, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRem, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, other_members) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRemRangeByRank, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRemRangeByScore, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, min) - ZEND_ARG_INFO(0, max) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zRemove, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, member) - ZEND_ARG_INFO(0, other_members) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zSize, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zinterstore, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, keys) - ZEND_ARG_INFO(0, weights) - ZEND_ARG_INFO(0, aggregate) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_zunionstore, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, keys) - ZEND_ARG_INFO(0, weights) - ZEND_ARG_INFO(0, aggregate) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xLen, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xAdd, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, id) - ZEND_ARG_INFO(0, pairs) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xRead, 0, 0, 1) - ZEND_ARG_INFO(0, streams) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xDel, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, id) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xRevRange, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, end) - ZEND_ARG_INFO(0, count) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xTrim, 0, 0, 1) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xGroupCreate, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, id) - ZEND_ARG_INFO(0, mkstream) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xGroupSetId, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, id) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xGroupDestroy, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xGroupCreateConsumer, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, consumer_name) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xGroupDelConsumer, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, consumer_name) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xReadGroup, 0, 0, 3) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, consumer_name) - ZEND_ARG_INFO(0, streams) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xPending, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xAck, 0, 0, 3) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, id) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xClaim, 0, 0, 5) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, consumer_name) - ZEND_ARG_INFO(0, min_idle_time) - ZEND_ARG_INFO(0, id) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xAutoClaim, 0, 0, 5) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) - ZEND_ARG_INFO(0, consumer_name) - ZEND_ARG_INFO(0, min_idle_time) - ZEND_ARG_INFO(0, start) - ZEND_ARG_INFO(0, options) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xInfoConsumers, 0, 0, 2) - ZEND_ARG_INFO(0, key) - ZEND_ARG_INFO(0, group_name) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xInfoGroups, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_redis_coro_xInfoStream, 0, 0, 1) - ZEND_ARG_INFO(0, key) -ZEND_END_ARG_INFO() -// clang-format on - -#define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a)) -#define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a)) - -struct RedisClient { - redisContext *context; - struct { - bool auth; - long db_num; - bool subscribe; - } session; - double connect_timeout; - double timeout; - bool serialize; - bool defer; - uint8_t reconnect_interval; - uint8_t reconnected_count; - bool auth; - bool compatibility_mode; - long database; - zval *zobject; - zval _zobject; - zend_object std; -}; - -#define SW_REDIS_COMMAND_CHECK \ - Coroutine::get_current_safe(); \ - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - -#define SW_REDIS_COMMAND_ARGV_FILL(str, str_len) \ - argvlen[i] = str_len; \ - argv[i] = estrndup(str, str_len); \ - i++; - -#define SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(_val) \ - if (redis->serialize) { \ - smart_str sstr = {}; \ - php_serialize_data_t s_ht; \ - PHP_VAR_SERIALIZE_INIT(s_ht); \ - php_var_serialize(&sstr, _val, &s_ht); \ - argvlen[i] = (size_t) sstr.s->len; \ - argv[i] = estrndup(sstr.s->val, sstr.s->len); \ - zend_string_release(sstr.s); \ - PHP_VAR_SERIALIZE_DESTROY(s_ht); \ - } else { \ - zend_string *convert_str = zval_get_string(_val); \ - argvlen[i] = ZSTR_LEN(convert_str); \ - argv[i] = estrndup(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)); \ - zend_string_release(convert_str); \ - } \ - i++; - -#define SW_REDIS_COMMAND_ALLOC_ARGV \ - size_t stack_argvlen[SW_REDIS_COMMAND_BUFFER_SIZE]; \ - char *stack_argv[SW_REDIS_COMMAND_BUFFER_SIZE]; \ - size_t *argvlen; \ - char **argv; \ - if (argc > SW_REDIS_COMMAND_BUFFER_SIZE) { \ - argvlen = (size_t *) emalloc(sizeof(size_t) * (argc)); \ - argv = (char **) emalloc(sizeof(char *) * (argc)); \ - } else { \ - argvlen = stack_argvlen; \ - argv = stack_argv; \ - } - -#define SW_REDIS_COMMAND_INCREASE_ARGV(_new_argc) \ - if (_new_argc > SW_REDIS_COMMAND_BUFFER_SIZE && _new_argc > argc) { \ - size_t *tmp_argvlen; \ - char **tmp_argv; \ - tmp_argvlen = (size_t *) emalloc(sizeof(size_t) * (_new_argc)); \ - tmp_argv = (char **) emalloc(sizeof(char *) * (_new_argc)); \ - for (int argc_i = 0; argc_i < argc; argc_i++) { \ - tmp_argvlen[argc_i] = argvlen[argc_i]; \ - tmp_argv[argc_i] = argv[argc_i]; \ - } \ - argvlen = tmp_argvlen; \ - argv = tmp_argv; \ - } \ - argc = _new_argc; - -#define SW_REDIS_COMMAND_FREE_ARGV \ - if (argv != stack_argv) { \ - efree(argvlen); \ - efree(argv); \ - } - -enum { SW_REDIS_MODE_MULTI, SW_REDIS_MODE_PIPELINE }; - -static void swoole_redis_coro_parse_result(RedisClient *redis, zval *return_value, redisReply *reply); - -static sw_inline RedisClient *php_swoole_redis_coro_fetch_object(zend_object *obj) { - return (RedisClient *) ((char *) obj - swoole_redis_coro_handlers.offset); -} - -static sw_inline RedisClient *php_swoole_get_redis_client(zval *zobject) { - RedisClient *redis = (RedisClient *) php_swoole_redis_coro_fetch_object(Z_OBJ_P(zobject)); - if (UNEXPECTED(!redis)) { - php_swoole_fatal_error(E_ERROR, "you must call Redis constructor first"); - } - return redis; -} - -static sw_inline Socket *swoole_redis_coro_get_socket(redisContext *context) { - if (context->fd > 0 && SwooleTG.reactor) { - return swoole_coroutine_get_socket_object(context->fd); - } - return nullptr; -} - -static sw_inline bool swoole_redis_coro_close(RedisClient *redis) { - if (redis->context) { - int sockfd = redis->context->fd; - Socket *socket = swoole_redis_coro_get_socket(redis->context); - swoole_trace_log(SW_TRACE_REDIS_CLIENT, "redis connection closed, fd=%d", sockfd); - zend_update_property_bool(swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("connected"), 0); - if (!(socket && socket->has_bound())) { - redisFreeKeepFd(redis->context); - redis->context = nullptr; - redis->session = {false, 0, false}; - } - if (socket) { - swoole_coroutine_close(sockfd); - } - return true; - } - return false; -} - -static void php_swoole_redis_coro_free_object(zend_object *object) { - RedisClient *redis = php_swoole_redis_coro_fetch_object(object); - - if (redis && redis->context) { - swoole_redis_coro_close(redis); - } - - zend_object_std_dtor(&redis->std); -} - -static zend_object *php_swoole_redis_coro_create_object(zend_class_entry *ce) { - RedisClient *redis = (RedisClient *) zend_object_alloc(sizeof(RedisClient), ce); - zend_object_std_init(&redis->std, ce); - object_properties_init(&redis->std, ce); - redis->std.handlers = &swoole_redis_coro_handlers; - return &redis->std; -} - -static sw_inline int sw_redis_convert_err(int err) { - switch (err) { - case SW_REDIS_ERR_IO: - return errno; - case SW_REDIS_ERR_EOF: - case SW_REDIS_ERR_CLOSED: - return ECONNRESET; - case SW_REDIS_ERR_OTHER: - return EINVAL; - case SW_REDIS_ERR_OOM: - case SW_REDIS_ERR_ALLOC: - return ENOMEM; - case SW_REDIS_ERR_PROTOCOL: - return EPROTO; - case SW_REDIS_ERR_NOAUTH: - return EACCES; - case 0: - return 0; - default: - return errno; - } -} - -static sw_inline void swoole_redis_handle_assoc_array_result(zval *return_value, bool str2double) { - zval *zkey, *zvalue; - zval zret; - bool is_key = false; - - array_init(&zret); - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(return_value), zvalue) { - if ((is_key = !is_key)) { - zkey = zvalue; - } else { - if (str2double) { - convert_to_double(zvalue); - } else { - Z_ADDREF_P(zvalue); - } - add_assoc_zval_ex(&zret, Z_STRVAL_P(zkey), Z_STRLEN_P(zkey), zvalue); - } - } - ZEND_HASH_FOREACH_END(); - - zval_ptr_dtor(return_value); - RETVAL_ZVAL(&zret, 1, 1); -} - -static bool redis_auth(RedisClient *redis, char *pw, size_t pw_len); -static bool redis_select_db(RedisClient *redis, long db_number); -static void redis_request( - RedisClient *redis, int argc, char **argv, size_t *argvlen, zval *return_value, bool retry = false); - -static bool swoole_redis_coro_connect(RedisClient *redis) { - zval *zobject = redis->zobject; - redisContext *context; - Socket *socket; - struct timeval tv; - zval *ztmp; - zval *zhost = sw_zend_read_property_ex(swoole_redis_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_HOST), 0); - zval *zport = sw_zend_read_property_ex(swoole_redis_coro_ce, zobject, SW_ZSTR_KNOWN(SW_ZEND_STR_PORT), 0); - zend::String host(zhost); - zend_long port = zval_get_long(zport); - - if (host.len() == 0) { - php_swoole_fatal_error(E_WARNING, "The host is empty"); - return false; - } - - if (redis->context) { - context = redis->context; - if (context->connection_type == REDIS_CONN_TCP && strcmp(context->tcp.host, host.val()) == 0 && - context->tcp.port == port) { - return true; - } else if (context->connection_type == REDIS_CONN_UNIX && - (strstr(host.val(), context->unix_sock.path) - host.val()) + strlen(context->unix_sock.path) == - host.len()) { - return true; - } else { - swoole_redis_coro_close(redis); - } - } - - php_swoole_check_reactor(); - - if (redis->connect_timeout > 0) { - tv.tv_sec = redis->connect_timeout; - tv.tv_usec = (redis->connect_timeout - (double) tv.tv_sec) * 1000 * 1000; - } - if (SW_STRCASECT(host.val(), host.len(), "unix:/")) { - context = redisConnectUnixWithTimeout(host.val() + 5 + strspn(host.val() + 5, "/") - 1, tv); - } else { - if (port <= 0 || port > SW_CLIENT_MAX_PORT) { - php_swoole_fatal_error(E_WARNING, "The port " ZEND_LONG_FMT " is invalid", port); - return false; - } - context = redisConnectWithTimeout(host.val(), (int) port, tv); - } - - redis->context = context; - - if (!context) { - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errType"), SW_REDIS_ERR_ALLOC); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sw_redis_convert_err(SW_REDIS_ERR_ALLOC)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "cannot allocate redis context"); - return false; - } - if (context->err) { - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errType"), context->err); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sw_redis_convert_err(context->err)); - zend_update_property_string(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), context->errstr); - swoole_redis_coro_close(redis); - return false; - } - if (!(socket = swoole_redis_coro_get_socket(context))) { - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), "Can not found the connection"); - swoole_redis_coro_close(redis); - return false; - } - - socket->set_timeout(redis->timeout, Socket::TIMEOUT_RDWR); - redis->reconnected_count = 0; - zend_update_property_bool(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("connected"), 1); - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("sock"), context->fd); - - // auth and select db after connected - zval *zsetting = - sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, redis->zobject, ZEND_STRL("setting"), 0); - HashTable *vht = Z_ARRVAL_P(zsetting); - - if (php_swoole_array_get_value(vht, "password", ztmp)) { - zend::String passowrd(ztmp); - if (passowrd.len() > 0 && !redis_auth(redis, passowrd.val(), passowrd.len())) { - swoole_redis_coro_close(redis); - return false; - } - } - if (php_swoole_array_get_value(vht, "database", ztmp)) { - zend_long db_number = zval_get_long(ztmp); - // default is 0, don't need select - if (db_number > 0 && !redis_select_db(redis, db_number)) { - swoole_redis_coro_close(redis); - return false; - } - } - return true; -} - -static sw_inline bool swoole_redis_coro_keep_liveness(RedisClient *redis) { - Socket *socket = nullptr; - if (!redis->context || !(socket = swoole_redis_coro_get_socket(redis->context)) || !socket->check_liveness()) { - if (socket) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), SW_REDIS_ERR_CLOSED); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errCode"), socket->errCode); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), socket->errMsg); - } - swoole_redis_coro_close(redis); - for (; redis->reconnected_count < redis->reconnect_interval; redis->reconnected_count++) { - if (swoole_redis_coro_connect(redis)) { - return true; - } - } - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), SW_REDIS_ERR_CLOSED); - // Notice: do not update errCode - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), "connection is not available"); - return false; - } - return true; -} - -static bool redis_auth(RedisClient *redis, char *pw, size_t pw_len) { - int i = 0; - size_t argvlen[2]; - char *argv[2]; - bool ret; - zval retval; - - SW_REDIS_COMMAND_ARGV_FILL("AUTH", 4) - SW_REDIS_COMMAND_ARGV_FILL(pw, pw_len) - redis_request(redis, 2, argv, argvlen, &retval); - ret = Z_BVAL_P(&retval); - if (ret) { - redis->session.auth = true; - } - return ret; -} - -static bool redis_select_db(RedisClient *redis, long db_number) { - int i = 0; - size_t argvlen[2]; - char *argv[2]; - char str[32]; - bool ret; - zval retval; - - SW_REDIS_COMMAND_ARGV_FILL("SELECT", 6) - sprintf(str, "%ld", db_number); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - redis_request(redis, 2, argv, argvlen, &retval); - ret = Z_BVAL_P(&retval); - if (ret) { - redis->session.db_num = db_number; - } - return ret; -} - -static void redis_request(RedisClient *redis, int argc, char **argv, size_t *argvlen, zval *return_value, bool retry) { - redisReply *reply = nullptr; - if (!swoole_redis_coro_keep_liveness(redis)) { - ZVAL_FALSE(return_value); - } else { - // must clear err before request - redis->context->err = 0; - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), 0); - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errCode"), 0); - zend_update_property_string(swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), ""); - if (redis->defer) { - if (redisAppendCommandArgv(redis->context, argc, (const char **) argv, (const size_t *) argvlen) == - REDIS_ERR) { - goto _error; - } else { - ZVAL_TRUE(return_value); - } - } else { - reply = - (redisReply *) redisCommandArgv(redis->context, argc, (const char **) argv, (const size_t *) argvlen); - if (reply == nullptr) { - _error: - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), redis->context->err); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(redis->context->err)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), redis->context->errstr); - ZVAL_FALSE(return_value); - swoole_redis_coro_close(redis); - } else { - // Redis Cluster - if (reply->type == REDIS_REPLY_ERROR && - (!strncmp(reply->str, "MOVED", 5) || !strcmp(reply->str, "ASK"))) { - char *p1, *p2; - // MOVED 1234 127.0.0.1:1234 - p1 = strrchr(reply->str, ' ') + 1; // MOVED 1234 [p1]27.0.0.1:1234 - p2 = strrchr(p1, ':'); // MOVED 1234 [p1]27.0.0.1[p2]1234 - *p2 = '\0'; - int port = atoi(p2 + 1); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("host"), p1); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("port"), port); - - if (swoole_redis_coro_connect(redis) > 0) { - freeReplyObject(reply); - redis_request(redis, argc, argv, argvlen, return_value, retry); - return; - } else { - ZVAL_FALSE(return_value); - } - } - // Normal Response - else { - swoole_redis_coro_parse_result(redis, return_value, reply); - } - freeReplyObject(reply); - } - } - } - SW_LOOP_N(argc) { - efree(argv[i]); - } -} - -static sw_inline void sw_redis_command_empty(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[1]; - char *argv[1]; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - redis_request(redis, 1, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_var_key( - INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len, int min_argc, int has_timeout) { - long timeout; - int argc = ZEND_NUM_ARGS(); - if (argc < min_argc) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (argc == 0 || zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - RETURN_FALSE; - } - zend_bool single_array = 0; - if (has_timeout == 0) { - single_array = argc == 1 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY; - } else { - single_array = argc == 2 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY && - SW_REDIS_COMMAND_ARGS_TYPE(z_args[1]) == IS_LONG; - timeout = SW_REDIS_COMMAND_ARGS_LVAL(z_args[1]); - } - if (single_array) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])) + 1; - } else { - argc++; - } - - SW_REDIS_COMMAND_ALLOC_ARGV - int i = 0; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - char buf[32]; - size_t buf_len; - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - if (has_timeout) { - buf_len = sw_snprintf(buf, sizeof(buf), "%ld", timeout); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len); - } - } else { - if (has_timeout && SW_REDIS_COMMAND_ARGS_TYPE(z_args[argc - 2]) != IS_LONG) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), "Timeout value must be a LONG"); - efree(z_args); - RETURN_FALSE; - } - int j, tail; - tail = has_timeout ? argc - 2 : argc - 1; - for (j = 0; j < tail; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - if (has_timeout) { - buf_len = sw_snprintf(buf, sizeof(buf), ZEND_LONG_FMT, SW_REDIS_COMMAND_ARGS_LVAL(z_args[tail])); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len); - } - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static inline void sw_redis_command_key(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[2]; - char *argv[2]; - int argc = 2; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode) { - if (ZVAL_IS_ARRAY(return_value) && sw_mem_equal(ZEND_STRL("HGETALL"), cmd, cmd_len)) { - swoole_redis_handle_assoc_array_result(return_value, false); - } else if (ZVAL_IS_NULL(return_value) && sw_mem_equal(ZEND_STRL("GET"), cmd, cmd_len)) { - RETURN_FALSE; - } - } -} - -static sw_inline void sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - int argc = ZEND_NUM_ARGS(); - // We at least need a key and one value - if (argc < 2) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - // Make sure we at least have a key, and we can get other args - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - RETURN_FALSE; - } - - int i = 0, j; - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - zend_string *convert_str = zval_get_string(&z_args[0]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - for (j = 1; j < argc - 1; ++j) { - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(SW_REDIS_COMMAND_ARGS_REF(z_args[j])) - } - efree(z_args); - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_long_val(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - long l_val; - zval *z_value; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &key, &key_len, &l_val, &z_value) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - int argc = 4; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%ld", l_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_value) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_long_str(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key, *val; - size_t key_len, val_len; - long l_val; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sls", &key, &key_len, &l_val, &val, &val_len) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - int argc = 4; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%ld", l_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - SW_REDIS_COMMAND_ARGV_FILL(val, val_len) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_long(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - long l_val; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sl", &key, &key_len, &l_val) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[3]; - char *argv[3]; - int argc = 3; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%ld", l_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_long_long(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - long l1_val, l2_val; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll", &key, &key_len, &l1_val, &l2_val) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - int argc = 4; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%ld", l1_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - sprintf(str, "%ld", l2_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_dbl(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - double d_val; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sd", &key, &key_len, &d_val) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[3]; - char *argv[3]; - int argc = 3; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%f", d_val); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_key(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key1, *key2; - size_t key1_len, key2_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key1, &key1_len, &key2, &key2_len) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[3]; - char *argv[3]; - int argc = 3; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key1, key1_len) - SW_REDIS_COMMAND_ARGV_FILL(key2, key2_len) - redis_request(redis, argc, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_val(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key; - size_t key_len; - zval *z_value; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &key, &key_len, &z_value) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[3]; - char *argv[3]; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_value) - redis_request(redis, 3, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_NULL(return_value) && strncmp("ZRANK", cmd, cmd_len) == 0) { - RETURN_FALSE; - } -} - -static sw_inline void sw_redis_command_key_str(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key, *val; - size_t key_len, val_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len, &val, &val_len) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[3]; - char *argv[3]; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(val, val_len) - redis_request(redis, 3, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_key_str_str(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key, *val1, *val2; - size_t key_len, val1_len, val2_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &key, &key_len, &val1, &val1_len, &val2, &val2_len) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(val1, val1_len) - SW_REDIS_COMMAND_ARGV_FILL(val2, val2_len) - redis_request(redis, 4, argv, argvlen, return_value); -} - -static sw_inline void sw_redis_command_xrange(INTERNAL_FUNCTION_PARAMETERS, const char *cmd, int cmd_len) { - char *key, *val1, *val2; - size_t key_len, val1_len, val2_len; - zend_long count = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|l", &key, &key_len, &val1, &val1_len, &val2, &val2_len, &count) == - FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc; - argc = ZEND_NUM_ARGS() == 4 ? 6 : 4; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL(cmd, cmd_len) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(val1, val1_len) - SW_REDIS_COMMAND_ARGV_FILL(val2, val2_len) - if (count > 0) { - SW_REDIS_COMMAND_ARGV_FILL("COUNT", 5) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, ZEND_LONG_FMT, count); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -SW_EXTERN_C_BEGIN -static PHP_METHOD(swoole_redis_coro, __construct); -static PHP_METHOD(swoole_redis_coro, __destruct); -static PHP_METHOD(swoole_redis_coro, connect); -static PHP_METHOD(swoole_redis_coro, getAuth); -static PHP_METHOD(swoole_redis_coro, getDBNum); -static PHP_METHOD(swoole_redis_coro, getOptions); -static PHP_METHOD(swoole_redis_coro, setOptions); -static PHP_METHOD(swoole_redis_coro, getDefer); -static PHP_METHOD(swoole_redis_coro, setDefer); -static PHP_METHOD(swoole_redis_coro, recv); -static PHP_METHOD(swoole_redis_coro, request); -static PHP_METHOD(swoole_redis_coro, close); -/*---------------------Redis Command------------------------*/ -static PHP_METHOD(swoole_redis_coro, set); -static PHP_METHOD(swoole_redis_coro, setBit); -static PHP_METHOD(swoole_redis_coro, setEx); -static PHP_METHOD(swoole_redis_coro, psetEx); -static PHP_METHOD(swoole_redis_coro, lSet); -static PHP_METHOD(swoole_redis_coro, get); -static PHP_METHOD(swoole_redis_coro, mGet); -static PHP_METHOD(swoole_redis_coro, del); -static PHP_METHOD(swoole_redis_coro, hDel); -static PHP_METHOD(swoole_redis_coro, hSet); -static PHP_METHOD(swoole_redis_coro, hMSet); -static PHP_METHOD(swoole_redis_coro, hSetNx); -static PHP_METHOD(swoole_redis_coro, mSet); -static PHP_METHOD(swoole_redis_coro, mSetNx); -static PHP_METHOD(swoole_redis_coro, getKeys); -static PHP_METHOD(swoole_redis_coro, exists); -static PHP_METHOD(swoole_redis_coro, type); -static PHP_METHOD(swoole_redis_coro, strLen); -static PHP_METHOD(swoole_redis_coro, lPop); -static PHP_METHOD(swoole_redis_coro, blPop); -static PHP_METHOD(swoole_redis_coro, rPop); -static PHP_METHOD(swoole_redis_coro, brPop); -static PHP_METHOD(swoole_redis_coro, bRPopLPush); -static PHP_METHOD(swoole_redis_coro, lSize); -static PHP_METHOD(swoole_redis_coro, sSize); -static PHP_METHOD(swoole_redis_coro, sPop); -static PHP_METHOD(swoole_redis_coro, sMembers); -static PHP_METHOD(swoole_redis_coro, sRandMember); -static PHP_METHOD(swoole_redis_coro, persist); -static PHP_METHOD(swoole_redis_coro, ttl); -static PHP_METHOD(swoole_redis_coro, pttl); -static PHP_METHOD(swoole_redis_coro, zCard); -static PHP_METHOD(swoole_redis_coro, hLen); -static PHP_METHOD(swoole_redis_coro, hKeys); -static PHP_METHOD(swoole_redis_coro, hVals); -static PHP_METHOD(swoole_redis_coro, hGetAll); -static PHP_METHOD(swoole_redis_coro, restore); -static PHP_METHOD(swoole_redis_coro, dump); -static PHP_METHOD(swoole_redis_coro, debug); -static PHP_METHOD(swoole_redis_coro, renameKey); -static PHP_METHOD(swoole_redis_coro, renameNx); -static PHP_METHOD(swoole_redis_coro, rpoplpush); -static PHP_METHOD(swoole_redis_coro, randomKey); -static PHP_METHOD(swoole_redis_coro, pfadd); -static PHP_METHOD(swoole_redis_coro, pfcount); -static PHP_METHOD(swoole_redis_coro, pfmerge); -static PHP_METHOD(swoole_redis_coro, ping); -static PHP_METHOD(swoole_redis_coro, auth); -static PHP_METHOD(swoole_redis_coro, unwatch); -static PHP_METHOD(swoole_redis_coro, watch); -static PHP_METHOD(swoole_redis_coro, save); -static PHP_METHOD(swoole_redis_coro, bgSave); -static PHP_METHOD(swoole_redis_coro, lastSave); -static PHP_METHOD(swoole_redis_coro, flushDB); -static PHP_METHOD(swoole_redis_coro, flushAll); -static PHP_METHOD(swoole_redis_coro, dbSize); -static PHP_METHOD(swoole_redis_coro, bgrewriteaof); -static PHP_METHOD(swoole_redis_coro, time); -static PHP_METHOD(swoole_redis_coro, role); -static PHP_METHOD(swoole_redis_coro, setRange); -static PHP_METHOD(swoole_redis_coro, setNx); -static PHP_METHOD(swoole_redis_coro, getSet); -static PHP_METHOD(swoole_redis_coro, append); -static PHP_METHOD(swoole_redis_coro, lPushx); -static PHP_METHOD(swoole_redis_coro, lPush); -static PHP_METHOD(swoole_redis_coro, rPush); -static PHP_METHOD(swoole_redis_coro, rPushx); -static PHP_METHOD(swoole_redis_coro, sContains); -static PHP_METHOD(swoole_redis_coro, zScore); -static PHP_METHOD(swoole_redis_coro, zRank); -static PHP_METHOD(swoole_redis_coro, zRevRank); -static PHP_METHOD(swoole_redis_coro, hGet); -static PHP_METHOD(swoole_redis_coro, hMGet); -static PHP_METHOD(swoole_redis_coro, hExists); -static PHP_METHOD(swoole_redis_coro, publish); -static PHP_METHOD(swoole_redis_coro, zIncrBy); -static PHP_METHOD(swoole_redis_coro, zAdd); -static PHP_METHOD(swoole_redis_coro, zPopMin); -static PHP_METHOD(swoole_redis_coro, zPopMax); -static PHP_METHOD(swoole_redis_coro, bzPopMin); -static PHP_METHOD(swoole_redis_coro, bzPopMax); -static PHP_METHOD(swoole_redis_coro, zDeleteRangeByScore); -static PHP_METHOD(swoole_redis_coro, zCount); -static PHP_METHOD(swoole_redis_coro, zRange); -static PHP_METHOD(swoole_redis_coro, zRevRange); -static PHP_METHOD(swoole_redis_coro, zRangeByScore); -static PHP_METHOD(swoole_redis_coro, zRevRangeByScore); -static PHP_METHOD(swoole_redis_coro, zRangeByLex); -static PHP_METHOD(swoole_redis_coro, zRevRangeByLex); -static PHP_METHOD(swoole_redis_coro, zInter); -static PHP_METHOD(swoole_redis_coro, zUnion); -static PHP_METHOD(swoole_redis_coro, incrBy); -static PHP_METHOD(swoole_redis_coro, hIncrBy); -static PHP_METHOD(swoole_redis_coro, incr); -static PHP_METHOD(swoole_redis_coro, decrBy); -static PHP_METHOD(swoole_redis_coro, decr); -static PHP_METHOD(swoole_redis_coro, getBit); -static PHP_METHOD(swoole_redis_coro, lGet); -static PHP_METHOD(swoole_redis_coro, lInsert); -static PHP_METHOD(swoole_redis_coro, setTimeout); -static PHP_METHOD(swoole_redis_coro, pexpire); -static PHP_METHOD(swoole_redis_coro, expireAt); -static PHP_METHOD(swoole_redis_coro, pexpireAt); -static PHP_METHOD(swoole_redis_coro, move); -static PHP_METHOD(swoole_redis_coro, select); -static PHP_METHOD(swoole_redis_coro, getRange); -static PHP_METHOD(swoole_redis_coro, listTrim); -static PHP_METHOD(swoole_redis_coro, lGetRange); -static PHP_METHOD(swoole_redis_coro, lRem); -static PHP_METHOD(swoole_redis_coro, zDeleteRangeByRank); -static PHP_METHOD(swoole_redis_coro, incrByFloat); -static PHP_METHOD(swoole_redis_coro, hIncrByFloat); -static PHP_METHOD(swoole_redis_coro, bitCount); -static PHP_METHOD(swoole_redis_coro, bitOp); -static PHP_METHOD(swoole_redis_coro, sAdd); -static PHP_METHOD(swoole_redis_coro, sMove); -static PHP_METHOD(swoole_redis_coro, sDiff); -static PHP_METHOD(swoole_redis_coro, sDiffStore); -static PHP_METHOD(swoole_redis_coro, sUnion); -static PHP_METHOD(swoole_redis_coro, sUnionStore); -static PHP_METHOD(swoole_redis_coro, sInter); -static PHP_METHOD(swoole_redis_coro, sInterStore); -static PHP_METHOD(swoole_redis_coro, sRemove); -static PHP_METHOD(swoole_redis_coro, zDelete); -static PHP_METHOD(swoole_redis_coro, subscribe); -static PHP_METHOD(swoole_redis_coro, pSubscribe); -static PHP_METHOD(swoole_redis_coro, unsubscribe); -static PHP_METHOD(swoole_redis_coro, pUnSubscribe); -static PHP_METHOD(swoole_redis_coro, multi); -static PHP_METHOD(swoole_redis_coro, exec); -static PHP_METHOD(swoole_redis_coro, eval); -static PHP_METHOD(swoole_redis_coro, evalSha); -static PHP_METHOD(swoole_redis_coro, script); -static PHP_METHOD(swoole_redis_coro, xLen); -static PHP_METHOD(swoole_redis_coro, xAdd); -static PHP_METHOD(swoole_redis_coro, xRead); -static PHP_METHOD(swoole_redis_coro, xDel); -static PHP_METHOD(swoole_redis_coro, xRange); -static PHP_METHOD(swoole_redis_coro, xRevRange); -static PHP_METHOD(swoole_redis_coro, xTrim); -static PHP_METHOD(swoole_redis_coro, xGroupCreate); -static PHP_METHOD(swoole_redis_coro, xGroupSetId); -static PHP_METHOD(swoole_redis_coro, xGroupDestroy); -static PHP_METHOD(swoole_redis_coro, xGroupCreateConsumer); -static PHP_METHOD(swoole_redis_coro, xGroupDelConsumer); -static PHP_METHOD(swoole_redis_coro, xReadGroup); -static PHP_METHOD(swoole_redis_coro, xPending); -static PHP_METHOD(swoole_redis_coro, xAck); -static PHP_METHOD(swoole_redis_coro, xClaim); -static PHP_METHOD(swoole_redis_coro, xAutoClaim); -static PHP_METHOD(swoole_redis_coro, xInfoConsumers); -static PHP_METHOD(swoole_redis_coro, xInfoGroups); -static PHP_METHOD(swoole_redis_coro, xInfoStream); -SW_EXTERN_C_END -/*---------------------Redis Command End------------------------*/ -// clang-format off -static const zend_function_entry swoole_redis_coro_methods[] = -{ - PHP_ME(swoole_redis_coro, __construct, arginfo_swoole_redis_coro_construct, ZEND_ACC_PUBLIC | ZEND_ACC_DEPRECATED) - PHP_ME(swoole_redis_coro, __destruct, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, connect, arginfo_swoole_redis_coro_connect, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getAuth, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getDBNum, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getOptions, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setOptions, arginfo_swoole_redis_coro_setOptions, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getDefer, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setDefer, arginfo_swoole_redis_coro_setDefer, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, recv, arginfo_swoole_redis_coro_void, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, request, arginfo_swoole_redis_coro_request, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, close, arginfo_swoole_redis_coro_close, ZEND_ACC_PUBLIC) - /*---------------------Redis Command------------------------*/ - PHP_ME(swoole_redis_coro, set, arginfo_swoole_redis_coro_set, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setBit, arginfo_swoole_redis_coro_setBit, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setEx, arginfo_swoole_redis_coro_setex, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, psetEx, arginfo_swoole_redis_coro_psetex, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lSet, arginfo_swoole_redis_coro_lSet, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, get, arginfo_swoole_redis_coro_get, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, mGet, arginfo_swoole_redis_coro_mget, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, del, arginfo_swoole_redis_coro_del, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hDel, arginfo_swoole_redis_coro_hDel, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hSet, arginfo_swoole_redis_coro_hSet, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hMSet, arginfo_swoole_redis_coro_hMset, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hSetNx, arginfo_swoole_redis_coro_hSetNx, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, delete, del, arginfo_swoole_redis_coro_del, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, mSet, arginfo_swoole_redis_coro_mset, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, mSetNx, arginfo_swoole_redis_coro_msetnx, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getKeys, arginfo_swoole_redis_coro_getKeys, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, keys, getKeys, arginfo_swoole_redis_coro_getKeys, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, exists, arginfo_swoole_redis_coro_exists, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, type, arginfo_swoole_redis_coro_type, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, strLen, arginfo_swoole_redis_coro_strlen, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lPop, arginfo_swoole_redis_coro_lPop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, blPop, arginfo_swoole_redis_coro_blPop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, rPop, arginfo_swoole_redis_coro_rPop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, brPop, arginfo_swoole_redis_coro_brPop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bRPopLPush, arginfo_swoole_redis_coro_brpoplpush, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lSize, arginfo_swoole_redis_coro_lSize, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, lLen, lSize, arginfo_swoole_redis_coro_lLen, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sSize, arginfo_swoole_redis_coro_sSize, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, scard, sSize, arginfo_swoole_redis_coro_scard, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sPop, arginfo_swoole_redis_coro_sPop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sMembers, arginfo_swoole_redis_coro_sMembers, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, sGetMembers, sMembers, arginfo_swoole_redis_coro_key, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sRandMember, arginfo_swoole_redis_coro_sRandMember, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, persist, arginfo_swoole_redis_coro_persist, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, ttl, arginfo_swoole_redis_coro_ttl, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pttl, arginfo_swoole_redis_coro_pttl, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zCard, arginfo_swoole_redis_coro_zCard, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zSize, zCard, arginfo_swoole_redis_coro_zSize, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hLen, arginfo_swoole_redis_coro_hLen, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hKeys, arginfo_swoole_redis_coro_hKeys, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hVals, arginfo_swoole_redis_coro_hVals, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hGetAll, arginfo_swoole_redis_coro_hGetAll, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, debug, arginfo_swoole_redis_coro_debug, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, restore, arginfo_swoole_redis_coro_restore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, dump, arginfo_swoole_redis_coro_dump, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, renameKey, arginfo_swoole_redis_coro_renameKey, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, rename, renameKey, arginfo_swoole_redis_coro_rename, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, renameNx, arginfo_swoole_redis_coro_renameNx, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, rpoplpush, arginfo_swoole_redis_coro_rpoplpush, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, randomKey, arginfo_swoole_redis_coro_randomKey, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pfadd, arginfo_swoole_redis_coro_pfadd, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pfcount, arginfo_swoole_redis_coro_pfcount, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pfmerge, arginfo_swoole_redis_coro_pfmerge, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, ping, arginfo_swoole_redis_coro_ping, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, auth, arginfo_swoole_redis_coro_auth, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, unwatch, arginfo_swoole_redis_coro_unwatch, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, watch, arginfo_swoole_redis_coro_watch, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, save, arginfo_swoole_redis_coro_save, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bgSave, arginfo_swoole_redis_coro_bgSave, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lastSave, arginfo_swoole_redis_coro_lastSave, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, flushDB, arginfo_swoole_redis_coro_flushDB, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, flushAll, arginfo_swoole_redis_coro_flushAll, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, dbSize, arginfo_swoole_redis_coro_dbSize, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bgrewriteaof, arginfo_swoole_redis_coro_bgrewriteaof, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, time, arginfo_swoole_redis_coro_time, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, role, arginfo_swoole_redis_coro_role, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setRange, arginfo_swoole_redis_coro_setRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setNx, arginfo_swoole_redis_coro_setnx, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getSet, arginfo_swoole_redis_coro_getSet, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, append, arginfo_swoole_redis_coro_append, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lPushx, arginfo_swoole_redis_coro_lPushx, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lPush, arginfo_swoole_redis_coro_lPush, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, rPush, arginfo_swoole_redis_coro_rPush, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, rPushx, arginfo_swoole_redis_coro_rPushx, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sContains, arginfo_swoole_redis_coro_sContains, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, sismember, sContains, arginfo_swoole_redis_coro_key_value, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zScore, arginfo_swoole_redis_coro_zScore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRank, arginfo_swoole_redis_coro_zRank, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRevRank, arginfo_swoole_redis_coro_zRevRank, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hGet, arginfo_swoole_redis_coro_hGet, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hMGet, arginfo_swoole_redis_coro_hMget, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hExists, arginfo_swoole_redis_coro_hExists, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, publish, arginfo_swoole_redis_coro_publish, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zIncrBy, arginfo_swoole_redis_coro_zIncrBy, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zAdd, arginfo_swoole_redis_coro_zAdd, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zPopMin, arginfo_swoole_redis_coro_zPopMin, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zPopMax, arginfo_swoole_redis_coro_zPopMax, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bzPopMin, arginfo_swoole_redis_coro_bzPopMin, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bzPopMax, arginfo_swoole_redis_coro_bzPopMax, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zDeleteRangeByScore, arginfo_swoole_redis_coro_zDeleteRangeByScore, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zRemRangeByScore, zDeleteRangeByScore, arginfo_swoole_redis_coro_zRemRangeByScore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zCount, arginfo_swoole_redis_coro_zCount, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRange, arginfo_swoole_redis_coro_zRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRevRange, arginfo_swoole_redis_coro_zRevRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRangeByScore, arginfo_swoole_redis_coro_zRangeByScore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRevRangeByScore, arginfo_swoole_redis_coro_zRevRangeByScore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRangeByLex, arginfo_swoole_redis_coro_zRangeByLex, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zRevRangeByLex, arginfo_swoole_redis_coro_zRevRangeByLex, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zInter, arginfo_swoole_redis_coro_zInter, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zinterstore, zInter, arginfo_swoole_redis_coro_zinterstore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zUnion, arginfo_swoole_redis_coro_zUnion, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zunionstore, zUnion, arginfo_swoole_redis_coro_zunionstore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, incrBy, arginfo_swoole_redis_coro_incrBy, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hIncrBy, arginfo_swoole_redis_coro_hIncrBy, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, incr, arginfo_swoole_redis_coro_incr, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, decrBy, arginfo_swoole_redis_coro_decrBy, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, decr, arginfo_swoole_redis_coro_decr, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getBit, arginfo_swoole_redis_coro_getBit, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lInsert, arginfo_swoole_redis_coro_lInsert, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lGet, arginfo_swoole_redis_coro_lGet, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, lIndex, lGet, arginfo_swoole_redis_coro_key_long, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, setTimeout, arginfo_swoole_redis_coro_setTimeout, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, expire, setTimeout, arginfo_swoole_redis_coro_key_long, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pexpire, arginfo_swoole_redis_coro_pexpire, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, expireAt, arginfo_swoole_redis_coro_expireAt, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pexpireAt, arginfo_swoole_redis_coro_pexpireAt, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, move, arginfo_swoole_redis_coro_move, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, select, arginfo_swoole_redis_coro_select, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, getRange, arginfo_swoole_redis_coro_getRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, listTrim, arginfo_swoole_redis_coro_listTrim, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, ltrim, listTrim, arginfo_swoole_redis_coro_ltrim, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lGetRange, arginfo_swoole_redis_coro_lGetRange, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, lRange, lGetRange, arginfo_swoole_redis_coro_lrange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, lRem, arginfo_swoole_redis_coro_lrem, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, lRemove,lRem, arginfo_swoole_redis_coro_lRemove, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zDeleteRangeByRank, arginfo_swoole_redis_coro_zDeleteRangeByRank, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zRemRangeByRank, zDeleteRangeByRank, arginfo_swoole_redis_coro_zRemRangeByRank, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, incrByFloat, arginfo_swoole_redis_coro_incrByFloat, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, hIncrByFloat, arginfo_swoole_redis_coro_hIncrByFloat, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bitCount, arginfo_swoole_redis_coro_bitcount, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, bitOp, arginfo_swoole_redis_coro_bitop, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sAdd, arginfo_swoole_redis_coro_sAdd, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sMove, arginfo_swoole_redis_coro_sMove, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sDiff, arginfo_swoole_redis_coro_sDiff, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sDiffStore, arginfo_swoole_redis_coro_sDiffStore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sUnion, arginfo_swoole_redis_coro_sUnion, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sUnionStore, arginfo_swoole_redis_coro_sUnionStore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sInter, arginfo_swoole_redis_coro_sInter, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sInterStore, arginfo_swoole_redis_coro_sInterStore, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, sRemove, arginfo_swoole_redis_coro_sRemove, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, srem, sRemove, arginfo_swoole_redis_coro_key_value, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, zDelete, arginfo_swoole_redis_coro_zDelete, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zRemove, zDelete, arginfo_swoole_redis_coro_zRemove, ZEND_ACC_PUBLIC) - PHP_MALIAS(swoole_redis_coro, zRem, zDelete, arginfo_swoole_redis_coro_zRem, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pSubscribe, arginfo_swoole_redis_coro_psubscribe, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, subscribe, arginfo_swoole_redis_coro_subscribe, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, unsubscribe, arginfo_swoole_redis_coro_unsubscribe, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, pUnSubscribe, arginfo_swoole_redis_coro_punsubscribe, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, multi, arginfo_swoole_redis_coro_multi, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, exec, arginfo_swoole_redis_coro_exec, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, eval, arginfo_swoole_redis_coro_eval, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, evalSha, arginfo_swoole_redis_coro_evalsha, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, script, arginfo_swoole_redis_coro_script, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xLen, arginfo_swoole_redis_coro_xLen, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xAdd, arginfo_swoole_redis_coro_xAdd, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xRead, arginfo_swoole_redis_coro_xRead, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xDel, arginfo_swoole_redis_coro_xDel, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xRange, arginfo_swoole_redis_coro_xRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xRevRange, arginfo_swoole_redis_coro_xRevRange, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xTrim, arginfo_swoole_redis_coro_xTrim, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xGroupCreate, arginfo_swoole_redis_coro_xGroupCreate, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xGroupSetId, arginfo_swoole_redis_coro_xGroupSetId, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xGroupDestroy, arginfo_swoole_redis_coro_xGroupDestroy, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xGroupCreateConsumer, arginfo_swoole_redis_coro_xGroupCreateConsumer, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xGroupDelConsumer, arginfo_swoole_redis_coro_xGroupDelConsumer, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xReadGroup, arginfo_swoole_redis_coro_xReadGroup, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xPending, arginfo_swoole_redis_coro_xPending, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xAck, arginfo_swoole_redis_coro_xAck, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xClaim, arginfo_swoole_redis_coro_xClaim, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xAutoClaim, arginfo_swoole_redis_coro_xAutoClaim, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xInfoConsumers, arginfo_swoole_redis_coro_xInfoConsumers, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xInfoGroups, arginfo_swoole_redis_coro_xInfoGroups, ZEND_ACC_PUBLIC) - PHP_ME(swoole_redis_coro, xInfoStream, arginfo_swoole_redis_coro_xInfoStream, ZEND_ACC_PUBLIC) - /*---------------------Redis Command End------------------------*/ - PHP_FE_END -}; -// clang-format on - -void php_swoole_redis_coro_minit(int module_number) { - SW_INIT_CLASS_ENTRY(swoole_redis_coro, "Swoole\\Coroutine\\Redis", "Co\\Redis", swoole_redis_coro_methods); - SW_SET_CLASS_NOT_SERIALIZABLE(swoole_redis_coro); - SW_SET_CLASS_CLONEABLE(swoole_redis_coro, sw_zend_class_clone_deny); - SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_redis_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CREATE_WITH_ITS_OWN_HANDLERS(swoole_redis_coro); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_redis_coro, php_swoole_redis_coro_create_object, php_swoole_redis_coro_free_object, RedisClient, std); - - zend_declare_property_string(swoole_redis_coro_ce, ZEND_STRL("host"), "", ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_redis_coro_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_null(swoole_redis_coro_ce, ZEND_STRL("setting"), ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_redis_coro_ce, ZEND_STRL("sock"), -1, ZEND_ACC_PUBLIC); - zend_declare_property_bool(swoole_redis_coro_ce, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_redis_coro_ce, ZEND_STRL("errType"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_redis_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_string(swoole_redis_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); - - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_MODE_MULTI", SW_REDIS_MODE_MULTI); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_MODE_PIPELINE", SW_REDIS_MODE_PIPELINE); - - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_NOT_FOUND", SW_REDIS_TYPE_NOT_FOUND); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_STRING", SW_REDIS_TYPE_STRING); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_SET", SW_REDIS_TYPE_SET); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_LIST", SW_REDIS_TYPE_LIST); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_ZSET", SW_REDIS_TYPE_ZSET); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_TYPE_HASH", SW_REDIS_TYPE_HASH); - - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_IO", SW_REDIS_ERR_IO); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_OTHER", SW_REDIS_ERR_OTHER); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_EOF", SW_REDIS_ERR_EOF); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_PROTOCOL", SW_REDIS_ERR_PROTOCOL); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_OOM", SW_REDIS_ERR_OOM); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_CLOSED", SW_REDIS_ERR_CLOSED); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_NOAUTH", SW_REDIS_ERR_NOAUTH); - SW_REGISTER_LONG_CONSTANT("SWOOLE_REDIS_ERR_ALLOC", SW_REDIS_ERR_ALLOC); -} - -static void swoole_redis_coro_set_options(RedisClient *redis, zval *zoptions, bool backward_compatibility = false) { - zval *zsettings = - sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, redis->zobject, ZEND_STRL("setting"), 0); - HashTable *vht = Z_ARRVAL_P(zoptions); - zval *ztmp; - - php_array_merge(Z_ARRVAL_P(zsettings), vht); - - if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { - redis->connect_timeout = zval_get_double(ztmp); - if (redis->connect_timeout <= 0) { - redis->connect_timeout = SW_TIMER_MAX_SEC; - } - } - if (php_swoole_array_get_value(vht, "timeout", ztmp)) { - redis->timeout = zval_get_double(ztmp); - if (backward_compatibility) { - redis->connect_timeout = redis->timeout; - if (redis->connect_timeout <= 0) { - redis->connect_timeout = SW_TIMER_MAX_SEC; - } - } - if (redis->context) { - Socket *socket = swoole_redis_coro_get_socket(redis->context); - if (socket) { - socket->set_timeout(redis->timeout, Socket::TIMEOUT_RDWR); - } - } - } - if (php_swoole_array_get_value(vht, "serialize", ztmp)) { - redis->serialize = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "reconnect", ztmp)) { - redis->reconnect_interval = (uint8_t) SW_MIN(zval_get_long(ztmp), UINT8_MAX); - } - if (php_swoole_array_get_value(vht, "compatibility_mode", ztmp)) { - redis->compatibility_mode = zval_is_true(ztmp); - } -} - -static PHP_METHOD(swoole_redis_coro, __construct) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - zval *zsettings = sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); - zval *zset = nullptr; - - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_ARRAY(zset) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - if (redis->zobject) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); - RETURN_FALSE; - } - - redis->zobject = &redis->_zobject; - redis->_zobject = *ZEND_THIS; - - redis->connect_timeout = network::Socket::default_connect_timeout; - redis->timeout = network::Socket::default_read_timeout; - redis->reconnect_interval = 1; - - // settings init - add_assoc_double(zsettings, "connect_timeout", redis->connect_timeout); - add_assoc_double(zsettings, "timeout", redis->timeout); - add_assoc_bool(zsettings, "serialize", redis->serialize); - add_assoc_long(zsettings, "reconnect", redis->reconnect_interval); - // after connected - add_assoc_string(zsettings, "password", (char *) ""); - add_assoc_long(zsettings, "database", 0); - - if (zset) { - swoole_redis_coro_set_options(redis, zset, true); - } -} - -static PHP_METHOD(swoole_redis_coro, connect) { - zval *zobject = ZEND_THIS; - char *host = nullptr; - size_t host_len = 0; - zend_long port = 0; - zend_bool serialize = 0; - - SW_REDIS_COMMAND_CHECK - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lb", &host, &host_len, &port, &serialize) == FAILURE) { - RETURN_FALSE; - } - - zend_update_property_string(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("host"), host); - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("port"), port); - redis->serialize = serialize; - - if (swoole_redis_coro_connect(redis) > 0) { - // clear the error code only when the developer manually tries to connect successfully - // if the kernel retries automatically, keep silent. - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errType"), 0); - zend_update_property_long(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), 0); - zend_update_property_string(swoole_redis_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), ""); - RETURN_TRUE; - } else { - RETURN_FALSE; - } -} - -static PHP_METHOD(swoole_redis_coro, getAuth) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - if (redis->session.auth) { - zval *ztmp = sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); - if (php_swoole_array_get_value(Z_ARRVAL_P(ztmp), "password", ztmp)) { - RETURN_ZVAL(ztmp, 1, 0); - } - RETURN_EMPTY_STRING(); - } - RETURN_FALSE; -} - -static PHP_METHOD(swoole_redis_coro, getDBNum) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - if (!redis->context) { - RETURN_FALSE; - } - RETURN_LONG(redis->session.db_num); -} - -static PHP_METHOD(swoole_redis_coro, getOptions) { - RETURN_ZVAL( - sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, setOptions) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - zval *zoptions; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ARRAY(zoptions) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - swoole_redis_coro_set_options(redis, zoptions); - - RETURN_TRUE; -} - -static PHP_METHOD(swoole_redis_coro, getDefer) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - - RETURN_BOOL(redis->defer); -} - -static PHP_METHOD(swoole_redis_coro, setDefer) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - zend_bool defer = 1; - - if (redis->session.subscribe) { - php_swoole_fatal_error(E_WARNING, "you should not use setDefer after subscribe"); - RETURN_FALSE; - } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &defer) == FAILURE) { - RETURN_FALSE; - } - redis->defer = defer; - - RETURN_TRUE; -} - -static PHP_METHOD(swoole_redis_coro, recv) { - SW_REDIS_COMMAND_CHECK - - if (UNEXPECTED(!redis->context)) { - RETURN_FALSE; - } - if (UNEXPECTED(!redis->defer && !redis->session.subscribe)) { - php_swoole_fatal_error(E_WARNING, "you should not use recv without defer or subscribe"); - RETURN_FALSE; - } - - redisReply *reply; -_recv: - if (redisGetReply(redis->context, (void **) &reply) == REDIS_OK) { - swoole_redis_coro_parse_result(redis, return_value, reply); - freeReplyObject(reply); - - if (redis->session.subscribe) { - zval *ztype; - - if (!ZVAL_IS_ARRAY(return_value)) { - zval_ptr_dtor(return_value); - goto _error; - } - - ztype = zend_hash_index_find(Z_ARRVAL_P(return_value), 0); - if (Z_TYPE_P(ztype) == IS_STRING) { - char *type = Z_STRVAL_P(ztype); - - if (!strcmp(type, "unsubscribe") || !strcmp(type, "punsubscribe")) { - zval *znum = zend_hash_index_find(Z_ARRVAL_P(return_value), 2); - if (Z_LVAL_P(znum) == 0) { - redis->session.subscribe = false; - } - - return; - } else if (!strcmp(type, "message") || !strcmp(type, "pmessage") || !strcmp(type, "subscribe") || - !strcmp(type, "psubscribe")) { - return; - } - } - - zval_ptr_dtor(return_value); - goto _recv; - } - } else { - _error: - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), redis->context->err); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(redis->context->err)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), redis->context->errstr); - - swoole_redis_coro_close(redis); - RETURN_FALSE; - } -} - -static PHP_METHOD(swoole_redis_coro, close) { - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - RETURN_BOOL(swoole_redis_coro_close(redis)); -} - -static PHP_METHOD(swoole_redis_coro, __destruct) { - SW_PREVENT_USER_DESTRUCT(); -} - -static PHP_METHOD(swoole_redis_coro, set) { - char *key, *exp_type = nullptr, *set_type = nullptr; - size_t key_len, argc = 3; - zval *z_value, *z_opts = nullptr; - zend_long expire = -1; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &key, &key_len, &z_value, &z_opts) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - - if (z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY && Z_TYPE_P(z_opts) != IS_NULL) { - RETURN_FALSE; - } - - if (z_opts && ZVAL_IS_ARRAY(z_opts)) { - HashTable *kt = Z_ARRVAL_P(z_opts); - - zend_string *zkey; - zend_ulong idx; - zval *zv; - - /* Iterate our option array */ - ZEND_HASH_FOREACH_KEY_VAL(kt, idx, zkey, zv) { - /* Detect PX or EX argument and validate timeout */ - if (!exp_type && zkey && IS_EX_PX_ARG(ZSTR_VAL(zkey))) { - /* Set expire type */ - exp_type = ZSTR_VAL(zkey); - - /* Try to extract timeout */ - if (Z_TYPE_P(zv) == IS_LONG) { - expire = Z_LVAL_P(zv); - } else if (Z_TYPE_P(zv) == IS_STRING) { - expire = atol(Z_STRVAL_P(zv)); - } - - /* Expiry can't be set < 1 */ - if (expire < 1) { - RETURN_FALSE; - } - argc += 2; - } else if (!set_type && Z_TYPE_P(zv) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_P(zv))) { - argc += 1; - set_type = Z_STRVAL_P(zv); - } - (void) idx; - } - ZEND_HASH_FOREACH_END(); - } else if (z_opts && Z_TYPE_P(z_opts) == IS_LONG) { - /* Grab expiry and fail if it's < 1 */ - expire = Z_LVAL_P(z_opts); - /* Expiry can't be set < 1 */ - if (expire < 1) { - RETURN_FALSE; - } - argc += 1; - } - - SW_REDIS_COMMAND_ALLOC_ARGV - - int i = 0; - if (exp_type || set_type) { - SW_REDIS_COMMAND_ARGV_FILL("SET", 3) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_value) - - if (set_type) { - SW_REDIS_COMMAND_ARGV_FILL(set_type, (size_t) strlen(set_type)) - } - - if (exp_type) { - SW_REDIS_COMMAND_ARGV_FILL(exp_type, (size_t) strlen(exp_type)) - - char str[32]; - sprintf(str, ZEND_LONG_FMT, expire); - SW_REDIS_COMMAND_ARGV_FILL(str, (size_t) strlen(str)) - } - } else if (expire > 0) { - SW_REDIS_COMMAND_ARGV_FILL("SETEX", 5) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - char str[32]; - sprintf(str, ZEND_LONG_FMT, expire); - SW_REDIS_COMMAND_ARGV_FILL(str, (size_t) strlen(str)) - - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_value) - } else { - SW_REDIS_COMMAND_ARGV_FILL("SET", 3) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_value) - } - - redis_request(redis, argc, argv, argvlen, return_value); - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, setBit) { - char *key; - size_t key_len; - long offset; - zend_bool val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "slb", &key, &key_len, &offset, &val) == FAILURE) { - return; - } - - // Validate our offset - if (offset < SW_BITOP_MIN_OFFSET || offset > SW_BITOP_MAX_OFFSET) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "Invalid OFFSET for bitop command (must be between 0-2^32-1)"); - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - - SW_REDIS_COMMAND_ARGV_FILL("SETBIT", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - char str[32]; - sprintf(str, "%ld", offset); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - - SW_REDIS_COMMAND_ARGV_FILL(val ? "1" : "0", 1) - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, setEx) { - sw_redis_command_key_long_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SETEX")); -} - -static PHP_METHOD(swoole_redis_coro, psetEx) { - sw_redis_command_key_long_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PSETEX")); -} - -static PHP_METHOD(swoole_redis_coro, lSet) { - sw_redis_command_key_long_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LSET")); -} - -static PHP_METHOD(swoole_redis_coro, restore) { - sw_redis_command_key_long_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RESTORE")); -} - -static PHP_METHOD(swoole_redis_coro, dump) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DUMP")); -} - -static PHP_METHOD(swoole_redis_coro, debug) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DEBUG")); -} - -static PHP_METHOD(swoole_redis_coro, get) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("GET")); -} - -static PHP_METHOD(swoole_redis_coro, mGet) { - zval *z_args; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_args) == FAILURE) { - RETURN_FALSE; - } - int argc; - argc = zend_hash_num_elements(Z_ARRVAL_P(z_args)); - if (argc == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - int i = 0; - zval *value; - SW_REDIS_COMMAND_ARGV_FILL("MGET", 4) - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(z_args), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, hSet) { - char *key, *field; - size_t key_len, field_len; - zval *z_val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &key, &key_len, &field, &field_len, &z_val) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("HSET", 4) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(field, field_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_val) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, hMSet) { - char *key; - size_t key_len, argc; - zval *z_arr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len, &z_arr) == FAILURE) { - return; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - argc = argc * 2 + 2; - zval *value; - char buf[32]; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("HMSET", 5) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - zend_ulong idx; - zend_string *_key; - ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(z_arr), idx, _key, value) { - if (_key == nullptr) { - key_len = sw_snprintf(buf, sizeof(buf), "%ld", (long) idx); - key = (char *) buf; - } else { - key_len = ZSTR_LEN(_key); - key = ZSTR_VAL(_key); - } - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(value) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, hSetNx) { - char *key, *field; - size_t key_len, field_len; - zval *z_val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &key, &key_len, &field, &field_len, &z_val) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t argvlen[4]; - char *argv[4]; - convert_to_string(z_val); - SW_REDIS_COMMAND_ARGV_FILL("HSETNX", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(field, field_len) - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_val), Z_STRLEN_P(z_val)) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, hDel) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (argc < 2 || zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - RETURN_FALSE; - } - argc++; - int i = 0, j; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("HDEL", 4) - for (j = 0; j < argc - 1; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - efree(z_args); - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, watch) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("WATCH"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, del) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DEL"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sDiff) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SDIFF"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sDiffStore) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SDIFFSTORE"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sUnion) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SUNION"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sUnionStore) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SUNIONSTORE"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sInter) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SINTER"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, sInterStore) { - sw_redis_command_var_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SINTERSTORE"), 1, 0); -} - -static PHP_METHOD(swoole_redis_coro, mSet) { - zval *z_args; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_args) == FAILURE) { - RETURN_FALSE; - } - int argc; - argc = zend_hash_num_elements(Z_ARRVAL_P(z_args)); - if (argc == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - argc *= 2; - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - int i = 0; - SW_REDIS_COMMAND_ARGV_FILL("MSET", 4) - zval *value; - char buf[32]; - char *key; - uint32_t key_len; - zend_ulong idx; - zend_string *_key; - ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(z_args), idx, _key, value) { - if (_key == nullptr) { - key_len = sw_snprintf(buf, sizeof(buf), "%ld", (long) idx); - key = (char *) buf; - } else { - key_len = ZSTR_LEN(_key); - key = ZSTR_VAL(_key); - } - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(value) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, mSetNx) { - zval *z_args; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_args) == FAILURE) { - return; - } - int argc; - argc = zend_hash_num_elements(Z_ARRVAL_P(z_args)); - if (argc == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - argc *= 2; - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - int i = 0; - SW_REDIS_COMMAND_ARGV_FILL("MSETNX", 6) - zval *value; - char buf[32]; - char *key; - uint32_t key_len; - zend_ulong idx; - zend_string *_key; - ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(z_args), idx, _key, value) { - if (_key == nullptr) { - key_len = sw_snprintf(buf, sizeof(buf), "%ld", (long) idx); - key = (char *) buf; - } else { - key_len = ZSTR_LEN(_key); - key = ZSTR_VAL(_key); - } - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(value) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, getKeys) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("KEYS")); -} - -static PHP_METHOD(swoole_redis_coro, exists) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("EXISTS")); -} - -static PHP_METHOD(swoole_redis_coro, type) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("TYPE")); -} - -static PHP_METHOD(swoole_redis_coro, strLen) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("STRLEN")); -} - -static PHP_METHOD(swoole_redis_coro, lPop) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LPOP")); -} - -static PHP_METHOD(swoole_redis_coro, bRPopLPush) { - char *key1, *key2; - size_t key1_len, key2_len; - long timeout; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key1, &key1_len, &key2, &key2_len, &timeout) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int argc, i = 0; - argc = timeout < 0 ? 3 : 4; - SW_REDIS_COMMAND_ALLOC_ARGV - if (timeout < 0) { - SW_REDIS_COMMAND_ARGV_FILL("RPOPLPUSH", 9) - SW_REDIS_COMMAND_ARGV_FILL(key1, key1_len) - SW_REDIS_COMMAND_ARGV_FILL(key2, key2_len) - } else { - SW_REDIS_COMMAND_ARGV_FILL("BRPOPLPUSH", 10) - SW_REDIS_COMMAND_ARGV_FILL(key1, key1_len) - SW_REDIS_COMMAND_ARGV_FILL(key2, key2_len) - char str[32]; - sprintf(str, "%ld", timeout); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, blPop) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 1) { - efree(z_args); - return; - } - - zend_bool single_array = 0; - if (argc == 2 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])) + 2; - single_array = 1; - } else { - argc += 1; - } - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("BLPOP", 5) - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - zend_string *convert_str = zval_get_string(&z_args[1]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } else { - int j; - for (j = 0; j < argc - 1; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, brPop) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 1) { - efree(z_args); - return; - } - - zend_bool single_array = 0; - if (argc == 2 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])) + 2; - single_array = 1; - } else { - argc += 1; - } - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("BRPOP", 5) - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - zend_string *convert_str = zval_get_string(&z_args[1]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } else { - int j; - for (j = 0; j < argc - 1; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, rPop) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RPOP")); -} - -static PHP_METHOD(swoole_redis_coro, lSize) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LLEN")); -} - -static PHP_METHOD(swoole_redis_coro, sSize) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SCARD")); -} - -static PHP_METHOD(swoole_redis_coro, sPop) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SPOP")); -} - -static PHP_METHOD(swoole_redis_coro, sMembers) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SMEMBERS")); -} - -static PHP_METHOD(swoole_redis_coro, sRandMember) { - char *key; - size_t key_len; - zend_long count = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len, &count) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc, buf_len; - char buf[32]; - argc = ZEND_NUM_ARGS() == 2 ? 3 : 2; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("SRANDMEMBER", 11); - SW_REDIS_COMMAND_ARGV_FILL(key, key_len); - if (argc == 3) { - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", count); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len); - } - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, persist) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PERSIST")); -} - -static PHP_METHOD(swoole_redis_coro, ttl) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("TTL")); -} - -static PHP_METHOD(swoole_redis_coro, pttl) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PTTL")); -} - -static PHP_METHOD(swoole_redis_coro, zCard) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZCARD")); -} - -static PHP_METHOD(swoole_redis_coro, hLen) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HLEN")); -} - -static PHP_METHOD(swoole_redis_coro, hKeys) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HKEYS")); -} - -static PHP_METHOD(swoole_redis_coro, hVals) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HVALS")); -} - -static PHP_METHOD(swoole_redis_coro, hGetAll) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HGETALL")); -} - -static PHP_METHOD(swoole_redis_coro, renameKey) { - sw_redis_command_key_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RENAME")); -} - -static PHP_METHOD(swoole_redis_coro, renameNx) { - sw_redis_command_key_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RENAMENX")); -} - -static PHP_METHOD(swoole_redis_coro, rpoplpush) { - sw_redis_command_key_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RPOPLPUSH")); -} - -static PHP_METHOD(swoole_redis_coro, randomKey) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RANDOMKEY")); -} - -static PHP_METHOD(swoole_redis_coro, unwatch) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("UNWATCH")); -} - -static PHP_METHOD(swoole_redis_coro, pfadd) { - char *key; - size_t key_len, argc; - zval *z_arr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len, &z_arr) == FAILURE) { - return; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - argc = argc + 2; - zval *value; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("PFADD", 5) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(z_arr), value) { - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)); - zend_string_release(convert_str); - } - SW_HASHTABLE_FOREACH_END() - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, pfcount) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc != 1) { - efree(z_args); - RETURN_FALSE; - } - - zend_bool single_array = 0; - if (SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])); - single_array = 1; - } - - argc += 1; - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("PFCOUNT", 7) - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END() - } else { - zend_string *convert_str = zval_get_string(&z_args[0]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, pfmerge) { - char *key; - size_t key_len, argc; - zval *z_arr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len, &z_arr) == FAILURE) { - RETURN_FALSE; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - argc = argc + 2; - zval *value; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("PFMERGE", 7) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(z_arr), value) { - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)); - zend_string_release(convert_str); - } - SW_HASHTABLE_FOREACH_END() - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, ping) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PING")); -} - -static PHP_METHOD(swoole_redis_coro, auth) { - char *pw; - size_t pw_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &pw, &pw_len) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - zval *zsetting = sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); - add_assoc_stringl(zsetting, "password", pw, pw_len); - RETURN_BOOL(redis_auth(redis, pw, pw_len)); -} - -static PHP_METHOD(swoole_redis_coro, save) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SAVE")); -} - -static PHP_METHOD(swoole_redis_coro, bgSave) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("BGSAVE")); -} - -static PHP_METHOD(swoole_redis_coro, lastSave) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LASTSAVE")); -} - -static PHP_METHOD(swoole_redis_coro, flushDB) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("FLUSHDB")); -} - -static PHP_METHOD(swoole_redis_coro, flushAll) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("FLUSHALL")); -} - -static PHP_METHOD(swoole_redis_coro, dbSize) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DBSIZE")); -} - -static PHP_METHOD(swoole_redis_coro, bgrewriteaof) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("BGREWRITEAOF")); -} - -static PHP_METHOD(swoole_redis_coro, time) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("TIME")); -} - -static PHP_METHOD(swoole_redis_coro, role) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ROLE")); -} - -static PHP_METHOD(swoole_redis_coro, setRange) { - sw_redis_command_key_long_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SETRANGE")); -} - -static PHP_METHOD(swoole_redis_coro, setNx) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SETNX")); -} - -static PHP_METHOD(swoole_redis_coro, getSet) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("GETSET")); -} - -static PHP_METHOD(swoole_redis_coro, append) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("APPEND")); -} - -static PHP_METHOD(swoole_redis_coro, lPushx) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LPUSHX")); -} - -static PHP_METHOD(swoole_redis_coro, lPush) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LPUSH")); -} - -static PHP_METHOD(swoole_redis_coro, rPush) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RPUSH")); -} - -static PHP_METHOD(swoole_redis_coro, rPushx) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("RPUSHX")); -} - -static PHP_METHOD(swoole_redis_coro, sContains) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SISMEMBER")); -} - -static PHP_METHOD(swoole_redis_coro, zRange) { - char *key; - size_t key_len; - zend_long start, end; - zend_bool ws = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|b", &key, &key_len, &start, &end, &ws) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc; - argc = ZEND_NUM_ARGS() + 1; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZRANGE", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char buf[32]; - size_t buf_len; - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", start); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", end); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - if (ws) { - SW_REDIS_COMMAND_ARGV_FILL("WITHSCORES", 10) - } else { - argc = 4; - } - - redis_request(redis, argc, argv, argvlen, return_value); - - if (ws && redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zRevRange) { - char *key; - size_t key_len; - zend_long start, end; - zend_bool ws = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll|b", &key, &key_len, &start, &end, &ws) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc; - argc = ZEND_NUM_ARGS() + 1; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZREVRANGE", 9) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char buf[32]; - size_t buf_len; - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", start); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", end); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - if (ws) { - SW_REDIS_COMMAND_ARGV_FILL("WITHSCORES", 10) - } else { - argc = 4; - } - - redis_request(redis, argc, argv, argvlen, return_value); - - if (ws && redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zUnion) { - char *key, *agg_op; - size_t key_len; - zval *z_keys, *z_weights = nullptr; - HashTable *ht_keys, *ht_weights = nullptr; - size_t argc = 2, agg_op_len = 0, keys_count; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|a!s", &key, &key_len, &z_keys, &z_weights, &agg_op, &agg_op_len) == - FAILURE) { - RETURN_FALSE; - } - - ht_keys = Z_ARRVAL_P(z_keys); - - if ((keys_count = zend_hash_num_elements(ht_keys)) == 0) { - RETURN_FALSE; - } else { - argc += keys_count + 1; - } - - if (z_weights != nullptr) { - ht_weights = Z_ARRVAL_P(z_weights); - if (zend_hash_num_elements(ht_weights) != keys_count) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "WEIGHTS and keys array should be the same size!"); - RETURN_FALSE; - } - argc += keys_count + 1; - } - - // AGGREGATE option - if (agg_op_len != 0) { - if (strncasecmp(agg_op, "SUM", sizeof("SUM")) && strncasecmp(agg_op, "MIN", sizeof("MIN")) && - strncasecmp(agg_op, "MAX", sizeof("MAX"))) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "Invalid AGGREGATE option provided!"); - RETURN_FALSE; - } - - // "AGGREGATE" + type - argc += 2; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, j; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZUNIONSTORE", 11) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, "%zu", keys_count); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - - // Process input keys - zval *value; - SW_HASHTABLE_FOREACH_START(ht_keys, value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - - // Weights - if (ht_weights != nullptr) { - SW_REDIS_COMMAND_ARGV_FILL("WEIGHTS", 7) - - SW_HASHTABLE_FOREACH_START(ht_weights, value) - if (Z_TYPE_P(value) != IS_LONG && Z_TYPE_P(value) != IS_DOUBLE && - strncasecmp(Z_STRVAL_P(value), "inf", sizeof("inf")) != 0 && - strncasecmp(Z_STRVAL_P(value), "-inf", sizeof("-inf")) != 0 && - strncasecmp(Z_STRVAL_P(value), "+inf", sizeof("+inf")) != 0) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errMsg"), - "Weights must be numeric or '-inf','inf','+inf'"); - for (j = 0; j < i; j++) { - efree((void *) argv[j]); - } - SW_REDIS_COMMAND_FREE_ARGV - RETURN_FALSE; - } - switch (Z_TYPE_P(value)) { - case IS_LONG: - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(value)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - break; - case IS_DOUBLE: - buf_len = sprintf(buf, "%f", Z_DVAL_P(value)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - break; - case IS_STRING: - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(value), Z_STRLEN_P(value)) - break; - } - SW_HASHTABLE_FOREACH_END(); - } - - // AGGREGATE - if (agg_op_len != 0) { - SW_REDIS_COMMAND_ARGV_FILL("AGGREGATE", 9) - SW_REDIS_COMMAND_ARGV_FILL(agg_op, agg_op_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zInter) { - char *key, *agg_op; - size_t key_len; - zval *z_keys, *z_weights = nullptr; - HashTable *ht_keys, *ht_weights = nullptr; - size_t argc = 2, agg_op_len = 0, keys_count; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|a!s", &key, &key_len, &z_keys, &z_weights, &agg_op, &agg_op_len) == - FAILURE) { - RETURN_FALSE; - } - - ht_keys = Z_ARRVAL_P(z_keys); - - if ((keys_count = zend_hash_num_elements(ht_keys)) == 0) { - RETURN_FALSE; - } else { - argc += keys_count + 1; - } - - if (z_weights != nullptr) { - ht_weights = Z_ARRVAL_P(z_weights); - if (zend_hash_num_elements(ht_weights) != keys_count) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "WEIGHTS and keys array should be the same size!"); - RETURN_FALSE; - } - - argc += keys_count + 1; - } - - // AGGREGATE option - if (agg_op_len != 0) { - if (strncasecmp(agg_op, "SUM", sizeof("SUM")) && strncasecmp(agg_op, "MIN", sizeof("MIN")) && - strncasecmp(agg_op, "MAX", sizeof("MAX"))) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "Invalid AGGREGATE option provided!"); - RETURN_FALSE; - } - - // "AGGREGATE" + type - argc += 2; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, j; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZINTERSTORE", 11) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, "%zu", keys_count); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - - // Process input keys - zval *value; - SW_HASHTABLE_FOREACH_START(ht_keys, value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - - // Weights - if (ht_weights != nullptr) { - SW_REDIS_COMMAND_ARGV_FILL("WEIGHTS", 7) - - SW_HASHTABLE_FOREACH_START(ht_weights, value) - if (Z_TYPE_P(value) != IS_LONG && Z_TYPE_P(value) != IS_DOUBLE && - strncasecmp(Z_STRVAL_P(value), "inf", sizeof("inf")) != 0 && - strncasecmp(Z_STRVAL_P(value), "-inf", sizeof("-inf")) != 0 && - strncasecmp(Z_STRVAL_P(value), "+inf", sizeof("+inf")) != 0) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "Weights must be numeric or '-inf','inf','+inf'"); - for (j = 0; j < i; j++) { - efree((void *) argv[j]); - } - SW_REDIS_COMMAND_FREE_ARGV - RETURN_FALSE; - } - switch (Z_TYPE_P(value)) { - case IS_LONG: - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(value)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - break; - case IS_DOUBLE: - buf_len = sprintf(buf, "%f", Z_DVAL_P(value)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - break; - case IS_STRING: - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(value), Z_STRLEN_P(value)) - break; - } - SW_HASHTABLE_FOREACH_END(); - } - - // AGGREGATE - if (agg_op_len != 0) { - SW_REDIS_COMMAND_ARGV_FILL("AGGREGATE", 9) - SW_REDIS_COMMAND_ARGV_FILL(agg_op, agg_op_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zRangeByLex) { - char *key, *min, *max; - size_t key_len, min_len, max_len; - zend_long offset = 0, count = 0; - size_t argc = ZEND_NUM_ARGS(); - - /* We need either 3 or 5 arguments for this to be valid */ - if (argc != 3 && argc != 5) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), "Must pass either 3 or 5 arguments"); - RETURN_FALSE; - } - - if (zend_parse_parameters(argc, "sss|ll", &key, &key_len, &min, &min_len, &max, &max_len, &offset, &count) == - FAILURE) { - RETURN_FALSE; - } - - /* min and max must start with '(' or '[', or be either '-' or '+' */ - if (min_len < 1 || max_len < 1 || - (min[0] != '(' && min[0] != '[' && (min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) || - (max[0] != '(' && max[0] != '[' && (max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1))) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "min and max arguments must start with '[' or '('"); - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - argc = argc == 3 ? 4 : 7; - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZRANGEBYLEX", 11) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(min, min_len) - SW_REDIS_COMMAND_ARGV_FILL(max, max_len) - if (argc == 7) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, ZEND_LONG_FMT, offset); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - buf_len = sprintf(buf, ZEND_LONG_FMT, count); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zRevRangeByLex) { - char *key, *min, *max; - size_t key_len, min_len, max_len; - zend_long offset = 0, count = 0; - int argc = ZEND_NUM_ARGS(); - - /* We need either 3 or 5 arguments for this to be valid */ - if (argc != 3 && argc != 5) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), "Must pass either 3 or 5 arguments"); - RETURN_FALSE; - } - - if (zend_parse_parameters(argc, "sss|ll", &key, &key_len, &min, &min_len, &max, &max_len, &offset, &count) == - FAILURE) { - RETURN_FALSE; - } - - /* min and max must start with '(' or '[', or be either '-' or '+' */ - if (min_len < 1 || max_len < 1 || - (min[0] != '(' && min[0] != '[' && (min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) || - (max[0] != '(' && max[0] != '[' && (max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1))) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "min and max arguments must start with '[' or '('"); - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - argc = argc == 3 ? 4 : 7; - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZREVRANGEBYLEX", 14) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(min, min_len) - SW_REDIS_COMMAND_ARGV_FILL(max, max_len) - if (argc == 7) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, ZEND_LONG_FMT, offset); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - buf_len = sprintf(buf, ZEND_LONG_FMT, count); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zRangeByScore) { - char *key; - size_t key_len; - char *start, *end; - size_t start_len, end_len; - long limit_low, limit_high; - zval *z_opt = nullptr, *z_ele; - zend_bool withscores = 0, has_limit = 0; - HashTable *ht_opt; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len, &start, &start_len, &end, &end_len, &z_opt) == - FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int argc = 4, i = 0; - // Check for an options array - if (z_opt && ZVAL_IS_ARRAY(z_opt)) { - ht_opt = Z_ARRVAL_P(z_opt); - - // Check for WITHSCORES - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("withscores"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - withscores = 1; - argc++; - } - - // LIMIT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("limit")))) { - HashTable *ht_limit = Z_ARRVAL_P(z_ele); - zval *z_off, *z_cnt; - z_off = zend_hash_index_find(ht_limit, 0); - z_cnt = zend_hash_index_find(ht_limit, 1); - if (z_off && z_cnt && Z_TYPE_P(z_off) == IS_LONG && Z_TYPE_P(z_cnt) == IS_LONG) { - has_limit = 1; - limit_low = Z_LVAL_P(z_off); - limit_high = Z_LVAL_P(z_cnt); - argc += 3; - } - } - } - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZRANGEBYSCORE", 13) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(start, start_len) - SW_REDIS_COMMAND_ARGV_FILL(end, end_len) - - if (withscores) { - SW_REDIS_COMMAND_ARGV_FILL("WITHSCORES", 10) - } - if (has_limit) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, "%ld", limit_low); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - buf_len = sprintf(buf, "%ld", limit_high); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - - if (withscores && redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zRevRangeByScore) { - char *key; - size_t key_len; - char *start, *end; - size_t start_len, end_len; - long limit_low, limit_high; - zval *z_opt = nullptr, *z_ele; - zend_bool withscores = 0, has_limit = 0; - HashTable *ht_opt; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a", &key, &key_len, &start, &start_len, &end, &end_len, &z_opt) == - FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int argc = 4, i = 0; - // Check for an options array - if (z_opt && ZVAL_IS_ARRAY(z_opt)) { - ht_opt = Z_ARRVAL_P(z_opt); - - // Check for WITHSCORES - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("withscores"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - withscores = 1; - argc++; - } - - // LIMIT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("limit")))) { - HashTable *ht_limit = Z_ARRVAL_P(z_ele); - zval *z_off, *z_cnt; - z_off = zend_hash_index_find(ht_limit, 0); - z_cnt = zend_hash_index_find(ht_limit, 1); - if (z_off && z_cnt && Z_TYPE_P(z_off) == IS_LONG && Z_TYPE_P(z_cnt) == IS_LONG) { - has_limit = 1; - limit_low = Z_LVAL_P(z_off); - limit_high = Z_LVAL_P(z_cnt); - argc += 3; - } - } - } - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZREVRANGEBYSCORE", 16) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(start, start_len) - SW_REDIS_COMMAND_ARGV_FILL(end, end_len) - - if (withscores) { - SW_REDIS_COMMAND_ARGV_FILL("WITHSCORES", 10) - } - if (has_limit) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, "%ld", limit_low); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - buf_len = sprintf(buf, "%ld", limit_high); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - } - - redis_request(redis, argc, argv, argvlen, return_value); - - if (withscores && redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zIncrBy) { - char *key; - size_t key_len; - double incrby; - zval *z_val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sdz", &key, &key_len, &incrby, &z_val) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK; - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("ZINCRBY", 7) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char buf[32]; - size_t buf_len; - buf_len = sprintf(buf, "%f", incrby); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_val) - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, zAdd) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) { - efree(z_args); - RETURN_FALSE; - } - - if (argc > 0) { - convert_to_string(&z_args[0]); - } - if (argc < 3 || SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) != IS_STRING) { - efree(z_args); - RETURN_FALSE; - } - - int i = 0, j, k, valid_params; - valid_params = argc - 1; - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZADD", 4) - SW_REDIS_COMMAND_ARGV_FILL(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), - (size_t) SW_REDIS_COMMAND_ARGS_STRLEN(z_args[0])) - k = 1; - - if (SW_REDIS_COMMAND_ARGS_TYPE(z_args[k]) == IS_STRING && IS_NX_XX_ARG(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[k]))) { - SW_REDIS_COMMAND_ARGV_FILL(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[k]), - (size_t) SW_REDIS_COMMAND_ARGS_STRLEN(z_args[k])) - k++; - valid_params--; - } - - if (SW_REDIS_COMMAND_ARGS_TYPE(z_args[k]) == IS_STRING && - strncasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[k]), "CH", 2) == 0) { - SW_REDIS_COMMAND_ARGV_FILL("CH", 2) - k++; - valid_params--; - } - - if (SW_REDIS_COMMAND_ARGS_TYPE(z_args[k]) == IS_STRING && - strncasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[k]), "INCR", 4) == 0) { - SW_REDIS_COMMAND_ARGV_FILL("INCR", 4) - k++; - valid_params--; - } - - if (valid_params % 2 != 0) { - for (i = 0; i < 1 + k; i++) { - efree((void *) argv[i]); - } - SW_REDIS_COMMAND_FREE_ARGV - efree(z_args); - RETURN_FALSE; - } - - char buf[32]; - size_t buf_len; - for (j = k; j < argc - 1; j += 2) { - buf_len = sw_snprintf(buf, sizeof(buf), "%f", zval_get_double(&z_args[j])); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(SW_REDIS_COMMAND_ARGS_REF(z_args[j + 1])) - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zPopMin) { - char *key; - size_t key_len; - zend_long count = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len, &count) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc, buf_len; - char buf[32]; - argc = ZEND_NUM_ARGS() == 2 ? 3 : 2; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZPOPMIN", 7); - SW_REDIS_COMMAND_ARGV_FILL(key, key_len); - if (argc == 3) { - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", count); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len); - } - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zPopMax) { - char *key; - size_t key_len; - zend_long count = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &key, &key_len, &count) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0, argc, buf_len; - char buf[32]; - argc = ZEND_NUM_ARGS() == 2 ? 3 : 2; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("ZPOPMAX", 7); - SW_REDIS_COMMAND_ARGV_FILL(key, key_len); - if (argc == 3) { - buf_len = sw_snprintf(buf, sizeof(buf), "%" PRId64 "", count); - SW_REDIS_COMMAND_ARGV_FILL((char *) buf, buf_len); - } - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, bzPopMin) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 1) { - efree(z_args); - return; - } - - zend_bool single_array = 0; - if (argc == 2 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])) + 2; - single_array = 1; - } else { - argc += 1; - } - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("BZPOPMIN", 8) - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - zend_string *convert_str = zval_get_string(&z_args[1]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } else { - int j; - for (j = 0; j < argc - 1; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, bzPopMax) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 1) { - efree(z_args); - return; - } - - zend_bool single_array = 0; - if (argc == 2 && SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) == IS_ARRAY) { - argc = zend_hash_num_elements(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0])) + 2; - single_array = 1; - } else { - argc += 1; - } - int i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("BZPOPMAX", 8) - if (single_array) { - zval *value; - SW_HASHTABLE_FOREACH_START(SW_REDIS_COMMAND_ARGS_ARRVAL(z_args[0]), value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - zend_string *convert_str = zval_get_string(&z_args[1]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } else { - int j; - for (j = 0; j < argc - 1; ++j) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - } - efree(z_args); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, zScore) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZSCORE")); -} - -static PHP_METHOD(swoole_redis_coro, zRank) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZRANK")); -} - -static PHP_METHOD(swoole_redis_coro, zRevRank) { - sw_redis_command_key_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZREVRANK")); -} - -static PHP_METHOD(swoole_redis_coro, hGet) { - sw_redis_command_key_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HGET")); -} - -static PHP_METHOD(swoole_redis_coro, hMGet) { - char *key; - zval *z_arr; - size_t argc, key_len; - HashTable *ht_chan; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa", &key, &key_len, &z_arr) == FAILURE) { - return; - } - - ht_chan = Z_ARRVAL_P(z_arr); - - if ((argc = zend_hash_num_elements(ht_chan)) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - zval *value; - int i = 0; - argc = argc + 2; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("HMGET", 5) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_HASHTABLE_FOREACH_START(ht_chan, value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - size_t index = 0; - zval *zkey, *zvalue; - zval zret; - array_init(&zret); - - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_arr), zkey) { - zend::String zkey_str(zkey); - - zvalue = zend_hash_index_find(Z_ARRVAL_P(return_value), index++); - if (ZVAL_IS_NULL(zvalue)) { - add_assoc_bool_ex(&zret, zkey_str.val(), zkey_str.len(), 0); - } else { - Z_ADDREF_P(zvalue); - add_assoc_zval_ex(&zret, zkey_str.val(), zkey_str.len(), zvalue); - } - } - ZEND_HASH_FOREACH_END(); - - zval_ptr_dtor(return_value); - RETVAL_ZVAL(&zret, 1, 1); - } -} - -static PHP_METHOD(swoole_redis_coro, hExists) { - sw_redis_command_key_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("HEXISTS")); - - RedisClient *redis = php_swoole_get_redis_client(ZEND_THIS); - if (redis->compatibility_mode && ZVAL_IS_LONG(return_value)) { - RETURN_BOOL(zval_get_long(return_value)); - } -} - -static PHP_METHOD(swoole_redis_coro, publish) { - sw_redis_command_key_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PUBLISH")); -} - -static PHP_METHOD(swoole_redis_coro, zDeleteRangeByScore) { - sw_redis_command_key_str_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZREMRANGEBYSCORE")); -} - -static PHP_METHOD(swoole_redis_coro, zCount) { - sw_redis_command_key_str_str(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZCOUNT")); -} - -static PHP_METHOD(swoole_redis_coro, incrBy) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("INCRBY")); -} - -static PHP_METHOD(swoole_redis_coro, hIncrBy) { - char *key, *mem; - size_t key_len, mem_len; - long byval; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &key, &key_len, &mem, &mem_len, &byval) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("HINCRBY", 7) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(mem, mem_len) - char str[32]; - sprintf(str, "%ld", byval); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, hIncrByFloat) { - char *key, *mem; - size_t key_len, mem_len; - double byval; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssd", &key, &key_len, &mem, &mem_len, &byval) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("HINCRBYFLOAT", 12) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(mem, mem_len) - char str[32]; - sprintf(str, "%f", byval); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, incr) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("INCR")); -} - -static PHP_METHOD(swoole_redis_coro, decrBy) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DECRBY")); -} - -static PHP_METHOD(swoole_redis_coro, decr) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("DECR")); -} - -static PHP_METHOD(swoole_redis_coro, getBit) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("GETBIT")); -} - -static PHP_METHOD(swoole_redis_coro, lInsert) { - char *key, *pos; - size_t key_len, pos_len; - zval *z_val, *z_pivot; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sszz", &key, &key_len, &pos, &pos_len, &z_pivot, &z_val) == FAILURE) { - return; - } - - if (strncasecmp(pos, "after", 5) && strncasecmp(pos, "before", 6)) { - php_swoole_error(E_WARNING, "Position must be either 'BEFORE' or 'AFTER'"); - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[5]; - char *argv[5]; - - SW_REDIS_COMMAND_ARGV_FILL("LINSERT", 7) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(pos, pos_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_pivot) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_val) - redis_request(redis, 5, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, lGet) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LINDEX")); -} - -static PHP_METHOD(swoole_redis_coro, setTimeout) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("EXPIRE")); -} - -static PHP_METHOD(swoole_redis_coro, pexpire) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PEXPIRE")); -} - -static PHP_METHOD(swoole_redis_coro, expireAt) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("EXPIREAT")); -} - -static PHP_METHOD(swoole_redis_coro, pexpireAt) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("PEXPIREAT")); -} - -static PHP_METHOD(swoole_redis_coro, move) { - sw_redis_command_key_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("MOVE")); -} - -static PHP_METHOD(swoole_redis_coro, select) { - zend_long db_number; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_LONG(db_number) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - SW_REDIS_COMMAND_CHECK - zval *zsetting = sw_zend_read_and_convert_property_array(swoole_redis_coro_ce, ZEND_THIS, ZEND_STRL("setting"), 0); - add_assoc_long(zsetting, "database", db_number); - RETURN_BOOL(redis_select_db(redis, db_number)); -} - -static PHP_METHOD(swoole_redis_coro, getRange) { - sw_redis_command_key_long_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("GETRANGE")); -} - -static PHP_METHOD(swoole_redis_coro, listTrim) { - sw_redis_command_key_long_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LTRIM")); -} - -static PHP_METHOD(swoole_redis_coro, lGetRange) { - sw_redis_command_key_long_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("LRANGE")); -} - -static PHP_METHOD(swoole_redis_coro, lRem) { - char *key; - size_t key_len; - zend_long count = 0; - zval *z_val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|l", &key, &key_len, &z_val, &count) == FAILURE) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("LREM", 4) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%d", (int) count); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_val) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, zDeleteRangeByRank) { - sw_redis_command_key_long_long(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZREMRANGEBYRANK")); -} - -static PHP_METHOD(swoole_redis_coro, incrByFloat) { - sw_redis_command_key_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("INCRBYFLOAT")); -} - -static PHP_METHOD(swoole_redis_coro, bitCount) { - char *key; - size_t key_len; - zend_long start = 0, end = -1; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &key, &key_len, &start, &end) == FAILURE) { - return; - } - - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("BITCOUNT", 8) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - char str[32]; - sprintf(str, "%d", (int) start); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - sprintf(str, "%d", (int) end); - SW_REDIS_COMMAND_ARGV_FILL(str, strlen(str)) - - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, bitOp) { - int argc = ZEND_NUM_ARGS(); - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || argc < 3 || - SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) != IS_STRING) { - efree(z_args); - return; - } - - int j, i = 0; - argc++; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("BITOP", 5) - SW_REDIS_COMMAND_ARGV_FILL(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), SW_REDIS_COMMAND_ARGS_STRLEN(z_args[0])) - for (j = 1; j < argc - 1; j++) { - zend_string *convert_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - } - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV - efree(z_args); -} - -static PHP_METHOD(swoole_redis_coro, sMove) { - char *src, *dst; - size_t src_len, dst_len; - zval *z_val; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssz", &src, &src_len, &dst, &dst_len, &z_val) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - - int i = 0; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("SMOVE", 5) - SW_REDIS_COMMAND_ARGV_FILL(src, src_len) - SW_REDIS_COMMAND_ARGV_FILL(dst, dst_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(z_val) - redis_request(redis, 4, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, sAdd) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SADD")); -} - -static PHP_METHOD(swoole_redis_coro, sRemove) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("SREM")); -} - -static PHP_METHOD(swoole_redis_coro, zDelete) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("ZREM")); -} - -static sw_inline void redis_subscribe(INTERNAL_FUNCTION_PARAMETERS, const char *cmd) { - zval *z_arr; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - if (redis->defer) { - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errType"), SW_REDIS_ERR_OTHER); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errCode"), - sw_redis_convert_err(SW_REDIS_ERR_OTHER)); - zend_update_property_string(swoole_redis_coro_ce, - SW_Z8_OBJ_P(ZEND_THIS), - ZEND_STRL("errMsg"), - "subscribe cannot be used with defer enabled"); - RETURN_FALSE; - } - - HashTable *ht_chan = Z_ARRVAL_P(z_arr); - size_t chan_num = zend_hash_num_elements(ht_chan); - int argc = 1 + chan_num, i = 0; - SW_REDIS_COMMAND_ALLOC_ARGV - - SW_REDIS_COMMAND_ARGV_FILL(cmd, strlen(cmd)); - - zval *value; - SW_HASHTABLE_FOREACH_START(ht_chan, value) - zend_string *convert_str = zval_get_string(value); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)) - zend_string_release(convert_str); - SW_HASHTABLE_FOREACH_END(); - - redis->defer = true; - redis_request(redis, argc, argv, argvlen, return_value); - redis->defer = false; - SW_REDIS_COMMAND_FREE_ARGV - - if (Z_TYPE_P(return_value) == IS_TRUE) { - redis->session.subscribe = true; - } -} - -static PHP_METHOD(swoole_redis_coro, subscribe) { - redis_subscribe(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SUBSCRIBE"); -} - -static PHP_METHOD(swoole_redis_coro, pSubscribe) { - redis_subscribe(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PSUBSCRIBE"); -} - -static PHP_METHOD(swoole_redis_coro, unsubscribe) { - redis_subscribe(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNSUBSCRIBE"); -} - -static PHP_METHOD(swoole_redis_coro, pUnSubscribe) { - redis_subscribe(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUNSUBSCRIBE"); -} - -static PHP_METHOD(swoole_redis_coro, multi) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("MULTI")); -} - -static PHP_METHOD(swoole_redis_coro, exec) { - sw_redis_command_empty(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("EXEC")); -} - -static PHP_METHOD(swoole_redis_coro, request) { - SW_REDIS_COMMAND_CHECK - - zval *params = nullptr; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", ¶ms) == FAILURE) { - RETURN_FALSE; - } - - int argc = zend_hash_num_elements(Z_ARRVAL_P(params)); - int i = 0; - zval *value; - - SW_REDIS_COMMAND_ALLOC_ARGV - - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(params), value) - if (i == argc) { - break; - } - zend_string *convert_str = zval_get_string(value); - argvlen[i] = ZSTR_LEN(convert_str); - argv[i] = estrndup(ZSTR_VAL(convert_str), ZSTR_LEN(convert_str)); - zend_string_release(convert_str); - i++; - SW_HASHTABLE_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, eval) { - char *script; - size_t script_len; - zval *params = nullptr; - zend_long keys_num = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|al", &script, &script_len, ¶ms, &keys_num) == FAILURE) { - RETURN_FALSE; - } - - HashTable *params_ht = nullptr; - uint32_t params_num = 0; - if (params) { - params_ht = Z_ARRVAL_P(params); - params_num = zend_hash_num_elements(params_ht); - } - - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t *argvlen = (size_t *) emalloc(sizeof(size_t) * (params_num + 3)); - char **argv = (char **) emalloc(sizeof(char *) * (params_num + 3)); - - SW_REDIS_COMMAND_ARGV_FILL("EVAL", 4) - SW_REDIS_COMMAND_ARGV_FILL(script, script_len) - - char keys_num_str[32] = {}; - sprintf(keys_num_str, ZEND_LONG_FMT, keys_num); - SW_REDIS_COMMAND_ARGV_FILL(keys_num_str, strlen(keys_num_str)); - - if (params_ht) { - zval *param; - SW_HASHTABLE_FOREACH_START(params_ht, param) - zend_string *param_str = zval_get_string(param); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(param_str), ZSTR_LEN(param_str)) - zend_string_release(param_str); - SW_HASHTABLE_FOREACH_END(); - } - - redis_request(redis, params_num + 3, argv, argvlen, return_value); - efree(argvlen); - efree(argv); -} - -static PHP_METHOD(swoole_redis_coro, evalSha) { - char *sha; - size_t sha_len; - zval *params = nullptr; - long keys_num = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|al", &sha, &sha_len, ¶ms, &keys_num) == FAILURE) { - RETURN_FALSE; - } - - HashTable *params_ht = nullptr; - uint32_t params_num = 0; - if (params) { - params_ht = Z_ARRVAL_P(params); - params_num = zend_hash_num_elements(params_ht); - } - - SW_REDIS_COMMAND_CHECK - int i = 0; - size_t *argvlen = (size_t *) emalloc(sizeof(size_t) * (params_num + 3)); - char **argv = (char **) emalloc(sizeof(char *) * (params_num + 3)); - - SW_REDIS_COMMAND_ARGV_FILL("EVALSHA", 7) - SW_REDIS_COMMAND_ARGV_FILL(sha, sha_len) - - char keys_num_str[32] = {}; - sprintf(keys_num_str, "%ld", keys_num); - SW_REDIS_COMMAND_ARGV_FILL(keys_num_str, strlen(keys_num_str)); - - if (params) { - zval *param; - SW_HASHTABLE_FOREACH_START(params_ht, param) - zend_string *param_str = zval_get_string(param); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(param_str), ZSTR_LEN(param_str)) - zend_string_release(param_str); - SW_HASHTABLE_FOREACH_END(); - } - - redis_request(redis, params_num + 3, argv, argvlen, return_value); - efree(argvlen); - efree(argv); -} - -static PHP_METHOD(swoole_redis_coro, script) { - int argc = ZEND_NUM_ARGS(); - if (argc < 1) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGS_ARR - if (zend_get_parameters_array(ht, argc, z_args) == FAILURE || SW_REDIS_COMMAND_ARGS_TYPE(z_args[0]) != IS_STRING) { - efree(z_args); - RETURN_FALSE; - } - - int i = 0; - if (!strcasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), "flush") || - !strcasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), "kill")) { - size_t argvlen[2]; - char *argv[2]; - SW_REDIS_COMMAND_ARGV_FILL("SCRIPT", 6) - SW_REDIS_COMMAND_ARGV_FILL(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), SW_REDIS_COMMAND_ARGS_STRLEN(z_args[0])) - redis_request(redis, 2, argv, argvlen, return_value); - efree(z_args); - } else if (!strcasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), "exists")) { - if (argc < 2) { - efree(z_args); - RETURN_FALSE; - } else { - size_t *argvlen = (size_t *) emalloc(sizeof(size_t) * (argc + 1)); - char **argv = (char **) emalloc(sizeof(char *) * (argc + 1)); - SW_REDIS_COMMAND_ARGV_FILL("SCRIPT", 6) - SW_REDIS_COMMAND_ARGV_FILL("EXISTS", 6) - int j = 1; - for (; j < argc; j++) { - zend_string *z_arg_str = zval_get_string(&z_args[j]); - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(z_arg_str), ZSTR_LEN(z_arg_str)) - zend_string_release(z_arg_str); - } - - redis_request(redis, argc + 1, argv, argvlen, return_value); - efree(argvlen); - efree(argv); - efree(z_args); - } - } else if (!strcasecmp(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[0]), "load")) { - if (argc < 2 || SW_REDIS_COMMAND_ARGS_TYPE(z_args[1]) != IS_STRING) { - efree(z_args); - RETURN_FALSE; - } else { - size_t argvlen[3]; - char *argv[3]; - SW_REDIS_COMMAND_ARGV_FILL("SCRIPT", 6) - SW_REDIS_COMMAND_ARGV_FILL("LOAD", 4) - SW_REDIS_COMMAND_ARGV_FILL(SW_REDIS_COMMAND_ARGS_STRVAL(z_args[1]), SW_REDIS_COMMAND_ARGS_STRLEN(z_args[1])) - redis_request(redis, 3, argv, argvlen, return_value); - efree(z_args); - } - } else { - efree(z_args); - RETURN_FALSE; - } -} - -static PHP_METHOD(swoole_redis_coro, xLen) { - sw_redis_command_key(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("XLEN")); -} - -static PHP_METHOD(swoole_redis_coro, xAdd) { - zval *z_options = nullptr, *z_ele; - HashTable *ht_opt, *ht_ele; - char *key, *id; - size_t key_len, id_len; - zval *z_arr; - int argc, options_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa|a", &key, &key_len, &id, &id_len, &z_arr, &z_options) == FAILURE) { - return; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_arr))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - int i = 0; - argc = argc * 2 + 3; - zval *value; - char buf[32]; - size_t buf_len; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XADD", 4) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - int has_maxlen_minid = 0; - int can_limit = 0; - // NOMKSTREAM - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("nomkstream"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL("NOMKSTREAM", 10) - options_argc++; - } - // MAXLEN - if (has_maxlen_minid == 0 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("maxlen")))) { - has_maxlen_minid = 1; - if (Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("MAXLEN", 6) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } else if (Z_TYPE_P(z_ele) == IS_ARRAY) { - ht_ele = Z_ARRVAL_P(z_ele); - zval *z_maxlen_p1 = zend_hash_index_find(ht_ele, 0); - zval *z_maxlen_p2 = zend_hash_index_find(ht_ele, 1); - if (Z_TYPE_P(z_maxlen_p1) == IS_STRING && Z_TYPE_P(z_maxlen_p2) == IS_LONG) { - char *maxlen_p1 = Z_STRVAL_P(z_maxlen_p1); - zend_long maxlen_p2 = Z_LVAL_P(z_maxlen_p2); - if ((strcmp(maxlen_p1, "=") == 0 || strcmp(maxlen_p1, "~") == 0) && maxlen_p2 >= 0) { - if ((strcmp(maxlen_p1, "~") == 0)) { - can_limit = 1; - } - SW_REDIS_COMMAND_ARGV_FILL("MAXLEN", 6) - SW_REDIS_COMMAND_ARGV_FILL(maxlen_p1, 1) - buf_len = sprintf(buf, ZEND_LONG_FMT, maxlen_p2); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 3; - } - } - } - } - // MINID - if (has_maxlen_minid == 0 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("minid")))) { - has_maxlen_minid = 1; - if (Z_TYPE_P(z_ele) == IS_STRING && Z_STRLEN_P(z_ele) > 0) { - SW_REDIS_COMMAND_ARGV_FILL("MINID", 5) - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)) - options_argc += 2; - } else if (Z_TYPE_P(z_ele) == IS_ARRAY) { - ht_ele = Z_ARRVAL_P(z_ele); - zval *z_minid_p1 = zend_hash_index_find(ht_ele, 0); - zval *z_minid_p2 = zend_hash_index_find(ht_ele, 1); - if (Z_TYPE_P(z_minid_p1) == IS_STRING && Z_TYPE_P(z_minid_p2) == IS_STRING) { - char *minid_p1 = Z_STRVAL_P(z_minid_p1); - char *minid_p2 = Z_STRVAL_P(z_minid_p2); - if ((strcmp(minid_p1, "=") == 0 || strcmp(minid_p1, "~") == 0) && strlen(minid_p2) > 0) { - if ((strcmp(minid_p1, "~") == 0)) { - can_limit = 1; - } - SW_REDIS_COMMAND_ARGV_FILL("MINID", 5) - SW_REDIS_COMMAND_ARGV_FILL(minid_p1, 1) - SW_REDIS_COMMAND_ARGV_FILL(minid_p2, strlen(minid_p2)) - options_argc += 3; - } - } - } - } - // LIMIT - if (can_limit == 1 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("limit"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - // id - SW_REDIS_COMMAND_ARGV_FILL(id, id_len) - - // k-v - zend_ulong idx; - zend_string *_key; - ZEND_HASH_FOREACH_KEY_VAL_IND(Z_ARRVAL_P(z_arr), idx, _key, value) { - if (_key == nullptr) { - key_len = sw_snprintf(buf, sizeof(buf), ZEND_LONG_FMT, idx); - key = (char *) buf; - } else { - key_len = ZSTR_LEN(_key); - key = ZSTR_VAL(_key); - } - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL_WITH_SERIALIZE(value) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xRead) { - zval *z_streams = nullptr, *z_options = nullptr, *z_ele; - HashTable *ht_opt; - int i = 0, argc = 0, options_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|a", &z_streams, &z_options) == FAILURE) { - RETURN_FALSE; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_streams))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - - argc = argc * 2 + 2; - char buf[32]; - size_t buf_len; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XREAD", 5) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - // COUNT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("count"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("COUNT", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // BLOCK - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("block"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("BLOCK", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - // streams - SW_REDIS_COMMAND_ARGV_FILL("STREAMS", 7) - zend_long _num_key; - zend_string *_str_key; - zval *_val; - ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(z_streams), _num_key, _str_key) { - if (_str_key == NULL) { - _str_key = zend_long_to_str(_num_key); - } - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(_str_key), ZSTR_LEN(_str_key)) - } - ZEND_HASH_FOREACH_END(); - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_streams), _val) { - convert_to_string(_val); - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(_val), Z_STRLEN_P(_val)) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xRange) { - sw_redis_command_xrange(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("XRANGE")); -} - -static PHP_METHOD(swoole_redis_coro, xRevRange) { - sw_redis_command_xrange(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("XREVRANGE")); -} - -static PHP_METHOD(swoole_redis_coro, xTrim) { - zval *z_options = nullptr, *z_ele; - HashTable *ht_opt, *ht_ele; - int i = 0, argc = 2, options_argc = 0; - char buf[32], *key; - size_t buf_len, key_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|a", &key, &key_len, &z_options) == FAILURE) { - RETURN_FALSE; - } - if (php_swoole_array_length_safe(z_options) < 1) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XTRIM", 5) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - int has_maxlen_minid = 0; - int can_limit = 0; - // MAXLEN - if (has_maxlen_minid == 0 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("maxlen")))) { - has_maxlen_minid = 1; - if (Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("MAXLEN", 6) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } else if (Z_TYPE_P(z_ele) == IS_ARRAY) { - ht_ele = Z_ARRVAL_P(z_ele); - zval *z_maxlen_p1 = zend_hash_index_find(ht_ele, 0); - zval *z_maxlen_p2 = zend_hash_index_find(ht_ele, 1); - if (Z_TYPE_P(z_maxlen_p1) == IS_STRING && Z_TYPE_P(z_maxlen_p2) == IS_LONG) { - char *maxlen_p1 = Z_STRVAL_P(z_maxlen_p1); - zend_long maxlen_p2 = Z_LVAL_P(z_maxlen_p2); - if ((strcmp(maxlen_p1, "=") == 0 || strcmp(maxlen_p1, "~") == 0) && maxlen_p2 >= 0) { - if ((strcmp(maxlen_p1, "~") == 0)) { - can_limit = 1; - } - SW_REDIS_COMMAND_ARGV_FILL("MAXLEN", 6) - SW_REDIS_COMMAND_ARGV_FILL(maxlen_p1, 1) - buf_len = sprintf(buf, ZEND_LONG_FMT, maxlen_p2); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 3; - } - } - } - } - // MINID - if (has_maxlen_minid == 0 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("minid")))) { - has_maxlen_minid = 1; - if (Z_TYPE_P(z_ele) == IS_STRING && Z_STRLEN_P(z_ele) > 0) { - SW_REDIS_COMMAND_ARGV_FILL("MINID", 5) - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)) - options_argc += 2; - } else if (Z_TYPE_P(z_ele) == IS_ARRAY) { - ht_ele = Z_ARRVAL_P(z_ele); - zval *z_minid_p1 = zend_hash_index_find(ht_ele, 0); - zval *z_minid_p2 = zend_hash_index_find(ht_ele, 1); - if (Z_TYPE_P(z_minid_p1) == IS_STRING && Z_TYPE_P(z_minid_p2) == IS_STRING) { - char *minid_p1 = Z_STRVAL_P(z_minid_p1); - char *minid_p2 = Z_STRVAL_P(z_minid_p2); - if ((strcmp(minid_p1, "=") == 0 || strcmp(minid_p1, "~") == 0) && strlen(minid_p2) > 0) { - if ((strcmp(minid_p1, "~") == 0)) { - can_limit = 1; - } - SW_REDIS_COMMAND_ARGV_FILL("MINID", 5) - SW_REDIS_COMMAND_ARGV_FILL(minid_p1, 1) - SW_REDIS_COMMAND_ARGV_FILL(minid_p2, strlen(minid_p2)) - options_argc += 3; - } - } - } - } - // LIMIT - if (can_limit == 1 && (z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("limit"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("LIMIT", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xDel) { - sw_redis_command_key_var_val(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRL("XDEL")); -} - -static PHP_METHOD(swoole_redis_coro, xGroupCreate) { - char *key, *group_name, *id; - size_t key_len, group_name_len, id_len; - zend_bool mkstream = 0; - - if (zend_parse_parameters( - ZEND_NUM_ARGS(), "sss|b", &key, &key_len, &group_name, &group_name_len, &id, &id_len, &mkstream) == - FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 5; - size_t argvlen[6]; - char *argv[6]; - SW_REDIS_COMMAND_ARGV_FILL("XGROUP", 6) - SW_REDIS_COMMAND_ARGV_FILL("CREATE", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(id, id_len) - if (mkstream) { - SW_REDIS_COMMAND_ARGV_FILL("MKSTREAM", 8) - argc = 6; - } - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, xGroupSetId) { - char *key, *group_name, *id; - size_t key_len, group_name_len, id_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss", &key, &key_len, &group_name, &group_name_len, &id, &id_len) == - FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 5; - size_t argvlen[5]; - char *argv[5]; - SW_REDIS_COMMAND_ARGV_FILL("XGROUP", 6) - SW_REDIS_COMMAND_ARGV_FILL("CREATECONSUMER", 14) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(id, id_len) - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, xGroupDestroy) { - char *key, *group_name; - size_t key_len, group_name_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len, &group_name, &group_name_len) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 4; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("XGROUP", 6) - SW_REDIS_COMMAND_ARGV_FILL("DESTROY", 7) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, xGroupCreateConsumer) { - char *key, *group_name, *consumer_name; - size_t key_len, group_name_len, consumer_name_len; - - if (zend_parse_parameters( - ZEND_NUM_ARGS(), "sss", &key, &key_len, &group_name, &group_name_len, &consumer_name, &consumer_name_len) == - FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 5; - size_t argvlen[5]; - char *argv[5]; - SW_REDIS_COMMAND_ARGV_FILL("XGROUP", 6) - SW_REDIS_COMMAND_ARGV_FILL("CREATECONSUMER", 14) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(consumer_name, consumer_name_len) - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, xGroupDelConsumer) { - char *key, *group_name, *consumer_name; - size_t key_len, group_name_len, consumer_name_len; - - if (zend_parse_parameters( - ZEND_NUM_ARGS(), "sss", &key, &key_len, &group_name, &group_name_len, &consumer_name, &consumer_name_len) == - FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 5; - size_t argvlen[5]; - char *argv[5]; - SW_REDIS_COMMAND_ARGV_FILL("XGROUP", 6) - SW_REDIS_COMMAND_ARGV_FILL("DELCONSUMER", 11) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(consumer_name, consumer_name_len) - - redis_request(redis, argc, argv, argvlen, return_value); -} - -static PHP_METHOD(swoole_redis_coro, xReadGroup) { - char *group_name, *consumer_name; - size_t group_name_len, consumer_name_len; - zval *z_streams = nullptr, *z_options = nullptr, *z_ele; - HashTable *ht_opt; - int i = 0, argc = 0, options_argc = 0; - char buf[32]; - size_t buf_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), - "ssa|a", - &group_name, - &group_name_len, - &consumer_name, - &consumer_name_len, - &z_streams, - &z_options) == FAILURE) { - RETURN_FALSE; - } - if ((argc = zend_hash_num_elements(Z_ARRVAL_P(z_streams))) == 0) { - RETURN_FALSE; - } - SW_REDIS_COMMAND_CHECK - argc = argc * 2 + 5; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XREADGROUP", 10) - SW_REDIS_COMMAND_ARGV_FILL("GROUP", 5) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(consumer_name, consumer_name_len) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - // COUNT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("count"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("COUNT", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // BLOCK - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("block"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("BLOCK", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // NOACK - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("noack"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL("NOACK", 5) - options_argc++; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - // streams - SW_REDIS_COMMAND_ARGV_FILL("STREAMS", 7) - zend_long _num_key; - zend_string *_str_key; - zval *_val; - ZEND_HASH_FOREACH_KEY(Z_ARRVAL_P(z_streams), _num_key, _str_key) { - if (_str_key == NULL) { - _str_key = zend_long_to_str(_num_key); - } - SW_REDIS_COMMAND_ARGV_FILL(ZSTR_VAL(_str_key), ZSTR_LEN(_str_key)) - } - ZEND_HASH_FOREACH_END(); - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_streams), _val) { - convert_to_string(_val); - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(_val), Z_STRLEN_P(_val)) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xPending) { - char *key, *group_name; - size_t key_len, group_name_len; - zval *z_options = nullptr, *z_ele; - HashTable *ht_opt; - int i = 0, argc = 3, options_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|a", &key, &key_len, &group_name, &group_name_len, &z_options) == - FAILURE) { - RETURN_FALSE; - } - - char buf[32]; - size_t buf_len; - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XPENDING", 8) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - // IDLE - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("idle"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("IDLE", 4) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // START - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("start"))) && Z_TYPE_P(z_ele) == IS_STRING) { - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)) - options_argc++; - } - // END - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("end"))) && Z_TYPE_P(z_ele) == IS_STRING) { - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)) - options_argc++; - } - // COUNT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("count"))) && Z_TYPE_P(z_ele) == IS_LONG) { - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc++; - } - // CONSUMER - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("consumer"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele)) - options_argc++; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xAck) { - char *key, *group_name; - size_t key_len, group_name_len; - zval *z_id = nullptr; - int i = 0, argc = 3, id_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssa", &key, &key_len, &group_name, &group_name_len, &z_id) == FAILURE) { - RETURN_FALSE; - } - if ((id_argc = zend_hash_num_elements(Z_ARRVAL_P(z_id))) == 0) { - RETURN_FALSE; - } - argc += id_argc; - SW_REDIS_COMMAND_CHECK - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XACK", 4) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - - // id - zval *_id; - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_id), _id) { - convert_to_string(_id); - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(_id), Z_STRLEN_P(_id)) - } - ZEND_HASH_FOREACH_END(); - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xClaim) { - char *key, *group_name, *consumer_name; - size_t key_len, group_name_len, consumer_name_len; - zend_long min_idle_time = 0; - zval *z_id = nullptr, *z_options = nullptr, *z_ele; - HashTable *ht_opt; - int i = 0, argc = 5, id_argc = 0, options_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), - "sssla|a", - &key, - &key_len, - &group_name, - &group_name_len, - &consumer_name, - &consumer_name_len, - &min_idle_time, - &z_id, - &z_options) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - id_argc = zend_hash_num_elements(Z_ARRVAL_P(z_id)); - argc += id_argc; - char buf[32]; - size_t buf_len; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XCLAIM", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(consumer_name, consumer_name_len) - buf_len = sprintf(buf, ZEND_LONG_FMT, min_idle_time); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - - // id - zval *_id; - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_id), _id) { - convert_to_string(_id); - SW_REDIS_COMMAND_ARGV_FILL(Z_STRVAL_P(_id), Z_STRLEN_P(_id)) - } - ZEND_HASH_FOREACH_END(); - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - // IDLE - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("idle"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("IDLE", 4) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // TIME - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("time"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("TIME", 4) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // RETRYCOUNT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("retrycount"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("RETRYCOUNT", 10) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // FORCE - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("force"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL("FORCE", 5) - options_argc++; - } - // JUSTID - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("justid"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL("JUSTID", 6) - options_argc++; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xAutoClaim) { - char *key, *group_name, *consumer_name, *start; - size_t key_len, group_name_len, consumer_name_len, start_len; - zend_long min_idle_time = 0; - zval *z_options = nullptr, *z_ele; - HashTable *ht_opt; - int i = 0, argc = 6, options_argc = 0; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), - "sssls|a", - &key, - &key_len, - &group_name, - &group_name_len, - &consumer_name, - &consumer_name_len, - &min_idle_time, - &start, - &start_len, - &z_options) == FAILURE) { - RETURN_FALSE; - } - - SW_REDIS_COMMAND_CHECK - char buf[32]; - size_t buf_len; - SW_REDIS_COMMAND_ALLOC_ARGV - SW_REDIS_COMMAND_ARGV_FILL("XAUTOCLAIM", 10) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - SW_REDIS_COMMAND_ARGV_FILL(consumer_name, consumer_name_len) - buf_len = sprintf(buf, ZEND_LONG_FMT, min_idle_time); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - SW_REDIS_COMMAND_ARGV_FILL(start, start_len) - - // options - if (z_options && ZVAL_IS_ARRAY(z_options)) { - ht_opt = Z_ARRVAL_P(z_options); - // COUNT - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("count"))) && Z_TYPE_P(z_ele) == IS_LONG) { - SW_REDIS_COMMAND_ARGV_FILL("COUNT", 5) - buf_len = sprintf(buf, ZEND_LONG_FMT, Z_LVAL_P(z_ele)); - SW_REDIS_COMMAND_ARGV_FILL(buf, buf_len) - options_argc += 2; - } - // JUSTID - if ((z_ele = zend_hash_str_find(ht_opt, ZEND_STRL("justid"))) && Z_TYPE_P(z_ele) == IS_TRUE) { - SW_REDIS_COMMAND_ARGV_FILL("JUSTID", 6) - options_argc++; - } - } - - SW_REDIS_COMMAND_INCREASE_ARGV(argc + options_argc) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } - - SW_REDIS_COMMAND_FREE_ARGV -} - -static PHP_METHOD(swoole_redis_coro, xInfoConsumers) { - char *key, *group_name; - size_t key_len, group_name_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &key, &key_len, &group_name, &group_name_len) == FAILURE) { - return; - } - - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 4; - size_t argvlen[4]; - char *argv[4]; - SW_REDIS_COMMAND_ARGV_FILL("XINFO", 5) - SW_REDIS_COMMAND_ARGV_FILL("CONSUMERS", 9) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - SW_REDIS_COMMAND_ARGV_FILL(group_name, group_name_len) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } -} - -static PHP_METHOD(swoole_redis_coro, xInfoGroups) { - char *key; - size_t key_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) { - return; - } - - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 3; - size_t argvlen[3]; - char *argv[3]; - SW_REDIS_COMMAND_ARGV_FILL("XINFO", 5) - SW_REDIS_COMMAND_ARGV_FILL("GROUPS", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } -} - -static PHP_METHOD(swoole_redis_coro, xInfoStream) { - char *key; - size_t key_len; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) { - return; - } - SW_REDIS_COMMAND_CHECK - int i = 0, argc = 3; - size_t argvlen[3]; - char *argv[3]; - SW_REDIS_COMMAND_ARGV_FILL("XINFO", 5) - SW_REDIS_COMMAND_ARGV_FILL("STREAM", 6) - SW_REDIS_COMMAND_ARGV_FILL(key, key_len) - - redis_request(redis, argc, argv, argvlen, return_value); - - if (redis->compatibility_mode && ZVAL_IS_ARRAY(return_value)) { - swoole_redis_handle_assoc_array_result(return_value, true); - } -} - -static void swoole_redis_coro_parse_result(RedisClient *redis, zval *return_value, redisReply *reply) { - int j; - zval _val, *val = &_val; - - switch (reply->type) { - case REDIS_REPLY_INTEGER: - ZVAL_LONG(return_value, reply->integer); - break; - - case REDIS_REPLY_DOUBLE: - ZVAL_DOUBLE(return_value, reply->dval); - break; - - case REDIS_REPLY_BOOL: - ZVAL_BOOL(return_value, reply->integer); - break; - - case REDIS_REPLY_ERROR: - ZVAL_FALSE(return_value); - if (redis->context->err == 0) { - if (strncmp(reply->str, "NOAUTH", 6) == 0) { - redis->context->err = SW_REDIS_ERR_NOAUTH; - } else { - redis->context->err = SW_REDIS_ERR_OTHER; - } - size_t str_len = strlen(reply->str); - memcpy(redis->context->errstr, reply->str, SW_MIN(str_len, sizeof(redis->context->errstr) - 1)); - } - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), redis->context->err); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(redis->context->err)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), redis->context->errstr); - break; - - case REDIS_REPLY_STATUS: - if (redis->context->err == 0) { - if (reply->len > 0) { - if (strncmp(reply->str, "OK", 2) == 0) { - ZVAL_TRUE(return_value); - break; - } - long l; - if (strncmp(reply->str, "string", 6) == 0) { - l = SW_REDIS_TYPE_STRING; - } else if (strncmp(reply->str, "set", 3) == 0) { - l = SW_REDIS_TYPE_SET; - } else if (strncmp(reply->str, "list", 4) == 0) { - l = SW_REDIS_TYPE_LIST; - } else if (strncmp(reply->str, "zset", 4) == 0) { - l = SW_REDIS_TYPE_ZSET; - } else if (strncmp(reply->str, "hash", 4) == 0) { - l = SW_REDIS_TYPE_HASH; - } else { - l = SW_REDIS_TYPE_NOT_FOUND; - } - ZVAL_LONG(return_value, l); - } else { - ZVAL_TRUE(return_value); - } - } else { - ZVAL_FALSE(return_value); - zend_update_property_long( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errType"), redis->context->err); - zend_update_property_long(swoole_redis_coro_ce, - SW_Z8_OBJ_P(redis->zobject), - ZEND_STRL("errCode"), - sw_redis_convert_err(redis->context->err)); - zend_update_property_string( - swoole_redis_coro_ce, SW_Z8_OBJ_P(redis->zobject), ZEND_STRL("errMsg"), redis->context->errstr); - } - break; - - case REDIS_REPLY_STRING: - if (redis->serialize) { - char *reserve_str = reply->str; - php_unserialize_data_t s_ht; - PHP_VAR_UNSERIALIZE_INIT(s_ht); - if (!php_var_unserialize(return_value, - (const unsigned char **) &reply->str, - (const unsigned char *) reply->str + reply->len, - &s_ht)) { - ZVAL_STRINGL(return_value, reply->str, reply->len); - } - PHP_VAR_UNSERIALIZE_DESTROY(s_ht); - reply->str = reserve_str; - } else { - ZVAL_STRINGL(return_value, reply->str, reply->len); - } - break; - - case REDIS_REPLY_ARRAY: - array_init(return_value); - for (j = 0; j < (int) reply->elements; j++) { - swoole_redis_coro_parse_result(redis, val, reply->element[j]); - (void) add_next_index_zval(return_value, val); - } - break; - - case REDIS_REPLY_NIL: - default: - ZVAL_NULL(return_value); - return; - } -} diff --git a/ext-src/swoole_redis_server.cc b/ext-src/swoole_redis_server.cc index edb8be271e..fbb7b76e41 100644 --- a/ext-src/swoole_redis_server.cc +++ b/ext-src/swoole_redis_server.cc @@ -29,13 +29,16 @@ using swoole::Connection; using swoole::ListenPort; using swoole::RecvData; using swoole::Server; +using swoole::String; namespace Redis = swoole::redis; zend_class_entry *swoole_redis_server_ce; zend_object_handlers swoole_redis_server_handlers; -static std::unordered_map redis_handlers; +static SW_THREAD_LOCAL std::unordered_map redis_handlers; + +static bool redis_response_format(String *buf, zend_long type, zval *value); SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_redis_server, setHandler); @@ -44,7 +47,7 @@ static PHP_METHOD(swoole_redis_server, format); SW_EXTERN_C_END // clang-format off -const zend_function_entry swoole_redis_server_methods[] = +static constexpr zend_function_entry swoole_redis_server_methods[] = { PHP_ME(swoole_redis_server, setHandler, arginfo_class_Swoole_Redis_Server_setHandler, ZEND_ACC_PUBLIC) PHP_ME(swoole_redis_server, getHandler, arginfo_class_Swoole_Redis_Server_getHandler, ZEND_ACC_PUBLIC) @@ -55,7 +58,7 @@ const zend_function_entry swoole_redis_server_methods[] = void php_swoole_redis_server_minit(int module_number) { SW_INIT_CLASS_ENTRY_EX( - swoole_redis_server, "Swoole\\Redis\\Server", nullptr, swoole_redis_server_methods, swoole_server); + swoole_redis_server, R"(Swoole\Redis\Server)", nullptr, swoole_redis_server_methods, swoole_server); SW_SET_CLASS_NOT_SERIALIZABLE(swoole_redis_server); SW_SET_CLASS_CLONEABLE(swoole_redis_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_redis_server, sw_zend_class_unset_property_deny); @@ -70,17 +73,17 @@ void php_swoole_redis_server_minit(int module_number) { } void php_swoole_redis_server_rshutdown() { - for (auto i = redis_handlers.begin(); i != redis_handlers.end(); i++) { - sw_zend_fci_cache_discard(&i->second); + for (const auto &redis_handler : redis_handlers) { + sw_callable_free(redis_handler.second); } redis_handlers.clear(); } int php_swoole_redis_server_onReceive(Server *serv, RecvData *req) { - int fd = req->info.fd; + auto fd = req->info.fd; Connection *conn = serv->get_connection_by_session_id(fd); if (!conn) { - swoole_warning("connection[%d] is closed", fd); + swoole_warning("connection[%ld] is closed", fd); return SW_ERR; } @@ -156,7 +159,11 @@ int php_swoole_redis_server_onReceive(Server *serv, RecvData *req) { char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%.*s", command_len, command); +#if PHP_VERSION_ID >= 80400 + zend_str_tolower(_command, _command_len); +#else php_strtolower(_command, _command_len); +#endif auto i = redis_handlers.find(std::string(_command, _command_len)); if (i == redis_handlers.end()) { @@ -165,14 +172,14 @@ int php_swoole_redis_server_onReceive(Server *serv, RecvData *req) { return serv->send(fd, err_msg, length) ? SW_OK : SW_ERR; } - zend_fcall_info_cache *fci_cache = &i->second; + auto fci_cache = i->second; zval args[2]; zval retval; ZVAL_LONG(&args[0], fd); args[1] = zparams; - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, &retval, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(fci_cache->ptr(), 2, args, &retval, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onRequest with command '%.*s' handler error", ZSTR_VAL(swoole_redis_server_ce->name), @@ -195,37 +202,38 @@ static PHP_METHOD(swoole_redis_server, setHandler) { size_t command_len; zval *zcallback; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &command, &command_len, &zcallback) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(command, command_len) + Z_PARAM_ZVAL(zcallback) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (command_len == 0 || command_len >= SW_REDIS_MAX_COMMAND_SIZE) { php_swoole_fatal_error(E_ERROR, "invalid command"); RETURN_FALSE; } - char *func_name; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(zcallback, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); + auto fci_cache = sw_callable_create(zcallback); + if (!fci_cache) { return; } - efree(func_name); char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%s", command); +#if PHP_VERSION_ID >= 80400 + zend_str_tolower(_command, _command_len); +#else php_strtolower(_command, _command_len); +#endif zend_update_property(swoole_redis_server_ce, SW_Z8_OBJ_P(ZEND_THIS), _command, _command_len, zcallback); std::string key(_command, _command_len); auto i = redis_handlers.find(key); if (i != redis_handlers.end()) { - sw_zend_fci_cache_discard(&i->second); + sw_callable_free(i->second); } - sw_zend_fci_cache_persist(fci_cache); - redis_handlers[key] = *fci_cache; + redis_handlers[key] = fci_cache; RETURN_TRUE; } @@ -234,125 +242,146 @@ static PHP_METHOD(swoole_redis_server, getHandler) { char *command; size_t command_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &command, &command_len) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(command, command_len) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); char _command[SW_REDIS_MAX_COMMAND_SIZE]; size_t _command_len = sw_snprintf(_command, sizeof(_command), "_handler_%s", command); +#if PHP_VERSION_ID >= 80400 + zend_str_tolower(_command, _command_len); +#else php_strtolower(_command, _command_len); +#endif zval rv; zval *handler = zend_read_property(swoole_redis_server_ce, SW_Z8_OBJ_P(ZEND_THIS), _command, _command_len, 1, &rv); RETURN_ZVAL(handler, 1, 0); } -static PHP_METHOD(swoole_redis_server, format) { - zend_long type; - zval *value = nullptr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|z", &type, &value) == FAILURE) { - RETURN_FALSE; +static void redis_response_format_array_item(String *buf, zval *item) { + switch (Z_TYPE_P(item)) { + case IS_LONG: + case IS_FALSE: + case IS_TRUE: + redis_response_format(buf, Redis::REPLY_INT, item); + break; + case IS_ARRAY: + if (zend_array_is_list(Z_ARRVAL_P(item))) { + redis_response_format(buf, Redis::REPLY_SET, item); + } else { + redis_response_format(buf, Redis::REPLY_MAP, item); + } + break; + case IS_NULL: + redis_response_format(buf, Redis::REPLY_NIL, item); + break; + default: + redis_response_format(buf, Redis::REPLY_STRING, item); + break; } +} - char message[256]; - int length; - - swoole::String *format_buffer = sw_tg_buffer(); - +static bool redis_response_format(String *buf, zend_long type, zval *value) { if (type == Redis::REPLY_NIL) { - RETURN_STRINGL(SW_REDIS_RETURN_NIL, sizeof(SW_REDIS_RETURN_NIL) - 1); - } else if (type == Redis::REPLY_STATUS) { - if (value) { - zend::String str_value(value); - length = sw_snprintf(message, sizeof(message), "+%.*s\r\n", (int) str_value.len(), str_value.val()); - } else { - length = sw_snprintf(message, sizeof(message), "+%s\r\n", "OK"); - } - RETURN_STRINGL(message, length); - } else if (type == Redis::REPLY_ERROR) { + buf->append(SW_STRL(SW_REDIS_RETURN_NIL)); + } else if (type == Redis::REPLY_ERROR || type == Redis::REPLY_STATUS) { + char flag = type == Redis::REPLY_ERROR ? '-' : '+'; + const char *default_message = type == Redis::REPLY_ERROR ? "ERR" : "OK"; if (value) { zend::String str_value(value); - length = sw_snprintf(message, sizeof(message), "-%.*s\r\n", (int) str_value.len(), str_value.val()); + buf->append_format("%c%.*s\r\n", flag, (int) str_value.len(), str_value.val()); } else { - length = sw_snprintf(message, sizeof(message), "-%s\r\n", "ERR"); + buf->append_format("%c%s\r\n", flag, default_message); } - RETURN_STRINGL(message, length); } else if (type == Redis::REPLY_INT) { if (!value) { goto _no_value; } - length = sw_snprintf(message, sizeof(message), ":" ZEND_LONG_FMT "\r\n", zval_get_long(value)); - RETURN_STRINGL(message, length); + buf->append_format(":" ZEND_LONG_FMT "\r\n", zval_get_long(value)); } else if (type == Redis::REPLY_STRING) { if (!value) { _no_value: - php_swoole_fatal_error(E_WARNING, "require more parameters"); - RETURN_FALSE; + zend_throw_exception(swoole_exception_ce, "require more parameters", SW_ERROR_INVALID_PARAMS); + return false; } zend::String str_value(value); - if (str_value.len() > SW_REDIS_MAX_STRING_SIZE || str_value.len() < 1) { - php_swoole_fatal_error(E_WARNING, "invalid string size"); - RETURN_FALSE; + if (sw_unlikely(str_value.len() > SW_REDIS_MAX_STRING_SIZE)) { + zend_throw_exception(swoole_exception_ce, + "the length of given string exceeds the maximum allowed value", + SW_ERROR_INVALID_PARAMS); + return false; + } else if (sw_unlikely(str_value.len() == 0)) { + buf->append("$0\r\n\r\n"); + } else { + buf->append_format("$%zu\r\n", str_value.len()); + buf->append(str_value.val(), str_value.len()); + buf->append(SW_CRLF, SW_CRLF_LEN); } - format_buffer->clear(); - length = sw_snprintf(message, sizeof(message), "$%zu\r\n", str_value.len()); - format_buffer->append(message, length); - format_buffer->append(str_value.val(), str_value.len()); - format_buffer->append(SW_CRLF, SW_CRLF_LEN); - RETURN_STRINGL(format_buffer->str, format_buffer->length); } else if (type == Redis::REPLY_SET) { if (!value) { goto _no_value; } if (!ZVAL_IS_ARRAY(value)) { - php_swoole_fatal_error(E_WARNING, "the second parameter should be an array"); + zend_throw_exception( + swoole_exception_ce, "the second parameter should be an array", SW_ERROR_INVALID_PARAMS); } - format_buffer->clear(); - length = sw_snprintf(message, sizeof(message), "*%d\r\n", zend_hash_num_elements(Z_ARRVAL_P(value))); - format_buffer->append(message, length); + buf->append_format("*%d\r\n", zend_hash_num_elements(Z_ARRVAL_P(value))); zval *item; - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(value), item) - zend::String str_value(item); - length = sw_snprintf(message, sizeof(message), "$%zu\r\n", str_value.len()); - format_buffer->append(message, length); - format_buffer->append(str_value.val(), str_value.len()); - format_buffer->append(SW_CRLF, SW_CRLF_LEN); - SW_HASHTABLE_FOREACH_END(); - - RETURN_STRINGL(format_buffer->str, format_buffer->length); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(value), item) { + redis_response_format_array_item(buf, item); + } + ZEND_HASH_FOREACH_END(); } else if (type == Redis::REPLY_MAP) { if (!value) { goto _no_value; } if (!ZVAL_IS_ARRAY(value)) { - php_swoole_fatal_error(E_WARNING, "the second parameter should be an array"); + zend_throw_exception( + swoole_exception_ce, "the second parameter should be an array", SW_ERROR_INVALID_PARAMS); } - format_buffer->clear(); - length = sw_snprintf(message, sizeof(message), "*%d\r\n", 2 * zend_hash_num_elements(Z_ARRVAL_P(value))); - format_buffer->append(message, length); + buf->append_format("*%d\r\n", 2 * zend_hash_num_elements(Z_ARRVAL_P(value))); - char *key; - uint32_t keylen; - int keytype; + zend_string *key; + zend_ulong num_key; zval *item; - - SW_HASHTABLE_FOREACH_START2(Z_ARRVAL_P(value), key, keylen, keytype, item) - if (key == nullptr || keylen == 0) { - continue; + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(value), num_key, key, item) { + if (key) { + buf->append_format("$%zu\r\n%.*s\r\n", ZSTR_LEN(key), (int) ZSTR_LEN(key), ZSTR_VAL(key)); + } else { + std::string _key = std::to_string(num_key); + buf->append_format("$%zu\r\n%.*s\r\n", _key.length(), (int) _key.length(), _key.c_str()); + } + redis_response_format_array_item(buf, item); } - zend::String str_value(item); - length = sw_snprintf(message, sizeof(message), "$%d\r\n%s\r\n$%zu\r\n", keylen, key, str_value.len()); - format_buffer->append(message, length); - format_buffer->append(str_value.val(), str_value.len()); - format_buffer->append(SW_CRLF, SW_CRLF_LEN); - (void) keytype; - SW_HASHTABLE_FOREACH_END(); - - RETURN_STRINGL(format_buffer->str, format_buffer->length); + ZEND_HASH_FOREACH_END(); } else { - php_swoole_error(E_WARNING, "Unknown type[" ZEND_LONG_FMT "]", type); + zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_INVALID_PARAMS, "Unknown type[%d]", (int) type); + return false; + } + + return true; +} + +static PHP_METHOD(swoole_redis_server, format) { + zend_long type; + zval *value = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(type) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + auto buf = std::shared_ptr(swoole::make_string(1024, sw_zend_string_allocator())); + if (!redis_response_format(buf.get(), type, value)) { RETURN_FALSE; } + + auto str = zend::fetch_zend_string_by_val(buf->str); + buf->set_null_terminated(); + str->len = buf->length; + buf->release(); + RETURN_STR(str); } diff --git a/ext-src/swoole_runtime.cc b/ext-src/swoole_runtime.cc index a3ef77b24b..5f4c508045 100644 --- a/ext-src/swoole_runtime.cc +++ b/ext-src/swoole_runtime.cc @@ -13,21 +13,43 @@ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ + #include "php_swoole_cxx.h" -#include "swoole_socket.h" +#include "php_swoole_api.h" +#include "php_swoole_coroutine.h" + #include "swoole_util.h" #include "thirdparty/php/standard/proc_open.h" + #ifdef SW_USE_CURL -#include "thirdparty/php/curl/curl_interface.h" +#include "swoole_curl_interface.h" #endif -#include "stubs/php_swoole_hook_sockets_arginfo.h" - #include BEGIN_EXTERN_C() #include "stubs/php_swoole_runtime_arginfo.h" + +#ifdef SW_USE_PGSQL +extern void swoole_pgsql_set_blocking(bool blocking); +#endif + +#ifdef SW_USE_ODBC +extern void swoole_odbc_set_blocking(bool blocking); +#endif + +#ifdef SW_USE_ORACLE +extern void swoole_oracle_set_blocking(bool blocking); +#endif + +#ifdef SW_USE_SQLITE +extern void swoole_sqlite_set_blocking(bool blocking); +#endif + +#ifdef SW_USE_FIREBIRD +extern void swoole_firebird_set_blocking(bool blocking); +#endif END_EXTERN_C() /* openssl */ @@ -47,7 +69,6 @@ END_EXTERN_C() using swoole::Coroutine; using swoole::PHPCoroutine; using swoole::coroutine::PollSocket; -using swoole::coroutine::Socket; using swoole::coroutine::System; SW_EXTERN_C_BEGIN @@ -61,6 +82,12 @@ static PHP_FUNCTION(swoole_time_sleep_until); static PHP_FUNCTION(swoole_stream_select); static PHP_FUNCTION(swoole_stream_socket_pair); static PHP_FUNCTION(swoole_user_func_handler); +#if defined(HAVE_PUTENV) && defined(SW_THREAD) +static PHP_FUNCTION(swoole_putenv); +#endif +#if PHP_VERSION_ID >= 80400 +extern PHP_FUNCTION(swoole_exit); +#endif SW_EXTERN_C_END static void inherit_class(const char *child_name, size_t child_length, const char *parent_name, size_t parent_length); @@ -73,7 +100,18 @@ static int socket_flush(php_stream *stream); static int socket_close(php_stream *stream, int close_handle); static int socket_stat(php_stream *stream, php_stream_statbuf *ssb); static int socket_cast(php_stream *stream, int castas, void **ret); -static bool socket_ssl_set_options(Socket *sock, php_stream_context *context); +static bool socket_ssl_set_options(SocketImpl *sock, php_stream_context *context); + +static php_stream *socket_create(const char *proto, + size_t protolen, + const char *resourcename, + size_t resourcenamelen, + const char *persistent_id, + int options, + int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC); + // clang-format off static zend_class_entry *swoole_runtime_ce; @@ -82,22 +120,19 @@ static php_stream_ops socket_ops { socket_read, socket_close, socket_flush, - "tcp_socket/coroutine", + "socket/coroutine", nullptr, /* seek */ socket_cast, socket_stat, socket_set_option, }; -struct php_swoole_netstream_data_t { +struct NetStream { php_netstream_data_t stream; - Socket *socket; + std::shared_ptr socket; bool blocking; }; -static bool runtime_hook_init = false; -static int runtime_hook_flags = 0; - static struct { php_stream_transport_factory tcp; php_stream_transport_factory udp; @@ -116,12 +151,18 @@ static struct { static std::vector unsafe_functions { "pcntl_fork", + "pcntl_rfork", "pcntl_wait", "pcntl_waitpid", "pcntl_sigtimedwait", + "pcntl_sigwaitinfo", }; -static const zend_function_entry swoole_runtime_methods[] = { +#if defined(HAVE_PUTENV) && defined(SW_THREAD) +static std::unordered_map swoole_runtime_environ; +#endif + +static constexpr zend_function_entry swoole_runtime_methods[] = { PHP_ME(swoole_runtime, enableCoroutine, arginfo_class_Swoole_Runtime_enableCoroutine, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_runtime, getHookFlags, arginfo_class_Swoole_Runtime_getHookFlags, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_runtime, setHookFlags, arginfo_class_Swoole_Runtime_setHookFlags, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) @@ -138,71 +179,114 @@ static void hook_func(const char *name, zend_internal_arg_info *arg_info = nullptr); static void unhook_func(const char *name, size_t l_name); +static bool extension_loaded(const char *name) { + zend_string *extension_name = zend_string_init(name, strlen(name), false); + zend_string *lcname = zend_string_tolower(extension_name); + bool rv = zend_hash_exists(&module_registry, lcname); + zend_string_release(lcname); + zend_string_release(extension_name); + return rv; +} + +static bool class_exists(const char *name) { + zend_string *class_name = zend_string_init(name, strlen(name), false); + zend_string *lcname = zend_string_tolower(class_name); + auto *ce = (zend_class_entry *) zend_hash_find_ptr(EG(class_table), lcname); + zend_string_release_ex(lcname, 0); + zend_string_release_ex(class_name, 0); + return !!ce; +} + static zend_internal_arg_info *get_arginfo(const char *name, size_t l_name) { - zend_function *zf = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, l_name); + auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { return nullptr; } return zf->internal_function.arg_info; } +static zend_internal_arg_info *copy_arginfo(const zend_internal_function *function, zend_internal_arg_info *_arg_info) { + uint32_t num_args = function->num_args + 1; + zend_internal_arg_info *arg_info = _arg_info - 1; + + auto new_arg_info = static_cast(pemalloc(sizeof(zend_internal_arg_info) * num_args, 1)); + memcpy(new_arg_info, arg_info, sizeof(zend_internal_arg_info) * num_args); + + if (function->fn_flags & ZEND_ACC_VARIADIC) { + num_args++; + } + + for (uint32_t i = 0; i < num_args; i++) { + if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { + auto old_list = ZEND_TYPE_LIST(arg_info[i].type); + auto *new_list = static_cast(pemalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types), 1)); + memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list); + +#if PHP_VERSION_ID >= 80500 + // For PHP 8.5+, ZEND_TYPE_LIST_FOREACH gives a const pointer, which we can't modify. + // We must use a manual loop over the list we allocated ourselves. + for (uint32_t j = 0; j < new_list->num_types; j++) { + zend_type *list_type = &new_list->types[j]; + if (ZEND_TYPE_HAS_NAME(*list_type)) { + zend_string *name = zend_string_dup(ZEND_TYPE_NAME(*list_type), 1); + ZEND_TYPE_SET_PTR(*list_type, name); + } + } +#else + zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(new_list, list_type) { + zend_string *name = zend_string_dup(ZEND_TYPE_NAME(*list_type), true); + ZEND_TYPE_SET_PTR(*list_type, name); + } + ZEND_TYPE_LIST_FOREACH_END(); +#endif + } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { + zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), true); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); + } + } + + return new_arg_info + 1; +} + +static void free_arg_info(const zend_internal_function *function, zend_internal_arg_info *arg_info_copy) { + zend_internal_arg_info *arg_info = arg_info_copy - 1; + uint32_t num_args = function->num_args + 1; + if (function->fn_flags & ZEND_ACC_VARIADIC) { + num_args++; + } + for (uint32_t i = 0; i < num_args; i++) { + zend_type_release(arg_info[i].type, true); + } + pefree(arg_info, 1); +} + #define SW_HOOK_FUNC(f) hook_func(ZEND_STRL(#f), PHP_FN(swoole_##f)) #define SW_UNHOOK_FUNC(f) unhook_func(ZEND_STRL(#f)) -#define SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(f) \ +#define SW_HOOK_WITH_NATIVE_FUNC(f) \ hook_func(ZEND_STRL(#f), PHP_FN(swoole_native_##f), get_arginfo(ZEND_STRL("swoole_native_" #f))) +#define SW_HOOK_WITH_PHP_FUNC(f) hook_func(ZEND_STRL(#f)) -#define SW_HOOK_SOCKETS_FUNC(f) hook_func(ZEND_STRL(#f), nullptr, get_arginfo(ZEND_STRL("swoole_native_" #f))) - -#define SW_HOOK_FE(name, arg_info) \ - ZEND_RAW_FENTRY("swoole_native_" #name, PHP_FN(swoole_user_func_handler), arg_info, 0) +#define SW_HOOK_LIBRARY_FE(name, arg_info) \ + ZEND_RAW_FENTRY("swoole_hook_" #name, PHP_FN(swoole_user_func_handler), arg_info, 0) -// clang-format off -static const zend_function_entry swoole_sockets_functions[] = { - SW_HOOK_FE(socket_create_listen, arginfo_swoole_native_socket_create_listen) - SW_HOOK_FE(socket_accept, arginfo_swoole_native_socket_accept) - SW_HOOK_FE(socket_set_nonblock, arginfo_swoole_native_socket_set_nonblock) - SW_HOOK_FE(socket_set_block, arginfo_swoole_native_socket_set_block) - SW_HOOK_FE(socket_listen, arginfo_swoole_native_socket_listen) - SW_HOOK_FE(socket_close, arginfo_swoole_native_socket_close) - SW_HOOK_FE(socket_write, arginfo_swoole_native_socket_write) - SW_HOOK_FE(socket_read, arginfo_swoole_native_socket_read) - SW_HOOK_FE(socket_getsockname, arginfo_swoole_native_socket_getsockname) - SW_HOOK_FE(socket_getpeername, arginfo_swoole_native_socket_getpeername) - SW_HOOK_FE(socket_create, arginfo_swoole_native_socket_create) - SW_HOOK_FE(socket_connect, arginfo_swoole_native_socket_connect) - SW_HOOK_FE(socket_strerror, arginfo_swoole_native_socket_strerror) - SW_HOOK_FE(socket_bind, arginfo_swoole_native_socket_bind) - SW_HOOK_FE(socket_recv, arginfo_swoole_native_socket_recv) - SW_HOOK_FE(socket_send, arginfo_swoole_native_socket_send) - SW_HOOK_FE(socket_recvfrom, arginfo_swoole_native_socket_recvfrom) - SW_HOOK_FE(socket_sendto, arginfo_swoole_native_socket_sendto) - SW_HOOK_FE(socket_get_option, arginfo_swoole_native_socket_get_option) - SW_HOOK_FE(socket_set_option, arginfo_swoole_native_socket_set_option) - SW_HOOK_FE(socket_getopt, arginfo_swoole_native_socket_getopt) - SW_HOOK_FE(socket_setopt, arginfo_swoole_native_socket_setopt) - SW_HOOK_FE(socket_shutdown, arginfo_swoole_native_socket_shutdown) - SW_HOOK_FE(socket_last_error, arginfo_swoole_native_socket_last_error) - SW_HOOK_FE(socket_clear_error, arginfo_swoole_native_socket_clear_error) - SW_HOOK_FE(socket_import_stream, arginfo_swoole_native_socket_import_stream) - ZEND_FE_END -}; -// clang-format on - -static zend_array *tmp_function_table = nullptr; +static int runtime_hook_flags = 0; +static zend_array *hook_function_table = nullptr; static std::unordered_map child_class_entries; +static zend::ConcurrencyHashMap ori_func_handlers(nullptr); +static zend::ConcurrencyHashMap ori_func_arg_infos(nullptr); SW_EXTERN_C_BEGIN #include "ext/standard/file.h" #include "thirdparty/php/streams/plain_wrapper.c" +#undef close SW_EXTERN_C_END void php_swoole_runtime_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE(swoole_runtime, "Swoole\\Runtime", nullptr, swoole_runtime_methods, nullptr); SW_SET_CLASS_CREATE(swoole_runtime, sw_zend_create_object_deny); - zend_unregister_functions(swoole_sockets_functions, -1, CG(function_table)); - zend_register_functions(NULL, swoole_sockets_functions, NULL, MODULE_PERSISTENT); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_TCP", PHPCoroutine::HOOK_TCP); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_UDP", PHPCoroutine::HOOK_UDP); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_UNIX", PHPCoroutine::HOOK_UNIX); @@ -218,49 +302,137 @@ void php_swoole_runtime_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PROC", PHPCoroutine::HOOK_PROC); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_CURL", PHPCoroutine::HOOK_CURL); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_NATIVE_CURL", PHPCoroutine::HOOK_NATIVE_CURL); - SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_BLOCKING_FUNCTION", PHPCoroutine::HOOK_BLOCKING_FUNCTION); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_SOCKETS", PHPCoroutine::HOOK_SOCKETS); +#ifdef SW_USE_PGSQL + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_PGSQL", PHPCoroutine::HOOK_PDO_PGSQL); +#endif +#ifdef SW_USE_ODBC + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_ODBC", PHPCoroutine::HOOK_PDO_ODBC); +#endif +#ifdef SW_USE_ORACLE + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_ORACLE", PHPCoroutine::HOOK_PDO_ORACLE); +#endif +#ifdef SW_USE_SQLITE + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_SQLITE", PHPCoroutine::HOOK_PDO_SQLITE); +#endif +#ifdef SW_USE_FIREBIRD + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_PDO_FIREBIRD", PHPCoroutine::HOOK_PDO_FIREBIRD); +#endif + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_NET_FUNCTION", PHPCoroutine::HOOK_NET_FUNCTION); + SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_MONGODB", PHPCoroutine::HOOK_MONGODB); SW_REGISTER_LONG_CONSTANT("SWOOLE_HOOK_ALL", PHPCoroutine::HOOK_ALL); #ifdef SW_USE_CURL swoole_native_curl_minit(module_number); #endif swoole_proc_open_init(module_number); + + php_stream_xport_register("async.tcp", socket_create); + php_stream_xport_register("async.udp", socket_create); + php_stream_xport_register("async.unix", socket_create); + php_stream_xport_register("async.udg", socket_create); + php_stream_xport_register("async.ssl", socket_create); + php_stream_xport_register("async.tls", socket_create); + + php_register_url_stream_wrapper(SW_ASYNC_FILE_PROTOCOL, &sw_php_plain_files_wrapper); } -struct real_func { +struct PhpFunc { zend_function *function; - zif_handler ori_handler; zend_internal_arg_info *ori_arg_info; + zif_handler ori_handler; + zend_internal_arg_info *arg_info_copy; uint32_t ori_fn_flags; - uint32_t ori_num_args; - zend_fcall_info_cache *fci_cache; + zend::Callable *fci_cache; zval name; }; void php_swoole_runtime_rinit() { - tmp_function_table = (zend_array *) emalloc(sizeof(zend_array)); - zend_hash_init(tmp_function_table, 8, nullptr, nullptr, 0); + if (!sw_is_main_thread()) { +#if PHP_VERSION_ID < 80300 + // After creating a thread, the main thread will not modify the runtime hook, + // so this `hook_function_table` is read-only and safe for multi-threaded reading + zend_string *key = NULL; + void *ptr; + ZEND_HASH_REVERSE_FOREACH_STR_KEY_PTR(hook_function_table, key, ptr) { + PhpFunc *rf = (PhpFunc *) ptr; + // The PHP 8.3 and later removed `function_copy_ctor`. The `zend_internal_function` pointer in + // `EG(function_table)` and `CG(function_table)` are in shared memory across all threads, so updating the + // handler and arginfo once in the main thread takes effect for every thread. In PHP 8.2 and earlier, each + // thread called `function_copy_ctor` to copy the `zend_internal_function` memory. As a result, + // `EG(function_table)` and `CG(function_table)` were thread-local, and must set the `handler` and + // `arginfo` again in the thread `RINIT` function. + if (rf->function && rf->function->internal_function.handler != rf->ori_handler) { + auto zf = zend::get_function(key); + auto ifn = &rf->function->internal_function; + zf->internal_function.handler = ifn->handler; + if (ifn->arg_info != rf->ori_arg_info && ifn->arg_info) { + zf->internal_function.arg_info = copy_arginfo(ifn, ifn->arg_info); + } + } + } + ZEND_HASH_FOREACH_END(); +#endif + return; + } + + hook_function_table = static_cast(emalloc(sizeof(zend_array))); + zend_hash_init(hook_function_table, 8, nullptr, nullptr, 0); + +#if defined(HAVE_PUTENV) && defined(SW_THREAD) + /** + * There are issues with the implementation of putenv in PHP, + * which can lead to memory invalid read in multi-thread environment. + */ + SW_HOOK_FUNC(putenv); +#endif + +#if PHP_VERSION_ID >= 80400 + SW_HOOK_FUNC(exit); +#endif + + HashTable *xport_hash = php_stream_xport_get_hash(); + ori_factory.tcp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tcp")); + ori_factory.udp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udp")); + ori_factory._unix = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("unix")); + ori_factory.udg = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udg")); + ori_factory.ssl = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("ssl")); + ori_factory.tls = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tls")); + + memcpy(&ori_php_plain_files_wrapper, &php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); + memcpy(&ori_php_stream_stdio_ops, &php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); } void php_swoole_runtime_rshutdown() { + if (!sw_is_main_thread()) { + return; + } + + PHPCoroutine::disable_hook(); + ori_func_handlers.clear(); + ori_func_arg_infos.clear(); + void *ptr; - ZEND_HASH_FOREACH_PTR(tmp_function_table, ptr) { - real_func *rf = reinterpret_cast(ptr); - /** - * php library function - */ + ZEND_HASH_FOREACH_PTR(hook_function_table, ptr) { + auto *rf = static_cast(ptr); if (rf->fci_cache) { + sw_callable_free(rf->fci_cache); + } + if (Z_TYPE(rf->name) == IS_STRING) { zval_dtor(&rf->name); - efree(rf->fci_cache); + } + if (rf->arg_info_copy) { + free_arg_info(&rf->function->internal_function, rf->arg_info_copy); + rf->arg_info_copy = nullptr; } rf->function->internal_function.handler = rf->ori_handler; rf->function->internal_function.arg_info = rf->ori_arg_info; efree(rf); } ZEND_HASH_FOREACH_END(); - zend_hash_destroy(tmp_function_table); - efree(tmp_function_table); - tmp_function_table = nullptr; + + zend_hash_destroy(hook_function_table); + efree(hook_function_table); + hook_function_table = nullptr; clear_class_entries(); } @@ -274,18 +446,17 @@ void php_swoole_runtime_mshutdown() { static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *portno, int get_err, zend_string **err) { char *colon; char *host = nullptr; - char *p; if (*(str) == '[' && str_len > 1) { /* IPV6 notation to specify raw address with port (i.e. [fe80::1]:80) */ - p = (char *) memchr(str + 1, ']', str_len - 2); + char *p = (char *) memchr(str + 1, ']', str_len - 2); if (!p || *(p + 1) != ':') { if (get_err) { *err = strpprintf(0, "Failed to parse IPv6 address \"%s\"", str); } return nullptr; } - *portno = atoi(p + 2); + *portno = sw_atoi(p + 2); return estrndup(str + 1, p - str - 1); } if (str_len) { @@ -294,7 +465,7 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po colon = nullptr; } if (colon) { - *portno = atoi(colon + 1); + *portno = sw_atoi(colon + 1); host = estrndup(str, colon - str); } else { if (get_err) { @@ -307,19 +478,14 @@ static inline char *parse_ip_address_ex(const char *str, size_t str_len, int *po } static php_stream_size_t socket_write(php_stream *stream, const char *buf, size_t count) { - php_swoole_netstream_data_t *abstract; - Socket *sock; ssize_t didwrite = -1; + std::shared_ptr sock; - abstract = (php_swoole_netstream_data_t *) stream->abstract; - if (UNEXPECTED(!abstract)) { - goto _exit; - } - - sock = (Socket *) abstract->socket; - if (UNEXPECTED(!sock)) { + auto *abstract = static_cast(stream->abstract); + if (UNEXPECTED(!abstract || !abstract->socket)) { goto _exit; } + sock = abstract->socket; if (abstract->blocking) { didwrite = sock->send_all(buf, count); @@ -329,11 +495,11 @@ static php_stream_size_t socket_write(php_stream *stream, const char *buf, size_ } if (didwrite < 0 || (size_t) didwrite != count) { - /* we do not expect the outer layer to continue to call the send syscall in a loop - * and didwrite is meaningless if it failed */ + /* we do not expect the outer layer to continue to call the `send` syscall in a loop + * and `didwrite` is meaningless if it failed */ didwrite = -1; abstract->stream.timeout_event = (sock->errCode == ETIMEDOUT); - php_error_docref(NULL, + php_error_docref(nullptr, E_NOTICE, "Send of " ZEND_LONG_FMT " bytes failed with errno=%d %s", (zend_long) count, @@ -358,19 +524,14 @@ static php_stream_size_t socket_write(php_stream *stream, const char *buf, size_ } static php_stream_size_t socket_read(php_stream *stream, char *buf, size_t count) { - php_swoole_netstream_data_t *abstract; - Socket *sock; + std::shared_ptr sock; ssize_t nr_bytes = -1; - abstract = (php_swoole_netstream_data_t *) stream->abstract; - if (UNEXPECTED(!abstract)) { - goto _exit; - } - - sock = (Socket *) abstract->socket; - if (UNEXPECTED(!sock)) { + auto *abstract = static_cast(stream->abstract); + if (UNEXPECTED(!abstract || !abstract->socket)) { goto _exit; } + sock = abstract->socket; if (abstract->blocking) { nr_bytes = sock->recv(buf, count); @@ -402,24 +563,21 @@ static int socket_flush(php_stream *stream) { } static int socket_close(php_stream *stream, int close_handle) { - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) stream->abstract; + const auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract)) { return FAILURE; } /** set it null immediately */ stream->abstract = nullptr; - Socket *sock = (Socket *) abstract->socket; - if (UNEXPECTED(!sock)) { - return FAILURE; - } /** * it's always successful (even if the destructor rule is violated) * every calls passes through the hook function in PHP * so there is unnecessary to worry about the null pointer. */ - sock->close(); - delete sock; - pefree(abstract, php_stream_is_persistent(stream)); + if (abstract->socket) { + abstract->socket->close(); + } + delete abstract; return SUCCESS; } @@ -439,19 +597,15 @@ enum { enum { STREAM_XPORT_CRYPTO_OP_SETUP, STREAM_XPORT_CRYPTO_OP_ENABLE }; static int socket_cast(php_stream *stream, int castas, void **ret) { - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) stream->abstract; - if (UNEXPECTED(!abstract)) { - return FAILURE; - } - Socket *sock = (Socket *) abstract->socket; - if (UNEXPECTED(!sock)) { + const auto *abstract = static_cast(stream->abstract); + if (UNEXPECTED(!abstract || !abstract->socket)) { return FAILURE; } - + const std::shared_ptr sock = abstract->socket; switch (castas) { case PHP_STREAM_AS_STDIO: if (ret) { - *(FILE **) ret = fdopen(sock->get_fd(), stream->mode); + *reinterpret_cast(ret) = fdopen(sock->get_fd(), stream->mode); if (*ret) { return SUCCESS; } @@ -461,26 +615,24 @@ static int socket_cast(php_stream *stream, int castas, void **ret) { case PHP_STREAM_AS_FD_FOR_SELECT: case PHP_STREAM_AS_FD: case PHP_STREAM_AS_SOCKETD: - if (ret) *(php_socket_t *) ret = sock->get_fd(); + if (ret) *reinterpret_cast(ret) = sock->get_fd(); return SUCCESS; default: return FAILURE; } } - static int socket_stat(php_stream *stream, php_stream_statbuf *ssb) { - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) stream->abstract; + const auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract)) { return FAILURE; } - Socket *sock = (Socket *) abstract->socket; - if (UNEXPECTED(!sock)) { + if (UNEXPECTED(!abstract->socket)) { return FAILURE; } - return zend_fstat(sock->get_fd(), &ssb->sb); + return zend_fstat(abstract->socket->get_fd(), &ssb->sb); } -static inline int socket_connect(php_stream *stream, Socket *sock, php_stream_xport_param *xparam) { +static inline int socket_connect(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam) { char *host = nullptr, *bindto = nullptr; int portno = 0, bindport = 0; int ret = 0; @@ -522,30 +674,34 @@ static inline int socket_connect(php_stream *stream, Socket *sock, php_stream_xp if (bindto == nullptr) { return FAILURE; } + if (strcmp(bindto, "0") == 0 || bindto[0] == '\0') { + efree(bindto); + bindto = nullptr; + } ON_SCOPE_EXIT { if (bindto) { efree(bindto); } }; - if (!sock->bind(bindto, bindport)) { + if (!sock->bind(bindto ? bindto : "0.0.0.0", bindport)) { return FAILURE; } } if (xparam->inputs.timeout) { - sock->set_timeout(xparam->inputs.timeout, Socket::TIMEOUT_CONNECT); + sock->set_timeout(xparam->inputs.timeout, SW_TIMEOUT_CONNECT); } if (sock->connect(host, portno) == false) { xparam->outputs.error_code = sock->errCode; if (sock->errMsg) { - xparam->outputs.error_text = zend_string_init(sock->errMsg, strlen(sock->errMsg), 0); + xparam->outputs.error_text = zend_string_init(sock->errMsg, strlen(sock->errMsg), false); } ret = -1; } return ret; } -static inline int socket_bind(php_stream *stream, Socket *sock, php_stream_xport_param *xparam STREAMS_DC) { +static inline int socket_bind(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { char *host = nullptr; int portno = 0; char *ip_address = nullptr; @@ -568,7 +724,7 @@ static inline int socket_bind(php_stream *stream, Socket *sock, php_stream_xport return ret; } -static inline int socket_accept(php_stream *stream, Socket *sock, php_stream_xport_param *xparam STREAMS_DC) { +static inline int socket_accept(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { int tcp_nodelay = 0; zval *tmpzval = nullptr; @@ -581,10 +737,10 @@ static inline int socket_accept(php_stream *stream, Socket *sock, php_stream_xpo } zend_string **textaddr = xparam->want_textaddr ? &xparam->outputs.textaddr : nullptr; - struct sockaddr **addr = xparam->want_addr ? &xparam->outputs.addr : nullptr; + sockaddr **addr = xparam->want_addr ? &xparam->outputs.addr : nullptr; socklen_t *addrlen = xparam->want_addr ? &xparam->outputs.addrlen : nullptr; - struct timeval *timeout = xparam->inputs.timeout; + timeval *timeout = xparam->inputs.timeout; zend_string **error_string = xparam->want_errortext ? &xparam->outputs.error_text : nullptr; int *error_code = &xparam->outputs.error_code; @@ -593,20 +749,17 @@ static inline int socket_accept(php_stream *stream, Socket *sock, php_stream_xpo socklen_t sl = sizeof(sa); if (timeout) { - sock->set_timeout(timeout, Socket::TIMEOUT_READ); + sock->set_timeout(timeout, SW_TIMEOUT_READ); } - Socket *clisock = sock->accept(); + std::shared_ptr clisock(sock->accept()); -#ifdef SW_USE_OPENSSL if (clisock != nullptr && clisock->ssl_is_enable()) { if (!clisock->ssl_handshake()) { sock->errCode = clisock->errCode; - delete clisock; - clisock = nullptr; + clisock.reset(); } } -#endif if (clisock == nullptr) { error = sock->errCode; @@ -618,31 +771,29 @@ static inline int socket_accept(php_stream *stream, Socket *sock, php_stream_xpo } return FAILURE; } else { - php_network_populate_name_from_sockaddr((struct sockaddr *) &sa, sl, textaddr, addr, addrlen); + php_network_populate_name_from_sockaddr((sockaddr *) &sa, sl, textaddr, addr, addrlen); #ifdef TCP_NODELAY if (tcp_nodelay) { clisock->get_socket()->set_tcp_nodelay(tcp_nodelay); } #endif - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) emalloc(sizeof(*abstract)); - memset(abstract, 0, sizeof(*abstract)); - + auto abstract = new NetStream(); abstract->socket = clisock; abstract->blocking = true; - xparam->outputs.client = php_stream_alloc_rel(stream->ops, (void *) abstract, nullptr, "r+"); + xparam->outputs.client = php_stream_alloc_rel(stream->ops, abstract, nullptr, "r+"); if (xparam->outputs.client) { xparam->outputs.client->ctx = stream->ctx; if (stream->ctx) { GC_ADDREF(stream->ctx); } } - return 0; + return SUCCESS; } } static inline int socket_recvfrom( - Socket *sock, char *buf, size_t buflen, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen) { + SocketImpl *sock, char *buf, size_t buflen, zend_string **textaddr, struct sockaddr **addr, socklen_t *addrlen) { int ret; int want_addr = textaddr || addr; @@ -669,7 +820,7 @@ static inline int socket_recvfrom( } static inline int socket_sendto( - Socket *sock, const char *buf, size_t buflen, struct sockaddr *addr, socklen_t addrlen) { + SocketImpl *sock, const char *buf, size_t buflen, struct sockaddr *addr, socklen_t addrlen) { if (addr) { return sendto(sock->get_fd(), buf, buflen, 0, addr, addrlen); } else { @@ -677,36 +828,18 @@ static inline int socket_sendto( } } -#ifdef SW_USE_OPENSSL - -#define GET_VER_OPT(name) \ - (PHP_STREAM_CONTEXT(stream) && \ - (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", name)) != nullptr) -#define GET_VER_OPT_STRING(name, str) \ - if (GET_VER_OPT(name)) { \ - convert_to_string_ex(val); \ - str = Z_STRVAL_P(val); \ - } -#define GET_VER_OPT_LONG(name, num) \ - if (GET_VER_OPT(name)) { \ - convert_to_long_ex(val); \ - num = Z_LVAL_P(val); \ - } - -static int socket_setup_crypto(php_stream *stream, Socket *sock, php_stream_xport_crypto_param *cparam STREAMS_DC) { +static int socket_setup_crypto(php_stream *stream, SocketImpl *sock, php_stream_xport_crypto_param *cparam STREAMS_DC) { return 0; } static int socket_xport_crypto_setup(php_stream *stream) { - php_stream_xport_crypto_param param; - int ret; + php_stream_xport_crypto_param param = {}; - memset(¶m, 0, sizeof(param)); param.op = (decltype(param.op)) STREAM_XPORT_CRYPTO_OP_SETUP; param.inputs.method = (php_stream_xport_crypt_method_t) 0; - param.inputs.session = NULL; + param.inputs.session = nullptr; - ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); + int ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); if (ret == PHP_STREAM_OPTION_RETURN_OK) { return param.outputs.returncode; @@ -718,14 +851,12 @@ static int socket_xport_crypto_setup(php_stream *stream) { } static int socket_xport_crypto_enable(php_stream *stream, int activate) { - php_stream_xport_crypto_param param; - int ret; + php_stream_xport_crypto_param param = {}; - memset(¶m, 0, sizeof(param)); param.op = (decltype(param.op)) STREAM_XPORT_CRYPTO_OP_ENABLE; param.inputs.activate = activate; - ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); + int ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, ¶m); if (ret == PHP_STREAM_OPTION_RETURN_OK) { return param.outputs.returncode; @@ -736,7 +867,7 @@ static int socket_xport_crypto_enable(php_stream *stream, int activate) { return ret; } -static bool php_openssl_capture_peer_certs(php_stream *stream, Socket *sslsock) { +static bool php_openssl_capture_peer_certs(php_stream *stream, SocketImpl *sslsock) { zval *val; std::string peer_cert = sslsock->ssl_get_peer_cert(); @@ -746,11 +877,12 @@ static bool php_openssl_capture_peer_certs(php_stream *stream, Socket *sslsock) zval argv[1]; ZVAL_STRINGL(&argv[0], peer_cert.c_str(), peer_cert.length()); - zend::function::ReturnValue retval = zend::function::call("openssl_x509_read", 1, argv); + auto retval = zend::function::call("openssl_x509_read", 1, argv); php_stream_context_set_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_certificate", &retval.value); zval_dtor(&argv[0]); - if (NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "capture_peer_cert_chain")) && + if (nullptr != + (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "ssl", "capture_peer_cert_chain")) && zend_is_true(val)) { zval arr; auto chain = sslsock->get_socket()->ssl_get_peer_cert_chain(INT_MAX); @@ -758,12 +890,12 @@ static bool php_openssl_capture_peer_certs(php_stream *stream, Socket *sslsock) if (!chain.empty()) { array_init(&arr); for (auto &cert : chain) { - zval argv[1]; - ZVAL_STRINGL(&argv[0], cert.c_str(), cert.length()); - zend::function::ReturnValue retval = zend::function::call("openssl_x509_read", 1, argv); - zval_add_ref(&retval.value); - add_next_index_zval(&arr, &retval.value); - zval_dtor(&argv[0]); + zval _argv[1]; + ZVAL_STRINGL(&_argv[0], cert.c_str(), cert.length()); + auto _retval = zend::function::call("openssl_x509_read", 1, _argv); + zval_add_ref(&_retval.value); + add_next_index_zval(&arr, &_retval.value); + zval_dtor(&_argv[0]); } } else { ZVAL_NULL(&arr); @@ -776,36 +908,34 @@ static bool php_openssl_capture_peer_certs(php_stream *stream, Socket *sslsock) return true; } -static int socket_enable_crypto(php_stream *stream, Socket *sock, php_stream_xport_crypto_param *cparam STREAMS_DC) { +static int socket_enable_crypto(php_stream *stream, + SocketImpl *sock, + php_stream_xport_crypto_param *cparam STREAMS_DC) { php_stream_context *context = PHP_STREAM_CONTEXT(stream); if (cparam->inputs.activate && !sock->ssl_is_available()) { sock->enable_ssl_encrypt(); - if (!sock->ssl_check_context()) { - return -1; - } if (!socket_ssl_set_options(sock, context)) { return -1; } if (!sock->ssl_handshake()) { return -1; } - return 0; - } else if (!cparam->inputs.activate && sock->ssl_is_available()) { - return sock->ssl_shutdown() ? 0 : -1; + } else if (!cparam->inputs.activate && sock->ssl_is_available() && sock->get_ssl()) { + sock->ssl_close(); + return -1; } - if (context) { + if (context && sock->ssl_is_available()) { zval *val = php_stream_context_get_option(context, "ssl", "capture_peer_cert"); if (val && zend_is_true(val) && !php_openssl_capture_peer_certs(stream, sock)) { return -1; } } - return 0; + return 1; } -#endif -static inline int socket_xport_api(php_stream *stream, Socket *sock, php_stream_xport_param *xparam STREAMS_DC) { +static inline int socket_xport_api(php_stream *stream, SocketImpl *sock, php_stream_xport_param *xparam STREAMS_DC) { static const int shutdown_how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR}; switch (xparam->op) { @@ -816,12 +946,10 @@ static inline int socket_xport_api(php_stream *stream, Socket *sock, php_stream_ case STREAM_XPORT_OP_CONNECT: case STREAM_XPORT_OP_CONNECT_ASYNC: xparam->outputs.returncode = socket_connect(stream, sock, xparam); -#ifdef SW_USE_OPENSSL if (sock->ssl_is_enable() && (socket_xport_crypto_setup(stream) < 0 || socket_xport_crypto_enable(stream, 1) < 0)) { xparam->outputs.returncode = -1; } -#endif break; case STREAM_XPORT_OP_BIND: { if (sock->get_sock_domain() != AF_UNIX) { @@ -921,11 +1049,12 @@ static inline int socket_xport_api(php_stream *stream, Socket *sock, php_stream_ } static int socket_set_option(php_stream *stream, int option, int value, void *ptrparam) { - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) stream->abstract; + auto *abstract = static_cast(stream->abstract); if (UNEXPECTED(!abstract || !abstract->socket)) { return PHP_STREAM_OPTION_RETURN_ERR; } - Socket *sock = (Socket *) abstract->socket; + std::shared_ptr sock_wrapped = abstract->socket; + auto sock = sock_wrapped.get(); switch (option) { case PHP_STREAM_OPTION_BLOCKING: if (abstract->blocking == (bool) value) { @@ -937,12 +1066,10 @@ static int socket_set_option(php_stream *stream, int option, int value, void *pt return socket_xport_api(stream, sock, (php_stream_xport_param *) ptrparam STREAMS_CC); } case PHP_STREAM_OPTION_META_DATA_API: { -#ifdef SW_USE_OPENSSL SSL *ssl = sock->get_socket() ? sock->get_socket()->ssl : nullptr; if (ssl) { zval tmp; const char *proto_str; - const SSL_CIPHER *cipher; array_init(&tmp); switch (SSL_version(ssl)) { @@ -974,26 +1101,24 @@ static int socket_set_option(php_stream *stream, int option, int value, void *pt break; } - cipher = SSL_get_current_cipher(ssl); - add_assoc_string(&tmp, "protocol", (char *) proto_str); - add_assoc_string(&tmp, "cipher_name", (char *) SSL_CIPHER_get_name(cipher)); + const auto cipher = SSL_get_current_cipher(ssl); + add_assoc_string(&tmp, "protocol", proto_str); + add_assoc_string(&tmp, "cipher_name", SSL_CIPHER_get_name(cipher)); add_assoc_long(&tmp, "cipher_bits", SSL_CIPHER_get_bits(cipher, nullptr)); - add_assoc_string(&tmp, "cipher_version", (char *) SSL_CIPHER_get_version(cipher)); + add_assoc_string(&tmp, "cipher_version", SSL_CIPHER_get_version(cipher)); add_assoc_zval((zval *) ptrparam, "crypto", &tmp); } -#endif add_assoc_bool((zval *) ptrparam, "timed_out", sock->errCode == ETIMEDOUT); add_assoc_bool((zval *) ptrparam, "eof", stream->eof); - add_assoc_bool((zval *) ptrparam, "blocked", 1); + add_assoc_bool((zval *) ptrparam, "blocked", true); break; } case PHP_STREAM_OPTION_READ_TIMEOUT: { - abstract->socket->set_timeout((struct timeval *) ptrparam, Socket::TIMEOUT_READ); + abstract->socket->set_timeout(static_cast(ptrparam), SW_TIMEOUT_READ); break; } -#ifdef SW_USE_OPENSSL case PHP_STREAM_OPTION_CRYPTO_API: { - php_stream_xport_crypto_param *cparam = (php_stream_xport_crypto_param *) ptrparam; + auto *cparam = static_cast(ptrparam); switch (cparam->op) { case STREAM_XPORT_CRYPTO_OP_SETUP: cparam->outputs.returncode = socket_setup_crypto(stream, sock, cparam STREAMS_CC); @@ -1008,7 +1133,6 @@ static int socket_set_option(php_stream *stream, int option, int value, void *pt } break; } -#endif case PHP_STREAM_OPTION_CHECK_LIVENESS: { return sock->check_liveness() ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; } @@ -1026,9 +1150,8 @@ static int socket_set_option(php_stream *stream, int option, int value, void *pt return PHP_STREAM_OPTION_RETURN_OK; } -static bool socket_ssl_set_options(Socket *sock, php_stream_context *context) { +static bool socket_ssl_set_options(SocketImpl *sock, php_stream_context *context) { if (context && ZVAL_IS_ARRAY(&context->options)) { -#ifdef SW_USE_OPENSSL zval *ztmp; if (sock->ssl_is_enable() && php_swoole_array_get_value(Z_ARRVAL_P(&context->options), "ssl", ztmp) && @@ -1040,8 +1163,7 @@ static bool socket_ssl_set_options(Socket *sock, php_stream_context *context) { auto add_alias = [&zalias, options](const char *name, const char *alias) { zval *ztmp; if (php_swoole_array_get_value_ex(options, name, ztmp)) { - add_assoc_zval_ex(&zalias, alias, strlen(alias), ztmp); - zval_add_ref(ztmp); + zend::array_set(&zalias, alias, strlen(alias), ztmp); } }; @@ -1056,18 +1178,48 @@ static bool socket_ssl_set_options(Socket *sock, php_stream_context *context) { add_alias("verify_depth", "ssl_verify_depth"); add_alias("disable_compression", "ssl_disable_compression"); - php_swoole_socket_set_ssl(sock, &zalias); - if (!sock->ssl_check_context()) { - return false; - } + bool ret = php_swoole_socket_set_ssl(sock, &zalias); zval_dtor(&zalias); + return ret; } -#endif } return true; } +static php_stream *socket_create_original(const char *proto, + size_t protolen, + const char *resourcename, + size_t resourcenamelen, + const char *persistent_id, + int options, + int flags, + struct timeval *timeout, + php_stream_context *context STREAMS_DC) { + php_stream_transport_factory factory = nullptr; + if (SW_STREQ(proto, protolen, "tcp")) { + factory = ori_factory.tcp; + } else if (SW_STREQ(proto, protolen, "ssl")) { + factory = ori_factory.ssl; + } else if (SW_STREQ(proto, protolen, "tls")) { + factory = ori_factory.tls; + } else if (SW_STREQ(proto, protolen, "unix")) { + factory = ori_factory._unix; + } else if (SW_STREQ(proto, protolen, "udp")) { + factory = ori_factory.udp; + } else if (SW_STREQ(proto, protolen, "udg")) { + factory = ori_factory.udg; + } + + if (factory) { + return factory( + proto, protolen, resourcename, resourcenamelen, persistent_id, options, flags, timeout, context STREAMS_CC); + } else { + php_swoole_fatal_error(E_WARNING, "unknown protocol '%s'", proto); + return nullptr; + } +} + static php_stream *socket_create(const char *proto, size_t protolen, const char *resourcename, @@ -1078,32 +1230,33 @@ static php_stream *socket_create(const char *proto, struct timeval *timeout, php_stream_context *context STREAMS_DC) { php_stream *stream = nullptr; - php_swoole_netstream_data_t *abstract = nullptr; - Socket *sock; + SocketImpl *sock = nullptr; - Coroutine::get_current_safe(); + auto co = Coroutine::get_current(); + if (sw_unlikely(co == nullptr)) { + return socket_create_original( + proto, protolen, resourcename, resourcenamelen, persistent_id, options, flags, timeout, context STREAMS_CC); + } + + if (SW_STR_STARTS_WITH(proto, protolen, "async.")) { + proto += sizeof("async.") - 1; + protolen -= sizeof("async.") - 1; + } if (SW_STREQ(proto, protolen, "tcp")) { - _tcp: - sock = new Socket(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); + sock = new SocketImpl(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); } else if (SW_STREQ(proto, protolen, "ssl") || SW_STREQ(proto, protolen, "tls")) { -#ifdef SW_USE_OPENSSL - sock = new Socket(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); + sock = new SocketImpl(resourcename[0] == '[' ? SW_SOCK_TCP6 : SW_SOCK_TCP); sock->enable_ssl_encrypt(); -#else - php_swoole_error(E_WARNING, - "you must configure with `--enable-openssl` to support ssl connection when compiling Swoole"); - return nullptr; -#endif } else if (SW_STREQ(proto, protolen, "unix")) { - sock = new Socket(SW_SOCK_UNIX_STREAM); + sock = new SocketImpl(SW_SOCK_UNIX_STREAM); } else if (SW_STREQ(proto, protolen, "udp")) { - sock = new Socket(SW_SOCK_UDP); + sock = new SocketImpl(SW_SOCK_UDP); } else if (SW_STREQ(proto, protolen, "udg")) { - sock = new Socket(SW_SOCK_UNIX_DGRAM); + sock = new SocketImpl(SW_SOCK_UNIX_DGRAM); } else { - /* abort? */ - goto _tcp; + php_swoole_fatal_error(E_WARNING, "unknown protocol '%s'", proto); + return nullptr; } if (UNEXPECTED(sock->get_fd() < 0)) { @@ -1118,14 +1271,14 @@ static php_stream *socket_create(const char *proto, sock->set_zero_copy(true); - abstract = (php_swoole_netstream_data_t *) pemalloc(sizeof(*abstract), persistent_id ? 1 : 0); - abstract->socket = sock; + auto abstract = new NetStream(); + abstract->socket.reset(sock); abstract->stream.socket = sock->get_fd(); abstract->blocking = true; stream = php_stream_alloc_rel(&socket_ops, abstract, persistent_id, "r+"); if (stream == nullptr) { - pefree(abstract, persistent_id ? 1 : 0); + delete abstract; goto _failed; } @@ -1141,44 +1294,49 @@ static ZEND_FUNCTION(swoole_display_disabled_function) { } static bool disable_func(const char *name, size_t l_name) { - real_func *rf = (real_func *) zend_hash_str_find_ptr(tmp_function_table, name, l_name); + auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (rf) { rf->function->internal_function.handler = ZEND_FN(swoole_display_disabled_function); return true; } - zend_function *zf = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, l_name); + auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { return false; } - rf = (real_func *) emalloc(sizeof(real_func)); + rf = static_cast(emalloc(sizeof(PhpFunc))); sw_memset_zero(rf, sizeof(*rf)); rf->function = zf; rf->ori_handler = zf->internal_function.handler; rf->ori_arg_info = zf->internal_function.arg_info; rf->ori_fn_flags = zf->internal_function.fn_flags; - rf->ori_num_args = zf->internal_function.num_args; zf->internal_function.handler = ZEND_FN(swoole_display_disabled_function); - zf->internal_function.arg_info = nullptr; - zf->internal_function.fn_flags &= ~(ZEND_ACC_VARIADIC | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_HAS_RETURN_TYPE); - zf->internal_function.num_args = 0; + zf->internal_function.fn_flags = ZEND_ACC_FAKE_CLOSURE; - zend_hash_add_ptr(tmp_function_table, zf->common.function_name, rf); + zend_hash_add_ptr(hook_function_table, zf->common.function_name, rf); return true; } static bool enable_func(const char *name, size_t l_name) { - real_func *rf = (real_func *) zend_hash_str_find_ptr(tmp_function_table, name, l_name); + auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (!rf) { return false; } rf->function->internal_function.handler = rf->ori_handler; - rf->function->internal_function.arg_info = rf->ori_arg_info; rf->function->internal_function.fn_flags = rf->ori_fn_flags; - rf->function->internal_function.num_args = rf->ori_num_args; + + return true; +} + +bool php_swoole_call_original_handler(const char *name, size_t len, INTERNAL_FUNCTION_PARAMETERS) { + auto ori_handler = php_swoole_get_original_handler(name, len); + if (!ori_handler) { + return false; + } + ori_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); return true; } @@ -1195,32 +1353,19 @@ void PHPCoroutine::enable_unsafe_function() { } } -bool PHPCoroutine::enable_hook(uint32_t flags) { - if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK)) { - swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK, &flags); - } - - if (!runtime_hook_init) { - HashTable *xport_hash = php_stream_xport_get_hash(); - // php_stream - ori_factory.tcp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tcp")); - ori_factory.udp = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udp")); - ori_factory._unix = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("unix")); - ori_factory.udg = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("udg")); - ori_factory.ssl = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("ssl")); - ori_factory.tls = (php_stream_transport_factory) zend_hash_str_find_ptr(xport_hash, ZEND_STRL("tls")); +static void hook_stream_throw_exception(const char *type) { + zend_throw_exception_ex( + swoole_exception_ce, SW_ERROR_PHP_FATAL_ERROR, "failed to register `%s` stream transport factory", type); +} - // file - memcpy((void *) &ori_php_plain_files_wrapper, &php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); - memcpy((void *) &ori_php_stream_stdio_ops, &php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); +static void hook_stream_factory(uint32_t *flags_ptr) { + uint32_t flags = *flags_ptr; - runtime_hook_init = true; - } - // php_stream if (flags & PHPCoroutine::HOOK_TCP) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_TCP)) { if (php_stream_xport_register("tcp", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_TCP; + hook_stream_throw_exception("tcp"); } } } else { @@ -1232,6 +1377,7 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UDP)) { if (php_stream_xport_register("udp", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UDP; + hook_stream_throw_exception("udp"); } } } else { @@ -1243,6 +1389,7 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UNIX)) { if (php_stream_xport_register("unix", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UNIX; + hook_stream_throw_exception("unix"); } } } else { @@ -1254,6 +1401,7 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_UDG)) { if (php_stream_xport_register("udg", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_UDG; + hook_stream_throw_exception("udg"); } } } else { @@ -1265,6 +1413,7 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_SSL)) { if (php_stream_xport_register("ssl", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_SSL; + hook_stream_throw_exception("ssl"); } } } else { @@ -1280,6 +1429,7 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_TLS)) { if (php_stream_xport_register("tls", socket_create) != SUCCESS) { flags ^= PHPCoroutine::HOOK_TLS; + hook_stream_throw_exception("tls"); } } } else { @@ -1291,35 +1441,101 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { } } } - if (flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { - if (!(runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION)) { - SW_HOOK_FUNC(stream_select); - SW_HOOK_FUNC(stream_socket_pair); - } - } else { - if (runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { - SW_UNHOOK_FUNC(stream_select); - SW_UNHOOK_FUNC(stream_socket_pair); - } - } + *flags_ptr = flags; +} + +static void hook_stream_ops(uint32_t flags) { // file if (flags & PHPCoroutine::HOOK_FILE) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_FILE)) { - memcpy((void *) &php_plain_files_wrapper, &sw_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); + memcpy(&php_plain_files_wrapper, &sw_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_FILE) { - memcpy((void *) &php_plain_files_wrapper, &ori_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); + memcpy(&php_plain_files_wrapper, &ori_php_plain_files_wrapper, sizeof(php_plain_files_wrapper)); } } // stdio if (flags & PHPCoroutine::HOOK_STDIO) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_STDIO)) { - memcpy((void *) &php_stream_stdio_ops, &sw_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); + memcpy(&php_stream_stdio_ops, &sw_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_STDIO) { - memcpy((void *) &php_stream_stdio_ops, &ori_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); + memcpy(&php_stream_stdio_ops, &ori_php_stream_stdio_ops, sizeof(php_stream_stdio_ops)); + } + } +} + +static void hook_pdo_driver(uint32_t flags) { +#ifdef SW_USE_PGSQL + if (flags & PHPCoroutine::HOOK_PDO_PGSQL) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_PGSQL)) { + swoole_pgsql_set_blocking(false); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_PGSQL) { + swoole_pgsql_set_blocking(true); + } + } +#endif +#ifdef SW_USE_ODBC + if (flags & PHPCoroutine::HOOK_PDO_ODBC) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_ODBC)) { + swoole_odbc_set_blocking(false); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_ODBC) { + swoole_odbc_set_blocking(true); + } + } +#endif +#ifdef SW_USE_ORACLE + if (flags & PHPCoroutine::HOOK_PDO_ORACLE) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_ORACLE)) { + swoole_oracle_set_blocking(0); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_ORACLE) { + swoole_oracle_set_blocking(1); + } + } +#endif +#ifdef SW_USE_SQLITE + if (flags & PHPCoroutine::HOOK_PDO_SQLITE) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_SQLITE)) { + swoole_sqlite_set_blocking(false); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_SQLITE) { + swoole_sqlite_set_blocking(true); + } + } +#endif +#ifdef SW_USE_FIREBIRD + if (flags & PHPCoroutine::HOOK_PDO_FIREBIRD) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_PDO_FIREBIRD)) { + swoole_firebird_set_blocking(false); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_PDO_FIREBIRD) { + swoole_firebird_set_blocking(true); + } + } +#endif +} + +static void hook_all_func(uint32_t flags) { + // stream func + if (flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION)) { + SW_HOOK_FUNC(stream_select); + SW_HOOK_FUNC(stream_socket_pair); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_STREAM_FUNCTION) { + SW_UNHOOK_FUNC(stream_select); + SW_UNHOOK_FUNC(stream_socket_pair); } } // sleep @@ -1354,53 +1570,40 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { SW_UNHOOK_FUNC(proc_terminate); } } - // blocking function - if (flags & PHPCoroutine::HOOK_BLOCKING_FUNCTION) { - if (!(runtime_hook_flags & PHPCoroutine::HOOK_BLOCKING_FUNCTION)) { - hook_func(ZEND_STRL("gethostbyname"), PHP_FN(swoole_coroutine_gethostbyname)); - hook_func(ZEND_STRL("exec")); - hook_func(ZEND_STRL("shell_exec")); - } - } else { - if (runtime_hook_flags & PHPCoroutine::HOOK_BLOCKING_FUNCTION) { - SW_UNHOOK_FUNC(gethostbyname); - SW_UNHOOK_FUNC(exec); - SW_UNHOOK_FUNC(shell_exec); - } - } + // ext-sockets if (flags & PHPCoroutine::HOOK_SOCKETS) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_SOCKETS)) { - SW_HOOK_SOCKETS_FUNC(socket_create); - SW_HOOK_SOCKETS_FUNC(socket_create_listen); - SW_HOOK_SOCKETS_FUNC(socket_create_pair); - SW_HOOK_SOCKETS_FUNC(socket_connect); - SW_HOOK_SOCKETS_FUNC(socket_write); - SW_HOOK_SOCKETS_FUNC(socket_read); - SW_HOOK_SOCKETS_FUNC(socket_send); - SW_HOOK_SOCKETS_FUNC(socket_recv); - SW_HOOK_SOCKETS_FUNC(socket_sendto); - SW_HOOK_SOCKETS_FUNC(socket_recvfrom); - SW_HOOK_SOCKETS_FUNC(socket_bind); - SW_HOOK_SOCKETS_FUNC(socket_listen); - SW_HOOK_SOCKETS_FUNC(socket_accept); - SW_HOOK_SOCKETS_FUNC(socket_getpeername); - SW_HOOK_SOCKETS_FUNC(socket_getsockname); - SW_HOOK_SOCKETS_FUNC(socket_getopt); - SW_HOOK_SOCKETS_FUNC(socket_get_option); - SW_HOOK_SOCKETS_FUNC(socket_setopt); - SW_HOOK_SOCKETS_FUNC(socket_set_option); - SW_HOOK_SOCKETS_FUNC(socket_set_block); - SW_HOOK_SOCKETS_FUNC(socket_set_nonblock); - SW_HOOK_SOCKETS_FUNC(socket_shutdown); - SW_HOOK_SOCKETS_FUNC(socket_close); - SW_HOOK_SOCKETS_FUNC(socket_clear_error); - SW_HOOK_SOCKETS_FUNC(socket_last_error); - SW_HOOK_SOCKETS_FUNC(socket_import_stream); + SW_HOOK_WITH_PHP_FUNC(socket_create); + SW_HOOK_WITH_PHP_FUNC(socket_create_listen); + SW_HOOK_WITH_PHP_FUNC(socket_create_pair); + SW_HOOK_WITH_PHP_FUNC(socket_connect); + SW_HOOK_WITH_PHP_FUNC(socket_write); + SW_HOOK_WITH_PHP_FUNC(socket_read); + SW_HOOK_WITH_PHP_FUNC(socket_send); + SW_HOOK_WITH_PHP_FUNC(socket_recv); + SW_HOOK_WITH_PHP_FUNC(socket_sendto); + SW_HOOK_WITH_PHP_FUNC(socket_recvfrom); + SW_HOOK_WITH_PHP_FUNC(socket_bind); + SW_HOOK_WITH_PHP_FUNC(socket_listen); + SW_HOOK_WITH_PHP_FUNC(socket_accept); + SW_HOOK_WITH_PHP_FUNC(socket_getpeername); + SW_HOOK_WITH_PHP_FUNC(socket_getsockname); + SW_HOOK_WITH_PHP_FUNC(socket_getopt); + SW_HOOK_WITH_PHP_FUNC(socket_get_option); + SW_HOOK_WITH_PHP_FUNC(socket_setopt); + SW_HOOK_WITH_PHP_FUNC(socket_set_option); + SW_HOOK_WITH_PHP_FUNC(socket_set_block); + SW_HOOK_WITH_PHP_FUNC(socket_set_nonblock); + SW_HOOK_WITH_PHP_FUNC(socket_shutdown); + SW_HOOK_WITH_PHP_FUNC(socket_close); + SW_HOOK_WITH_PHP_FUNC(socket_clear_error); + SW_HOOK_WITH_PHP_FUNC(socket_last_error); + SW_HOOK_WITH_PHP_FUNC(socket_import_stream); inherit_class(ZEND_STRL("Swoole\\Coroutine\\Socket"), ZEND_STRL("Socket")); } } else { - if (runtime_hook_flags & PHPCoroutine::HOOK_BLOCKING_FUNCTION) { + if (runtime_hook_flags & PHPCoroutine::HOOK_SOCKETS) { SW_UNHOOK_FUNC(socket_create); SW_UNHOOK_FUNC(socket_create_listen); SW_UNHOOK_FUNC(socket_create_pair); @@ -1433,36 +1636,37 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { } #ifdef SW_USE_CURL + // curl native if (flags & PHPCoroutine::HOOK_NATIVE_CURL) { if (flags & PHPCoroutine::HOOK_CURL) { php_swoole_fatal_error(E_WARNING, "cannot enable both hooks HOOK_NATIVE_CURL and HOOK_CURL at same time"); flags ^= PHPCoroutine::HOOK_CURL; } if (!(runtime_hook_flags & PHPCoroutine::HOOK_NATIVE_CURL)) { - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_close); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_copy_handle); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_errno); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_error); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_exec); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_getinfo); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_init); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_setopt); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_setopt_array); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_reset); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_pause); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_escape); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_unescape); - - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_init); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_add_handle); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_exec); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_errno); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_select); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_setopt); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_getcontent); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_info_read); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_remove_handle); - SW_HOOK_NATIVE_FUNC_WITH_ARG_INFO(curl_multi_close); + SW_HOOK_WITH_NATIVE_FUNC(curl_close); + SW_HOOK_WITH_NATIVE_FUNC(curl_copy_handle); + SW_HOOK_WITH_NATIVE_FUNC(curl_errno); + SW_HOOK_WITH_NATIVE_FUNC(curl_error); + SW_HOOK_WITH_NATIVE_FUNC(curl_exec); + SW_HOOK_WITH_NATIVE_FUNC(curl_getinfo); + SW_HOOK_WITH_NATIVE_FUNC(curl_init); + SW_HOOK_WITH_NATIVE_FUNC(curl_setopt); + SW_HOOK_WITH_NATIVE_FUNC(curl_setopt_array); + SW_HOOK_WITH_NATIVE_FUNC(curl_reset); + SW_HOOK_WITH_NATIVE_FUNC(curl_pause); + SW_HOOK_WITH_NATIVE_FUNC(curl_escape); + SW_HOOK_WITH_NATIVE_FUNC(curl_unescape); + + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_init); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_add_handle); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_exec); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_errno); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_select); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_setopt); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_getcontent); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_info_read); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_remove_handle); + SW_HOOK_WITH_NATIVE_FUNC(curl_multi_close); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_NATIVE_CURL) { @@ -1493,19 +1697,21 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { } } #endif - + // curl if (flags & PHPCoroutine::HOOK_CURL) { if (!(runtime_hook_flags & PHPCoroutine::HOOK_CURL)) { - hook_func(ZEND_STRL("curl_init")); - hook_func(ZEND_STRL("curl_setopt")); - hook_func(ZEND_STRL("curl_setopt_array")); - hook_func(ZEND_STRL("curl_exec")); - hook_func(ZEND_STRL("curl_getinfo")); - hook_func(ZEND_STRL("curl_errno")); - hook_func(ZEND_STRL("curl_error")); - hook_func(ZEND_STRL("curl_reset")); - hook_func(ZEND_STRL("curl_close")); - hook_func(ZEND_STRL("curl_multi_getcontent")); + SW_HOOK_WITH_PHP_FUNC(curl_init); + SW_HOOK_WITH_PHP_FUNC(curl_setopt); + SW_HOOK_WITH_PHP_FUNC(curl_setopt_array); + SW_HOOK_WITH_PHP_FUNC(curl_exec); + SW_HOOK_WITH_PHP_FUNC(curl_getinfo); + SW_HOOK_WITH_PHP_FUNC(curl_errno); + SW_HOOK_WITH_PHP_FUNC(curl_error); + SW_HOOK_WITH_PHP_FUNC(curl_reset); + SW_HOOK_WITH_PHP_FUNC(curl_close); + SW_HOOK_WITH_PHP_FUNC(curl_multi_getcontent); + + inherit_class(ZEND_STRL("Swoole\\Curl\\Handler"), ZEND_STRL("CurlHandle")); } } else { if (runtime_hook_flags & PHPCoroutine::HOOK_CURL) { @@ -1519,14 +1725,84 @@ bool PHPCoroutine::enable_hook(uint32_t flags) { SW_UNHOOK_FUNC(curl_reset); SW_UNHOOK_FUNC(curl_close); SW_UNHOOK_FUNC(curl_multi_getcontent); + + detach_parent_class("Swoole\\Curl\\Handler"); + } + } + // network functions + if (flags & PHPCoroutine::HOOK_NET_FUNCTION) { + if (!(runtime_hook_flags & PHPCoroutine::HOOK_NET_FUNCTION)) { + hook_func(ZEND_STRL("gethostbyname"), PHP_FN(swoole_coroutine_gethostbyname)); + SW_HOOK_WITH_PHP_FUNC(gethostbynamel); + SW_HOOK_WITH_PHP_FUNC(mail); + SW_HOOK_WITH_PHP_FUNC(dns_check_record); + SW_HOOK_WITH_PHP_FUNC(checkdnsrr); + SW_HOOK_WITH_PHP_FUNC(dns_get_mx); + SW_HOOK_WITH_PHP_FUNC(getmxrr); + SW_HOOK_WITH_PHP_FUNC(dns_get_record); + SW_HOOK_WITH_PHP_FUNC(gethostbyaddr); + } + } else { + if (runtime_hook_flags & PHPCoroutine::HOOK_NET_FUNCTION) { + SW_UNHOOK_FUNC(gethostbyname); + SW_UNHOOK_FUNC(gethostbynamel); + SW_UNHOOK_FUNC(mail); + SW_UNHOOK_FUNC(dns_check_record); + SW_UNHOOK_FUNC(checkdnsrr); + SW_UNHOOK_FUNC(dns_get_mx); + SW_UNHOOK_FUNC(getmxrr); + SW_UNHOOK_FUNC(dns_get_record); + SW_UNHOOK_FUNC(gethostbyaddr); } } +} - if (swoole_isset_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK)) { - swoole_call_hook((enum swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK, &flags); +static void hook_mongodb(uint32_t flags) { + if (flags & PHPCoroutine::HOOK_MONGODB) { + auto alias_name = R"(MongoDB\Client)"; + if (!(runtime_hook_flags & PHPCoroutine::HOOK_MONGODB) && !class_exists(alias_name)) { + zend::String class_name(R"(Swoole\MongoDB\Client)"); + auto ce = zend_lookup_class_ex(class_name.get(), NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (ce) { + if (zend_register_class_alias(alias_name, ce) != SUCCESS) { + zend_error(E_WARNING, "Cannot declare class %s, because the name is already in use", alias_name); + } + } + } + } +} + +bool PHPCoroutine::enable_hook(uint32_t flags) { + if (!sw_is_main_thread() || sw_active_thread_count() > 1) { + swoole_warning("The runtime hook can only set on the main thread and no child threads have been created"); + return false; + } + + if (swoole_isset_hook((swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK)) { + swoole_call_hook((swGlobalHookType) PHP_SWOOLE_HOOK_BEFORE_ENABLE_HOOK, &flags); + } + + if (!extension_loaded("sockets")) { + sw_unset_bit(flags, HOOK_SOCKETS); + } + + if (!SWOOLE_G(enable_library)) { + sw_unset_bit(flags, HOOK_NET_FUNCTION); + sw_unset_bit(flags, HOOK_MONGODB); + } + + hook_stream_factory(&flags); + hook_stream_ops(flags); + hook_pdo_driver(flags); + hook_all_func(flags); + hook_mongodb(flags); + + if (swoole_isset_hook((swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK)) { + swoole_call_hook((swGlobalHookType) PHP_SWOOLE_HOOK_AFTER_ENABLE_HOOK, &flags); } runtime_hook_flags = flags; + return true; } @@ -1539,44 +1815,19 @@ static PHP_METHOD(swoole_runtime, enableCoroutine) { php_swoole_fatal_error(E_ERROR, "must be used in PHP CLI mode"); RETURN_FALSE; } - zval *zflags = nullptr; zend_long flags = PHPCoroutine::HOOK_ALL; - ZEND_PARSE_PARAMETERS_START(0, 2) + ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(zflags) // or zenable Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (zflags) { - if (Z_TYPE_P(zflags) == IS_LONG) { - flags = SW_MAX(0, Z_LVAL_P(zflags)); - } else if (ZVAL_IS_BOOL(zflags)) { - if (!Z_BVAL_P(zflags)) { - flags = 0; - } - } else { - const char *space, *class_name = get_active_class_name(&space); - zend_type_error("%s%s%s() expects parameter %d to be %s, %s given", - class_name, - space, - get_active_function_name(), - 1, - "bool or long", - zend_zval_type_name(zflags)); - } - } - PHPCoroutine::set_hook_flags(flags); RETURN_BOOL(PHPCoroutine::enable_hook(flags)); } static PHP_METHOD(swoole_runtime, getHookFlags) { - if (runtime_hook_init) { - RETURN_LONG(runtime_hook_flags); - } else { - RETURN_LONG(PHPCoroutine::get_hook_flags()); - } + RETURN_LONG(PHPCoroutine::get_hook_flags()); } static PHP_METHOD(swoole_runtime, setHookFlags) { @@ -1596,12 +1847,14 @@ static PHP_METHOD(swoole_runtime, setHookFlags) { static PHP_FUNCTION(swoole_sleep) { zend_long num; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(num) + ZEND_PARSE_PARAMETERS_END(); + if (num < 0) { - php_error_docref(nullptr, E_WARNING, "Number of seconds must be greater than or equal to 0"); - RETURN_FALSE; + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); } if (Coroutine::get_current()) { @@ -1613,13 +1866,16 @@ static PHP_FUNCTION(swoole_sleep) { static PHP_FUNCTION(swoole_usleep) { zend_long num; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &num) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(num) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + if (num < 0) { - php_error_docref(nullptr, E_WARNING, "Number of seconds must be greater than or equal to 0"); - RETURN_FALSE; + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); } + double sec = (double) num / 1000000; if (Coroutine::get_current()) { System::sleep(sec); @@ -1630,18 +1886,21 @@ static PHP_FUNCTION(swoole_usleep) { static PHP_FUNCTION(swoole_time_nanosleep) { zend_long tv_sec, tv_nsec; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &tv_sec, &tv_nsec) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(tv_sec) + Z_PARAM_LONG(tv_nsec) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (tv_sec < 0) { - php_error_docref(nullptr, E_WARNING, "The seconds value must be greater than 0"); - RETURN_FALSE; + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); } if (tv_nsec < 0) { - php_error_docref(nullptr, E_WARNING, "The nanoseconds value must be greater than 0"); - RETURN_FALSE; + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); } + double _time = (double) tv_sec + (double) tv_nsec / 1000000000.00; if (Coroutine::get_current()) { System::sleep(_time); @@ -1664,42 +1923,37 @@ static PHP_FUNCTION(swoole_time_nanosleep) { } static PHP_FUNCTION(swoole_time_sleep_until) { - double d_ts, c_ts; - struct timeval tm; - struct timespec php_req, php_rem; + double target_secs; + const uint64_t ns_per_sec = 1000000000; + const double top_target_sec = (double) (UINT64_MAX / ns_per_sec); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "d", &d_ts) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_DOUBLE(target_secs) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (gettimeofday((struct timeval *) &tm, nullptr) != 0) { - RETURN_FALSE; + if (UNEXPECTED(!(target_secs >= 0 && target_secs <= top_target_sec))) { + zend_argument_value_error(1, "must be between 0 and %" PRIu64, (uint64_t) top_target_sec); + RETURN_THROWS(); } - c_ts = (double) (d_ts - tm.tv_sec - tm.tv_usec / 1000000.00); - if (c_ts < 0) { - php_error_docref(nullptr, E_WARNING, "Sleep until to time is less than current time"); - RETURN_FALSE; - } + using namespace std::chrono; + using dseconds = duration; - php_req.tv_sec = (time_t) c_ts; - if (php_req.tv_sec > c_ts) { - php_req.tv_sec--; + const auto now = system_clock::now(); + const auto target_duration = duration_cast(dseconds(target_secs)); + const auto target_time = system_clock::time_point(target_duration); + + if (target_time <= now) { + php_error_docref(NULL, E_WARNING, "Argument #1 ($timestamp) must be greater than or equal to the current time"); + RETURN_FALSE; } - php_req.tv_nsec = (long) ((c_ts - php_req.tv_sec) * 1000000000.00); - double _time = (double) php_req.tv_sec + (double) php_req.tv_nsec / 1000000000.00; + const auto sleep_duration = target_time - now; if (Coroutine::get_current()) { - System::sleep(_time); + const double sleep_seconds = duration_cast(sleep_duration).count(); + System::sleep(sleep_seconds); } else { - while (nanosleep(&php_req, &php_rem)) { - if (errno == EINTR) { - php_req.tv_sec = php_rem.tv_sec; - php_req.tv_nsec = php_rem.tv_nsec; - } else { - RETURN_FALSE; - } - } + std::this_thread::sleep_until(target_time); } RETURN_TRUE; } @@ -1708,7 +1962,6 @@ static void stream_array_to_fd_set(zval *stream_array, std::unordered_mapsecond.events |= event; } @@ -1731,8 +1984,7 @@ static void stream_array_to_fd_set(zval *stream_array, std::unordered_map(i.second.ptr); delete kv; } }; /* slight hack to support buffered data; if there is data sitting in the - * read buffer of any of the streams in the read array, let's pretend + * read buffer of the streams in the read array, let's pretend * that we selected, but return only the readable sockets */ if (r_array != nullptr) { retval = stream_array_emulate_read_fd_set(r_array); @@ -1866,7 +2126,7 @@ static PHP_FUNCTION(swoole_stream_select) { } for (auto &i : fds) { - zend::KeyValue *kv = (zend::KeyValue *) i.second.ptr; + auto *kv = static_cast(i.second.ptr); int revents = i.second.revents; SW_ASSERT((revents & (~(SW_EVENT_READ | SW_EVENT_WRITE | SW_EVENT_ERROR))) == 0); if (revents > 0) { @@ -1887,7 +2147,7 @@ static PHP_FUNCTION(swoole_stream_select) { } static void hook_func(const char *name, size_t l_name, zif_handler handler, zend_internal_arg_info *arg_info) { - real_func *rf = (real_func *) zend_hash_str_find_ptr(tmp_function_table, name, l_name); + auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); bool use_php_func = false; /** * use php library function @@ -1896,6 +2156,7 @@ static void hook_func(const char *name, size_t l_name, zif_handler handler, zend handler = PHP_FN(swoole_user_func_handler); use_php_func = true; } + if (rf) { rf->function->internal_function.handler = handler; if (arg_info) { @@ -1904,43 +2165,46 @@ static void hook_func(const char *name, size_t l_name, zif_handler handler, zend return; } - zend_function *zf = (zend_function *) zend_hash_str_find_ptr(EG(function_table), name, l_name); + auto *zf = zend::get_function(name, l_name); if (zf == nullptr) { + swoole_warning("The function named `%s` is not found", name); return; } - rf = (real_func *) emalloc(sizeof(real_func)); + auto fn_str = zf->common.function_name; + rf = static_cast(emalloc(sizeof(PhpFunc))); sw_memset_zero(rf, sizeof(*rf)); rf->function = zf; - rf->ori_handler = zf->internal_function.handler; - rf->ori_arg_info = zf->internal_function.arg_info; + + auto fn_name = std::string(fn_str->val, fn_str->len); + + if (!ori_func_arg_infos.exists(fn_name)) { + ori_func_handlers.set(fn_name, zf->internal_function.handler); + ori_func_arg_infos.set(fn_name, zf->internal_function.arg_info); + } + rf->ori_handler = ori_func_handlers.get(fn_name); + rf->ori_arg_info = ori_func_arg_infos.get(fn_name); + zf->internal_function.handler = handler; if (arg_info) { - zf->internal_function.arg_info = arg_info; + zf->internal_function.arg_info = copy_arginfo(&zf->internal_function, arg_info); + rf->arg_info_copy = zf->internal_function.arg_info; } if (use_php_func) { char func[128]; memcpy(func, ZEND_STRL("swoole_")); - memcpy(func + 7, zf->common.function_name->val, zf->common.function_name->len); - - ZVAL_STRINGL(&rf->name, func, zf->common.function_name->len + 7); + memcpy(func + 7, fn_str->val, fn_str->len); - char *func_name; - zend_fcall_info_cache *func_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(&rf->name, nullptr, 0, &func_name, nullptr, func_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return; - } - efree(func_name); - rf->fci_cache = func_cache; + ZVAL_STRINGL(&rf->name, func, fn_str->len + 7); + rf->fci_cache = sw_callable_create(&rf->name); } - zend_hash_add_ptr(tmp_function_table, zf->common.function_name, rf); + zend_hash_add_ptr(hook_function_table, fn_str, rf); } static void unhook_func(const char *name, size_t l_name) { - real_func *rf = (real_func *) zend_hash_str_find_ptr(tmp_function_table, name, l_name); + auto *rf = static_cast(zend_hash_str_find_ptr(hook_function_table, name, l_name)); if (rf == nullptr) { return; } @@ -1949,23 +2213,19 @@ static void unhook_func(const char *name, size_t l_name) { } php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, int type, int protocol STREAMS_DC) { - Socket *sock = new Socket(_fd, domain, type, protocol); - + auto *abstract = new NetStream(); + abstract->socket = std::make_shared(_fd, domain, type, protocol); if (FG(default_socket_timeout) > 0) { - sock->set_timeout((double) FG(default_socket_timeout)); + abstract->socket->set_timeout((double) FG(default_socket_timeout)); } - - php_swoole_netstream_data_t *abstract = (php_swoole_netstream_data_t *) ecalloc(1, sizeof(*abstract)); - - abstract->socket = sock; abstract->stream.timeout.tv_sec = FG(default_socket_timeout); - abstract->stream.socket = sock->get_fd(); + abstract->stream.socket = abstract->socket->get_fd(); abstract->blocking = true; php_stream *stream = php_stream_alloc_rel(&socket_ops, abstract, nullptr, "r+"); if (stream == nullptr) { - delete sock; + delete abstract; } else { stream->flags |= PHP_STREAM_FLAG_AVOID_BLOCKING; } @@ -1973,13 +2233,106 @@ php_stream *php_swoole_create_stream_from_socket(php_socket_t _fd, int domain, i return stream; } +zend_string *php_async_socket_error_str(long err) { + const char *errstr = swoole_strerror(err); + return zend_string_init(errstr, strlen(errstr), 0); +} + +int php_async_socket_connect_to_host(const char *host, + unsigned short port, + int socktype, + int asynchronous, + struct timeval *timeout, + zend_string **error_string, + int *error_code, + const char *bindto, + unsigned short bindport, + long sockopts) { + if (!swoole_coroutine_is_in()) { + php_swoole_fatal_error(E_WARNING, "This API must be called in coroutine"); + return -1; + } + + auto domain = swoole::network::Address::verify_ip(AF_INET6, host) ? AF_INET6 : AF_INET; + auto sock = swoole_coroutine_socket(domain, SOCK_STREAM, 0); + if (sock < 0) { + return -1; + } + + auto sockobj = swoole_coroutine_get_socket_object(sock); + if (bindto) { + if (!sockobj->bind(bindto, bindport)) { + php_swoole_fatal_error(E_WARNING, + "Failed to bind to '%s:%d', system said: %s", + bindto, + bindport, + swoole_strerror(sockobj->errCode)); + } + } + +#ifdef SO_BROADCAST + if (sockopts & STREAM_SOCKOP_SO_BROADCAST) { + sockobj->set_option(SOL_SOCKET, SO_BROADCAST, 1); + } +#endif + +#ifdef TCP_NODELAY + if (sockopts & STREAM_SOCKOP_TCP_NODELAY) { + sockobj->set_option(IPPROTO_TCP, TCP_NODELAY, 1); + } +#endif + + if (timeout) { + sockobj->set_timeout(timeout, SW_TIMEOUT_ALL); + } + + if (!sockobj->connect(host, port)) { + if (error_code) { + *error_code = sockobj->errCode; + } + if (error_string) { + *error_string = php_async_socket_error_str(sockobj->errCode); + } + if (sockobj->errCode == SW_ERROR_DNSLOOKUP_RESOLVE_FAILED) { + php_swoole_fatal_error(E_WARNING, "getaddrinfo for '%s' failed, error: %s", host, sockobj->get_err()); + } + swoole_coroutine_close(sock); + return -1; + } + + return sock; +} + +int php_async_socket_poll(php_socket_t fd, int events, int timeout) { + auto sockobj = swoole_coroutine_get_socket_object(fd); + if (!sockobj) { + return -1; + } + return sockobj->poll(events & POLLOUT ? SW_EVENT_WRITE : SW_EVENT_READ, timeout / 1000.0); +} + +php_stream *php_swoole_create_stream_from_pipe(int fd, const char *mode, const char *persistent_id STREAMS_DC) { + return _sw_php_stream_fopen_from_fd(fd, mode, persistent_id, false STREAMS_CC); +} + php_stream_ops *php_swoole_get_ori_php_stream_stdio_ops() { return &ori_php_stream_stdio_ops; } +zif_handler php_swoole_get_original_handler(const char *name, size_t len) { + zif_handler handler = ori_func_handlers.get(std::string(name, len)); + if (handler) { + return handler; + } + auto *zf = zend::get_function(name, len); + if (zf && zf->type == ZEND_INTERNAL_FUNCTION && zf->internal_function.handler) { + return zf->internal_function.handler; + } + return nullptr; +} + static PHP_FUNCTION(swoole_stream_socket_pair) { zend_long domain, type, protocol; - php_stream *s1, *s2; php_socket_t pair[2]; ZEND_PARSE_PARAMETERS_START(3, 3) @@ -1997,8 +2350,8 @@ static PHP_FUNCTION(swoole_stream_socket_pair) { php_swoole_check_reactor(); - s1 = php_swoole_create_stream_from_socket(pair[0], domain, type, protocol STREAMS_CC); - s2 = php_swoole_create_stream_from_socket(pair[1], domain, type, protocol STREAMS_CC); + php_stream *s1 = php_swoole_create_stream_from_socket(pair[0], domain, type, protocol STREAMS_CC); + php_stream *s2 = php_swoole_create_stream_from_socket(pair[1], domain, type, protocol STREAMS_CC); /* set the __exposed flag. * php_stream_to_zval() does, add_next_index_resource() does not */ @@ -2010,21 +2363,32 @@ static PHP_FUNCTION(swoole_stream_socket_pair) { } static PHP_FUNCTION(swoole_user_func_handler) { + auto fn_str = execute_data->func->common.function_name; + if (!swoole_coroutine_is_in()) { + auto ori_handler = ori_func_handlers.get(std::string(fn_str->val, fn_str->len)); + ori_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); + return; + } + + auto *rf = static_cast(zend_hash_find_ptr(hook_function_table, fn_str)); + if (!rf || !rf->fci_cache) { + zend_throw_exception_ex(swoole_exception_ce, SW_ERROR_UNDEFINED_BEHAVIOR, "%s func not exists", fn_str->val); + return; + } + zend_fcall_info fci; fci.size = sizeof(fci); fci.object = nullptr; - ZVAL_UNDEF(&fci.function_name); fci.retval = return_value; fci.param_count = ZEND_NUM_ARGS(); fci.params = ZEND_CALL_ARG(execute_data, 1); - fci.named_params = NULL; - - real_func *rf = (real_func *) zend_hash_find_ptr(tmp_function_table, execute_data->func->common.function_name); - zend_call_function(&fci, rf->fci_cache); + fci.named_params = nullptr; + ZVAL_UNDEF(&fci.function_name); + zend_call_function(&fci, rf->fci_cache->ptr()); } zend_class_entry *find_class_entry(const char *name, size_t length) { - zend_string *search_key = zend_string_init(name, length, 0); + zend_string *search_key = zend_string_init(name, length, false); zend_class_entry *class_ce = zend_lookup_class(search_key); zend_string_release(search_key); return class_ce ? class_ce : nullptr; @@ -2074,8 +2438,61 @@ static void detach_parent_class(const char *child_name) { } static void clear_class_entries() { - for (auto iter = child_class_entries.begin(); iter != child_class_entries.end(); iter++) { - start_detach_parent_class(iter->second); + for (const auto &child_class_entry : child_class_entries) { + start_detach_parent_class(child_class_entry.second); } child_class_entries.clear(); } + +#if defined(HAVE_PUTENV) && defined(SW_THREAD) +/* {{{ Set the value of an environment variable */ +static PHP_FUNCTION(swoole_putenv) { + char *setting; + size_t setting_len; + char *p; + bool result; + std::string key; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(setting, setting_len) + ZEND_PARSE_PARAMETERS_END(); + + if (setting_len == 0 || setting[0] == '=') { + zend_argument_value_error(1, "must have a valid syntax"); + RETURN_THROWS(); + } + + if ((p = strchr(setting, '='))) { + key = std::string(setting, p - setting); + } else { + key = std::string(setting, setting_len); + } + + tsrm_env_lock(); + swoole_runtime_environ[key] = std::string(setting, setting_len); + auto iter = swoole_runtime_environ.find(key); + +#ifdef HAVE_UNSETENV + if (!p) { /* no '=' means we want to unset it */ + unsetenv(iter->second.c_str()); + } + if (!p || putenv((char *) iter->second.c_str()) == 0) { /* success */ +#else + if (putenv((char *) iter->second.c_str()) == 0) { /* success */ +#endif + +#ifdef HAVE_TZSET + if (zend_binary_strcasecmp(key.c_str(), key.length(), ZEND_STRL("TZ")) == 0) { + tzset(); + } +#endif + result = true; + } else { + result = false; + } + + tsrm_env_unlock(); + RETURN_BOOL(result); +} +/* }}} */ +#endif diff --git a/ext-src/swoole_server.cc b/ext-src/swoole_server.cc index 41afb0b2e2..b86a0b5655 100644 --- a/ext-src/swoole_server.cc +++ b/ext-src/swoole_server.cc @@ -17,16 +17,16 @@ #include "php_swoole_server.h" #include "php_swoole_http_server.h" #include "php_swoole_process.h" +#include "php_swoole_thread.h" +#include "php_swoole_call_stack.h" #include "swoole_msg_queue.h" +#include "swoole_coroutine_system.h" #include "ext/standard/php_var.h" #include "zend_smart_str.h" -#ifdef SW_HAVE_ZLIB -#include -#endif - BEGIN_EXTERN_C() +#include "ext/json/php_json.h" #include "stubs/php_swoole_server_arginfo.h" END_EXTERN_C() @@ -69,32 +69,39 @@ static std::unordered_map server_event_map({ static void php_swoole_server_onStart(Server *); static void php_swoole_server_onBeforeShutdown(Server *serv); static void php_swoole_server_onShutdown(Server *); -static void php_swoole_server_onWorkerStart(Server *, int worker_id); +static void php_swoole_server_onWorkerStart(Server *, Worker *worker); static void php_swoole_server_onBeforeReload(Server *serv); static void php_swoole_server_onAfterReload(Server *serv); -static void php_swoole_server_onWorkerStop(Server *, int worker_id); -static void php_swoole_server_onWorkerExit(Server *serv, int worker_id); +static void php_swoole_server_onWorkerStop(Server *, Worker *worker); +static void php_swoole_server_onWorkerExit(Server *serv, Worker *worker); static void php_swoole_server_onUserWorkerStart(Server *serv, Worker *worker); -static int php_swoole_server_onTask(Server *, EventData *task); -static int php_swoole_server_onFinish(Server *, EventData *task); +static int php_swoole_server_onTask(Server *, EventData *req); +static int php_swoole_server_onFinish(Server *, EventData *req); static void php_swoole_server_onPipeMessage(Server *serv, EventData *req); -static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const ExitStatus &exit_status); +static void php_swoole_server_onWorkerError(Server *serv, Worker *worker, const ExitStatus &exit_status); static void php_swoole_server_onManagerStart(Server *serv); static void php_swoole_server_onManagerStop(Server *serv); -static int php_swoole_task_finish(Server *serv, zval *zdata, EventData *current_task); +static bool php_swoole_server_task_finish(Server *serv, zval *zdata, EventData *current_task); +static TaskId php_swoole_server_task_pack(zval *data, EventData *task); +static bool php_swoole_server_task_unpack(zval *zresult, EventData *task_result); static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendData *data); static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort *port); void php_swoole_server_rshutdown() { - if (!sw_server()) { + if (!sw_server() || !sw_worker()) { return; } Server *serv = sw_server(); + Worker *worker = sw_worker(); serv->drain_worker_pipe(); - if (serv->is_started() && !serv->is_user_worker()) { + if (serv->is_started() && worker->is_running() && !serv->is_user_worker()) { + worker->shutdown(); + if (serv->is_event_worker()) { + serv->clean_worker_connections(worker); + } if (php_swoole_is_fatal_error()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_PHP_FATAL_ERROR, @@ -133,6 +140,12 @@ static zend_object_handlers swoole_server_status_info_handlers; static zend_class_entry *swoole_server_task_result_ce; static zend_object_handlers swoole_server_task_result_handlers; +static SW_THREAD_LOCAL zval swoole_server_instance; +#ifdef SW_THREAD +static SW_THREAD_LOCAL WorkerFn worker_thread_fn; +static SW_THREAD_LOCAL std::vector swoole_server_port_properties; +#endif + static sw_inline ServerObject *server_fetch_object(zend_object *obj) { return (ServerObject *) ((char *) obj - swoole_server_handlers.offset); } @@ -149,16 +162,35 @@ Server *php_swoole_server_get_and_check_server(zval *zobject) { return serv; } -zval *php_swoole_server_get_zval_object(Server *serv) { - return (zval *) serv->private_data_2; +zval *php_swoole_server_zval_ptr(Server *serv) { + return &swoole_server_instance; +} + +ServerPortProperty *php_swoole_server_get_port_property(ListenPort *port) { +#ifdef SW_THREAD + return swoole_server_port_properties.at(port->object_id); +#else + return (ServerPortProperty *) port->ptr; +#endif +} + +void php_swoole_server_set_port_property(ListenPort *port, ServerPortProperty *property) { +#ifdef SW_THREAD + if (swoole_server_port_properties.size() < (size_t) port->object_id + 1) { + swoole_server_port_properties.resize((size_t) port->object_id + 1); + } + swoole_server_port_properties[port->object_id] = property; +#else + port->ptr = property; +#endif } ServerObject *php_swoole_server_get_zend_object(Server *serv) { - return server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); + return server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); } bool php_swoole_server_isset_callback(Server *serv, ListenPort *port, int event_type) { - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); + ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); return server_object->isset_callback(port, event_type); } @@ -173,21 +205,17 @@ static void server_free_object(zend_object *object) { if (serv) { if (serv->private_data_3) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) serv->private_data_3); - efree(serv->private_data_3); + sw_callable_free(serv->private_data_3); } - if (serv->private_data_2) { - efree(serv->private_data_2); - } - for (int i = 0; i < PHP_SWOOLE_SERVER_CALLBACK_NUM; i++) { - zend_fcall_info_cache *fci_cache = property->callbacks[i]; + for (auto &callback : property->callbacks) { + auto fci_cache = callback; if (fci_cache) { - efree(fci_cache); - property->callbacks[i] = nullptr; + sw_callable_free(fci_cache); + callback = nullptr; } } - for (auto i = property->user_processes.begin(); i != property->user_processes.end(); i++) { - sw_zval_free(*i); + for (auto &user_processe : property->user_processes) { + sw_zval_free(user_processe); } for (auto zport : property->ports) { php_swoole_server_port_deref(Z_OBJ_P(zport)); @@ -197,20 +225,24 @@ static void server_free_object(zend_object *object) { } for (auto fci_cache : property->command_callbacks) { - sw_zend_fci_cache_discard(fci_cache); - efree(fci_cache); + sw_callable_free(fci_cache); } delete property; zend_object_std_dtor(object); if (serv && serv->is_master()) { +#ifdef SW_THREAD + if (serv->is_thread_mode()) { + zend_string_release((zend_string *) serv->private_data_4); + } +#endif delete serv; } } static zend_object *server_create_object(zend_class_entry *ce) { - ServerObject *server_object = (ServerObject *) zend_object_alloc(sizeof(ServerObject), ce); + auto *server_object = static_cast(zend_object_alloc(sizeof(ServerObject), ce)); zend_object_std_init(&server_object->std, ce); object_properties_init(&server_object->std, ce); server_object->std.handlers = &swoole_server_handlers; @@ -244,8 +276,7 @@ static void php_swoole_connection_iterator_free_object(zend_object *object) { } static zend_object *php_swoole_connection_iterator_create_object(zend_class_entry *ce) { - ConnectionIteratorObject *connection = - (ConnectionIteratorObject *) zend_object_alloc(sizeof(ConnectionIteratorObject), ce); + auto *connection = static_cast(zend_object_alloc(sizeof(ConnectionIteratorObject), ce)); zend_object_std_init(&connection->std, ce); object_properties_init(&connection->std, ce); connection->std.handlers = &swoole_connection_iterator_handlers; @@ -291,7 +322,7 @@ static void php_swoole_server_task_free_object(zend_object *object) { } static zend_object *php_swoole_server_task_create_object(zend_class_entry *ce) { - ServerTaskObject *server_task = (ServerTaskObject *) zend_object_alloc(sizeof(ServerTaskObject), ce); + auto *server_task = static_cast(zend_object_alloc(sizeof(ServerTaskObject), ce)); zend_object_std_init(&server_task->std, ce); object_properties_init(&server_task->std, ce); server_task->std.handlers = &swoole_server_task_handlers; @@ -362,6 +393,7 @@ static PHP_METHOD(swoole_connection_iterator, __destruct); */ static PHP_METHOD(swoole_server_task, finish); static PHP_METHOD(swoole_server_task, pack); +static PHP_METHOD(swoole_server_task, unpack); SW_EXTERN_C_END // clang-format off @@ -416,7 +448,7 @@ static zend_function_entry swoole_server_methods[] = { PHP_ME(swoole_server, getSocket, arginfo_class_Swoole_Server_getSocket, ZEND_ACC_PUBLIC) #endif PHP_ME(swoole_server, bind, arginfo_class_Swoole_Server_bind, ZEND_ACC_PUBLIC) - {nullptr, nullptr, nullptr} + PHP_FE_END }; static const zend_function_entry swoole_connection_iterator_methods[] = @@ -436,14 +468,17 @@ static const zend_function_entry swoole_connection_iterator_methods[] = PHP_FE_END }; -static const zend_function_entry swoole_server_task_methods[] = +static constexpr zend_function_entry swoole_server_task_methods[] = { PHP_ME(swoole_server_task, finish, arginfo_class_Swoole_Server_Task_finish, ZEND_ACC_PUBLIC) PHP_ME(swoole_server_task, pack, arginfo_class_Swoole_Server_Task_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_server_task, unpack, arginfo_class_Swoole_Server_Task_unpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; // clang-format on +#define SWOOLE_DISPATCH_STREAM 7 // Deprecated, kept for forward compatibility + void php_swoole_server_minit(int module_number) { // ---------------------------------------Server------------------------------------- SW_INIT_CLASS_ENTRY(swoole_server, "Swoole\\Server", nullptr, swoole_server_methods); @@ -484,6 +519,7 @@ void php_swoole_server_minit(int module_number) { // ---------------------------------------PipeMessage------------------------------------- SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_server_pipe_message, "Swoole\\Server\\PipeMessage"); zend_declare_property_long(swoole_server_pipe_message_ce, ZEND_STRL("source_worker_id"), 0, ZEND_ACC_PUBLIC); + zend_declare_property_long(swoole_server_pipe_message_ce, ZEND_STRL("worker_id"), 0, ZEND_ACC_PUBLIC); zend_declare_property_double(swoole_server_pipe_message_ce, ZEND_STRL("dispatch_time"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_pipe_message_ce, ZEND_STRL("data"), ZEND_ACC_PUBLIC); // ---------------------------------------StatusInfo------------------------------------- @@ -511,9 +547,7 @@ void php_swoole_server_minit(int module_number) { ConnectionIteratorObject, std); zend_class_implements(swoole_connection_iterator_ce, 2, zend_ce_iterator, zend_ce_arrayaccess); -#ifdef SW_HAVE_COUNTABLE zend_class_implements(swoole_connection_iterator_ce, 1, zend_ce_countable); -#endif // ---------------------------------------Server Property------------------------------------- zend_declare_property_null(swoole_server_ce, ZEND_STRL("onStart"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_ce, ZEND_STRL("onBeforeShutdown"), ZEND_ACC_PRIVATE); @@ -545,13 +579,18 @@ void php_swoole_server_minit(int module_number) { zend_declare_property_long(swoole_server_ce, ZEND_STRL("worker_pid"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("stats_timer"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_server_ce, ZEND_STRL("admin_server"), ZEND_ACC_PUBLIC); +#ifdef SW_THREAD + zend_declare_property_string(swoole_server_ce, ZEND_STRL("bootstrap"), "", ZEND_ACC_PUBLIC); +#endif /** * mode type */ SW_REGISTER_LONG_CONSTANT("SWOOLE_BASE", swoole::Server::MODE_BASE); SW_REGISTER_LONG_CONSTANT("SWOOLE_PROCESS", swoole::Server::MODE_PROCESS); - +#ifdef SW_THREAD + SW_REGISTER_LONG_CONSTANT("SWOOLE_THREAD", swoole::Server::MODE_THREAD); +#endif /** * task ipc mode */ @@ -572,7 +611,7 @@ void php_swoole_server_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_IPMOD", Server::DISPATCH_IPMOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_UIDMOD", Server::DISPATCH_UIDMOD); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_USERFUNC", Server::DISPATCH_USERFUNC); - SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_STREAM", Server::DISPATCH_STREAM); + SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_STREAM", SWOOLE_DISPATCH_STREAM); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CO_CONN_LB", Server::DISPATCH_CO_CONN_LB); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CO_REQ_LB", Server::DISPATCH_CO_REQ_LB); SW_REGISTER_LONG_CONSTANT("SWOOLE_DISPATCH_CONCURRENT_LB", Server::DISPATCH_CONCURRENT_LB); @@ -595,19 +634,18 @@ void php_swoole_server_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("SWOOLE_WORKER_EXIT", SW_WORKER_EXIT); } -zend_fcall_info_cache *php_swoole_server_get_fci_cache(Server *serv, int server_fd, int event_type) { +zend::Callable *php_swoole_server_get_callback(Server *serv, int server_fd, int event_type) { ListenPort *port = serv->get_port_by_server_fd(server_fd); - ServerPortProperty *property; - zend_fcall_info_cache *fci_cache; - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); + ServerPortProperty *property = php_swoole_server_get_port_property(port); + zend::Callable *cb; if (sw_unlikely(!port)) { return nullptr; } - if ((property = (ServerPortProperty *) port->ptr) && (fci_cache = property->caches[event_type])) { - return fci_cache; + if (property && ((cb = property->callbacks[event_type]))) { + return cb; } else { - return server_object->property->primary_port->caches[event_type]; + return php_swoole_server_get_port_property(serv->get_primary_port())->callbacks[event_type]; } } @@ -618,7 +656,7 @@ int php_swoole_create_dir(const char *path, size_t length) { return php_stream_mkdir(path, 0777, PHP_STREAM_MKDIR_RECURSIVE | REPORT_ERRORS, nullptr) ? 0 : -1; } -TaskId php_swoole_task_pack(EventData *task, zval *zdata) { +static TaskId php_swoole_server_task_pack(zval *zdata, EventData *task) { smart_str serialized_data = {}; php_serialize_data_t var_hash; @@ -666,7 +704,7 @@ void php_swoole_get_recv_data(Server *serv, zval *zdata, RecvData *req) { } else { if (req->info.flags & SW_EVENT_DATA_OBJ_PTR) { zend::assign_zend_string_by_val(zdata, (char *) data, length); - serv->message_bus.move_packet(); + serv->get_worker_message_bus()->move_packet(); } else if (req->info.flags & SW_EVENT_DATA_POP_PTR) { String *recv_buffer = serv->get_recv_buffer(serv->get_connection_by_session_id(req->info.fd)->socket); zend::assign_zend_string_by_val(zdata, recv_buffer->pop(serv->recv_buffer_size), length); @@ -676,7 +714,7 @@ void php_swoole_get_recv_data(Server *serv, zval *zdata, RecvData *req) { } } -static sw_inline int php_swoole_check_task_param(Server *serv, zend_long dst_worker_id) { +static inline int php_swoole_server_task_check_param(Server *serv, zend_long dst_worker_id) { if (UNEXPECTED(serv->task_worker_num == 0)) { php_swoole_fatal_error(E_WARNING, "task method can't be executed without task worker"); return SW_ERR; @@ -692,40 +730,31 @@ static sw_inline int php_swoole_check_task_param(Server *serv, zend_long dst_wor return SW_OK; } -zval *php_swoole_task_unpack(EventData *task_result) { - zval *result_data, *result_unserialized_data; +static bool php_swoole_server_task_unpack(zval *zresult, EventData *task_result) { php_unserialize_data_t var_hash; PacketPtr packet; if (!Server::task_unpack(task_result, sw_tg_buffer(), &packet)) { - return nullptr; + return false; } - char *result_data_str = packet.data; - size_t result_data_len = packet.length; - if (task_result->info.ext_flags & SW_TASK_SERIALIZE) { - result_unserialized_data = sw_malloc_zval(); - + char *p = packet.data; + size_t l = packet.length; PHP_VAR_UNSERIALIZE_INIT(var_hash); - // unserialize success - if (php_var_unserialize(result_unserialized_data, - (const unsigned char **) &result_data_str, - (const unsigned char *) (result_data_str + result_data_len), - &var_hash)) { - result_data = result_unserialized_data; - } - // failed - else { - result_data = sw_malloc_zval(); - ZVAL_STRINGL(result_data, result_data_str, result_data_len); - } + zend_bool unserialized = php_var_unserialize(zresult, (const uchar **) &p, (const uchar *) (p + l), &var_hash); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + if (!unserialized) { + swoole_warning("unserialize() failed, Error at offset " ZEND_LONG_FMT " of %zd bytes", + (zend_long) ((char *) p - packet.data), + l); + return false; + } } else { - result_data = sw_malloc_zval(); - ZVAL_STRINGL(result_data, result_data_str, result_data_len); + ZVAL_STRINGL(zresult, packet.data, packet.length); } - return result_data; + + return true; } extern ListenPort *php_swoole_server_port_get_and_check_ptr(zval *zobject); @@ -733,11 +762,9 @@ extern void php_swoole_server_port_set_ptr(zval *zobject, ListenPort *port); extern ServerPortProperty *php_swoole_server_port_get_property(zval *zobject); static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort *port) { - /* port */ - zval *zport; Server *serv = server_object->serv; - zport = sw_malloc_zval(); + zval *zport = sw_malloc_zval(); object_init_ex(zport, swoole_server_port_ce); server_object->property->ports.push_back(zport); @@ -750,7 +777,7 @@ static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort property->port = port; /* linked */ - port->ptr = property; + php_swoole_server_set_port_property(port, property); zend_update_property_string(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("host"), port->get_host()); zend_update_property_long(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("port"), port->get_port()); @@ -759,10 +786,10 @@ static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort zend_update_property_bool(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("ssl"), port->ssl); do { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); zval *zports = sw_zend_read_and_convert_property_array(Z_OBJCE_P(zserv), zserv, ZEND_STRL("ports"), 0); (void) add_next_index_zval(zports, zport); - } while (0); + } while (false); /* iterator */ do { @@ -775,11 +802,22 @@ static zval *php_swoole_server_add_port(ServerObject *server_object, ListenPort zend_update_property(swoole_server_port_ce, SW_Z8_OBJ_P(zport), ZEND_STRL("connections"), &connection_iterator); zval_ptr_dtor(&connection_iterator); - } while (0); + } while (false); return zport; } +void ServerObject::copy_setting(zval *zsetting) const { + zend_array *new_array = zend_array_dup(Z_ARRVAL_P(zsetting)); + zend_hash_apply(new_array, [](zval *el) -> int { + return sw_zval_is_serializable(el) ? ZEND_HASH_APPLY_KEEP : ZEND_HASH_APPLY_REMOVE; + }); + zval znew_array; + ZVAL_ARR(&znew_array, new_array); + serv->private_data_4 = php_swoole_serialize(&znew_array); + zval_ptr_dtor(&znew_array); +} + void ServerObject::on_before_start() { /** * create swoole server @@ -789,7 +827,7 @@ void ServerObject::on_before_start() { return; } - zval *zobject = get_object(); + zval *zobject = php_swoole_server_zval_ptr(serv); auto primary_port = serv->get_primary_port(); #ifdef SW_LOG_TRACE_OPEN @@ -802,7 +840,7 @@ void ServerObject::on_before_start() { #endif if (serv->enable_coroutine) { - serv->reload_async = 1; + serv->reload_async = true; } if (serv->send_yield) { @@ -813,7 +851,7 @@ void ServerObject::on_before_start() { serv->message_bus.set_allocator(sw_zend_string_allocator()); - if (serv->is_base_mode()) { + if (serv->is_base_mode() || serv->is_thread_mode()) { serv->recv_buffer_allocator = sw_zend_string_allocator(); } @@ -849,7 +887,7 @@ void ServerObject::on_before_start() { add_assoc_bool(zsetting, "open_eof_check", 0); add_assoc_bool(zsetting, "open_length_check", 0); primary_port->clear_protocol(); - primary_port->open_redis_protocol = 1; + primary_port->open_redis_protocol = true; serv->onReceive = php_swoole_redis_server_onReceive; } else if (is_http_server()) { if (is_websocket_server()) { @@ -878,7 +916,7 @@ void ServerObject::on_before_start() { protocol_flag |= SW_WEBSOCKET_PROTOCOL; } primary_port->clear_protocol(); - primary_port->open_http_protocol = 1; + primary_port->open_http_protocol = true; primary_port->open_http2_protocol = !!(protocol_flag & SW_HTTP2_PROTOCOL); primary_port->open_websocket_protocol = !!(protocol_flag & SW_WEBSOCKET_PROTOCOL); find_http_port = true; @@ -906,8 +944,7 @@ void ServerObject::on_before_start() { } } - for (size_t i = 0; i < property->ports.size(); i++) { - zval *zport = property->ports.at(i); + for (auto zport : property->ports) { ListenPort *port = php_swoole_server_port_get_and_check_ptr(zport); if (serv->if_require_packet_callback(port, isset_callback(port, SW_SERVER_CB_onPacket))) { @@ -915,12 +952,14 @@ void ServerObject::on_before_start() { return; } -#ifdef SW_USE_OPENSSL - if (port->ssl_context && port->ssl_context->verify_peer && port->ssl_context->client_cert_file.empty()) { - php_swoole_fatal_error(E_ERROR, "server open verify peer require client_cert_file config"); + if (port->ssl_is_enable() && port->get_ssl_verify_peer() && port->get_ssl_client_cert_file().empty() && + port->get_ssl_cafile().empty() && port->get_ssl_capath().empty()) { + php_swoole_fatal_error( + E_ERROR, + "server open verify peer require `ssl_client_cert_file` or `ssl_capath` or `ssl_cafile` config"); return; } -#endif + if (port->open_http2_protocol && !serv->is_hash_dispatch_mode()) { php_swoole_fatal_error( E_ERROR, @@ -962,14 +1001,23 @@ void ServerObject::on_before_start() { if (find_http_port) { serv->onReceive = php_swoole_http_server_onReceive; + if (serv->is_base_mode()) { + serv->onClose = php_swoole_http_server_onClose; + } } +#ifdef SW_THREAD + if (serv->is_thread_mode()) { + copy_setting(zsetting); + } +#endif + if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onBeforeStart", 1, zobject); + zend::function::call(R"(\Swoole\Server\Helper::onBeforeStart)", 1, zobject); } } -void ServerObject::register_callback() { +void ServerObject::register_callback() const { // control plane serv->onStart = php_swoole_server_onStart; serv->onBeforeShutdown = php_swoole_server_onBeforeShutdown; @@ -996,13 +1044,12 @@ void ServerObject::register_callback() { } } -static int php_swoole_task_finish(Server *serv, zval *zdata, EventData *current_task) { +static bool php_swoole_server_task_finish(Server *serv, zval *zdata, EventData *current_task) { int flags = 0; smart_str serialized_data = {}; php_serialize_data_t var_hash; char *data_str; size_t data_len = 0; - int ret; // need serialize if (Z_TYPE_P(zdata) != IS_STRING) { @@ -1020,18 +1067,18 @@ static int php_swoole_task_finish(Server *serv, zval *zdata, EventData *current_ data_len = Z_STRLEN_P(zdata); } - ret = serv->reply_task_result(data_str, data_len, flags, current_task); + bool success = serv->finish(data_str, data_len, flags, current_task); smart_str_free(&serialized_data); - return ret; + return success; } static void php_swoole_server_onPipeMessage(Server *serv, EventData *req) { - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); - zend_fcall_info_cache *fci_cache = server_object->property->callbacks[SW_SERVER_CB_onPipeMessage]; - zval *zserv = (zval *) serv->private_data_2; + ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); + auto cb = server_object->get_callback(SW_SERVER_CB_onPipeMessage); + zval *zserv = php_swoole_server_zval_ptr(serv); - zval *zdata = php_swoole_task_unpack(req); - if (UNEXPECTED(zdata == nullptr)) { + zend::Variable zresult; + if (UNEXPECTED(!php_swoole_server_task_unpack(zresult.ptr(), req))) { return; } @@ -1053,33 +1100,35 @@ static void php_swoole_server_onPipeMessage(Server *serv, EventData *req) { zend_update_property_long(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_id"), - (zend_long) req->info.reactor_id); + (zend_long) serv->get_task_src_worker_id(req)); + zend_update_property_long(swoole_server_pipe_message_ce, + SW_Z8_OBJ_P(object), + ZEND_STRL("source_worker_id"), + (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_double( swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time); - zend_update_property(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zdata); + zend_update_property(swoole_server_pipe_message_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zresult.ptr()); argc = 2; } else { - ZVAL_LONG(&args[1], (zend_long) req->info.reactor_id); - args[2] = *zdata; + ZVAL_LONG(&args[1], (zend_long) serv->get_task_src_worker_id(req)); + args[2] = zresult.value; argc = 3; } - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onPipeMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (serv->event_object) { zval_ptr_dtor(&args[1]); } - - sw_zval_free(zdata); } int php_swoole_server_onReceive(Server *serv, RecvData *req) { - auto fci_cache = php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onReceive); + auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onReceive); - if (fci_cache) { - zval *zserv = (zval *) serv->private_data_2; + if (cb) { + zval *zserv = php_swoole_server_zval_ptr(serv); zval args[4]; int argc; @@ -1106,7 +1155,7 @@ int php_swoole_server_onReceive(Server *serv, RecvData *req) { argc = 4; } - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) { + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onReceive handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); serv->close(req->info.fd, false); } @@ -1121,13 +1170,13 @@ int php_swoole_server_onReceive(Server *serv, RecvData *req) { } int php_swoole_server_onPacket(Server *serv, RecvData *req) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; - DgramPacket *packet = (DgramPacket *) req->data; + auto *packet = (DgramPacket *) req->data; if (serv->event_object) { zval zobject; @@ -1196,8 +1245,8 @@ int php_swoole_server_onPacket(Server *serv, RecvData *req) { argc = 3; } - auto fci_cache = php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onPacket); - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) { + auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onPacket); + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onPipeMessage handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } @@ -1214,9 +1263,12 @@ static sw_inline void php_swoole_create_task_object(zval *ztask, Server *serv, E php_swoole_server_task_set_server(ztask, serv); php_swoole_server_task_set_info(ztask, &req->info); + zend_update_property_long(swoole_server_task_ce, + SW_Z8_OBJ_P(ztask), + ZEND_STRL("worker_id"), + (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_long( - swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("worker_id"), (zend_long) req->info.reactor_id); - zend_update_property_long(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("id"), (zend_long) req->info.fd); + swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("id"), (zend_long) serv->get_task_id(req)); zend_update_property(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("data"), zdata); zend_update_property_double(swoole_server_task_ce, SW_Z8_OBJ_P(ztask), ZEND_STRL("dispatch_time"), req->info.time); zend_update_property_long( @@ -1226,11 +1278,11 @@ static sw_inline void php_swoole_create_task_object(zval *ztask, Server *serv, E static int php_swoole_server_onTask(Server *serv, EventData *req) { sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - zval *zdata = php_swoole_task_unpack(req); - if (zdata == nullptr) { + zend::Variable zresult; + if (!php_swoole_server_task_unpack(zresult.ptr(), req)) { return SW_ERR; } @@ -1241,16 +1293,16 @@ static int php_swoole_server_onTask(Server *serv, EventData *req) { if (serv->task_enable_coroutine || serv->task_object) { argc = 2; argv[0] = *zserv; - php_swoole_create_task_object(&argv[1], serv, req, zdata); + php_swoole_create_task_object(&argv[1], serv, req, zresult.ptr()); } else { argc = 4; argv[0] = *zserv; - ZVAL_LONG(&argv[1], (zend_long) req->info.fd); - ZVAL_LONG(&argv[2], (zend_long) req->info.reactor_id); - argv[3] = *zdata; + ZVAL_LONG(&argv[1], (zend_long) serv->get_task_id(req)); + ZVAL_LONG(&argv[2], (zend_long) serv->get_task_src_worker_id(req)); + argv[3] = zresult.value; } - if (UNEXPECTED(!zend::function::call(server_object->property->callbacks[SW_SERVER_CB_onTask], + if (UNEXPECTED(!zend::function::call(server_object->get_callback(SW_SERVER_CB_onTask)->ptr(), argc, argv, &retval, @@ -1261,10 +1313,9 @@ static int php_swoole_server_onTask(Server *serv, EventData *req) { if (argc == 2) { zval_ptr_dtor(&argv[1]); } - sw_zval_free(zdata); if (!ZVAL_IS_NULL(&retval)) { - php_swoole_task_finish(serv, &retval, req); + php_swoole_server_task_finish(serv, &retval, req); zval_ptr_dtor(&retval); } @@ -1272,36 +1323,32 @@ static int php_swoole_server_onTask(Server *serv, EventData *req) { } static int php_swoole_server_onFinish(Server *serv, EventData *req) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); + TaskId task_id = serv->get_task_id(req); - zval *zdata = php_swoole_task_unpack(req); - if (zdata == nullptr) { + zend::Variable zresult; + if (!php_swoole_server_task_unpack(zresult.ptr(), req)) { return SW_ERR; } if (req->info.ext_flags & SW_TASK_COROUTINE) { - TaskId task_id = req->info.fd; auto task_co_iterator = server_object->property->task_coroutine_map.find(task_id); - if (task_co_iterator == server_object->property->task_coroutine_map.end()) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_TASK_TIMEOUT, "task[%ld] has expired", task_id); - _fail: - sw_zval_free(zdata); return SW_OK; } TaskCo *task_co = task_co_iterator->second; // Server->taskwait if (task_co->list == nullptr) { - ZVAL_COPY_VALUE(task_co->result, zdata); + ZVAL_COPY_VALUE(task_co->result, zresult.ptr()); + zresult.reset(); task_co->co->resume(); return SW_OK; } // Server->taskCo - uint32_t i; int task_index = -1; - zval *result = task_co->result; - for (i = 0; i < task_co->count; i++) { + SW_LOOP_N(task_co->count) { if (task_co->list[i] == task_id) { task_index = i; break; @@ -1309,32 +1356,32 @@ static int php_swoole_server_onFinish(Server *serv, EventData *req) { } if (task_index < 0) { php_swoole_fatal_error(E_WARNING, "task[%ld] is invalid", task_id); - goto _fail; + return SW_OK; } - (void) add_index_zval(result, task_index, zdata); - efree(zdata); + (void) add_index_zval(task_co->result, task_index, zresult.ptr()); + zresult.reset(); server_object->property->task_coroutine_map.erase(task_id); - if (php_swoole_array_length(result) == task_co->count) { + if (php_swoole_array_length(task_co->result) == task_co->count) { task_co->co->resume(); } return SW_OK; } - zend_fcall_info_cache *fci_cache = nullptr; + zend::Callable *cb = nullptr; if (req->info.ext_flags & SW_TASK_CALLBACK) { - auto callback_iterator = server_object->property->task_callbacks.find(req->info.fd); + auto callback_iterator = server_object->property->task_callbacks.find(task_id); if (callback_iterator == server_object->property->task_callbacks.end()) { req->info.ext_flags = req->info.ext_flags & (~SW_TASK_CALLBACK); } else { - fci_cache = &callback_iterator->second; + cb = callback_iterator->second; } } else { - fci_cache = server_object->property->callbacks[SW_SERVER_CB_onFinish]; + cb = server_object->get_callback(SW_SERVER_CB_onFinish); } - if (UNEXPECTED(fci_cache == nullptr)) { - sw_zval_free(zdata); - php_swoole_fatal_error(E_WARNING, "require onFinish callback"); + + if (UNEXPECTED(cb == nullptr)) { + php_swoole_fatal_error(E_WARNING, "require 'onFinish' callback"); return SW_ERR; } @@ -1346,29 +1393,28 @@ static int php_swoole_server_onFinish(Server *serv, EventData *req) { zval *object = &args[1]; object_init_ex(object, swoole_server_task_result_ce); zend_update_property_long( - swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("task_id"), (zend_long) req->info.fd); + swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("task_id"), (zend_long) task_id); zend_update_property_long(swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("task_worker_id"), - (zend_long) req->info.reactor_id); + (zend_long) serv->get_task_src_worker_id(req)); zend_update_property_double( swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("dispatch_time"), req->info.time); - zend_update_property(swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zdata); + zend_update_property(swoole_server_task_result_ce, SW_Z8_OBJ_P(object), ZEND_STRL("data"), zresult.ptr()); argc = 2; } else { - ZVAL_LONG(&args[1], req->info.fd); - args[2] = *zdata; + ZVAL_LONG(&args[1], (zend_long) task_id); + args[2] = zresult.value; argc = 3; } - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) { + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onFinish handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } if (req->info.ext_flags & SW_TASK_CALLBACK) { - sw_zend_fci_cache_discard(fci_cache); - server_object->property->task_callbacks.erase(req->info.fd); + sw_callable_free(server_object->property->task_callbacks[task_id]); + server_object->property->task_callbacks.erase(task_id); } - sw_zval_free(zdata); if (serv->event_object) { zval_ptr_dtor(&args[1]); } @@ -1377,176 +1423,171 @@ static int php_swoole_server_onFinish(Server *serv, EventData *req) { } static void php_swoole_server_onStart(Server *serv) { - serv->lock(); - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onStart]; + auto cb = server_object->get_callback(SW_SERVER_CB_onStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onStart", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onStart)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, serv->is_enable_coroutine()))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } - serv->unlock(); } static void php_swoole_server_onManagerStart(Server *serv) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onManagerStart]; + auto cb = server_object->get_callback(SW_SERVER_CB_onManagerStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onManagerStart", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onManagerStart)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onManagerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onManagerStop(Server *serv) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onManagerStop]; + auto cb = server_object->get_callback(SW_SERVER_CB_onManagerStop); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onManagerStop", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onManagerStop)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onManagerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onBeforeShutdown(Server *serv) { - serv->lock(); - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onBeforeShutdown]; + auto cb = server_object->get_callback(SW_SERVER_CB_onBeforeShutdown); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onBeforeShutdown", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onBeforeShutdown)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, serv->is_enable_coroutine()))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onBeforeShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } - serv->unlock(); } static void php_swoole_server_onShutdown(Server *serv) { - serv->lock(); - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onShutdown]; + auto cb = server_object->get_callback(SW_SERVER_CB_onShutdown); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onShutdown", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onShutdown)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onShutdown handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } - serv->unlock(); } -static void php_swoole_server_onWorkerStart(Server *serv, int worker_id) { - zval *zserv = (zval *) serv->private_data_2; +static void php_swoole_server_onWorkerStart(Server *serv, Worker *worker) { + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onWorkerStart]; + auto cb = server_object->get_callback(SW_SERVER_CB_onWorkerStart); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); - zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("worker_id"), worker_id); + zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("worker_id"), worker->id); zend_update_property_bool(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("taskworker"), serv->is_task_worker()); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("worker_pid"), getpid()); - if (serv->is_task_worker() && !serv->task_enable_coroutine) { + if (serv->is_task_worker() && !serv->task_enable_coroutine && !serv->is_thread_mode()) { PHPCoroutine::disable_hook(); } + serv->get_worker_message_bus()->set_allocator(sw_zend_string_allocator()); zval args[2]; args[0] = *zserv; - ZVAL_LONG(&args[1], worker_id); + ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onWorkerStart", 2, args); + zend::function::call(R"(\Swoole\Server\Helper::onWorkerStart)", 2, args); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onWorkerStart handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onBeforeReload(Server *serv) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onBeforeReload]; + auto cb = server_object->get_callback(SW_SERVER_CB_onBeforeReload); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onBeforeReload", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onBeforeReload)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBeforeReload handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } static void php_swoole_server_onAfterReload(Server *serv) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onAfterReload]; + auto cb = server_object->get_callback(SW_SERVER_CB_onAfterReload); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onAfterReload", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onAfterReload)", 1, zserv); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 1, zserv, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 1, zserv, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onAfterReload handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } -static void php_swoole_server_onWorkerStop(Server *serv, int worker_id) { - if (SwooleWG.shutdown) { +static void php_swoole_server_onWorkerStop(Server *serv, Worker *worker) { + if (!worker->is_running()) { return; } - SwooleWG.shutdown = true; - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onWorkerStop]; + auto cb = server_object->get_callback(SW_SERVER_CB_onWorkerStop); + zval args[2]; args[0] = *zserv; - ZVAL_LONG(&args[1], worker_id); + ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onWorkerStop", 2, args); + zend::function::call(R"(\Swoole\Server\Helper::onWorkerStop)", 2, args); } - if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, false))) { + if (cb && UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onWorkerStop handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } -static void php_swoole_server_onWorkerExit(Server *serv, int worker_id) { - zval *zserv = (zval *) serv->private_data_2; +static void php_swoole_server_onWorkerExit(Server *serv, Worker *worker) { + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onWorkerExit]; + auto fci_cache = server_object->get_callback(SW_SERVER_CB_onWorkerExit); zval args[2]; args[0] = *zserv; - ZVAL_LONG(&args[1], worker_id); + ZVAL_LONG(&args[1], worker->id); if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onWorkerExit", 2, args); + zend::function::call(R"(\Swoole\Server\Helper::onWorkerExit)", 2, args); } if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, false))) { @@ -1555,20 +1596,29 @@ static void php_swoole_server_onWorkerExit(Server *serv, int worker_id) { } static void php_swoole_server_onUserWorkerStart(Server *serv, Worker *worker) { - zval *object = (zval *) worker->ptr; - zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(object), ZEND_STRL("id"), SwooleG.process_id); + zval *object; + zval *zserv = php_swoole_server_zval_ptr(serv); + + if (serv->is_thread_mode()) { + ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); + int index = worker->id - serv->worker_num - serv->task_worker_num; + object = server_object->property->user_processes[index]; + serv->get_worker_message_bus()->set_allocator(sw_zend_string_allocator()); + } else { + object = (zval *) worker->ptr; + } - zval *zserv = (zval *) serv->private_data_2; + zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(object), ZEND_STRL("id"), worker->id); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("master_pid"), serv->gs->master_pid); zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("manager_pid"), serv->gs->manager_pid); php_swoole_process_start(worker, object); } -static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const ExitStatus &exit_status) { - zval *zserv = (zval *) serv->private_data_2; +static void php_swoole_server_onWorkerError(Server *serv, Worker *worker, const ExitStatus &exit_status) { + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); - auto fci_cache = server_object->property->callbacks[SW_SERVER_CB_onWorkerError]; + auto fci_cache = server_object->get_callback(SW_SERVER_CB_onWorkerError); zval args[5]; int argc; @@ -1577,7 +1627,8 @@ static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const E if (serv->event_object) { zval *object = &args[1]; object_init_ex(object, swoole_server_status_info_ce); - zend_update_property_long(swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_id"), worker_id); + zend_update_property_long( + swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_id"), worker->id); zend_update_property_long( swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("worker_pid"), exit_status.get_pid()); zend_update_property_long( @@ -1588,7 +1639,7 @@ static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const E swoole_server_status_info_ce, SW_Z8_OBJ_P(object), ZEND_STRL("signal"), exit_status.get_signal()); argc = 2; } else { - ZVAL_LONG(&args[1], worker_id); + ZVAL_LONG(&args[1], worker->id); ZVAL_LONG(&args[2], exit_status.get_pid()); ZVAL_LONG(&args[3], exit_status.get_code()); ZVAL_LONG(&args[4], exit_status.get_signal()); @@ -1596,7 +1647,7 @@ static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const E } if (SWOOLE_G(enable_library)) { - zend::function::call("\\Swoole\\Server\\Helper::onWorkerError", 1, zserv); + zend::function::call(R"(\Swoole\Server\Helper::onWorkerError)", 1, zserv); } if (fci_cache && UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, false))) { @@ -1609,12 +1660,12 @@ static void php_swoole_server_onWorkerError(Server *serv, int worker_id, const E } void php_swoole_server_onConnect(Server *serv, DataHead *info) { - auto fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onConnect); - if (!fci_cache) { + auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onConnect); + if (!cb) { return; } - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; @@ -1634,7 +1685,7 @@ void php_swoole_server_onConnect(Server *serv, DataHead *info) { argc = 3; } - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) { + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onConnect handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } @@ -1644,7 +1695,7 @@ void php_swoole_server_onConnect(Server *serv, DataHead *info) { } void php_swoole_server_onClose(Server *serv, DataHead *info) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); SessionId session_id = info->fd; @@ -1663,7 +1714,7 @@ void php_swoole_server_onClose(Server *serv, DataHead *info) { } } - auto *fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onClose); + auto *cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onClose); Connection *conn = serv->get_connection_by_session_id(session_id); if (!conn) { return; @@ -1672,11 +1723,11 @@ void php_swoole_server_onClose(Server *serv, DataHead *info) { ListenPort *port = serv->get_port_by_server_fd(info->server_fd); if (port && port->open_websocket_protocol && php_swoole_server_isset_callback(serv, port, SW_SERVER_CB_onDisconnect)) { - fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onDisconnect); + cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onDisconnect); } } - if (fci_cache) { - zval *zserv = (zval *) serv->private_data_2; + if (cb) { + zval *zserv = php_swoole_server_zval_ptr(serv); zval args[3]; int argc; args[0] = *zserv; @@ -1697,7 +1748,7 @@ void php_swoole_server_onClose(Server *serv, DataHead *info) { argc = 3; } - if (UNEXPECTED(!zend::function::call(fci_cache, argc, args, nullptr, serv->enable_coroutine))) { + if (UNEXPECTED(!zend::function::call(cb, argc, args, nullptr, serv->enable_coroutine))) { php_swoole_error(E_WARNING, "%s->onClose handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } @@ -1706,34 +1757,39 @@ void php_swoole_server_onClose(Server *serv, DataHead *info) { } } if (conn->http2_stream) { - swoole_http2_server_session_free(conn); + php_swoole_http2_server_onClose(serv, conn->session_id); } } void php_swoole_server_onBufferFull(Server *serv, DataHead *info) { - zval *zserv = (zval *) serv->private_data_2; - auto fci_cache = php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onBufferFull); + zval *zserv = php_swoole_server_zval_ptr(serv); + auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onBufferFull); - if (fci_cache) { + if (cb) { zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], info->fd); - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, false))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBufferFull handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } } -void php_swoole_server_send_yield(Server *serv, SessionId session_id, zval *zdata, zval *return_value) { - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); +void php_swoole_server_check_kernel_nobufs(Server *serv, SessionId session_id) { + if (swoole_coroutine_is_in() && serv->has_kernel_nobufs_error(session_id)) { + swoole::coroutine::System::sleep(0.01); + } +} + +bool php_swoole_server_send_yield(Server *serv, SessionId session_id, zend_string *sdata) { + ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); Coroutine *co = Coroutine::get_current_safe(); - char *data; - size_t length = php_swoole_get_send_data(zdata, &data); + size_t length = ZSTR_LEN(sdata); if (length == 0) { - RETURN_FALSE; + return false; } SW_LOOP { @@ -1749,35 +1805,35 @@ void php_swoole_server_send_yield(Server *serv, SessionId session_id, zval *zdat auto iter = std::prev(co_list->end()); if (!co->yield_ex(serv->send_timeout)) { co_list->erase(iter); - RETURN_FALSE; + return false; } - bool ret = serv->send(session_id, data, length); - if (!ret && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD && serv->send_yield) { + bool rv = serv->send(session_id, ZSTR_VAL(sdata), length); + if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD && serv->send_yield) { continue; - } else { - RETURN_BOOL(ret); } + return rv; } + + return false; } static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendData *data) { - serv->lock(); - - auto fci_cache = (zend_fcall_info_cache *) serv->private_data_3; + auto cb = (zend::Callable *) serv->private_data_3; zval args[4]; zval *zserv = &args[0], *zfd = &args[1], *ztype = &args[2], *zdata = nullptr; zval retval; zend_long worker_id = -1; - *zserv = *((zval *) serv->private_data_2); + *zserv = *(php_swoole_server_zval_ptr(serv)); ZVAL_LONG(zfd, conn ? conn->session_id : data->info.fd); - ZVAL_LONG(ztype, (zend_long)(data ? data->info.type : (int) SW_SERVER_EVENT_CLOSE)); - if (data && sw_zend_function_max_num_args(fci_cache->function_handler) > 3) { + ZVAL_LONG(ztype, (zend_long) (data ? data->info.type : (int) SW_SERVER_EVENT_CLOSE)); + if (data && sw_zend_function_max_num_args(cb->ptr()->function_handler) > 3) { // TODO: reduce memory copy zdata = &args[3]; ZVAL_STRINGL(zdata, data->data, data->info.len > SW_IPC_BUFFER_SIZE ? SW_IPC_BUFFER_SIZE : data->info.len); } - if (UNEXPECTED(sw_zend_call_function_ex(nullptr, fci_cache, zdata ? 4 : 3, args, &retval) != SUCCESS)) { + HOOK_PHP_CALL_STACK(auto call_result = sw_zend_call_function_ex(nullptr, cb->ptr(), zdata ? 4 : 3, args, &retval);); + if (UNEXPECTED(call_result != SUCCESS)) { php_swoole_error(E_WARNING, "%s->onDispatch handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } else if (!ZVAL_IS_NULL(&retval)) { worker_id = zval_get_long(&retval); @@ -1791,8 +1847,6 @@ static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendD zval_ptr_dtor(zdata); } - serv->unlock(); - /* the exception should only be thrown after unlocked */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); @@ -1802,7 +1856,7 @@ static int php_swoole_server_dispatch_func(Server *serv, Connection *conn, SendD } void php_swoole_server_onBufferEmpty(Server *serv, DataHead *info) { - zval *zserv = (zval *) serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(serv); ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); if (serv->send_yield) { @@ -1819,25 +1873,56 @@ void php_swoole_server_onBufferEmpty(Server *serv, DataHead *info) { } } - zend_fcall_info_cache *fci_cache = - php_swoole_server_get_fci_cache(serv, info->server_fd, SW_SERVER_CB_onBufferEmpty); - if (fci_cache) { + auto cb = php_swoole_server_get_callback(serv, info->server_fd, SW_SERVER_CB_onBufferEmpty); + if (cb) { zval args[2]; args[0] = *zserv; ZVAL_LONG(&args[1], info->fd); - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, false))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, false))) { php_swoole_error(E_WARNING, "%s->onBufferEmpty handler error", SW_Z_OBJCE_NAME_VAL_P(zserv)); } } } +static void server_ctor(zval *zserv, Server *serv) { + ServerObject *server_object = server_fetch_object(Z_OBJ_P(zserv)); + *php_swoole_server_zval_ptr(serv) = *zserv; + server_set_ptr(zserv, serv); + + /* primary port */ + for (auto ls : serv->ports) { + php_swoole_server_add_port(server_object, ls); + } + + /* iterator */ + do { + zval connection_iterator; + object_init_ex(&connection_iterator, swoole_connection_iterator_ce); + + ConnectionIterator *iterator = php_swoole_connection_iterator_get_ptr(&connection_iterator); + iterator->serv = serv; + + zend_update_property(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("connections"), &connection_iterator); + zval_ptr_dtor(&connection_iterator); + } while (false); + + /* info */ + auto port = serv->get_primary_port(); + zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("mode"), serv->get_mode()); + zend_update_property_stringl( + swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("host"), port->host.c_str(), port->host.length()); + zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("port"), port->get_port()); + zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("type"), port->get_type()); + zend_update_property_bool(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("ssl"), port->ssl); +} + static PHP_METHOD(swoole_server, __construct) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); Server *serv = server_object->serv; if (serv) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } @@ -1848,19 +1933,12 @@ static PHP_METHOD(swoole_server, __construct) { zend_long serv_port = 0; zend_long serv_mode = Server::MODE_BASE; - // only cli env if (!SWOOLE_G(cli)) { zend_throw_exception_ex( swoole_exception_ce, -1, "%s can only be used in CLI mode", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } - if (sw_server() != nullptr) { - zend_throw_exception_ex( - swoole_exception_ce, -3, "server is running. unable to create %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); - RETURN_FALSE; - } - ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 4) Z_PARAM_STRING(host, host_len) Z_PARAM_OPTIONAL @@ -1869,67 +1947,62 @@ static PHP_METHOD(swoole_server, __construct) { Z_PARAM_LONG(sock_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (serv_mode != Server::MODE_BASE && serv_mode != Server::MODE_PROCESS) { - zend_throw_error(NULL, "invalid $mode parameters %d", (int) serv_mode); + if (serv_mode != Server::MODE_BASE && serv_mode != Server::MODE_PROCESS +#ifdef SW_THREAD + && serv_mode != Server::MODE_THREAD +#endif + ) { + swoole_set_last_error(SW_ERROR_INVALID_PARAMS); + zend_throw_error(nullptr, "invalid $mode parameters %d", (int) serv_mode); + RETURN_FALSE; + } + +#ifdef SW_THREAD + if (sw_server() && sw_server()->is_worker_thread()) { + server_ctor(ZEND_THIS, sw_server()); + return; + } + if (!tsrm_is_main_thread()) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_exception_ex(swoole_exception_ce, -2, "This operation is only allowed in the main thread"); + RETURN_FALSE; + } +#else + if (sw_server() != nullptr) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + zend_throw_exception_ex( + swoole_exception_ce, -3, "server is running. unable to create %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } +#endif serv = new Server((enum Server::Mode) serv_mode); - serv->private_data_2 = sw_zval_dup(zserv); - server_set_ptr(zserv, serv); if (serv_mode == Server::MODE_BASE) { serv->reactor_num = 1; serv->worker_num = 1; } - /* primary port */ - do { - if (serv_port == 0 && strcasecmp(host, "SYSTEMD") == 0) { - if (serv->add_systemd_socket() <= 0) { - zend_throw_error(NULL, "failed to add systemd socket"); - RETURN_FALSE; - } - } else { - ListenPort *port = serv->add_port((enum swSocketType) sock_type, host, serv_port); - if (!port) { - zend_throw_exception_ex(swoole_exception_ce, - errno, - "failed to listen server port[%s:" ZEND_LONG_FMT "], Error: %s[%d]", - host, - serv_port, - strerror(errno), - errno); - RETURN_FALSE; - } + if (serv_port == 0 && strcasecmp(host, "SYSTEMD") == 0) { + if (serv->add_systemd_socket() <= 0) { + zend_throw_error(nullptr, "failed to add systemd socket"); + RETURN_FALSE; } - - for (auto ls : serv->ports) { - php_swoole_server_add_port(server_object, ls); + } else { + auto *port = serv->add_port((SocketType) sock_type, host, serv_port); + if (!port) { + zend_throw_exception_ex(swoole_exception_ce, + swoole_get_last_error(), + "failed to listen server port[%s:" ZEND_LONG_FMT "], Error: %s[%d]", + host, + serv_port, + swoole_strerror(swoole_get_last_error()), + swoole_get_last_error()); + RETURN_FALSE; } + } - server_object->property->primary_port = (ServerPortProperty *) serv->get_primary_port()->ptr; - } while (0); - - /* iterator */ - do { - zval connection_iterator; - object_init_ex(&connection_iterator, swoole_connection_iterator_ce); - - ConnectionIterator *iterator = php_swoole_connection_iterator_get_ptr(&connection_iterator); - iterator->serv = serv; - - zend_update_property(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("connections"), &connection_iterator); - zval_ptr_dtor(&connection_iterator); - } while (0); - - /* info */ - auto port = serv->get_primary_port(); - zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("mode"), serv_mode); - zend_update_property_stringl(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("host"), host, host_len); - zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("port"), port->get_port()); - zend_update_property_long(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("type"), port->get_type()); - zend_update_property_bool(swoole_server_ce, SW_Z8_OBJ_P(zserv), ZEND_STRL("ssl"), port->ssl); + server_ctor(zserv, serv); } static PHP_METHOD(swoole_server, __destruct) {} @@ -1937,6 +2010,10 @@ static PHP_METHOD(swoole_server, __destruct) {} static PHP_METHOD(swoole_server, set) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); + if (serv->is_worker_thread()) { + swoole_set_last_error(SW_ERROR_SERVER_UNRELATED_THREAD); + RETURN_FALSE; + } if (serv->is_started()) { php_swoole_fatal_error( E_WARNING, "server is running, unable to execute %s->set", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); @@ -1993,7 +2070,7 @@ static PHP_METHOD(swoole_server, set) { serv->max_wait_time = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "max_queued_bytes", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); serv->max_queued_bytes = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "max_concurrency", ztmp)) { @@ -2010,7 +2087,7 @@ static PHP_METHOD(swoole_server, set) { if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { serv->enable_coroutine = zval_is_true(ztmp); } else { - serv->enable_coroutine = SWOOLE_G(enable_coroutine); + serv->enable_coroutine = SwooleG.enable_coroutine; } if (php_swoole_array_get_value(vht, "send_timeout", ztmp)) { serv->send_timeout = zval_get_double(ztmp); @@ -2030,36 +2107,15 @@ static PHP_METHOD(swoole_server, set) { serv->send_yield = serv->enable_coroutine; } if (php_swoole_array_get_value(vht, "dispatch_func", ztmp)) { - Server::DispatchFunction c_dispatch_func = nullptr; - while (1) { - if (Z_TYPE_P(ztmp) == IS_STRING) { - c_dispatch_func = (Server::DispatchFunction) swoole_get_function(Z_STRVAL_P(ztmp), Z_STRLEN_P(ztmp)); - if (c_dispatch_func) { - break; - } - } -#ifdef ZTS - if (serv->is_process_mode() && !serv->single_thread) { - php_swoole_fatal_error(E_ERROR, "option [dispatch_func] does not support with ZTS"); - } -#endif - char *func_name = nullptr; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(ztmp, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return; - } - efree(func_name); - sw_zend_fci_cache_persist(fci_cache); + auto fci_cache = sw_callable_create(ztmp); + if (fci_cache) { if (serv->private_data_3) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) serv->private_data_3); - efree(serv->private_data_3); + sw_callable_free(serv->private_data_3); } - serv->private_data_3 = (void *) fci_cache; - c_dispatch_func = php_swoole_server_dispatch_func; - break; + serv->private_data_3 = fci_cache; + serv->dispatch_func = php_swoole_server_dispatch_func; + serv->single_thread = true; } - serv->dispatch_func = c_dispatch_func; } /** * for dispatch_mode = 1/3 @@ -2118,8 +2174,8 @@ static PHP_METHOD(swoole_server, set) { serv->task_max_request = SW_MAX(0, SW_MIN(v, UINT32_MAX)); // task_max_request_grace if (php_swoole_array_get_value(vht, "task_max_request_grace", ztmp)) { - zend_long v = zval_get_long(ztmp); - serv->task_max_request_grace = SW_MAX(0, SW_MIN(v, UINT32_MAX)); + zend_long v2 = zval_get_long(ztmp); + serv->task_max_request_grace = SW_MAX(0, SW_MIN(v2, UINT32_MAX)); } else if (serv->task_max_request > SW_WORKER_MIN_REQUEST) { serv->task_max_request_grace = serv->task_max_request / 2; } @@ -2146,8 +2202,8 @@ static PHP_METHOD(swoole_server, set) { serv->max_request = SW_MAX(0, SW_MIN(v, UINT32_MAX)); // max_request_grace if (php_swoole_array_get_value(vht, "max_request_grace", ztmp)) { - zend_long v = zval_get_long(ztmp); - serv->max_request_grace = SW_MAX(0, SW_MIN(v, UINT32_MAX)); + zend_long v2 = zval_get_long(ztmp); + serv->max_request_grace = SW_MAX(0, SW_MIN(v2, UINT32_MAX)); } else if (serv->max_request > SW_WORKER_MIN_REQUEST) { serv->max_request_grace = serv->max_request / 2; } @@ -2225,7 +2281,7 @@ static PHP_METHOD(swoole_server, set) { } if (php_swoole_array_get_value(vht, "http_compression_min_length", ztmp) || php_swoole_array_get_value(vht, "compression_min_length", ztmp)) { - serv->compression_min_length = zval_get_long(ztmp); + serv->compression_min_length = php_swoole_parse_to_size(ztmp); } #endif @@ -2245,7 +2301,7 @@ static PHP_METHOD(swoole_server, set) { serv->upload_tmp_dir = str_v.to_std_string(); } if (php_swoole_array_get_value(vht, "upload_max_filesize", ztmp)) { - serv->upload_max_filesize = zval_get_long(ztmp); + serv->upload_max_filesize = php_swoole_parse_to_size(ztmp); } /** * http static file handler @@ -2268,9 +2324,9 @@ static PHP_METHOD(swoole_server, set) { if (ZVAL_IS_ARRAY(ztmp)) { zval *_http_index_files; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), _http_index_files) - zend::String __http_index_files(_http_index_files); - if (__http_index_files.len() > 0) { - serv->add_static_handler_index_files(__http_index_files.to_std_string()); + zend::String obj_http_index_files(_http_index_files); + if (obj_http_index_files.len() > 0) { + serv->add_static_handler_index_files(obj_http_index_files.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { @@ -2300,9 +2356,9 @@ static PHP_METHOD(swoole_server, set) { if (ZVAL_IS_ARRAY(ztmp)) { zval *_location; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztmp), _location) - zend::String __location(_location); - if (__location.len() > 0 && __location.val()[0] == '/') { - serv->add_static_handler_location(__location.to_std_string()); + zend::String obj_location(_location); + if (obj_location.len() > 0 && obj_location.val()[0] == '/') { + serv->add_static_handler_location(obj_location.to_std_string()); } SW_HASHTABLE_FOREACH_END(); } else { @@ -2310,12 +2366,35 @@ static PHP_METHOD(swoole_server, set) { RETURN_FALSE; } } + /** + * [url_rewrite] rules + */ + if (php_swoole_array_get_value(vht, "url_rewrite_rules", ztmp)) { + if (ZVAL_IS_ARRAY(ztmp)) { + zval *replacement; + zend_string *pattern; + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(ztmp), pattern, replacement) { + ZVAL_DEREF(replacement); + if (Z_TYPE_P(replacement) == IS_STRING) { + serv->add_rewrite_rule(std::string(ZSTR_VAL(pattern), ZSTR_LEN(pattern)), + std::string(Z_STRVAL_P(replacement), Z_STRLEN_P(replacement))); + } else { + php_swoole_fatal_error(E_ERROR, "The `replacement` must be string"); + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + } else { + php_swoole_fatal_error(E_ERROR, "The `url_rewrite_rules` must be array"); + RETURN_FALSE; + } + } /** * buffer input size */ if (php_swoole_array_get_value(vht, "input_buffer_size", ztmp) || php_swoole_array_get_value(vht, "buffer_input_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); serv->input_buffer_size = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } /** @@ -2323,7 +2402,7 @@ static PHP_METHOD(swoole_server, set) { */ if (php_swoole_array_get_value(vht, "output_buffer_size", ztmp) || php_swoole_array_get_value(vht, "buffer_output_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); serv->output_buffer_size = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } // message queue key @@ -2331,10 +2410,31 @@ static PHP_METHOD(swoole_server, set) { zend_long v = zval_get_long(ztmp); serv->message_queue_key = SW_MAX(0, SW_MIN(v, INT64_MAX)); } +#ifdef SW_THREAD + // bootstrap + if (php_swoole_array_get_value(vht, "bootstrap", ztmp)) { + zend::object_set(ZEND_THIS, ZEND_STRL("bootstrap"), ztmp); + } else { + zend::object_set(ZEND_THIS, ZEND_STRL("bootstrap"), SG(request_info).path_translated); + } + // thread arguments + if (php_swoole_array_get_value(vht, "init_arguments", ztmp)) { + server_object->init_arguments = *ztmp; + } else { + ZVAL_NULL(&server_object->init_arguments); + } +#endif + +#ifndef HAVE_MSGQUEUE + if (serv->task_ipc_mode == Server::TASK_IPC_MSGQUEUE || serv->task_ipc_mode == Server::TASK_IPC_PREEMPTIVE) { + php_swoole_fatal_error(E_ERROR, "not support `sysvmsg`"); + RETURN_FALSE; + } +#endif if (serv->task_enable_coroutine && (serv->task_ipc_mode == Server::TASK_IPC_MSGQUEUE || serv->task_ipc_mode == Server::TASK_IPC_PREEMPTIVE)) { - php_swoole_fatal_error(E_ERROR, "cannot use msgqueue when task_enable_coroutine is enable"); + php_swoole_fatal_error(E_ERROR, "cannot use msgqueue when `task_enable_coroutine` is enable"); RETURN_FALSE; } @@ -2349,7 +2449,7 @@ static PHP_METHOD(swoole_server, set) { static PHP_METHOD(swoole_server, on) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - if (serv->is_started()) { + if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "server is running, unable to register event callback function"); RETURN_FALSE; } @@ -2357,17 +2457,10 @@ static PHP_METHOD(swoole_server, on) { zval *name; zval *cb; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &name, &cb) == FAILURE) { - RETURN_FALSE; - } - - char *func_name = nullptr; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(cb, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return; - } - efree(func_name); + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(name) + Z_PARAM_ZVAL(cb) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); zend::String _event_name_ori(name); zend::String _event_name_tolower(zend_string_tolower(_event_name_ori.get()), false); @@ -2378,7 +2471,6 @@ static PHP_METHOD(swoole_server, on) { if (i == server_event_map.end()) { zval *port_object = server_object->property->ports.at(0); zval retval; - efree(fci_cache); sw_zend_call_method_with_2_params(port_object, swoole_server_port_ce, nullptr, "on", &retval, name, cb); RETURN_BOOL(Z_BVAL_P(&retval)); } else { @@ -2389,8 +2481,14 @@ static PHP_METHOD(swoole_server, on) { swoole_server_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), cb); if (server_object->property->callbacks[event_type]) { - efree(server_object->property->callbacks[event_type]); + sw_callable_free(server_object->property->callbacks[event_type]); + } + + auto fci_cache = sw_callable_create(cb); + if (!fci_cache) { + RETURN_FALSE; } + server_object->property->callbacks[event_type] = fci_cache; RETURN_TRUE; @@ -2412,7 +2510,7 @@ static PHP_METHOD(swoole_server, getCallback) { // Notice: we should use Z_OBJCE_P instead of swoole_server_ce, because we need to consider the subclasses. zval rv, *property = zend_read_property( - Z_OBJCE_P(ZEND_THIS), SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), 1, &rv); + Z_OBJCE_P(ZEND_THIS), SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), true, &rv); if (!ZVAL_IS_NULL(property)) { RETURN_ZVAL(property, 1, 0); } @@ -2425,21 +2523,28 @@ static PHP_METHOD(swoole_server, getCallback) { static PHP_METHOD(swoole_server, listen) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - if (serv->is_started()) { - php_swoole_fatal_error(E_WARNING, "server is running, can't add listener"); + if (!serv->is_worker_thread() && serv->is_started()) { + php_swoole_fatal_error(E_WARNING, "server is running, cannot add listener"); RETURN_FALSE; } char *host; size_t host_len; - long sock_type; - long port; + zend_long sock_type; + zend_long port; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sll", &host, &host_len, &port, &sock_type) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_STRING(host, host_len) + Z_PARAM_LONG(port) + Z_PARAM_LONG(sock_type) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ListenPort *ls = serv->add_port((enum swSocketType) sock_type, host, (int) port); + ListenPort *ls; + if (serv->is_worker_thread()) { + ls = serv->get_port((enum swSocketType) sock_type, host, (int) port); + } else { + ls = serv->add_port((enum swSocketType) sock_type, host, (int) port); + } if (!ls) { RETURN_FALSE; } @@ -2449,27 +2554,24 @@ static PHP_METHOD(swoole_server, listen) { RETURN_ZVAL(port_object, 1, 0); } -extern Worker *php_swoole_process_get_and_check_worker(zval *zobject); +extern Worker *php_swoole_process_get_and_check_worker(const zval *zobject); static PHP_METHOD(swoole_server, addProcess) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - if (serv->is_started()) { - php_swoole_fatal_error(E_WARNING, "server is running, can't add process"); + if (!serv->is_worker_thread() && serv->is_started()) { + php_swoole_fatal_error(E_WARNING, "server is running, cannot add process"); RETURN_FALSE; } - zval *process = nullptr; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &process) == FAILURE) { - RETURN_FALSE; - } + int worker_id; + Worker *worker; + zval *process; - if (ZVAL_IS_NULL(process)) { - php_swoole_fatal_error(E_WARNING, "the first parameter can't be empty"); - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(process) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (!instanceof_function(Z_OBJCE_P(process), swoole_process_ce)) { + if (!ZVAL_IS_OBJECT(process) || !instanceof_function(Z_OBJCE_P(process), swoole_process_ce)) { php_swoole_fatal_error(E_ERROR, "object is not instanceof swoole_process"); RETURN_FALSE; } @@ -2481,22 +2583,31 @@ static PHP_METHOD(swoole_server, addProcess) { zval *tmp_process = (zval *) emalloc(sizeof(zval)); memcpy(tmp_process, process, sizeof(zval)); process = tmp_process; + Z_TRY_ADDREF_P(process); ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); server_object->property->user_processes.push_back(process); - Z_TRY_ADDREF_P(process); - - Worker *worker = php_swoole_process_get_and_check_worker(process); - worker->ptr = process; - - int id = serv->add_worker(worker); - if (id < 0) { - php_swoole_fatal_error(E_WARNING, "Server::add_worker() failed"); - RETURN_FALSE; + if (serv->is_worker_thread()) { + if (!serv->is_user_worker()) { + swoole_set_last_error(SW_ERROR_SERVER_UNRELATED_THREAD); + RETURN_FALSE; + } + worker_id = swoole_get_worker_id(); + worker = serv->get_worker(worker_id); + worker->redirect_stdin = worker->redirect_stdout = worker->redirect_stderr = false; + worker_id -= serv->get_core_worker_num(); + } else { + worker = php_swoole_process_get_and_check_worker(process); + worker_id = serv->add_worker(worker); + if (worker_id < 0) { + php_swoole_fatal_error(E_WARNING, "failed to add worker"); + RETURN_FALSE; + } + worker->ptr = process; } - zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(process), ZEND_STRL("id"), id); - RETURN_LONG(id); + zend_update_property_long(swoole_process_ce, SW_Z8_OBJ_P(process), ZEND_STRL("id"), worker_id); + RETURN_LONG(worker_id); } static PHP_METHOD(swoole_server, addCommand) { @@ -2509,13 +2620,12 @@ static PHP_METHOD(swoole_server, addCommand) { char *name; size_t l_name; zend_long accepted_process_types; - zend_fcall_info fci; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); + zval *zfn; ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_STRING(name, l_name) Z_PARAM_LONG(accepted_process_types) - Z_PARAM_FUNC(fci, *fci_cache) + Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (accepted_process_types & Server::Command::REACTOR_THREAD) { @@ -2523,32 +2633,37 @@ static PHP_METHOD(swoole_server, addCommand) { RETURN_FALSE; } - Server::Command::Handler fn = [fci_cache](Server *serv, const std::string &msg) { - zval *zserv = (zval *) serv->private_data_2; + auto cb = sw_callable_create(zfn); + if (!cb) { + RETURN_FALSE; + } + + Server::Command::Handler fn = [cb](Server *serv, const std::string &msg) { + zval *zserv = php_swoole_server_zval_ptr(serv); zval argv[2]; argv[0] = *zserv; ZVAL_STRINGL(&argv[1], msg.c_str(), msg.length()); zval return_value; - if (UNEXPECTED(!zend::function::call(fci_cache, 2, argv, &return_value, false))) { + if (UNEXPECTED(!zend::function::call(cb, 2, argv, &return_value, false))) { php_swoole_fatal_error(E_WARNING, "%s: command handler error", ZSTR_VAL(swoole_server_ce->name)); - return std::string("{\"data\": \"failed to call function\", \"code\": -1}"); + return std::string(R"({"data": "failed to call function", "code": -1})"); } if (!ZVAL_IS_STRING(&return_value)) { - return std::string("{\"data\": \"wrong return type\", \"code\": -2}"); + return std::string(R"({"data": "wrong return type", "code": -2})"); } return std::string(Z_STRVAL(return_value), Z_STRLEN(return_value)); }; if (!serv->add_command(std::string(name, l_name), accepted_process_types, fn)) { + sw_callable_free(cb); RETURN_FALSE; } ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); - sw_zend_fci_cache_persist(fci_cache); - server_object->property->command_callbacks.push_back(fci_cache); + server_object->property->command_callbacks.push_back(cb); RETURN_TRUE; } @@ -2556,31 +2671,91 @@ static PHP_METHOD(swoole_server, start) { zval *zserv = ZEND_THIS; Server *serv = php_swoole_server_get_and_check_server(zserv); +#ifdef SW_THREAD + if (serv->is_worker_thread()) { + zval *zsetting = sw_zend_read_and_convert_property_array(Z_OBJCE_P(ZEND_THIS), zserv, ZEND_STRL("setting"), 0); + php_swoole_unserialize((zend_string *) serv->private_data_4, zsetting); + + auto ht = Z_ARRVAL_P(zsetting); + /** + * The coroutine configurations are thread-local variables, + * and each worker thread must reset them once. + */ + php_swoole_set_coroutine_option(ht); + + worker_thread_fn(); + RETURN_TRUE; + } +#endif + if (serv->is_started()) { php_swoole_fatal_error( - E_WARNING, "server is running, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); + E_WARNING, "The server is running, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } if (serv->is_shutdown()) { php_swoole_fatal_error( - E_WARNING, "server have been shutdown, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); + E_WARNING, "The server have been shutdown, unable to execute %s->start()", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } - if (SwooleTG.reactor) { + if (sw_reactor()) { php_swoole_fatal_error( - E_WARNING, "eventLoop has already been created, unable to start %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); + E_WARNING, "The event-loop has already been created, unable to start %s", SW_Z_OBJCE_NAME_VAL_P(zserv)); RETURN_FALSE; } - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); + ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); + +#ifdef SW_THREAD + zend_string *bootstrap = nullptr; + ZendArray *thread_argv = nullptr; + + if (serv->is_thread_mode()) { + zval *_bootstrap = zend::object_get(ZEND_THIS, ZEND_STRL("bootstrap")); + bootstrap = zend_string_dup(Z_STR_P(_bootstrap), true); + + if (!ZVAL_IS_NULL(&server_object->init_arguments)) { + zval _thread_argv; + call_user_function(NULL, nullptr, &server_object->init_arguments, &_thread_argv, 0, nullptr); + if (ZVAL_IS_ARRAY(&_thread_argv)) { + thread_argv = ZendArray::from(Z_ARRVAL(_thread_argv)); + } + zval_ptr_dtor(&_thread_argv); + } + + serv->worker_thread_start = [bootstrap, thread_argv](std::shared_ptr thread, const WorkerFn &fn) { + worker_thread_fn = fn; + zend_string *bootstrap_copy = zend_string_dup(bootstrap, true); + if (thread_argv) { + thread_argv->add_ref(); + } + php_swoole_thread_start(thread, bootstrap_copy, thread_argv); + }; + + // The runtime hook must be enabled before creating child threads. + if (PHPCoroutine::get_hook_flags() > 0) { + PHPCoroutine::enable_hook(PHPCoroutine::get_hook_flags()); + } + } +#endif + server_object->register_callback(); server_object->on_before_start(); if (serv->start() < 0) { - php_swoole_fatal_error(E_ERROR, "failed to start server. Error: %s", sw_error); + php_swoole_fatal_error(E_ERROR, "failed to start server. Error: %s", serv->get_startup_error_message()); } +#ifdef SW_THREAD + if (bootstrap) { + zend_string_release(bootstrap); + } + if (thread_argv) { + thread_argv->del_ref(); + } +#endif + RETURN_TRUE; } @@ -2591,14 +2766,13 @@ static PHP_METHOD(swoole_server, send) { RETURN_FALSE; } - zend_long fd; zval *zfd; - zval *zdata; + zend_string *sdata; zend_long server_socket = -1; ZEND_PARSE_PARAMETERS_START(2, 3) Z_PARAM_ZVAL(zfd) - Z_PARAM_ZVAL(zdata) + Z_PARAM_STR(sdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(server_socket) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); @@ -2608,11 +2782,11 @@ static PHP_METHOD(swoole_server, send) { RETURN_FALSE; } - char *data; - size_t length = php_swoole_get_send_data(zdata, &data); + auto data = ZSTR_VAL(sdata); + size_t length = ZSTR_LEN(sdata); if (length == 0) { - php_swoole_fatal_error(E_WARNING, "data is empty"); + php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } @@ -2625,18 +2799,18 @@ static PHP_METHOD(swoole_server, send) { RETURN_BOOL(sock->sendto(Z_STRVAL_P(zfd), 0, data, length) > 0); } - fd = zval_get_long(zfd); + zend_long fd = zval_get_long(zfd); if (UNEXPECTED(fd <= 0)) { php_swoole_fatal_error(E_WARNING, "invalid fd[" ZEND_LONG_FMT "]", fd); RETURN_FALSE; } - bool ret = serv->send(fd, data, length); - if (!ret && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { - zval_add_ref(zdata); - php_swoole_server_send_yield(serv, fd, zdata, return_value); + bool rv = serv->send(fd, data, length); + if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { + rv = php_swoole_server_send_yield(serv, fd, sdata); } else { - RETURN_BOOL(ret); + php_swoole_server_check_kernel_nobufs(serv, fd); } + RETURN_BOOL(rv); } static PHP_METHOD(swoole_server, sendto) { @@ -2663,7 +2837,7 @@ static PHP_METHOD(swoole_server, sendto) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (len == 0) { - php_swoole_fatal_error(E_WARNING, "data is empty"); + php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } @@ -2722,9 +2896,13 @@ static PHP_METHOD(swoole_server, sendfile) { zend_long offset = 0; zend_long length = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ls|ll", &fd, &filename, &len, &offset, &length) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_LONG(fd) + Z_PARAM_STRING(filename, len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + Z_PARAM_LONG(length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (serv->is_master()) { php_swoole_fatal_error(E_WARNING, "can't sendfile[%s] to the connections in master process", filename); @@ -2761,9 +2939,11 @@ static PHP_METHOD(swoole_server, pause) { } zend_long fd; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &fd) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(fd) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + Connection *conn = serv->get_connection_verify(fd); if (!conn) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); @@ -2780,9 +2960,11 @@ static PHP_METHOD(swoole_server, resume) { } zend_long fd; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &fd) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(fd) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + Connection *conn = serv->get_connection_verify(fd); if (!conn) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); @@ -2800,7 +2982,7 @@ static PHP_METHOD(swoole_server, stats) { array_init(return_value); add_assoc_long_ex(return_value, ZEND_STRL("start_time"), serv->gs->start_time); - add_assoc_long_ex(return_value, ZEND_STRL("connection_num"), serv->gs->connection_num); + add_assoc_long_ex(return_value, ZEND_STRL("connection_num"), serv->get_connection_num()); add_assoc_long_ex(return_value, ZEND_STRL("abort_count"), serv->gs->abort_count); add_assoc_long_ex(return_value, ZEND_STRL("accept_count"), serv->gs->accept_count); add_assoc_long_ex(return_value, ZEND_STRL("close_count"), serv->gs->close_count); @@ -2814,20 +2996,22 @@ static PHP_METHOD(swoole_server, stats) { add_assoc_long_ex(return_value, ZEND_STRL("total_recv_bytes"), serv->gs->total_recv_bytes); add_assoc_long_ex(return_value, ZEND_STRL("total_send_bytes"), serv->gs->total_send_bytes); add_assoc_long_ex(return_value, ZEND_STRL("pipe_packet_msg_id"), serv->gs->pipe_packet_msg_id); + add_assoc_long_ex(return_value, ZEND_STRL("concurrency"), serv->get_concurrency()); add_assoc_long_ex(return_value, ZEND_STRL("session_round"), serv->gs->session_round); add_assoc_long_ex(return_value, ZEND_STRL("min_fd"), serv->gs->min_fd); add_assoc_long_ex(return_value, ZEND_STRL("max_fd"), serv->gs->max_fd); - if (SwooleWG.worker) { - add_assoc_long_ex(return_value, ZEND_STRL("worker_request_count"), SwooleWG.worker->request_count); - add_assoc_long_ex(return_value, ZEND_STRL("worker_response_count"), SwooleWG.worker->response_count); - add_assoc_long_ex(return_value, ZEND_STRL("worker_dispatch_count"), SwooleWG.worker->dispatch_count); + if (sw_worker()) { + add_assoc_long_ex(return_value, ZEND_STRL("worker_request_count"), sw_worker()->request_count); + add_assoc_long_ex(return_value, ZEND_STRL("worker_response_count"), sw_worker()->response_count); + add_assoc_long_ex(return_value, ZEND_STRL("worker_dispatch_count"), sw_worker()->dispatch_count); + add_assoc_long_ex(return_value, ZEND_STRL("worker_concurrency"), sw_worker()->concurrency); } - if (serv->task_ipc_mode > Server::TASK_IPC_UNIXSOCK && serv->gs->task_workers.queue) { + if (serv->task_ipc_mode > Server::TASK_IPC_UNIXSOCK && serv->get_task_worker_pool()->queue) { size_t queue_num = -1; size_t queue_bytes = -1; - if (serv->gs->task_workers.queue->stat(&queue_num, &queue_bytes)) { + if (serv->get_task_worker_pool()->queue->stat(&queue_num, &queue_bytes)) { add_assoc_long_ex(return_value, ZEND_STRL("task_queue_num"), queue_num); add_assoc_long_ex(return_value, ZEND_STRL("task_queue_bytes"), queue_bytes); } @@ -2835,7 +3019,8 @@ static PHP_METHOD(swoole_server, stats) { if (serv->task_worker_num > 0) { add_assoc_long_ex(return_value, ZEND_STRL("task_idle_worker_num"), serv->get_idle_task_worker_num()); - add_assoc_long_ex(return_value, ZEND_STRL("tasking_num"), serv->get_task_count()); + add_assoc_long_ex(return_value, ZEND_STRL("tasking_num"), serv->get_tasking_num()); + add_assoc_long_ex(return_value, ZEND_STRL("task_count"), serv->gs->task_count); } add_assoc_long_ex(return_value, ZEND_STRL("coroutine_num"), Coroutine::count()); @@ -2849,18 +3034,14 @@ static PHP_METHOD(swoole_server, reload) { RETURN_FALSE; } - zend_bool only_reload_taskworker = 0; + zend_bool only_reload_task_workers = false; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &only_reload_taskworker) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(only_reload_task_workers) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - int sig = only_reload_taskworker ? SIGUSR2 : SIGUSR1; - if (swoole_kill(serv->gs->manager_pid, sig) < 0) { - php_swoole_sys_error(E_WARNING, "failed to send the reload signal"); - RETURN_FALSE; - } - RETURN_TRUE; + RETURN_BOOL(serv->reload(!only_reload_task_workers)); } static PHP_METHOD(swoole_server, heartbeat) { @@ -2870,11 +3051,12 @@ static PHP_METHOD(swoole_server, heartbeat) { RETURN_FALSE; } - zend_bool close_connection = 0; + zend_bool close_connection = false; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &close_connection) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(close_connection) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (serv->heartbeat_check_interval < 1) { RETURN_FALSE; @@ -2912,31 +3094,31 @@ static PHP_METHOD(swoole_server, taskwait) { RETURN_FALSE; } - EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - zval *zdata; double timeout = SW_TASKWAIT_TIMEOUT; zend_long dst_worker_id = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|dl", &zdata, &timeout, &dst_worker_id) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_ZVAL(zdata) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + Z_PARAM_LONG(dst_worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (php_swoole_check_task_param(serv, dst_worker_id) < 0) { + if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } - if (php_swoole_task_pack(&buf, zdata) < 0) { + EventData buf; + if (php_swoole_server_task_pack(zdata, &buf) < 0) { RETURN_FALSE; } - int _dst_worker_id = (int) dst_worker_id; - TaskId task_id = buf.info.fd; + TaskId task_id = serv->get_task_id(&buf); // coroutine - if (PHPCoroutine::get_cid() >= 0) { - ServerObject *server_object = server_fetch_object(Z_OBJ_P((zval *) serv->private_data_2)); + if (swoole_coroutine_is_in()) { + ServerObject *server_object = server_fetch_object(Z_OBJ_P(php_swoole_server_zval_ptr(serv))); buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); TaskCo task_co{}; @@ -2944,9 +3126,7 @@ static PHP_METHOD(swoole_server, taskwait) { task_co.count = 1; task_co.result = return_value; - sw_atomic_fetch_add(&serv->gs->tasking_num, 1); - if (serv->gs->task_workers.dispatch(&buf, &_dst_worker_id) < 0) { - sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); + if (!serv->task(&buf, (int *) &dst_worker_id)) { RETURN_FALSE; } @@ -2957,54 +3137,26 @@ static PHP_METHOD(swoole_server, taskwait) { if (!retval) { RETURN_FALSE; } - return; - } - - uint64_t notify; - EventData *task_result = &(serv->task_result[SwooleG.process_id]); - sw_memset_zero(task_result, sizeof(*task_result)); - Pipe *pipe = serv->task_notify_pipes.at(SwooleG.process_id).get(); - network::Socket *task_notify_socket = pipe->get_socket(false); - - // clear history task - while (task_notify_socket->wait_event(0, SW_EVENT_READ) == SW_OK) { - if (task_notify_socket->read(¬ify, sizeof(notify)) <= 0) { - break; + } else { + auto retval = serv->task_sync(&buf, (int *) &dst_worker_id, timeout); + if (!retval) { + RETURN_FALSE; } - } - - sw_atomic_fetch_add(&serv->gs->tasking_num, 1); - - if (serv->gs->task_workers.dispatch_blocking(&buf, &_dst_worker_id) == SW_OK) { - while (1) { - if (task_notify_socket->wait_event((int) (timeout * 1000), SW_EVENT_READ) != SW_OK) { - break; - } - if (pipe->read(¬ify, sizeof(notify)) > 0) { - if (task_result->info.fd != task_id) { - continue; - } - zval *task_notify_data = php_swoole_task_unpack(task_result); - if (task_notify_data == nullptr) { - RETURN_FALSE; - } else { - RETVAL_ZVAL(task_notify_data, 0, 0); - efree(task_notify_data); - return; - } - break; - } else { - php_swoole_sys_error(E_WARNING, "taskwait failed"); - break; - } + zval zresult; + auto task_result = serv->get_task_result(); + if (!php_swoole_server_task_unpack(&zresult, task_result)) { + RETURN_FALSE; + } else { + RETURN_ZVAL(&zresult, 0, 0); } - } else { - sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); } - RETURN_FALSE; } static PHP_METHOD(swoole_server, taskWaitMulti) { + if (swoole_coroutine_is_in()) { + return ZEND_MN(swoole_server_taskCo)(INTERNAL_FUNCTION_PARAM_PASSTHRU); + } + Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { php_swoole_fatal_error(E_WARNING, "server is not running"); @@ -3015,128 +3167,46 @@ static PHP_METHOD(swoole_server, taskWaitMulti) { RETURN_FALSE; } - EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - zval *ztasks; - zval *ztask; double timeout = SW_TASKWAIT_TIMEOUT; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|d", &ztasks, &timeout) == FAILURE) { + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(ztasks) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (php_swoole_server_task_check_param(serv, -1) < 0) { RETURN_FALSE; } array_init(return_value); - int dst_worker_id; - int i = 0; int n_task = php_swoole_array_length(ztasks); - if (n_task >= SW_MAX_CONCURRENT_TASK) { php_swoole_fatal_error(E_WARNING, "too many concurrent tasks"); RETURN_FALSE; } - int list_of_id[SW_MAX_CONCURRENT_TASK] = {}; - - uint64_t notify; - EventData *task_result = &(serv->task_result[SwooleG.process_id]); - sw_memset_zero(task_result, sizeof(*task_result)); - Pipe *pipe = serv->task_notify_pipes.at(SwooleG.process_id).get(); - Worker *worker = serv->get_worker(SwooleG.process_id); - - File fp = swoole::make_tmpfile(); - if (!fp.ready()) { - RETURN_FALSE; - } - std::string file_path = fp.get_path(); - fp.close(); - - int *finish_count = (int *) task_result->data; - - worker->lock->lock(); - *finish_count = 0; - - swoole_strlcpy(task_result->data + 4, file_path.c_str(), SW_TASK_TMP_PATH_SIZE); - worker->lock->unlock(); - - // clear history task - network::Socket *task_notify_socket = pipe->get_socket(false); - task_notify_socket->set_nonblock(); - while (task_notify_socket->read(¬ify, sizeof(notify)) > 0) { - } - task_notify_socket->set_block(); - - SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztasks), ztask) - TaskId task_id = php_swoole_task_pack(&buf, ztask); - if (task_id < 0) { - php_swoole_fatal_error(E_WARNING, "task pack failed"); - goto _fail; - } - buf.info.ext_flags |= SW_TASK_WAITALL; - dst_worker_id = -1; - sw_atomic_fetch_add(&serv->gs->tasking_num, 1); - if (serv->gs->task_workers.dispatch_blocking(&buf, &dst_worker_id) < 0) { - php_swoole_sys_error(E_WARNING, "taskwait failed"); - task_id = -1; - _fail: - add_index_bool(return_value, i, 0); - n_task--; - } else { - sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); - } - list_of_id[i] = task_id; - i++; - SW_HASHTABLE_FOREACH_END(); - - if (n_task == 0) { - swoole_set_last_error(SW_ERROR_TASK_DISPATCH_FAIL); - RETURN_FALSE; - } + Server::MultiTask mt(n_task); + mt.pack = [ztasks](uint16_t i, EventData *buf) { + auto *ztask = zend::array_get(ztasks, (zend_ulong) i); + return php_swoole_server_task_pack(ztask, buf); + }; - pipe->set_timeout(timeout); - double _now = microtime(); - while (n_task > 0) { - int ret = pipe->read(¬ify, sizeof(notify)); - if (ret > 0 && *finish_count < n_task) { - if (microtime() - _now < timeout) { - continue; - } + mt.unpack = [return_value](uint16_t i, EventData *result) { + zval zresult; + if (php_swoole_server_task_unpack(&zresult, result)) { + add_index_zval(return_value, i, &zresult); } - break; - } + }; - worker->lock->lock(); - auto content = swoole::file_get_contents(file_path); - worker->lock->unlock(); + mt.fail = [return_value](uint16_t i) { add_index_bool(return_value, i, false); }; - if (content.get() == nullptr) { + if (!serv->task_sync(mt, timeout)) { + zval_ptr_dtor(return_value); RETURN_FALSE; } - - EventData *result; - zval *zdata; - uint32_t j; - - do { - result = (EventData *) (content->str + content->offset); - TaskId task_id = result->info.fd; - zdata = php_swoole_task_unpack(result); - if (zdata == nullptr) { - goto _next; - } - for (j = 0; j < php_swoole_array_length(ztasks); j++) { - if (list_of_id[j] == task_id) { - break; - } - } - (void) add_index_zval(return_value, j, zdata); - efree(zdata); - _next: - content->offset += sizeof(DataHead) + result->info.len; - } while (content->offset < 0 || (size_t) content->offset < content->length); - // delete tmp file - unlink(file_path.c_str()); } static PHP_METHOD(swoole_server, taskCo) { @@ -3153,31 +3223,29 @@ static PHP_METHOD(swoole_server, taskCo) { ServerObject *server_object = server_fetch_object(Z_OBJ_P(ZEND_THIS)); zval *ztasks; - zval *ztask; double timeout = SW_TASKWAIT_TIMEOUT; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|d", &ztasks, &timeout) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(ztasks) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); int dst_worker_id = -1; TaskId task_id; int i = 0; uint32_t n_task = php_swoole_array_length(ztasks); - EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - if (n_task >= SW_MAX_CONCURRENT_TASK) { php_swoole_fatal_error(E_WARNING, "too many concurrent tasks"); RETURN_FALSE; } - if (php_swoole_check_task_param(serv, dst_worker_id) < 0) { + if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } - int *list = (int *) ecalloc(n_task, sizeof(int)); + auto *list = static_cast(ecalloc(n_task, sizeof(TaskId))); if (list == nullptr) { RETURN_FALSE; } @@ -3187,21 +3255,21 @@ static PHP_METHOD(swoole_server, taskCo) { array_init_size(return_value, n_task); + zval *ztask; SW_HASHTABLE_FOREACH_START(Z_ARRVAL_P(ztasks), ztask) { - task_id = php_swoole_task_pack(&buf, ztask); + EventData buf; + task_id = php_swoole_server_task_pack(ztask, &buf); if (task_id < 0) { php_swoole_fatal_error(E_WARNING, "failed to pack task"); goto _fail; } buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_COROUTINE); dst_worker_id = -1; - sw_atomic_fetch_add(&serv->gs->tasking_num, 1); - if (serv->gs->task_workers.dispatch(&buf, &dst_worker_id) < 0) { + if (!serv->task(&buf, &dst_worker_id)) { task_id = -1; _fail: - add_index_bool(return_value, i, 0); + add_index_bool(return_value, i, false); n_task--; - sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); } else { server_object->property->task_coroutine_map[task_id] = &task_co; } @@ -3220,10 +3288,13 @@ static PHP_METHOD(swoole_server, taskCo) { task_co.count = n_task; if (!task_co.co->yield_ex(timeout)) { - for (uint32_t i = 0; i < n_task; i++) { - if (!zend_hash_index_exists(Z_ARRVAL_P(return_value), i)) { - add_index_bool(return_value, i, 0); - server_object->property->task_coroutine_map.erase(list[i]); + bool is_called_in_taskCo = strcasecmp(EX(func)->internal_function.function_name->val, "taskCo") == 0; + for (uint32_t j = 0; j < n_task; j++) { + if (!zend_hash_index_exists(Z_ARRVAL_P(return_value), j)) { + if (is_called_in_taskCo) { + add_index_bool(return_value, j, false); + } + server_object->property->task_coroutine_map.erase(list[j]); } } } @@ -3239,46 +3310,43 @@ static PHP_METHOD(swoole_server, task) { zval *zdata; zend_long dst_worker_id = -1; - zend_fcall_info fci = empty_fcall_info; - zend_fcall_info_cache fci_cache = empty_fcall_info_cache; + zval *zfn = nullptr; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(dst_worker_id) - Z_PARAM_FUNC_EX(fci, fci_cache, 1, 0) + Z_PARAM_ZVAL(zfn) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (php_swoole_check_task_param(serv, dst_worker_id) < 0) { + if (php_swoole_server_task_check_param(serv, dst_worker_id) < 0) { RETURN_FALSE; } EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - - if (php_swoole_task_pack(&buf, zdata) < 0) { + TaskId task_id = php_swoole_server_task_pack(zdata, &buf); + if (task_id < 0) { RETURN_FALSE; } if (!serv->is_worker()) { buf.info.ext_flags |= SW_TASK_NOREPLY; - } else if (fci.size) { + } else if (zfn && zval_is_true(zfn)) { buf.info.ext_flags |= SW_TASK_CALLBACK; - sw_zend_fci_cache_persist(&fci_cache); - server_object->property->task_callbacks[buf.info.fd] = fci_cache; + auto cb = sw_callable_create(zfn); + if (!cb) { + RETURN_FALSE; + } + server_object->property->task_callbacks[task_id] = cb; } buf.info.ext_flags |= SW_TASK_NONBLOCK; - int _dst_worker_id = (int) dst_worker_id; - sw_atomic_fetch_add(&serv->gs->tasking_num, 1); - - if (serv->gs->task_workers.dispatch(&buf, &_dst_worker_id) >= 0) { - RETURN_LONG(buf.info.fd); + if (serv->task(&buf, (int *) &dst_worker_id)) { + RETURN_LONG(task_id); + } else { + RETURN_FALSE; } - - sw_atomic_fetch_sub(&serv->gs->tasking_num, 1); - RETURN_FALSE; } static PHP_METHOD(swoole_server, command) { @@ -3303,28 +3371,18 @@ static PHP_METHOD(swoole_server, command) { Z_PARAM_BOOL(json_decode) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - std::string msg; - - auto result = zend::function::call("json_encode", 1, zdata); - if (!ZVAL_IS_STRING(&result.value)) { + smart_str buf = {}; + if (php_json_encode(&buf, zdata, 0) == FAILURE || !buf.s) { RETURN_FALSE; } - msg.append(Z_STRVAL(result.value), Z_STRLEN(result.value)); auto co = Coroutine::get_current_safe(); bool donot_yield = false; Server::Command::Callback fn = [co, return_value, json_decode, &donot_yield](Server *serv, const std::string &msg) { if (json_decode) { - zval argv[2]; - ZVAL_STRINGL(&argv[0], msg.c_str(), msg.length()); - ZVAL_BOOL(&argv[1], true); - auto result = zend::function::call("json_decode", 2, argv); - if (!zend_is_true(&result.value)) { - RETURN_FALSE; - } else { - ZVAL_DUP(return_value, &result.value); + if (php_json_decode(return_value, msg.c_str(), (int) msg.length(), true, 0) == FAILURE) { + RETVAL_FALSE; } - zval_dtor(&argv[0]); } else { ZVAL_STRINGL(return_value, msg.c_str(), msg.length()); } @@ -3336,10 +3394,15 @@ static PHP_METHOD(swoole_server, command) { } }; - if (!serv->command( - (uint16_t) process_id, (Server::Command::ProcessType) process_type, std::string(name, l_name), msg, fn)) { + if (!serv->command((uint16_t) process_id, + (Server::Command::ProcessType) process_type, + std::string(name, l_name), + std::string(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s)), + fn)) { + smart_str_free(&buf); RETURN_FALSE; } + smart_str_free(&buf); if (!donot_yield) { co->yield(); } @@ -3359,31 +3422,26 @@ static PHP_METHOD(swoole_server, sendMessage) { zval *zmessage; zend_long worker_id = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zl", &zmessage, &worker_id) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zmessage) + Z_PARAM_LONG(worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if ((serv->is_worker() || serv->is_task_worker()) && worker_id == SwooleG.process_id) { + if ((serv->is_worker() || serv->is_task_worker()) && worker_id == swoole_get_worker_id()) { php_swoole_fatal_error(E_WARNING, "can't send messages to self"); RETURN_FALSE; } - if (worker_id < 0 || worker_id >= serv->worker_num + serv->task_worker_num) { + if (worker_id < 0 || worker_id >= (long) serv->get_core_worker_num()) { php_swoole_fatal_error(E_WARNING, "worker_id[%d] is invalid", (int) worker_id); RETURN_FALSE; } EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - - if (php_swoole_task_pack(&buf, zmessage) < 0) { + if (php_swoole_server_task_pack(zmessage, &buf) < 0) { RETURN_FALSE; } - buf.info.type = SW_SERVER_EVENT_PIPE_MESSAGE; - - Worker *to_worker = serv->get_worker(worker_id); - SW_CHECK_RETURN(serv->send_to_worker_from_worker( - to_worker, &buf, sizeof(buf.info) + buf.info.len, SW_PIPE_MASTER | SW_PIPE_NONBLOCK)); + RETURN_BOOL(serv->send_pipe_message(worker_id, &buf)); } static PHP_METHOD(swoole_server, finish) { @@ -3405,7 +3463,7 @@ static PHP_METHOD(swoole_server, finish) { Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - SW_CHECK_RETURN(php_swoole_task_finish(serv, zdata, nullptr)); + RETURN_BOOL(php_swoole_server_task_finish(serv, zdata, nullptr)); } static PHP_METHOD(swoole_server_task, finish) { @@ -3422,25 +3480,35 @@ static PHP_METHOD(swoole_server_task, finish) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); DataHead *info = php_swoole_server_task_get_info(ZEND_THIS); - SW_CHECK_RETURN(php_swoole_task_finish(serv, zdata, (EventData *) info)); + RETURN_BOOL(php_swoole_server_task_finish(serv, zdata, (EventData *) info)); } static PHP_METHOD(swoole_server_task, pack) { - EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); - zval *zdata; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zdata) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (php_swoole_task_pack(&buf, zdata) < 0) { + EventData buf; + if (php_swoole_server_task_pack(zdata, &buf) < 0) { RETURN_FALSE; } buf.info.ext_flags |= (SW_TASK_NONBLOCK | SW_TASK_NOREPLY); - RETURN_STRINGL((char *) &buf, sizeof(buf.info) + buf.info.len); + RETURN_STRINGL((char *) &buf, buf.size()); +} + +static PHP_METHOD(swoole_server_task, unpack) { + zval *zdata; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zdata) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + auto *buf = reinterpret_cast(Z_STRVAL_P(zdata)); + if (!php_swoole_server_task_unpack(return_value, buf)) { + RETURN_FALSE; + } } static PHP_METHOD(swoole_server, bind) { @@ -3453,9 +3521,10 @@ static PHP_METHOD(swoole_server, bind) { zend_long fd = 0; zend_long uid = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &fd, &uid) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(fd) + Z_PARAM_LONG(uid) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (uid > UINT32_MAX || uid < INT32_MIN) { php_swoole_fatal_error(E_WARNING, "uid can not be greater than %u or less than %d", UINT32_MAX, INT32_MIN); @@ -3483,11 +3552,12 @@ static PHP_METHOD(swoole_server, getSocket) { zend_long port = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &port) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(port) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - ListenPort *lp = serv->get_port(port); + const ListenPort *lp = serv->get_port(port); php_socket *socket_object = php_swoole_convert_to_socket(lp->get_fd()); if (!socket_object) { @@ -3508,60 +3578,56 @@ static PHP_METHOD(swoole_server, getClientInfo) { zend_long fd; zend_long reactor_id = -1; - zend_bool dont_check_connection = 0; + zend_bool dont_check_connection = false; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|lb", &fd, &reactor_id, &dont_check_connection) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_LONG(fd) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(reactor_id) + Z_PARAM_BOOL(dont_check_connection) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(fd); if (!conn) { RETURN_FALSE; } - // connection is closed - if (conn->active == 0 && !dont_check_connection) { - RETURN_FALSE; - } else { - array_init(return_value); - if (conn->uid > 0 || serv->dispatch_mode == Server::DISPATCH_UIDMOD) { - add_assoc_long(return_value, "uid", conn->uid); - } - if (conn->worker_id > 0 || serv->dispatch_mode == Server::DISPATCH_CO_CONN_LB) { - add_assoc_long(return_value, "worker_id", conn->worker_id); - } + array_init(return_value); - ListenPort *port = serv->get_port_by_fd(conn->fd); - if (port && port->open_websocket_protocol) { - add_assoc_long(return_value, "websocket_status", conn->websocket_status); - } + if (conn->uid > 0 || serv->dispatch_mode == Server::DISPATCH_UIDMOD) { + add_assoc_long(return_value, "uid", conn->uid); + } + if (conn->worker_id > 0 || serv->dispatch_mode == Server::DISPATCH_CO_CONN_LB) { + add_assoc_long(return_value, "worker_id", conn->worker_id); + } -#ifdef SW_USE_OPENSSL - if (conn->ssl_client_cert && conn->ssl_client_cert_pid == SwooleG.pid) { - add_assoc_stringl( - return_value, "ssl_client_cert", conn->ssl_client_cert->str, conn->ssl_client_cert->length); - } -#endif - // server socket - Connection *server_socket = serv->get_connection(conn->server_fd); - if (server_socket) { - add_assoc_long(return_value, "server_port", server_socket->info.get_port()); - } - add_assoc_long(return_value, "server_fd", conn->server_fd); - add_assoc_long(return_value, "socket_fd", conn->fd); - add_assoc_long(return_value, "socket_type", conn->socket_type); - add_assoc_long(return_value, "remote_port", conn->info.get_port()); - add_assoc_string(return_value, "remote_ip", (char *) conn->info.get_ip()); - add_assoc_long(return_value, "reactor_id", conn->reactor_id); - add_assoc_long(return_value, "connect_time", conn->connect_time); - add_assoc_long(return_value, "last_time", (int) conn->last_recv_time); - add_assoc_double(return_value, "last_recv_time", conn->last_recv_time); - add_assoc_double(return_value, "last_send_time", conn->last_send_time); - add_assoc_double(return_value, "last_dispatch_time", conn->last_dispatch_time); - add_assoc_long(return_value, "close_errno", conn->close_errno); - add_assoc_long(return_value, "recv_queued_bytes", conn->recv_queued_bytes); - add_assoc_long(return_value, "send_queued_bytes", conn->send_queued_bytes); + ListenPort *port = serv->get_port_by_fd(conn->fd); + if (port && port->open_websocket_protocol) { + add_assoc_long(return_value, "websocket_status", conn->websocket_status); + } + + if (conn->ssl_client_cert && conn->ssl_client_cert_pid == swoole_get_worker_pid()) { + add_assoc_stringl(return_value, "ssl_client_cert", conn->ssl_client_cert->str, conn->ssl_client_cert->length); + } + // server socket + Connection *server_socket = serv->get_connection(conn->server_fd); + if (server_socket) { + add_assoc_long(return_value, "server_port", server_socket->info.get_port()); } + add_assoc_long(return_value, "server_fd", conn->server_fd); + add_assoc_long(return_value, "socket_fd", conn->fd); + add_assoc_long(return_value, "socket_type", conn->socket_type); + add_assoc_long(return_value, "remote_port", conn->info.get_port()); + add_assoc_string(return_value, "remote_ip", (char *) conn->info.get_addr()); + add_assoc_long(return_value, "reactor_id", conn->reactor_id); + add_assoc_long(return_value, "connect_time", conn->connect_time); + add_assoc_long(return_value, "last_time", (int) conn->last_recv_time); + add_assoc_double(return_value, "last_recv_time", conn->last_recv_time); + add_assoc_double(return_value, "last_send_time", conn->last_send_time); + add_assoc_double(return_value, "last_dispatch_time", conn->last_dispatch_time); + add_assoc_long(return_value, "close_errno", conn->close_errno); + add_assoc_long(return_value, "recv_queued_bytes", conn->recv_queued_bytes); + add_assoc_long(return_value, "send_queued_bytes", conn->send_queued_bytes); } static PHP_METHOD(swoole_server, getClientList) { @@ -3574,9 +3640,11 @@ static PHP_METHOD(swoole_server, getClientList) { zend_long start_session_id = 0; zend_long find_count = 10; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", &start_session_id, &find_count) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(start_session_id) + Z_PARAM_LONG(find_count) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); // exceeded the maximum number of searches if (find_count > SW_MAX_FIND_COUNT) { @@ -3635,22 +3703,21 @@ static PHP_METHOD(swoole_server, sendwait) { } zend_long fd; - zval *zdata; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz", &fd, &zdata) == FAILURE) { - RETURN_FALSE; - } - char *data; - size_t length = php_swoole_get_send_data(zdata, &data); + size_t length; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(fd) + Z_PARAM_STRING(data, length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (length == 0) { - php_swoole_fatal_error(E_WARNING, "data is empty"); + php_swoole_error_ex(E_WARNING, SW_ERROR_NO_PAYLOAD, "the data sent must not be empty"); RETURN_FALSE; } if (serv->is_process_mode() || serv->is_task_worker()) { - php_swoole_fatal_error(E_WARNING, "can't sendwait"); + php_swoole_fatal_error(E_WARNING, "can only be used with base mode and must be within worker process"); RETURN_FALSE; } @@ -3671,7 +3738,7 @@ static PHP_METHOD(swoole_server, exists) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); - if (!conn || conn->closed) { + if (!conn || conn->closed || conn->closing) { RETURN_FALSE; } else { RETURN_TRUE; @@ -3686,11 +3753,13 @@ static PHP_METHOD(swoole_server, protect) { } zend_long session_id; - zend_bool value = 1; + zend_bool value = true; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|b", &session_id, &value) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(session_id) + Z_PARAM_OPTIONAL + Z_PARAM_BOOL(value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); if (!conn || conn->closed) { @@ -3706,7 +3775,7 @@ static PHP_METHOD(swoole_server, getWorkerId) { if (!serv->is_worker() && !serv->is_task_worker()) { RETURN_FALSE; } else { - RETURN_LONG(SwooleG.process_id); + RETURN_LONG(swoole_get_worker_id()); } } @@ -3718,17 +3787,14 @@ static PHP_METHOD(swoole_server, getWorkerStatus) { } zend_long worker_id = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &worker_id) == FAILURE) { - RETURN_FALSE; - } - Worker *worker; - if (worker_id == -1) { - worker = SwooleWG.worker; - } else { - worker = serv->get_worker(worker_id); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; + Worker *worker = serv->get_worker(worker_id); if (!worker) { RETURN_FALSE; } else { @@ -3739,10 +3805,14 @@ static PHP_METHOD(swoole_server, getWorkerStatus) { static PHP_METHOD(swoole_server, getWorkerPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); zend_long worker_id = -1; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &worker_id) == FAILURE) { - RETURN_FALSE; - } - Worker *worker = worker_id < 0 ? SwooleWG.worker : serv->get_worker(worker_id); + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; + Worker *worker = serv->get_worker(worker_id); if (!worker) { RETURN_FALSE; } @@ -3751,27 +3821,17 @@ static PHP_METHOD(swoole_server, getWorkerPid) { static PHP_METHOD(swoole_server, getManagerPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - RETURN_LONG(serv->gs->manager_pid); + RETURN_LONG(serv->get_manager_pid()); } static PHP_METHOD(swoole_server, getMasterPid) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - RETURN_LONG(serv->gs->master_pid); + RETURN_LONG(serv->get_master_pid()); } static PHP_METHOD(swoole_server, shutdown) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); - if (sw_unlikely(!serv->is_started())) { - php_swoole_fatal_error(E_WARNING, "server is not running"); - RETURN_FALSE; - } - - if (swoole_kill(serv->gs->master_pid, SIGTERM) < 0) { - php_swoole_sys_error(E_WARNING, "failed to shutdown, kill(%d, SIGTERM) failed", serv->gs->master_pid); - RETURN_FALSE; - } else { - RETURN_TRUE; - } + RETURN_BOOL(serv->shutdown()); } static PHP_METHOD(swoole_server, stop) { @@ -3781,39 +3841,20 @@ static PHP_METHOD(swoole_server, stop) { RETURN_FALSE; } - zend_bool wait_reactor = 0; - long worker_id = SwooleG.process_id; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|lb", &worker_id, &wait_reactor) == FAILURE) { - RETURN_FALSE; - } + zend_long worker_id = -1; - if (worker_id == SwooleG.process_id && wait_reactor == 0) { - if (SwooleTG.reactor != nullptr) { - SwooleTG.reactor->defer( - [](void *data) { - Reactor *reactor = (Reactor *) data; - reactor->running = false; - }, - SwooleTG.reactor); - } - serv->running = false; - } else { - Worker *worker = serv->get_worker(worker_id); - if (worker == nullptr) { - RETURN_FALSE; - } else if (swoole_kill(worker->pid, SIGTERM) < 0) { - php_swoole_sys_error(E_WARNING, "swKill(%d, SIGTERM) failed", worker->pid); - RETURN_FALSE; - } - } - RETURN_TRUE; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(worker_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_BOOL(serv->kill_worker(worker_id)); } // swoole_connection_iterator static PHP_METHOD(swoole_connection_iterator, __construct) { - zend_throw_error(NULL, "please use the Swoole\\Server->connections"); - return; + zend_throw_error(nullptr, "please use the Swoole\\Server->connections"); } static PHP_METHOD(swoole_connection_iterator, rewind) { @@ -3864,32 +3905,36 @@ static PHP_METHOD(swoole_connection_iterator, key) { static PHP_METHOD(swoole_connection_iterator, count) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); if (iterator->port) { - RETURN_LONG(iterator->port->gs->connection_num); + RETURN_LONG(iterator->port->get_connection_num()); } else { - RETURN_LONG(iterator->serv->gs->connection_num); + RETURN_LONG(iterator->serv->get_connection_num()); } } static PHP_METHOD(swoole_connection_iterator, offsetExists) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); - zval *zserv = (zval *) iterator->serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(iterator->serv); zval *zfd; zval retval; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zfd) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zfd) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + sw_zend_call_method_with_1_params(zserv, swoole_server_ce, nullptr, "exists", &retval, zfd); RETVAL_BOOL(Z_BVAL_P(&retval)); } static PHP_METHOD(swoole_connection_iterator, offsetGet) { ConnectionIterator *iterator = php_swoole_connection_iterator_get_and_check_ptr(ZEND_THIS); - zval *zserv = (zval *) iterator->serv->private_data_2; + zval *zserv = php_swoole_server_zval_ptr(iterator->serv); zval *zfd; zval retval; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zfd) == FAILURE) { - RETURN_FALSE; - } + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zfd) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + sw_zend_call_method_with_1_params(zserv, swoole_server_ce, nullptr, "getClientInfo", &retval, zfd); RETVAL_ZVAL(&retval, 0, 0); } diff --git a/ext-src/swoole_server_port.cc b/ext-src/swoole_server_port.cc index 36660a3a6e..77e5efe9a0 100644 --- a/ext-src/swoole_server_port.cc +++ b/ext-src/swoole_server_port.cc @@ -15,6 +15,7 @@ */ #include "php_swoole_server.h" +#include "php_swoole_call_stack.h" BEGIN_EXTERN_C() #include "stubs/php_swoole_server_port_arginfo.h" @@ -37,8 +38,8 @@ static std::unordered_map server_port_event_map({ { "bufferfull", ServerPortEvent(SW_SERVER_CB_onBufferFull, "BufferFull") }, { "bufferempty", ServerPortEvent(SW_SERVER_CB_onBufferEmpty, "BufferEmpty") }, { "request", ServerPortEvent(SW_SERVER_CB_onRequest, "Request") }, - { "handshake", ServerPortEvent(SW_SERVER_CB_onHandShake, "Handshake") }, - { "beforehandshakeresponse", ServerPortEvent(SW_SERVER_CB_onBeforeHandShakeResponse, "BeforeHandShakeResponse") }, + { "handshake", ServerPortEvent(SW_SERVER_CB_onHandshake, "Handshake") }, + { "beforehandshakeresponse", ServerPortEvent(SW_SERVER_CB_onBeforeHandshakeResponse, "BeforeHandshakeResponse") }, { "open", ServerPortEvent(SW_SERVER_CB_onOpen, "Open") }, { "message", ServerPortEvent(SW_SERVER_CB_onMessage, "Message") }, { "disconnect", ServerPortEvent(SW_SERVER_CB_onDisconnect, "Disconnect") }, @@ -91,10 +92,10 @@ void php_swoole_server_port_deref(zend_object *object) { ServerPortObject *server_port = php_swoole_server_port_fetch_object(object); ServerPortProperty *property = &server_port->property; if (property->serv) { - for (int j = 0; j < PHP_SWOOLE_SERVER_PORT_CALLBACK_NUM; j++) { - if (property->caches[j]) { - efree(property->caches[j]); - property->caches[j] = nullptr; + for (auto &callback : property->callbacks) { + if (callback) { + sw_callable_free(callback); + callback = nullptr; } } property->serv = nullptr; @@ -102,10 +103,9 @@ void php_swoole_server_port_deref(zend_object *object) { ListenPort *port = server_port->port; if (port) { - if (port->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) port->protocol.private_data); - efree(port->protocol.private_data); - port->protocol.private_data = nullptr; + if (port->protocol.private_data_1) { + sw_callable_free(port->protocol.private_data_1); + port->protocol.private_data_1 = nullptr; } server_port->port = nullptr; } @@ -117,7 +117,7 @@ static void php_swoole_server_port_free_object(zend_object *object) { } static zend_object *php_swoole_server_port_create_object(zend_class_entry *ce) { - ServerPortObject *server_port = (ServerPortObject *) zend_object_alloc(sizeof(ServerPortObject), ce); + auto *server_port = static_cast(zend_object_alloc(sizeof(ServerPortObject), ce)); zend_object_std_init(&server_port->std, ce); object_properties_init(&server_port->std, ce); server_port->std.handlers = &swoole_server_port_handlers; @@ -153,7 +153,9 @@ const zend_function_entry swoole_server_port_methods[] = void php_swoole_server_port_minit(int module_number) { SW_INIT_CLASS_ENTRY(swoole_server_port, "Swoole\\Server\\Port", nullptr, swoole_server_port_methods); +#ifndef SW_THREAD SW_SET_CLASS_NOT_SERIALIZABLE(swoole_server_port); +#endif SW_SET_CLASS_CLONEABLE(swoole_server_port, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_server_port, sw_zend_class_unset_property_deny); SW_SET_CLASS_CUSTOM_OBJECT(swoole_server_port, @@ -169,10 +171,11 @@ void php_swoole_server_port_minit(int module_number) { zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBufferFull"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBufferEmpty"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onRequest"), ZEND_ACC_PRIVATE); - zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onHandShake"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onHandshake"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onOpen"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onMessage"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onDisconnect"), ZEND_ACC_PRIVATE); + zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("onBeforeHandshakeResponse"), ZEND_ACC_PRIVATE); zend_declare_property_null(swoole_server_port_ce, ZEND_STRL("host"), ZEND_ACC_PUBLIC); zend_declare_property_long(swoole_server_port_ce, ZEND_STRL("port"), 0, ZEND_ACC_PUBLIC); @@ -185,20 +188,17 @@ void php_swoole_server_port_minit(int module_number) { } /** - * [Master-Process] + * [Master/Worker] */ static ssize_t php_swoole_server_length_func(const Protocol *protocol, network::Socket *conn, PacketLength *pl) { - Server *serv = (Server *) protocol->private_data_2; - serv->lock(); - - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) protocol->private_data; + auto *cb = static_cast(protocol->private_data_1); zval zdata; zval retval; ssize_t ret = -1; - // TODO: reduce memory copy ZVAL_STRINGL(&zdata, pl->buf, pl->buf_size); - if (UNEXPECTED(sw_zend_call_function_ex(nullptr, fci_cache, 1, &zdata, &retval) != SUCCESS)) { + HOOK_PHP_CALL_STACK(auto call_result = sw_zend_call_function_ex(nullptr, cb->ptr(), 1, &zdata, &retval);); + if (UNEXPECTED(call_result) != SUCCESS) { php_swoole_fatal_error(E_WARNING, "length function handler error"); } else { ret = zval_get_long(&retval); @@ -206,8 +206,6 @@ static ssize_t php_swoole_server_length_func(const Protocol *protocol, network:: } zval_ptr_dtor(&zdata); - serv->unlock(); - /* the exception should only be thrown after unlocked */ if (UNEXPECTED(EG(exception))) { zend_exception_error(EG(exception), E_ERROR); @@ -217,34 +215,30 @@ static ssize_t php_swoole_server_length_func(const Protocol *protocol, network:: } static PHP_METHOD(swoole_server_port, __construct) { - zend_throw_error(NULL, "please use the Swoole\\Server->listen method"); + zend_throw_error(nullptr, "please use the Swoole\\Server->listen method"); RETURN_FALSE; } static PHP_METHOD(swoole_server_port, __destruct) {} -#ifdef SW_USE_OPENSSL static bool php_swoole_server_set_ssl_option(zend_array *vht, SSLContext *ctx) { zval *ztmp; if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { zend::String str_v(ztmp); - if (access(str_v.val(), R_OK) < 0) { + if (!ctx->set_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl cert file[%s] not found", str_v.val()); return false; } - ctx->cert_file = str_v.to_std_string(); } if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { zend::String str_v(ztmp); - if (access(str_v.val(), R_OK) < 0) { + if (!ctx->set_key_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl key file[%s] not found", str_v.val()); return false; } - ctx->key_file = str_v.to_std_string(); } return true; } -#endif static PHP_METHOD(swoole_server_port, set) { zval *zset = nullptr; @@ -271,7 +265,7 @@ static PHP_METHOD(swoole_server_port, set) { port->backlog = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->socket_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->socket_buffer_size <= 0) { port->socket_buffer_size = INT_MAX; @@ -281,7 +275,7 @@ static PHP_METHOD(swoole_server_port, set) { * !!! Don't set this option, for tests only. */ if (php_swoole_array_get_value(vht, "kernel_socket_recv_buffer_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->kernel_socket_recv_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->kernel_socket_recv_buffer_size <= 0) { port->kernel_socket_recv_buffer_size = INT_MAX; @@ -291,7 +285,7 @@ static PHP_METHOD(swoole_server_port, set) { * !!! Don't set this option, for tests only. */ if (php_swoole_array_get_value(vht, "kernel_socket_send_buffer_size", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->kernel_socket_send_buffer_size = SW_MAX(INT_MIN, SW_MIN(v, INT_MAX)); if (port->kernel_socket_send_buffer_size <= 0) { port->kernel_socket_send_buffer_size = INT_MAX; @@ -303,18 +297,18 @@ static PHP_METHOD(swoole_server_port, set) { port->heartbeat_idle_time = SW_MAX(0, SW_MIN(v, UINT16_MAX)); } if (php_swoole_array_get_value(vht, "buffer_high_watermark", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->buffer_high_watermark = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } if (php_swoole_array_get_value(vht, "buffer_low_watermark", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->buffer_low_watermark = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } // server: tcp_nodelay if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { port->open_tcp_nodelay = zval_is_true(ztmp); } else { - port->open_tcp_nodelay = 1; + port->open_tcp_nodelay = true; } // tcp_defer_accept if (php_swoole_array_get_value(vht, "tcp_defer_accept", ztmp)) { @@ -333,7 +327,7 @@ static PHP_METHOD(swoole_server_port, set) { if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { port->protocol.split_by_eof = zval_is_true(ztmp); if (port->protocol.split_by_eof) { - port->open_eof_check = 1; + port->open_eof_check = true; } } // package eof @@ -357,26 +351,16 @@ static PHP_METHOD(swoole_server_port, set) { if (php_swoole_array_get_value(vht, "open_websocket_protocol", ztmp)) { port->open_websocket_protocol = zval_is_true(ztmp); if (port->open_websocket_protocol) { - port->open_http_protocol = 1; + port->open_http_protocol = true; } } - if (php_swoole_array_get_value(vht, "websocket_subprotocol", ztmp)) { - port->websocket_subprotocol = zend::String(ztmp).to_std_string(); - } - if (php_swoole_array_get_value(vht, "open_websocket_close_frame", ztmp)) { - port->open_websocket_close_frame = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "open_websocket_ping_frame", ztmp)) { - port->open_websocket_ping_frame = zval_is_true(ztmp); - } - if (php_swoole_array_get_value(vht, "open_websocket_pong_frame", ztmp)) { - port->open_websocket_pong_frame = zval_is_true(ztmp); - } + // websocket settings + php_swoole_server_set_websocket_option(port, vht); // http2 protocol if (php_swoole_array_get_value(vht, "open_http2_protocol", ztmp)) { port->open_http2_protocol = zval_is_true(ztmp); if (port->open_http2_protocol) { - port->open_http_protocol = 1; + port->open_http_protocol = true; } } // buffer: mqtt protocol @@ -388,7 +372,8 @@ static PHP_METHOD(swoole_server_port, set) { port->open_redis_protocol = zval_get_long(ztmp); } if (php_swoole_array_get_value(vht, "max_idle_time", ztmp)) { - port->max_idle_time = zval_get_double(ztmp); + double v = zval_get_double(ztmp); + port->max_idle_time = SW_MAX(v, SW_TIMER_MIN_SEC); } // tcp_keepidle if (php_swoole_array_get_value(vht, "tcp_keepidle", ztmp)) { @@ -424,7 +409,7 @@ static PHP_METHOD(swoole_server_port, set) { port->protocol.package_length_type = str_v.val()[0]; port->protocol.package_length_size = swoole_type_size(port->protocol.package_length_type); if (port->protocol.package_length_size == 0) { - php_swoole_fatal_error(E_ERROR, "unknown package_length_type, see pack(). Link: http://php.net/pack"); + php_swoole_fatal_error(E_ERROR, "unknown package_length_type, see pack(). Link: https://php.net/pack"); RETURN_FALSE; } } @@ -447,108 +432,73 @@ static PHP_METHOD(swoole_server_port, set) { } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { - while (1) { - if (Z_TYPE_P(ztmp) == IS_STRING) { - Protocol::LengthFunc func = Protocol::get_function(std::string(Z_STRVAL_P(ztmp), Z_STRLEN_P(ztmp))); - if (func != nullptr) { - port->protocol.get_package_length = func; - break; - } - } -#ifdef ZTS - Server *serv = property->serv; - if (serv->is_process_mode() && !serv->single_thread) { - php_swoole_fatal_error(E_ERROR, "option [package_length_func] does not support with ZTS"); - } -#endif - char *func_name; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(ztmp, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return; - } - efree(func_name); + auto cb = sw_callable_create(ztmp); + if (cb) { port->protocol.get_package_length = php_swoole_server_length_func; - if (port->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) port->protocol.private_data); - efree(port->protocol.private_data); + if (port->protocol.private_data_1) { + sw_callable_free(port->protocol.private_data_1); } - sw_zend_fci_cache_persist(fci_cache); - port->protocol.private_data = fci_cache; - break; + port->protocol.private_data_1 = cb; + port->protocol.package_length_size = 0; + port->protocol.package_length_type = '\0'; + port->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; + property->serv->single_thread = true; } - - port->protocol.package_length_size = 0; - port->protocol.package_length_type = '\0'; - port->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; } /** * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); port->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } -#ifdef SW_USE_OPENSSL if (port->ssl) { - if (!php_swoole_server_set_ssl_option(vht, port->ssl_context)) { + if (!php_swoole_server_set_ssl_option(vht, port->get_ssl_context())) { RETURN_FALSE; } if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { - port->ssl_context->disable_compress = !zval_is_true(ztmp); + port->set_ssl_disable_compress(!zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { - zend_long v = zval_get_long(ztmp); - port->ssl_context->protocols = v; -#ifdef SW_SUPPORT_DTLS - if (port->is_dtls() && !port->is_dgram()) { - port->ssl_context->protocols ^= SW_SSL_DTLS; - } -#endif + port->set_ssl_protocols(zval_get_long(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { - port->ssl_context->verify_peer = zval_is_true(ztmp); + port->set_ssl_verify_peer(zval_is_true(ztmp)); } if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { - port->ssl_context->allow_self_signed = zval_is_true(ztmp); + port->set_ssl_allow_self_signed(zval_is_true(ztmp)); } - // verify client cert if (php_swoole_array_get_value(vht, "ssl_client_cert_file", ztmp)) { zend::String str_v(ztmp); - if (access(str_v.val(), R_OK) < 0) { + if (!port->set_ssl_client_cert_file(str_v.to_std_string())) { php_swoole_fatal_error(E_ERROR, "ssl_client_cert_file[%s] not found", str_v.val()); return; } - port->ssl_context->client_cert_file = str_v.to_std_string(); + } + if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { + zend::String str_v(ztmp); + port->set_ssl_cafile(str_v.to_std_string()); + } + if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { + zend::String str_v(ztmp); + port->set_ssl_capath(str_v.to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { zend_long v = zval_get_long(ztmp); - port->ssl_context->verify_depth = SW_MAX(0, SW_MIN(v, UINT8_MAX)); + port->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); } if (php_swoole_array_get_value(vht, "ssl_prefer_server_ciphers", ztmp)) { - port->ssl_context->prefer_server_ciphers = zval_is_true(ztmp); - } - // if ((v = zend_hash_str_find(vht, ZEND_STRL("ssl_session_tickets")))) - // { - // port->ssl_context->session_tickets = zval_is_true(v); - // } - // if ((v = zend_hash_str_find(vht, ZEND_STRL("ssl_stapling")))) - // { - // port->ssl_context->stapling = zval_is_true(v); - // } - // if ((v = zend_hash_str_find(vht, ZEND_STRL("ssl_stapling_verify")))) - // { - // port->ssl_context->stapling_verify = zval_is_true(v); - // } + port->set_ssl_prefer_server_ciphers(zval_is_true(ztmp)); + } if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { - port->ssl_context->ciphers = zend::String(ztmp).to_std_string(); + port->set_ssl_ciphers(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_ecdh_curve", ztmp)) { - port->ssl_context->ecdh_curve = zend::String(ztmp).to_std_string(); + port->set_ssl_ecdh_curve(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_dhparam", ztmp)) { - port->ssl_context->dhparam = zend::String(ztmp).to_std_string(); + port->set_ssl_dhparam(zend::String(ztmp).to_std_string()); } if (php_swoole_array_get_value(vht, "ssl_sni_certs", ztmp)) { if (Z_TYPE_P(ztmp) != IS_ARRAY) { @@ -570,39 +520,31 @@ static PHP_METHOD(swoole_server_port, set) { php_swoole_fatal_error(E_WARNING, "invalid SNI_cert setting"); RETURN_FALSE; } - SSLContext *context = new SSLContext(); - *context = *port->ssl_context; - if (!php_swoole_server_set_ssl_option(Z_ARRVAL_P(current), context)) { - delete context; + auto context = port->dup_ssl_context(); + if (!php_swoole_server_set_ssl_option(Z_ARRVAL_P(current), context.get())) { RETURN_FALSE; } if (!port->ssl_add_sni_cert(std::string(key->val, key->len), context)) { php_swoole_fatal_error(E_ERROR, "ssl_add_sni_cert() failed"); - delete context; RETURN_FALSE; } } ZEND_HASH_FOREACH_END(); } - if (!port->ssl_context->cert_file.empty() || port->sni_contexts.empty()) { + if (!port->get_ssl_cert_file().empty() || !port->has_sni_contexts()) { if (!port->ssl_init()) { php_swoole_fatal_error(E_ERROR, "ssl_init() failed"); RETURN_FALSE; } } - // if ((v = zend_hash_str_find(vht, ZEND_STRL("ssl_session_cache")))) - // { - // port->ssl_context->session_cache = zend::string_dup(v); - // } } -#endif if (SWOOLE_G(enable_library)) { zval params[1] = { *zset, }; - zend::function::call("\\Swoole\\Server\\Helper::checkOptions", 1, params); + zend::function::call(R"(\Swoole\Server\Helper::checkOptions)", 1, params); } zval *zsetting = sw_zend_read_and_convert_property_array(swoole_server_port_ce, ZEND_THIS, ZEND_STRL("setting"), 0); @@ -617,41 +559,37 @@ static PHP_METHOD(swoole_server_port, on) { ServerPortProperty *property = php_swoole_server_port_get_and_check_property(ZEND_THIS); Server *serv = property->serv; - if (serv->is_started()) { + if (!serv->is_worker_thread() && serv->is_started()) { php_swoole_fatal_error(E_WARNING, "can't register event callback function after server started"); RETURN_FALSE; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz", &name, &len, &cb) == FAILURE) { - RETURN_FALSE; - } - - char *func_name = nullptr; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(cb, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_ERROR, "function '%s' is not callable", func_name); - return; - } - efree(func_name); + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(name, len) + Z_PARAM_ZVAL(cb) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); bool found = false; - for (auto i = server_port_event_map.begin(); i != server_port_event_map.end(); i++) { - if (!swoole_strcaseeq(name, len, i->first.c_str(), i->first.length())) { + for (auto &i : server_port_event_map) { + if (!swoole_strcaseeq(name, len, i.first.c_str(), i.first.length())) { continue; } found = true; - int index = i->second.type; - std::string property_name = std::string("on") + i->second.name; + int index = i.second.type; + std::string property_name = std::string("on") + i.second.name; zend_update_property( swoole_server_port_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), cb); - property->callbacks[index] = - sw_zend_read_property(swoole_server_port_ce, ZEND_THIS, property_name.c_str(), property_name.length(), 0); - sw_copy_to_stack(property->callbacks[index], property->_callbacks[index]); - if (property->caches[index]) { - efree(property->caches[index]); + + if (property->callbacks[index]) { + sw_callable_free(property->callbacks[index]); + } + + auto fci_cache = sw_callable_create(cb); + if (!fci_cache) { + RETURN_FALSE; } - property->caches[index] = fci_cache; + property->callbacks[index] = fci_cache; if (index == SW_SERVER_CB_onConnect && !serv->onConnect) { serv->onConnect = php_swoole_server_onConnect; @@ -669,7 +607,6 @@ static PHP_METHOD(swoole_server_port, on) { if (!found) { php_swoole_error(E_WARNING, "unknown event types[%s]", name); - efree(fci_cache); RETURN_FALSE; } RETURN_TRUE; @@ -687,9 +624,12 @@ static PHP_METHOD(swoole_server_port, getCallback) { auto i = server_port_event_map.find(_event_name_tolower.to_std_string()); if (i != server_port_event_map.end()) { std::string property_name = "on" + i->second.name; - zval rv, - *property = zend_read_property( - swoole_server_port_ce, SW_Z8_OBJ_P(ZEND_THIS), property_name.c_str(), property_name.length(), 1, &rv); + zval rv, *property = zend_read_property(swoole_server_port_ce, + SW_Z8_OBJ_P(ZEND_THIS), + property_name.c_str(), + property_name.length(), + true, + &rv); if (!ZVAL_IS_NULL(property)) { RETURN_ZVAL(property, 1, 0); } diff --git a/ext-src/swoole_socket_coro.cc b/ext-src/swoole_socket_coro.cc index ac79f22e20..fc67afb3a7 100644 --- a/ext-src/swoole_socket_coro.cc +++ b/ext-src/swoole_socket_coro.cc @@ -17,6 +17,8 @@ */ #include "php_swoole_cxx.h" +#include "php_swoole_thread.h" + #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_util.h" @@ -31,9 +33,10 @@ BEGIN_EXTERN_C() #include "stubs/php_swoole_socket_coro_arginfo.h" END_EXTERN_C() +using swoole::HttpProxy; using swoole::PacketLength; using swoole::Protocol; -using swoole::coroutine::Socket; +using swoole::Socks5Proxy; using swoole::network::Address; zend_class_entry *swoole_socket_coro_ce; @@ -43,7 +46,7 @@ static zend_class_entry *swoole_socket_coro_exception_ce; static zend_object_handlers swoole_socket_coro_exception_handlers; struct SocketObject { - Socket *socket; + SocketImpl *socket; zval zstream; bool reference; zend_object std; @@ -56,6 +59,7 @@ static PHP_METHOD(swoole_socket_coro, listen); static PHP_METHOD(swoole_socket_coro, accept); static PHP_METHOD(swoole_socket_coro, connect); static PHP_METHOD(swoole_socket_coro, checkLiveness); +static PHP_METHOD(swoole_socket_coro, getBoundCid); static PHP_METHOD(swoole_socket_coro, peek); static PHP_METHOD(swoole_socket_coro, recv); static PHP_METHOD(swoole_socket_coro, send); @@ -74,9 +78,7 @@ static PHP_METHOD(swoole_socket_coro, sendto); static PHP_METHOD(swoole_socket_coro, getOption); static PHP_METHOD(swoole_socket_coro, setOption); static PHP_METHOD(swoole_socket_coro, setProtocol); -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_socket_coro, sslHandshake); -#endif static PHP_METHOD(swoole_socket_coro, shutdown); static PHP_METHOD(swoole_socket_coro, close); static PHP_METHOD(swoole_socket_coro, cancel); @@ -95,6 +97,7 @@ static const zend_function_entry swoole_socket_coro_methods[] = PHP_ME(swoole_socket_coro, accept, arginfo_class_Swoole_Coroutine_Socket_accept, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, connect, arginfo_class_Swoole_Coroutine_Socket_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, checkLiveness, arginfo_class_Swoole_Coroutine_Socket_checkLiveness, ZEND_ACC_PUBLIC) + PHP_ME(swoole_socket_coro, getBoundCid, arginfo_class_Swoole_Coroutine_Socket_getBoundCid, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, peek, arginfo_class_Swoole_Coroutine_Socket_peek, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recv, arginfo_class_Swoole_Coroutine_Socket_recv, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, recvAll, arginfo_class_Swoole_Coroutine_Socket_recvAll, ZEND_ACC_PUBLIC) @@ -113,9 +116,7 @@ static const zend_function_entry swoole_socket_coro_methods[] = PHP_ME(swoole_socket_coro, getOption, arginfo_class_Swoole_Coroutine_Socket_getOption, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, setProtocol, arginfo_class_Swoole_Coroutine_Socket_setProtocol, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, setOption, arginfo_class_Swoole_Coroutine_Socket_setOption, ZEND_ACC_PUBLIC) -#ifdef SW_USE_OPENSSL PHP_ME(swoole_socket_coro, sslHandshake, arginfo_class_Swoole_Coroutine_Socket_sslHandshake, ZEND_ACC_PUBLIC) -#endif PHP_ME(swoole_socket_coro, shutdown, arginfo_class_Swoole_Coroutine_Socket_shutdown, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, cancel, arginfo_class_Swoole_Coroutine_Socket_cancel, ZEND_ACC_PUBLIC) PHP_ME(swoole_socket_coro, close, arginfo_class_Swoole_Coroutine_Socket_close, ZEND_ACC_PUBLIC) @@ -127,39 +128,40 @@ static const zend_function_entry swoole_socket_coro_methods[] = }; // clang-format on -#define SW_BAD_SOCKET ((Socket *) -1) #define swoole_get_socket_coro(_sock, _zobject) \ - SocketObject *_sock = php_swoole_socket_coro_fetch_object(Z_OBJ_P(_zobject)); \ + SocketObject *_sock = socket_coro_fetch_object(Z_OBJ_P(_zobject)); \ if (UNEXPECTED(!sock->socket)) { \ - php_swoole_fatal_error(E_ERROR, "you must call Socket constructor first"); \ + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); \ } \ - if (UNEXPECTED(_sock->socket == SW_BAD_SOCKET)) { \ + if (UNEXPECTED(_sock->socket->is_closed())) { \ zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(_zobject), ZEND_STRL("errCode"), EBADF); \ zend_update_property_string( \ swoole_socket_coro_ce, SW_Z8_OBJ_P(_zobject), ZEND_STRL("errMsg"), strerror(EBADF)); \ RETURN_FALSE; \ } -static sw_inline SocketObject *php_swoole_socket_coro_fetch_object(zend_object *obj) { - return (SocketObject *) ((char *) obj - swoole_socket_coro_handlers.offset); +static sw_inline SocketObject *socket_coro_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_socket_coro_handlers.offset); } -static void php_swoole_socket_coro_free_object(zend_object *object) { - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(object); - if (!sock->reference && sock->socket && sock->socket != SW_BAD_SOCKET) { +/** + * cannot execute close in the destructor, it may be shutting down, + * executing close will try to resume other coroutines. + */ +static void socket_coro_free_object(zend_object *object) { + auto *sock = socket_coro_fetch_object(object); + if (!sock->reference && sock->socket) { if (!Z_ISUNDEF(sock->zstream)) { sock->socket->move_fd(); zval_ptr_dtor(&sock->zstream); - } else { - sock->socket->close(); } delete sock->socket; } zend_object_std_dtor(&sock->std); } -static zend_object *php_swoole_socket_coro_create_object(zend_class_entry *ce) { - SocketObject *sock = (SocketObject *) zend_object_alloc(sizeof(SocketObject), ce); +static zend_object *socket_coro_create_object(zend_class_entry *ce) { + auto *sock = static_cast(zend_object_alloc(sizeof(SocketObject), ce)); zend_object_std_init(&sock->std, ce); /* Even if you don't use properties yourself you should still call object_properties_init(), * because extending classes may use properties. (Generally a lot of the stuff you will do is @@ -169,7 +171,7 @@ static zend_object *php_swoole_socket_coro_create_object(zend_class_entry *ce) { return &sock->std; } -static void swoole_socket_coro_register_constants(int module_number) { +static void socket_coro_register_constants(int module_number) { REGISTER_LONG_CONSTANT("AF_UNIX", AF_UNIX, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AF_INET", AF_INET, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("AF_INET6", AF_INET6, CONST_CS | CONST_PERSISTENT); @@ -710,19 +712,21 @@ void php_swoole_socket_coro_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_socket_coro); SW_SET_CLASS_CLONEABLE(swoole_socket_coro, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_socket_coro, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT(swoole_socket_coro, - php_swoole_socket_coro_create_object, - php_swoole_socket_coro_free_object, - SocketObject, - std); - - zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("domain"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC); - zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("protocol"), 0, ZEND_ACC_PUBLIC); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_socket_coro, socket_coro_create_object, socket_coro_free_object, SocketObject, std); + + zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("fd"), -1, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); + zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("domain"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); + zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("type"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); + zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("protocol"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_socket_coro_ce, ZEND_STRL("errMsg"), "", ZEND_ACC_PUBLIC); +#ifdef SWOOLE_SOCKETS_SUPPORT + zend_declare_property_bool(swoole_socket_coro_ce, ZEND_STRL("__ext_sockets_nonblock"), 0, ZEND_ACC_PUBLIC); + zend_declare_property_long(swoole_socket_coro_ce, ZEND_STRL("__ext_sockets_timeout"), 0, ZEND_ACC_PUBLIC); +#endif + SW_INIT_CLASS_ENTRY_EX(swoole_socket_coro_exception, "Swoole\\Coroutine\\Socket\\Exception", "Co\\Socket\\Exception", @@ -730,19 +734,22 @@ void php_swoole_socket_coro_minit(int module_number) { swoole_exception); if (!zend_hash_str_find_ptr(&module_registry, ZEND_STRL("sockets"))) { - swoole_socket_coro_register_constants(module_number); + socket_coro_register_constants(module_number); } #ifdef ECANCELED SW_REGISTER_LONG_CONSTANT("SOCKET_ECANCELED", ECANCELED); #endif +#ifdef TCP_INFO + SW_REGISTER_LONG_CONSTANT("TCP_INFO", TCP_INFO); +#endif } -static sw_inline void swoole_socket_coro_sync_properties(zval *zobject, SocketObject *sock) { +static sw_inline void socket_coro_sync_properties(const zval *zobject, const SocketObject *sock) { zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), sock->socket->errCode); zend_update_property_string(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), sock->socket->errMsg); } -static void sw_inline php_swoole_init_socket(zval *zobject, SocketObject *sock) { +static void sw_inline socket_coro_init(const zval *zobject, const SocketObject *sock) { sock->socket->set_zero_copy(true); sock->socket->set_buffer_allocator(sw_zend_string_allocator()); zend_update_property_long(swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("fd"), sock->socket->get_fd()); @@ -754,22 +761,22 @@ static void sw_inline php_swoole_init_socket(zval *zobject, SocketObject *sock) swoole_socket_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("protocol"), sock->socket->get_sock_protocol()); } -SW_API bool php_swoole_export_socket(zval *zobject, Socket *_socket) { - zend_object *object = php_swoole_socket_coro_create_object(swoole_socket_coro_ce); +SW_API bool php_swoole_export_socket(zval *zobject, SocketImpl *_socket) { + zend_object *object = socket_coro_create_object(swoole_socket_coro_ce); if (!object) { return false; } - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(object); - sock->reference = 1; + auto *sock = socket_coro_fetch_object(object); + sock->reference = true; sock->socket = _socket; ZVAL_OBJ(zobject, object); - php_swoole_init_socket(zobject, sock); + socket_coro_init(zobject, sock); return true; } -SW_API zend_object *php_swoole_dup_socket(int fd, enum swSocketType type) { +SW_API zend_object *php_swoole_dup_socket(int fd, swSocketType type) { php_swoole_check_reactor(); int new_fd = dup(fd); if (new_fd < 0) { @@ -779,12 +786,12 @@ SW_API zend_object *php_swoole_dup_socket(int fd, enum swSocketType type) { return php_swoole_create_socket_from_fd(new_fd, type); } -SW_API zend_object *php_swoole_create_socket_from_fd(int fd, enum swSocketType type) { +SW_API zend_object *php_swoole_create_socket(swSocketType type) { zval zobject; - zend_object *object = php_swoole_socket_coro_create_object(swoole_socket_coro_ce); - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(object); + zend_object *object = socket_coro_create_object(swoole_socket_coro_ce); + auto *sock = socket_coro_fetch_object(object); - sock->socket = new Socket(fd, type); + sock->socket = new SocketImpl(type); if (UNEXPECTED(sock->socket->get_fd() < 0)) { php_swoole_sys_error(E_WARNING, "new Socket() failed"); delete sock->socket; @@ -794,30 +801,71 @@ SW_API zend_object *php_swoole_create_socket_from_fd(int fd, enum swSocketType t } ZVAL_OBJ(&zobject, object); - php_swoole_init_socket(&zobject, sock); + socket_coro_init(&zobject, sock); return object; } -SW_API Socket *php_swoole_get_socket(zval *zobject) { +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code, const char *msg) { + swoole_set_last_error(code); + zend_update_property_long(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errCode"), code); + zend_update_property_string(Z_OBJCE_P(zobject), SW_Z8_OBJ_P(zobject), ZEND_STRL("errMsg"), msg); +} + +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, int code) { + php_swoole_socket_set_error_properties(zobject, code, swoole_strerror(code)); +} + +SW_API void php_swoole_socket_set_error_properties(const zval *zobject, const SocketImpl *socket) { + php_swoole_socket_set_error_properties(zobject, socket->errCode, socket->errMsg); +} + +static zend_object *socket_coro_create_object(SocketImpl *socket) { + zval zobject; + auto *object = socket_coro_create_object(swoole_socket_coro_ce); + auto *sock = socket_coro_fetch_object(object); + + sock->socket = socket; + if (UNEXPECTED(sock->socket->get_fd() < 0)) { + php_swoole_sys_error(E_WARNING, "new Socket() failed"); + delete sock->socket; + sock->socket = nullptr; + OBJ_RELEASE(object); + return nullptr; + } + + ZVAL_OBJ(&zobject, object); + socket_coro_init(&zobject, sock); + return object; +} + +SW_API zend_object *php_swoole_create_socket_from_fd(int fd, swSocketType type) { + return socket_coro_create_object(new SocketImpl(fd, type)); +} + +SW_API zend_object *php_swoole_create_socket_from_fd(int fd, int _domain, int _type, int _protocol) { + return socket_coro_create_object(new SocketImpl(fd, _domain, _type, _protocol)); +} + +SW_API SocketImpl *php_swoole_get_socket(const zval *zobject) { SW_ASSERT(Z_OBJCE_P(zobject) == swoole_socket_coro_ce); - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(Z_OBJ_P(zobject)); + auto *sock = socket_coro_fetch_object(Z_OBJ_P(zobject)); return sock->socket; } -SW_API bool php_swoole_socket_is_closed(zval *zobject) { - SocketObject *_sock = php_swoole_socket_coro_fetch_object(Z_OBJ_P(zobject)); - return _sock->socket == SW_BAD_SOCKET || _sock->socket->is_closed(); +SW_API bool php_swoole_socket_is_closed(const zval *zobject) { + auto *_sock = socket_coro_fetch_object(Z_OBJ_P(zobject)); + return _sock->socket == nullptr || _sock->socket->is_closed(); } -SW_API void php_swoole_init_socket_object(zval *zobject, Socket *socket) { - zend_object *object = php_swoole_socket_coro_create_object(swoole_socket_coro_ce); - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(object); +SW_API void php_swoole_init_socket_object(zval *zobject, SocketImpl *socket) { + auto *object = socket_coro_create_object(swoole_socket_coro_ce); + auto *sock = socket_coro_fetch_object(object); sock->socket = socket; ZVAL_OBJ(zobject, object); - php_swoole_init_socket(zobject, sock); + socket_coro_init(zobject, sock); } -SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { +SW_API bool php_swoole_socket_set_protocol(SocketImpl *sock, const zval *zset) { HashTable *vht = Z_ARRVAL_P(zset); zval *ztmp; bool ret = true; @@ -825,18 +873,19 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { /** * ssl */ -#ifdef SW_USE_OPENSSL if (php_swoole_array_get_value(vht, "open_ssl", ztmp)) { if (zval_is_true(ztmp)) { sock->enable_ssl_encrypt(); } } + if (php_swoole_array_get_value(vht, "open_http2_protocol", ztmp)) { + sock->http2 = zval_is_true(ztmp); + } if (sock->ssl_is_enable()) { if (!php_swoole_socket_set_ssl(sock, zset)) { ret = false; } } -#endif /** * protocol */ @@ -848,7 +897,7 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { if (php_swoole_array_get_value(vht, "open_eof_split", ztmp)) { sock->protocol.split_by_eof = zval_is_true(ztmp); if (sock->protocol.split_by_eof) { - sock->open_eof_check = 1; + sock->open_eof_check = true; } } // package eof @@ -874,7 +923,7 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { sock->protocol.package_body_offset = 0; sock->protocol.get_package_length = [](const Protocol *protocol, swoole::network::Socket *conn, PacketLength *pl) { - const uint8_t *p = (const uint8_t *) pl->buf; + const auto *p = reinterpret_cast(pl->buf); ssize_t length = 0; if (pl->buf_size >= FCGI_HEADER_LEN) { length = ((p[4] << 8) | p[5]) + p[6]; @@ -906,7 +955,7 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { sock->protocol.package_length_size = swoole_type_size(sock->protocol.package_length_type); if (sock->protocol.package_length_size == 0) { php_swoole_fatal_error(E_WARNING, - "Unknown package_length_type name '%c', see pack(). Link: http://php.net/pack", + "Unknown package_length_type name '%c', see pack(). Link: https://php.net/pack", sock->protocol.package_length_type); ret = false; } @@ -923,40 +972,23 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { } // length function if (php_swoole_array_get_value(vht, "package_length_func", ztmp)) { - do { - Protocol::LengthFunc func; - if (Z_TYPE_P(ztmp) == IS_STRING && - (func = Protocol::get_function(std::string(Z_STRVAL_P(ztmp), Z_STRLEN_P(ztmp))))) { - sock->protocol.get_package_length = func; - } else { - char *func_name; - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) ecalloc(1, sizeof(zend_fcall_info_cache)); - if (!sw_zend_is_callable_ex(ztmp, nullptr, 0, &func_name, nullptr, fci_cache, nullptr)) { - php_swoole_fatal_error(E_WARNING, "function '%s' is not callable", func_name); - efree(func_name); - efree(fci_cache); - ret = false; - break; - } - efree(func_name); - sock->protocol.get_package_length = php_swoole_length_func; - if (sock->protocol.private_data) { - sw_zend_fci_cache_discard((zend_fcall_info_cache *) sock->protocol.private_data); - efree(sock->protocol.private_data); - } - sw_zend_fci_cache_persist(fci_cache); - sock->protocol.private_data = fci_cache; + auto cb = sw_callable_create(ztmp); + if (cb) { + sock->protocol.get_package_length = php_swoole_length_func; + if (sock->protocol.private_data_1) { + sw_callable_free(sock->protocol.private_data_1); } + sock->protocol.private_data_1 = cb; sock->protocol.package_length_size = 0; sock->protocol.package_length_type = '\0'; sock->protocol.package_length_offset = SW_IPC_BUFFER_SIZE; - } while (0); + } } /** * package max length */ if (php_swoole_array_get_value(vht, "package_max_length", ztmp)) { - zend_long v = zval_get_long(ztmp); + zend_long v = php_swoole_parse_to_size(ztmp); sock->protocol.package_max_length = SW_MAX(0, SW_MIN(v, UINT32_MAX)); } else { sock->protocol.package_max_length = SW_INPUT_BUFFER_SIZE; @@ -965,6 +997,199 @@ SW_API bool php_swoole_socket_set_protocol(Socket *sock, zval *zset) { return ret; } +SW_API bool php_swoole_socket_set(SocketImpl *cli, const zval *zset) { + HashTable *vht = Z_ARRVAL_P(zset); + zval *ztmp; + bool ret = true; + + /** + * timeout + */ + if (php_swoole_array_get_value(vht, "timeout", ztmp)) { + cli->set_timeout(zval_get_double(ztmp)); + } + if (php_swoole_array_get_value(vht, "connect_timeout", ztmp)) { + cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_CONNECT); + } + if (php_swoole_array_get_value(vht, "read_timeout", ztmp)) { + cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_READ); + } + if (php_swoole_array_get_value(vht, "write_timeout", ztmp)) { + cli->set_timeout(zval_get_double(ztmp), SW_TIMEOUT_WRITE); + } + std::string _bind_address; + int _bind_port = 0; + if (php_swoole_array_get_value(vht, "bind_port", ztmp)) { + zend_long v = zval_get_long(ztmp); + _bind_port = SW_MAX(0, SW_MIN(v, UINT16_MAX)); + } + if (php_swoole_array_get_value(vht, "bind_address", ztmp)) { + zend::String tmp = ztmp; + _bind_address = tmp.to_std_string(); + } + if (!_bind_address.empty() && !cli->bind(_bind_address, _bind_port)) { + ret = false; + } + /** + * socket send/recv buffer size + */ + if (php_swoole_array_get_value(vht, "socket_buffer_size", ztmp)) { + zend_long size = php_swoole_parse_to_size(ztmp); + if (size <= 0) { + php_swoole_fatal_error(E_WARNING, "socket buffer size must be greater than 0, got " ZEND_LONG_FMT, size); + ret = false; + } else { + cli->set_option(SOL_SOCKET, SO_RCVBUF, size) && cli->set_option(SOL_SOCKET, SO_SNDBUF, size); + } + } + /** + * client: tcp_nodelay + */ + if (php_swoole_array_get_value(vht, "open_tcp_nodelay", ztmp)) { + if (cli->get_type() == SW_SOCK_TCP || cli->get_type() != SW_SOCK_TCP6) { + cli->get_socket()->set_tcp_nodelay(zval_is_true(ztmp)); + } + } + /** + * openssl and protocol options + */ + if (!php_swoole_socket_set_protocol(cli, zset)) { + ret = false; + } + /** + * socks5 proxy + */ + if (php_swoole_array_get_value(vht, "socks5_host", ztmp)) { + if (!cli->get_socket()->is_inet()) { + zend_throw_exception_ex( + swoole_exception_ce, SW_ERROR_OPERATION_NOT_SUPPORT, "Only tcp socket supports socks5 proxy settings"); + return false; + } + + zend::String host(ztmp); + if (php_swoole_array_get_value(vht, "socks5_port", ztmp)) { + std::string user, pwd; + auto socks5_port = zval_get_long(ztmp); + if (php_swoole_array_get_value(vht, "socks5_username", ztmp)) { + user = zend::String(ztmp).to_std_string(); + if (!user.empty() && php_swoole_array_get_value(vht, "socks5_password", ztmp)) { + pwd = zend::String(ztmp).to_std_string(); + } else { + php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); + ret = false; + } + } + cli->set_socks5_proxy(host.to_std_string(), socks5_port, user, pwd); + } else { + php_swoole_fatal_error(E_WARNING, "socks5_port should not be null"); + ret = false; + } + } + /** + * http proxy + */ + else if (php_swoole_array_get_value(vht, "http_proxy_host", ztmp)) { + if (!cli->get_socket()->is_inet()) { + zend_throw_exception_ex( + swoole_exception_ce, SW_ERROR_OPERATION_NOT_SUPPORT, "Only tcp socket supports http proxy settings"); + return false; + } + zend::String host(ztmp); + if (php_swoole_array_get_value(vht, "http_proxy_port", ztmp)) { + std::string user, pwd; + auto http_proxy_port = zval_get_long(ztmp); + if (php_swoole_array_get_value(vht, "http_proxy_username", ztmp) || + php_swoole_array_get_value(vht, "http_proxy_user", ztmp)) { + user = zend::String(ztmp).to_std_string(); + if (!user.empty() && php_swoole_array_get_value(vht, "http_proxy_password", ztmp)) { + pwd = zend::String(ztmp).to_std_string(); + } else { + php_swoole_fatal_error(E_WARNING, "socks5_password should not be null"); + ret = false; + } + } + cli->set_http_proxy(host.to_std_string(), http_proxy_port, user, pwd); + } else { + php_swoole_fatal_error(E_WARNING, "http_proxy_port should not be null"); + ret = false; + } + } + + return ret; +} + +SW_API bool php_swoole_socket_set_ssl(SocketImpl *sock, const zval *zset) { + HashTable *vht = Z_ARRVAL_P(zset); + zval *ztmp; + + if (php_swoole_array_get_value(vht, "ssl_protocols", ztmp)) { + zend_long v = zval_get_long(ztmp); + sock->set_ssl_protocols(v); + } + if (php_swoole_array_get_value(vht, "ssl_compress", ztmp)) { + sock->set_ssl_disable_compress(!zval_is_true(ztmp)); + } else if (php_swoole_array_get_value(vht, "ssl_disable_compression", ztmp)) { + sock->set_ssl_disable_compress(!zval_is_true(ztmp)); + } + if (php_swoole_array_get_value(vht, "ssl_cert_file", ztmp)) { + zend::String str_v(ztmp); + if (!sock->set_ssl_cert_file(str_v.to_std_string())) { + php_swoole_fatal_error(E_WARNING, "ssl cert file[%s] not found", str_v.val()); + return false; + } + } + if (php_swoole_array_get_value(vht, "ssl_key_file", ztmp)) { + zend::String str_v(ztmp); + if (!sock->set_ssl_key_file(str_v.to_std_string())) { + php_swoole_fatal_error(E_WARNING, "ssl key file[%s] not found", str_v.val()); + return false; + } + } + if (!sock->get_ssl_cert_file().empty() && sock->get_ssl_key_file().empty()) { + php_swoole_fatal_error(E_WARNING, "ssl require key file"); + } + if (!sock->get_ssl_key_file().empty() && sock->get_ssl_cert_file().empty()) { + php_swoole_fatal_error(E_WARNING, "ssl require cert file"); + } + if (php_swoole_array_get_value(vht, "ssl_passphrase", ztmp)) { + sock->set_ssl_passphrase(zend::String(ztmp).to_std_string()); + } +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (php_swoole_array_get_value(vht, "ssl_host_name", ztmp)) { + sock->set_tls_host_name(zend::String(ztmp).to_std_string()); + } +#endif + if (php_swoole_array_get_value(vht, "ssl_verify_peer", ztmp)) { + sock->set_ssl_verify_peer(zval_is_true(ztmp)); + } + if (php_swoole_array_get_value(vht, "ssl_allow_self_signed", ztmp)) { + sock->set_ssl_allow_self_signed(zval_is_true(ztmp)); + } + if (php_swoole_array_get_value(vht, "ssl_cafile", ztmp)) { + sock->set_ssl_cafile(zend::String(ztmp).to_std_string()); + } + if (php_swoole_array_get_value(vht, "ssl_capath", ztmp)) { + sock->set_ssl_capath(zend::String(ztmp).to_std_string()); + } + if (php_swoole_array_get_value(vht, "ssl_verify_depth", ztmp)) { + zend_long v = zval_get_long(ztmp); + sock->set_ssl_verify_depth(SW_MAX(0, SW_MIN(v, UINT8_MAX))); + } + if (php_swoole_array_get_value(vht, "ssl_ciphers", ztmp)) { + sock->set_ssl_ciphers(zend::String(ztmp).to_std_string()); + } + if (php_swoole_array_get_value(vht, "ssl_ecdh_curve", ztmp)) { + sock->set_ssl_ecdh_curve(zend::String(ztmp).to_std_string()); + } +#ifdef OPENSSL_IS_BORINGSSL + if (php_swoole_array_get_value(vht, "ssl_grease", ztmp)) { + zend_long v = zval_get_long(ztmp); + sock->set_ssl_grease(SW_MAX(0, SW_MIN(v, UINT8_MAX))); + } +#endif + return true; +} + PHP_FUNCTION(swoole_coroutine_socketpair) { zend_long domain, type, protocol; php_socket_t pair[2]; @@ -976,13 +1201,13 @@ PHP_FUNCTION(swoole_coroutine_socketpair) { ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); if (0 != socketpair((int) domain, (int) type, (int) protocol, pair)) { - php_swoole_error(E_WARNING, "failed to create sockets: [%d]: %s", errno, strerror(errno)); + php_swoole_sys_error(E_WARNING, "failed to create socket"); RETURN_FALSE; } php_swoole_check_reactor(); - auto sock_type = swoole::network::Socket::convert_to_type(domain, type, protocol); + auto sock_type = swoole::network::Socket::convert_to_type(domain, type); zend_object *s1 = php_swoole_create_socket_from_fd(pair[0], sock_type); if (s1 == nullptr) { @@ -1014,20 +1239,22 @@ static PHP_METHOD(swoole_socket_coro, __construct) { Z_PARAM_LONG(protocol) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - SocketObject *sock = (SocketObject *) php_swoole_socket_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); + auto *sock = socket_coro_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (sock->socket) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + RETURN_FALSE; + } - if (EXPECTED(!sock->socket)) { - php_swoole_check_reactor(); - sock->socket = new Socket((int) domain, (int) type, (int) protocol); - if (UNEXPECTED(sock->socket->get_fd() < 0)) { - zend_throw_exception_ex( - swoole_socket_coro_exception_ce, errno, "new Socket() failed. Error: %s [%d]", strerror(errno), errno); - delete sock->socket; - sock->socket = nullptr; - RETURN_FALSE; - } - php_swoole_init_socket(ZEND_THIS, sock); + php_swoole_check_reactor(); + sock->socket = new SocketImpl((int) domain, (int) type, (int) protocol); + if (UNEXPECTED(sock->socket->get_fd() < 0)) { + zend_throw_exception_ex( + swoole_socket_coro_exception_ce, errno, "new Socket() failed. Error: %s [%d]", strerror(errno), errno); + delete sock->socket; + sock->socket = nullptr; + RETURN_FALSE; } + socket_coro_init(ZEND_THIS, sock); } static PHP_METHOD(swoole_socket_coro, bind) { @@ -1044,7 +1271,7 @@ static PHP_METHOD(swoole_socket_coro, bind) { swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->bind(std::string(address, l_address), port)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; @@ -1061,7 +1288,7 @@ static PHP_METHOD(swoole_socket_coro, listen) { swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->listen(backlog)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; @@ -1077,21 +1304,20 @@ static PHP_METHOD(swoole_socket_coro, accept) { swoole_get_socket_coro(sock, ZEND_THIS); - Socket *conn = sock->socket->accept(timeout); + SocketImpl *conn = sock->socket->accept(timeout); if (conn) { - zend_object *client = php_swoole_socket_coro_create_object(swoole_socket_coro_ce); - SocketObject *client_sock = (SocketObject *) php_swoole_socket_coro_fetch_object(client); + zend_object *client = socket_coro_create_object(swoole_socket_coro_ce); + auto *client_sock = socket_coro_fetch_object(client); client_sock->socket = conn; ZVAL_OBJ(return_value, &client_sock->std); - if (conn->protocol.private_data) { - zend_fcall_info_cache *fci_cache = (zend_fcall_info_cache *) emalloc(sizeof(*fci_cache)); - *fci_cache = *(zend_fcall_info_cache *) conn->protocol.private_data; - sw_zend_fci_cache_persist(fci_cache); - conn->protocol.private_data = fci_cache; + socket_coro_init(return_value, client_sock); + // It must be copied once to avoid destroying the function when the connection closes. + if (sock->socket->protocol.private_data_1) { + auto *cb = static_cast(sock->socket->protocol.private_data_1); + conn->protocol.private_data_1 = cb->dup(); } - php_swoole_init_socket(return_value, client_sock); } else { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } } @@ -1111,7 +1337,7 @@ static PHP_METHOD(swoole_socket_coro, connect) { swoole_get_socket_coro(sock, ZEND_THIS); - if (sock->socket->get_sock_domain() == AF_INET6 || sock->socket->get_sock_domain() == AF_INET) { + if (sock->socket->is_port_required()) { if (ZEND_NUM_ARGS() == 1) { php_swoole_error(E_WARNING, "Socket of type AF_INET/AF_INET6 requires port argument"); RETURN_FALSE; @@ -1120,9 +1346,9 @@ static PHP_METHOD(swoole_socket_coro, connect) { RETURN_FALSE; } } - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_CONNECT); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_CONNECT); if (!sock->socket->connect(std::string(host, l_host), port)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; @@ -1132,10 +1358,20 @@ static PHP_METHOD(swoole_socket_coro, checkLiveness) { swoole_get_socket_coro(sock, ZEND_THIS); bool liveness = sock->socket->check_liveness(); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_BOOL(liveness); } +static PHP_METHOD(swoole_socket_coro, getBoundCid) { + zend_long event; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(event) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + swoole_get_socket_coro(sock, ZEND_THIS); + RETURN_LONG(sock->socket->get_bound_cid((swEventType) event)); +} + static PHP_METHOD(swoole_socket_coro, peek) { zend_long length = SW_BUFFER_SIZE_BIG; @@ -1150,9 +1386,9 @@ static PHP_METHOD(swoole_socket_coro, peek) { swoole_get_socket_coro(sock, ZEND_THIS); - zend_string *buf = zend_string_alloc(length, 0); + zend_string *buf = zend_string_alloc(length, false); ssize_t bytes = sock->socket->peek(ZSTR_VAL(buf), length); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(bytes < 0)) { zend_string_free(buf); RETURN_FALSE; @@ -1171,7 +1407,7 @@ enum RecvMode { SOCKET_RECV_WITH_BUFFER, }; -static inline void swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMode type) { +static inline void socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMode type) { zend_long length = SW_BUFFER_SIZE_BIG; double timeout = 0; @@ -1187,8 +1423,8 @@ static inline void swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMod swoole_get_socket_coro(sock, ZEND_THIS); - zend_string *buf = zend_string_alloc(length, 0); - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_READ); + zend_string *buf = zend_string_alloc(length, false); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t bytes = -1; switch (type) { case SOCKET_RECV: @@ -1207,7 +1443,7 @@ static inline void swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMod assert(0); break; } - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(bytes < 0)) { zend_string_free(buf); RETURN_FALSE; @@ -1220,19 +1456,19 @@ static inline void swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAMETERS, RecvMod } static PHP_METHOD(swoole_socket_coro, recv) { - swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV); + socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV); } static PHP_METHOD(swoole_socket_coro, recvAll) { - swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_ALL); + socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_ALL); } static PHP_METHOD(swoole_socket_coro, recvLine) { - swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_LINE); + socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_LINE); } static PHP_METHOD(swoole_socket_coro, recvWithBuffer) { - swoole_socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_WITH_BUFFER); + socket_coro_recv(INTERNAL_FUNCTION_PARAM_PASSTHRU, SOCKET_RECV_WITH_BUFFER); } static PHP_METHOD(swoole_socket_coro, recvPacket) { @@ -1245,7 +1481,7 @@ static PHP_METHOD(swoole_socket_coro, recvPacket) { swoole_get_socket_coro(sock, ZEND_THIS); ssize_t retval = sock->socket->recv_packet(timeout); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (retval < 0) { RETURN_FALSE; } else if (retval == 0) { @@ -1262,7 +1498,7 @@ static PHP_METHOD(swoole_socket_coro, recvPacket) { } } -static sw_inline void swoole_socket_coro_send(INTERNAL_FUNCTION_PARAMETERS, const bool all) { +static sw_inline void socket_coro_send(INTERNAL_FUNCTION_PARAMETERS, const bool all) { char *data; size_t length; double timeout = 0; @@ -1275,9 +1511,9 @@ static sw_inline void swoole_socket_coro_send(INTERNAL_FUNCTION_PARAMETERS, cons swoole_get_socket_coro(sock, ZEND_THIS); - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_WRITE); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_WRITE); ssize_t retval = all ? sock->socket->send_all(data, length) : sock->socket->send(data, length); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (UNEXPECTED(retval < 0)) { RETURN_FALSE; } else { @@ -1286,13 +1522,12 @@ static sw_inline void swoole_socket_coro_send(INTERNAL_FUNCTION_PARAMETERS, cons } static PHP_METHOD(swoole_socket_coro, send) { - swoole_socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); + socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } -static void swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { +static void socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { zval *ziov = nullptr; zval *zelement = nullptr; - HashTable *vht; double timeout = 0; int iovcnt = 0; int iov_index = 0; @@ -1306,14 +1541,14 @@ static void swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const swoole_get_socket_coro(sock, ZEND_THIS); ON_SCOPE_EXIT { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); }; - vht = Z_ARRVAL_P(ziov); + HashTable *vht = Z_ARRVAL_P(ziov); iovcnt = zend_array_count(vht); if (iovcnt > IOV_MAX) { - sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, IOV_MAX_ERROR_MSG, IOV_MAX); + sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, SW_IOV_MAX_ERROR_MSG, IOV_MAX); sock->socket->set_err(EINVAL, sw_tg_buffer()->to_std_string()); RETURN_FALSE; } @@ -1341,7 +1576,7 @@ static void swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const swoole::network::IOVector io_vector((struct iovec *) iov.get(), iovcnt); - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_WRITE); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_WRITE); ssize_t retval = all ? sock->socket->writev_all(&io_vector) : sock->socket->writev(&io_vector); if (UNEXPECTED(retval < 0)) { RETURN_FALSE; @@ -1351,17 +1586,16 @@ static void swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAMETERS, const } static PHP_METHOD(swoole_socket_coro, writeVector) { - swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); + socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_socket_coro, writeVectorAll) { - swoole_socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); + socket_coro_write_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } -static void swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { +static void socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const bool all) { zval *ziov = nullptr; zval *zelement = nullptr; - HashTable *vht; double timeout = 0; int iovcnt = 0; int iov_index = 0; @@ -1376,14 +1610,14 @@ static void swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const b swoole_get_socket_coro(sock, ZEND_THIS); ON_SCOPE_EXIT { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); }; - vht = Z_ARRVAL_P(ziov); + HashTable *vht = Z_ARRVAL_P(ziov); iovcnt = zend_array_count(vht); if (iovcnt > IOV_MAX) { - sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, IOV_MAX_ERROR_MSG, IOV_MAX); + sw_tg_buffer()->length = sw_snprintf(sw_tg_buffer()->str, sw_tg_buffer()->size, SW_IOV_MAX_ERROR_MSG, IOV_MAX); sock->socket->set_err(EINVAL, sw_tg_buffer()->to_std_string()); RETURN_FALSE; } @@ -1406,7 +1640,7 @@ static void swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const b } size_t iov_len = Z_LVAL_P(zelement); - iov[iov_index].iov_base = zend_string_alloc(iov_len, 0)->val; + iov[iov_index].iov_base = zend_string_alloc(iov_len, false)->val; iov[iov_index].iov_len = iov_len; iov_index++; total_length += iov_len; @@ -1415,7 +1649,7 @@ static void swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const b swoole::network::IOVector io_vector((struct iovec *) iov.get(), iovcnt); - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_READ); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t retval = all ? sock->socket->readv_all(&io_vector) : sock->socket->readv(&io_vector); auto free_func = [](const iovec *iov, int iovcnt, int iov_index) { @@ -1459,11 +1693,11 @@ static void swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAMETERS, const b } static PHP_METHOD(swoole_socket_coro, readVector) { - swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); + socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } static PHP_METHOD(swoole_socket_coro, readVectorAll) { - swoole_socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); + socket_coro_read_vector(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_socket_coro, sendFile) { @@ -1472,9 +1706,13 @@ static PHP_METHOD(swoole_socket_coro, sendFile) { zend_long offset = 0; zend_long length = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &file, &file_len, &offset, &length) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(file, file_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(offset) + Z_PARAM_LONG(length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + if (file_len == 0) { php_swoole_fatal_error(E_WARNING, "file to send is empty"); RETURN_FALSE; @@ -1482,10 +1720,7 @@ static PHP_METHOD(swoole_socket_coro, sendFile) { swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->sendfile(file, offset, length)) { - zend_update_property_long( - swoole_socket_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), sock->socket->errCode); - zend_update_property_string( - swoole_socket_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errMsg"), sock->socket->errMsg); + socket_coro_sync_properties(ZEND_THIS, sock); RETVAL_FALSE; } else { RETVAL_TRUE; @@ -1493,7 +1728,7 @@ static PHP_METHOD(swoole_socket_coro, sendFile) { } static PHP_METHOD(swoole_socket_coro, sendAll) { - swoole_socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); + socket_coro_send(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); } static PHP_METHOD(swoole_socket_coro, recvfrom) { @@ -1508,10 +1743,10 @@ static PHP_METHOD(swoole_socket_coro, recvfrom) { swoole_get_socket_coro(sock, ZEND_THIS); - zend_string *buf = zend_string_alloc(SW_BUFFER_SIZE_BIG, 0); - Socket::TimeoutSetter ts(sock->socket, timeout, Socket::TIMEOUT_READ); + zend_string *buf = zend_string_alloc(SW_BUFFER_SIZE_BIG, false); + SocketImpl::TimeoutSetter ts(sock->socket, timeout, SW_TIMEOUT_READ); ssize_t bytes = sock->socket->recvfrom(ZSTR_VAL(buf), SW_BUFFER_SIZE_BIG); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (bytes < 0) { zend_string_free(buf); RETURN_FALSE; @@ -1521,7 +1756,7 @@ static PHP_METHOD(swoole_socket_coro, recvfrom) { } else { zval_dtor(peername); array_init(peername); - add_assoc_string(peername, "address", (char *) sock->socket->get_ip()); + add_assoc_string(peername, "address", (char *) sock->socket->get_addr()); add_assoc_long(peername, "port", sock->socket->get_port()); ZSTR_LEN(buf) = bytes; @@ -1546,7 +1781,7 @@ static PHP_METHOD(swoole_socket_coro, sendto) { swoole_get_socket_coro(sock, ZEND_THIS); ssize_t retval = sock->socket->sendto(std::string(addr, l_addr), port, data, l_data); - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); if (retval < 0) { RETURN_FALSE; } else { @@ -1565,7 +1800,7 @@ static PHP_METHOD(swoole_socket_coro, shutdown) { swoole_get_socket_coro(sock, ZEND_THIS); if (!sock->socket->shutdown(how)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } RETURN_TRUE; @@ -1577,15 +1812,14 @@ static PHP_METHOD(swoole_socket_coro, close) { php_swoole_error(E_WARNING, "cannot close the referenced resource"); RETURN_FALSE; } - if (sock->socket->protocol.private_data) { - zend_fcall_info_cache *package_length_func = (zend_fcall_info_cache *) sock->socket->protocol.private_data; - sw_zend_fci_cache_discard(package_length_func); - efree(package_length_func); + if (sock->socket->protocol.private_data_1) { + sw_callable_free(sock->socket->protocol.private_data_1); + sock->socket->protocol.private_data_1 = nullptr; } if (!Z_ISUNDEF(sock->zstream)) { - php_stream *stream = NULL; + php_stream *stream = nullptr; php_stream_from_zval_no_verify(stream, &sock->zstream); - if (stream != NULL) { + if (stream != nullptr) { /* close & destroy stream, incl. removing it from the rsrc list; * resource stored in php_sock->zstream will become invalid */ php_stream_free(stream, @@ -1594,13 +1828,8 @@ static PHP_METHOD(swoole_socket_coro, close) { } ZVAL_UNDEF(&sock->zstream); sock->socket->move_fd(); - delete sock->socket; - sock->socket = SW_BAD_SOCKET; } else { - if (sock->socket->close()) { - delete sock->socket; - sock->socket = SW_BAD_SOCKET; - } + sock->socket->close(); } RETURN_TRUE; } @@ -1608,15 +1837,14 @@ static PHP_METHOD(swoole_socket_coro, close) { static PHP_METHOD(swoole_socket_coro, getsockname) { swoole_get_socket_coro(sock, ZEND_THIS); - Address sa; - if (!sock->socket->getsockname(&sa)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + if (!sock->socket->getsockname()) { + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } array_init(return_value); - add_assoc_string(return_value, "address", (char *) sa.get_ip()); - add_assoc_long(return_value, "port", sa.get_port()); + add_assoc_string(return_value, "address", sock->socket->get_addr()); + add_assoc_long(return_value, "port", sock->socket->get_port()); } static PHP_METHOD(swoole_socket_coro, getpeername) { @@ -1624,12 +1852,12 @@ static PHP_METHOD(swoole_socket_coro, getpeername) { Address sa; if (!sock->socket->getpeername(&sa)) { - swoole_socket_coro_sync_properties(ZEND_THIS, sock); + socket_coro_sync_properties(ZEND_THIS, sock); RETURN_FALSE; } array_init(return_value); - add_assoc_string(return_value, "address", (char *) sa.get_ip()); + add_assoc_string(return_value, "address", sa.get_addr()); add_assoc_long(return_value, "port", sa.get_port()); } @@ -1639,9 +1867,10 @@ static PHP_METHOD(swoole_socket_coro, getOption) { int other_val; zend_long level, optname; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "ll", &level, &optname) == FAILURE) { - return; - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(level) + Z_PARAM_LONG(optname) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); auto _socket = sock->socket->get_socket(); @@ -1652,7 +1881,7 @@ static PHP_METHOD(swoole_socket_coro, getOption) { struct in_addr if_addr; unsigned int if_index; optlen = sizeof(if_addr); - if (_socket->get_option(level, optname, (char *) &if_addr, &optlen) != 0) { + if (_socket->get_option(level, optname, &if_addr, &optlen) != 0) { php_swoole_sys_error(E_WARNING, "getsockopt(%d, " ZEND_LONG_FMT ", " ZEND_LONG_FMT ")", sock->socket->get_fd(), @@ -1666,6 +1895,8 @@ static PHP_METHOD(swoole_socket_coro, getOption) { RETURN_FALSE; } } + default: + break; } } else if (level == IPPROTO_IPV6) { int ret = php_do_getsockopt_ipv6_rfc3542(sock->socket, level, optname, return_value); @@ -1697,14 +1928,29 @@ static PHP_METHOD(swoole_socket_coro, getOption) { } case SO_RCVTIMEO: case SO_SNDTIMEO: { - double timeout = - sock->socket->get_timeout(optname == SO_RCVTIMEO ? Socket::TIMEOUT_READ : Socket::TIMEOUT_WRITE); + double timeout = sock->socket->get_timeout(optname == SO_RCVTIMEO ? SW_TIMEOUT_READ : SW_TIMEOUT_WRITE); array_init(return_value); int sec = (int) timeout; add_assoc_long(return_value, "sec", (int) timeout); add_assoc_long(return_value, "usec", (timeout - (double) sec) * 1000000); break; } +#ifdef TCP_INFO + case TCP_INFO: { + tcp_info info; + socklen_t len = sizeof(info); + if (_socket->get_option(SOL_TCP, TCP_INFO, &info, &len) < 0) { + php_swoole_sys_error(E_WARNING, "getsockopt(%d, SOL_TCP, TCP_INFO)", sock->socket->get_fd()); + } else { + array_init(return_value); + auto info_map = sw_socket_parse_tcp_info(&info); + for (const auto &iter : info_map) { + add_assoc_long_ex(return_value, iter.first.c_str(), iter.first.length(), (zend_long) iter.second); + } + } + break; + } +#endif default: { optlen = sizeof(other_val); @@ -1729,22 +1975,24 @@ static PHP_METHOD(swoole_socket_coro, getOption) { } static PHP_METHOD(swoole_socket_coro, setOption) { - zval *arg4; + zval *optval; struct linger lv; int ov, optlen, retval; struct timeval tv; zend_long level, optname; char *opt_ptr; HashTable *opt_ht; - zval *l_onoff, *l_linger; - zval *sec, *usec; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "llz", &level, &optname, &arg4) == FAILURE) { - return; - } + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_LONG(level) + Z_PARAM_LONG(optname) + Z_PARAM_ZVAL(optval) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); swoole_get_socket_coro(sock, ZEND_THIS); + uint32_t optval_arg_index = 3; + #define HANDLE_SUBCALL(res) \ do { \ if (res == 1) { \ @@ -1757,12 +2005,12 @@ static PHP_METHOD(swoole_socket_coro, setOption) { } while (0) if (level == IPPROTO_IP) { - int res = php_do_setsockopt_ip_mcast(sock->socket, level, optname, arg4); + int res = php_do_setsockopt_ip_mcast(sock->socket, level, optname, optval); HANDLE_SUBCALL(res); } else if (level == IPPROTO_IPV6) { - int res = php_do_setsockopt_ipv6_mcast(sock->socket, level, optname, arg4); + int res = php_do_setsockopt_ipv6_mcast(sock->socket, level, optname, optval); if (res == 1) { - res = php_do_setsockopt_ipv6_rfc3542(sock->socket, level, optname, arg4); + res = php_do_setsockopt_ipv6_rfc3542(sock->socket, level, optname, optval); } HANDLE_SUBCALL(res); } @@ -1772,9 +2020,20 @@ static PHP_METHOD(swoole_socket_coro, setOption) { const char l_onoff_key[] = "l_onoff"; const char l_linger_key[] = "l_linger"; - convert_to_array_ex(arg4); - opt_ht = Z_ARRVAL_P(arg4); + if (Z_TYPE_P(optval) != IS_ARRAY) { + if (UNEXPECTED(Z_TYPE_P(optval) != IS_OBJECT)) { + zend_argument_type_error(optval_arg_index, + "must be of type array when argument $option is SO_LINGER, %s given", + zend_zval_value_name(optval)); + RETURN_THROWS(); + } else { + opt_ht = Z_OBJPROP_P(optval); + } + } else { + opt_ht = Z_ARRVAL_P(optval); + } + zval *l_onoff, *l_linger; if ((l_onoff = zend_hash_str_find(opt_ht, l_onoff_key, sizeof(l_onoff_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", l_onoff_key); RETURN_FALSE; @@ -1784,11 +2043,21 @@ static PHP_METHOD(swoole_socket_coro, setOption) { RETURN_FALSE; } - convert_to_long_ex(l_onoff); - convert_to_long_ex(l_linger); + zend_long val_lonoff = zval_get_long(l_onoff); + zend_long val_linger = zval_get_long(l_linger); + + if (val_lonoff < 0 || val_lonoff > USHRT_MAX) { + zend_argument_value_error(optval_arg_index, "\"%s\" must be between 0 and %u", l_onoff_key, USHRT_MAX); + RETURN_THROWS(); + } + + if (val_linger < 0 || val_linger > USHRT_MAX) { + zend_argument_value_error(optval_arg_index, "\"%s\" must be between 0 and %d", l_linger, USHRT_MAX); + RETURN_THROWS(); + } - lv.l_onoff = (unsigned short) Z_LVAL_P(l_onoff); - lv.l_linger = (unsigned short) Z_LVAL_P(l_linger); + lv.l_onoff = (unsigned short) val_lonoff; + lv.l_linger = (unsigned short) val_linger; optlen = sizeof(lv); opt_ptr = (char *) &lv; @@ -1797,11 +2066,25 @@ static PHP_METHOD(swoole_socket_coro, setOption) { case SO_RCVTIMEO: case SO_SNDTIMEO: { - const char sec_key[] = "sec"; - const char usec_key[] = "usec"; + constexpr char sec_key[] = "sec"; + constexpr char usec_key[] = "usec"; + zval *sec, *usec; + + if (Z_TYPE_P(optval) != IS_ARRAY) { + if (UNEXPECTED(Z_TYPE_P(optval) != IS_OBJECT)) { + zend_argument_type_error(optval_arg_index, + "must be of type array when argument $option is %s, %s given", + optname == SO_RCVTIMEO ? "SO_RCVTIMEO" : "SO_SNDTIMEO", + zend_zval_value_name(optval)); + RETURN_THROWS(); + } else { + opt_ht = Z_OBJPROP_P(optval); + } + } else { + opt_ht = Z_ARRVAL_P(optval); + } - convert_to_array_ex(arg4); - opt_ht = Z_ARRVAL_P(arg4); + opt_ht = Z_ARRVAL_P(optval); if ((sec = zend_hash_str_find(opt_ht, sec_key, sizeof(sec_key) - 1)) == nullptr) { php_error_docref(nullptr, E_WARNING, "no key \"%s\" passed in optval", sec_key); @@ -1812,20 +2095,21 @@ static PHP_METHOD(swoole_socket_coro, setOption) { RETURN_FALSE; } - convert_to_long_ex(sec); - convert_to_long_ex(usec); - tv.tv_sec = Z_LVAL_P(sec); - tv.tv_usec = Z_LVAL_P(usec); - sock->socket->set_timeout( - &tv, optname == SO_RCVTIMEO ? Socket::TIMEOUT_READ : Socket::TIMEOUT_CONNECT | Socket::TIMEOUT_WRITE); + zend_long valsec = zval_get_long(sec); + zend_long valusec = zval_get_long(usec); + + tv.tv_sec = valsec; + tv.tv_usec = valusec; + sock->socket->set_timeout(&tv, + optname == SO_RCVTIMEO ? SW_TIMEOUT_READ : SW_TIMEOUT_CONNECT | SW_TIMEOUT_WRITE); RETURN_TRUE; break; } #ifdef SO_BINDTODEVICE case SO_BINDTODEVICE: { - if (Z_TYPE_P(arg4) == IS_STRING) { - opt_ptr = Z_STRVAL_P(arg4); - optlen = Z_STRLEN_P(arg4); + if (Z_TYPE_P(optval) == IS_STRING) { + opt_ptr = Z_STRVAL_P(optval); + optlen = Z_STRLEN_P(optval); } else { opt_ptr = (char *) ""; optlen = 0; @@ -1834,15 +2118,15 @@ static PHP_METHOD(swoole_socket_coro, setOption) { } #endif - default: + default: { default_case: - convert_to_long_ex(arg4); - ov = Z_LVAL_P(arg4); + ov = zval_get_long(optval); optlen = sizeof(ov); opt_ptr = (char *) &ov; break; } + } retval = sock->socket->get_socket()->set_option(level, optname, opt_ptr, optlen); if (retval != 0) { @@ -1880,13 +2164,11 @@ static PHP_METHOD(swoole_socket_coro, setProtocol) { } } -#ifdef SW_USE_OPENSSL static PHP_METHOD(swoole_socket_coro, sslHandshake) { swoole_get_socket_coro(sock, ZEND_THIS); RETURN_BOOL(sock->socket->ssl_handshake()); } -#endif static PHP_METHOD(swoole_socket_coro, isClosed) { RETURN_BOOL(php_swoole_socket_is_closed(ZEND_THIS)); @@ -1902,7 +2184,7 @@ static PHP_METHOD(swoole_socket_coro, import) { php_stream_from_zval(stream, zstream); - enum swSocketType type = SW_SOCK_TCP; + swSocketType type = SW_SOCK_TCP; int socket_fd; if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void **) &socket_fd, 1)) { @@ -1911,14 +2193,20 @@ static PHP_METHOD(swoole_socket_coro, import) { } int sock_domain = AF_INET, sock_type = SOCK_STREAM; + php_sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); #ifdef SO_DOMAIN socklen_t sock_domain_len = sizeof(sock_domain); - if (getsockopt(socket_fd, SOL_SOCKET, SO_DOMAIN, &sock_domain, &sock_domain_len) < 0) { - php_swoole_sys_error(E_WARNING, "getsockopt(SOL_SOCKET, SO_DOMAIN) failed"); + if (getsockopt(socket_fd, SOL_SOCKET, SO_DOMAIN, &sock_domain, &sock_domain_len) == 0) { + } else +#endif + if (getsockname(socket_fd, (struct sockaddr *) &addr, &addr_len) == 0) { + sock_domain = addr.ss_family; + } else { + php_swoole_sys_error(E_WARNING, "getsockname() failed"); RETURN_FALSE; } -#endif #ifdef SO_TYPE socklen_t sock_type_len = sizeof(sock_type); @@ -1937,7 +2225,7 @@ static PHP_METHOD(swoole_socket_coro, import) { RETURN_FALSE; } zend_object *object = php_swoole_create_socket_from_fd(socket_fd, type); - SocketObject *sock = php_swoole_socket_coro_fetch_object(object); + SocketObject *sock = socket_coro_fetch_object(object); ZVAL_COPY(&sock->zstream, zstream); php_stream_set_option(stream, PHP_STREAM_OPTION_READ_BUFFER, PHP_STREAM_BUFFER_NONE, NULL); diff --git a/ext-src/swoole_sqlite.cc b/ext-src/swoole_sqlite.cc new file mode 100644 index 0000000000..83d75bc624 --- /dev/null +++ b/ext-src/swoole_sqlite.cc @@ -0,0 +1,132 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2018 The Swoole Group | + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ +#include "php_swoole_private.h" +#include "php_swoole_cxx.h" +#include "swoole_coroutine.h" +#include "php_swoole_sqlite.h" + +#ifdef SW_USE_SQLITE +using swoole::Coroutine; + +static bool swoole_sqlite_blocking = true; + +void swoole_sqlite_set_blocking(bool blocking) { + if (blocking) { + swoole_sqlite_blocking = blocking; + return; + } + + int thread_safe_mode = sqlite3_threadsafe(); + if (!thread_safe_mode) { + swoole_warning("hook sqlite coroutine failed because thread safe mode is single-thread."); + return; + } + swoole_sqlite_blocking = blocking; +} + +int swoole_sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_open_v2"); + + if (!swoole_sqlite_blocking && Coroutine::get_current()) { + flags |= SQLITE_OPEN_FULLMUTEX; + } + + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_open_v2(filename, ppDb, flags, zVfs); }); + + return result; +} + +int swoole_sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_prepare_v2"); + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_prepare_v2(db, zSql, nByte, ppStmt, pzTail); }); + + return result; +} + +int swoole_sqlite3_exec( + sqlite3 *db, const char *sql, int (*callback)(void *, int, char **, char **), void *argument, char **errmsg) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_exec"); + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_exec(db, sql, callback, argument, errmsg); }); + + return result; +} + +int swoole_sqlite3_close(sqlite3 *db) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_close"); + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_close(db); }); + + return result; +} + +int swoole_sqlite3_close_v2(sqlite3 *db) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_close_v2"); + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_close_v2(db); }); + + return result; +} + +int swoole_sqlite3_step(sqlite3_stmt *stmt) { + swoole_trace_log(SW_TRACE_CO_SQLITE, "sqlite3_step"); + int result = 0; + php_swoole_async(swoole_sqlite_blocking, [&]() { result = sqlite3_step(stmt); }); + + return result; +} + +void php_swoole_sqlite_minit(int module_id) { + if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("SQLITE_ATTR_OPEN_FLAGS")) == nullptr) { +#ifdef SQLITE_DETERMINISTIC +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); +#else + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); +#endif +#endif + +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_CREATE", (zend_long) SQLITE_OPEN_CREATE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_EXTENDED_RESULT_CODES", + (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); +#else + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_CREATE", (zend_long) SQLITE_OPEN_CREATE); + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); + REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_EXTENDED_RESULT_CODES", + (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); +#endif + } + + php_pdo_unregister_driver(&swoole_pdo_sqlite_driver); + php_pdo_register_driver(&swoole_pdo_sqlite_driver); +} + +void php_swoole_sqlite_mshutdown() { + php_pdo_unregister_driver(&swoole_pdo_sqlite_driver); +} +#endif diff --git a/ext-src/swoole_stdext.cc b/ext-src/swoole_stdext.cc new file mode 100644 index 0000000000..63e0a375ad --- /dev/null +++ b/ext-src/swoole_stdext.cc @@ -0,0 +1,1163 @@ +/* + +----------------------------------------------------------------------+ + | 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_private.h" + +#ifdef SW_STDEXT +#include "php_swoole_stdext.h" +#include "php_swoole_cxx.h" +#include "php_variables.h" + +SW_EXTERN_C_BEGIN +#include "ext/pcre/php_pcre.h" +#include "ext/json/php_json.h" +#include "thirdparty/php/zend/zend_execute.c" +SW_EXTERN_C_END + +enum HashFlag { + HASH_FLAG_TYPED_ARRAY = (1 << 12), +}; + +/** + * This module aims to enhance the PHP standard library without modifying the php-src core code. + * It seeks to introduce strongly-typed arrays and enable the use of built-in methods directly on arrays and strings, + * instead of relying on array_* or str_* functions. + */ + +struct CallInfo { + zval func; + zval this_; + uint8_t op1_type; +}; + +struct ArrayTypeValue { + uint8_t type_of_value; + uint8_t type_of_key; + uint16_t offset_of_value_type_str; + uint16_t len_of_value_type_str; + + bool parse(const char *type_str, size_t len_of_type_str); +}; + +struct ArrayTypeInfo { + ArrayTypeValue self; + ArrayTypeValue element; + zend_class_entry *value_ce; + uint16_t len_of_type_str; + char type_str[0]; + + bool parse(zend_string *type_def); + bool equals(const ArrayTypeInfo *other) const { + return self.type_of_key == other->self.type_of_key && self.type_of_value == other->self.type_of_value && + len_of_type_str == other->len_of_type_str && memcmp(type_str, other->type_str, len_of_type_str) == 0; + } + bool element_type_equals(const ArrayTypeInfo *element_array_type_info) const { + return element_array_type_info->get_type_of_key() == element.type_of_key && + element_array_type_info->get_type_of_value() == element.type_of_value && + element_array_type_info->len_of_type_str == get_len_of_value_type_str() && + memcmp(element_array_type_info->type_str, get_value_type_str(), get_len_of_value_type_str()) == 0; + } + const char *get_value_type_str() const { + return type_str + self.offset_of_value_type_str; + } + uint16_t get_len_of_type_str() const { + return len_of_type_str; + } + uint16_t get_len_of_value_type_str() const { + return self.len_of_value_type_str; + } + uint8_t get_type_of_key() const { + return self.type_of_key; + } + uint8_t get_type_of_value() const { + return self.type_of_value; + } + bool is_list() const { + return self.type_of_key == 0; + } + bool value_is_bool() const { + return self.type_of_value == IS_TRUE || self.type_of_value == IS_FALSE; + } + bool value_is_object() const { + return self.type_of_value == IS_OBJECT; + } + bool value_is_array() const { + return self.type_of_value == IS_ARRAY; + } + bool value_is_string() const { + return self.type_of_value == IS_STRING; + } + bool value_is_numeric() const { + return self.type_of_value == IS_LONG || self.type_of_value == IS_DOUBLE; + } + bool instance_of(const zval *value) const { + return instanceof_function(Z_OBJCE_P(value), value_ce); + } + bool check(const zend_array *ht, const zval *key, const zval *value) const; + ArrayTypeInfo *dup() const { + const auto copy = static_cast(emalloc(sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1)); + memcpy(copy, this, sizeof(ArrayTypeInfo) + get_len_of_type_str() + 1); + return copy; + } +}; + +static zend_function *fn_swoole_call_array_method = nullptr; +static zend_function *fn_swoole_call_string_method = nullptr; +static zend_function *fn_swoole_call_stream_method = nullptr; +static zend_function *fn_array_push = nullptr; +static zend_function *fn_array_unshift = nullptr; +static zend_function *fn_array_splice = nullptr; +static zif_handler ori_handler_array_push; +static zif_handler ori_handler_array_unshift; +static zif_handler ori_handler_array_splice; + +static int opcode_handler_array_assign(zend_execute_data *execute_data); +static int opcode_handler_array_assign_op(zend_execute_data *execute_data); +static int opcode_handler_array_unset(zend_execute_data *execute_data); +static int opcode_handler_foreach_begin(zend_execute_data *execute_data); +static int opcode_handler_method_call(zend_execute_data *execute_data); +static ArrayTypeInfo *get_type_info(zend_array *array); + +static PHP_FUNCTION(swoole_array_push); +static PHP_FUNCTION(swoole_array_unshift); +static PHP_FUNCTION(swoole_array_splice); + +static bool is_typed_array(const zval *zval) { + return HT_FLAGS(Z_ARRVAL_P(zval)) & HASH_FLAG_TYPED_ARRAY; +} + +static std::unordered_map array_methods = { + {"all", "array_all"}, + {"any", "array_any"}, + {"changeKeyCase", "array_change_key_case"}, + {"chunk", "array_chunk"}, + {"column", "array_column"}, + {"countValues", "array_count_values"}, + {"diff", "array_diff"}, + {"diffAssoc", "array_diff_assoc"}, + {"diffKey", "array_diff_key"}, + {"filter", "array_filter"}, + {"find", "array_find"}, + {"flip", "array_flip"}, + {"intersect", "array_intersect"}, + {"intersectAssoc", "array_intersect_assoc"}, + {"isList", "array_is_list"}, + {"keyExists", "swoole_array_key_exists"}, + {"keyFirst", "array_key_first"}, + {"keyLast", "array_key_last"}, + {"keys", "array_keys"}, + {"map", "swoole_array_map"}, + {"pad", "array_pad"}, + {"product", "array_product"}, + {"rand", "array_rand"}, + {"reduce", "array_reduce"}, + {"replace", "array_replace"}, + {"reverse", "array_reverse"}, + {"search", "swoole_array_search"}, + {"slice", "array_slice"}, + {"sum", "array_sum"}, + {"unique", "array_unique"}, + {"values", "array_values"}, + {"count", "count"}, + {"merge", "array_merge"}, + {"contains", "swoole_array_contains"}, + {"join", "swoole_array_join"}, + {"isTyped", "swoole_array_is_typed"}, + {"isEmpty", "swoole_array_is_empty"}, + // pass by ref + {"sort", "sort"}, + {"pop", "array_pop"}, + {"push", "array_push"}, + {"shift", "array_shift"}, + {"unshift", "array_unshift"}, + {"splice", "array_splice"}, + {"walk", "array_walk"}, + {"replaceStr", "swoole_array_replace_str"}, + {"iReplaceStr", "swoole_array_ireplace_str"}, + // serialize + {"serialize", "serialize"}, + {"marshal", "serialize"}, + {"jsonEncode", "json_encode"}, +}; + +/** + * i=ignore case, l=left, r=right + */ +static std::unordered_map string_methods = { + {"length", "strlen"}, + {"isEmpty", "swoole_str_is_empty"}, + {"lower", "strtolower"}, + {"upper", "strtoupper"}, + {"lowerFirst", "lcfirst"}, + {"upperFirst", "ucfirst"}, + {"upperWords", "ucwords"}, + {"addCSlashes", "addcslashes"}, + {"addSlashes", "addslashes"}, + {"chunkSplit", "chunk_split"}, + {"countChars", "count_chars"}, + {"htmlEntityDecode", "html_entity_decode"}, + {"htmlEntityEncode", "htmlentities"}, + {"htmlSpecialCharsEncode", "htmlspecialchars"}, + {"htmlSpecialCharsDecode", "htmlspecialchars_decode"}, + {"trim", "trim"}, + {"lTrim", "ltrim"}, + {"rTrim", "rtrim"}, + {"parseStr", "swoole_parse_str"}, + {"parseUrl", "parse_url"}, + {"contains", "str_contains"}, + {"incr", "str_increment"}, + {"decr", "str_decrement"}, + {"pad", "str_pad"}, + {"repeat", "str_repeat"}, + {"replace", "swoole_str_replace"}, + {"iReplace", "swoole_str_ireplace"}, + {"shuffle", "str_shuffle"}, + {"split", "swoole_str_split"}, // explode + {"startsWith", "str_starts_with"}, + {"endsWith", "str_ends_with"}, + {"wordCount", "str_word_count"}, + {"iCompare", "strcasecmp"}, + {"compare", "strcmp"}, + {"find", "strstr"}, + {"iFind", "stristr"}, + {"stripTags", "strip_tags"}, + {"stripCSlashes", "stripcslashes"}, + {"stripSlashes", "stripslashes"}, + {"iIndexOf", "stripos"}, + {"indexOf", "strpos"}, + {"lastIndexOf", "strrpos"}, + {"iLastIndexOf", "strripos"}, + {"lastCharIndexOf", "strrchr"}, + {"substr", "substr"}, + {"substrCompare", "substr_compare"}, + {"substrCount", "substr_count"}, + {"substrReplace", "substr_replace"}, + {"reverse", "strrev"}, + {"md5", "md5"}, + {"sha1", "sha1"}, + {"crc32", "crc32"}, + {"hash", "swoole_hash"}, + {"hashCode", "swoole_hashcode"}, + {"base64Decode", "base64_decode"}, + {"base64Encode", "base64_encode"}, + {"urlDecode", "urldecode"}, + {"urlEncode", "urlencode"}, + {"rawUrlEncode", "rawurlencode"}, + {"rawUrlDecode", "rawurldecode"}, + {"match", "swoole_str_match"}, + {"matchAll", "swoole_str_match_all"}, + {"isNumeric", "is_numeric"}, + // mbstring + {"mbUpperFirst", "mb_ucfirst"}, + {"mbLowerFirst", "mb_lcfirst"}, + {"mbTrim", "mb_trim"}, + {"mbSubstrCount", "mb_substr_count"}, + {"mbSubstr", "mb_substr"}, + {"mbUpper", "mb_strtoupper"}, + {"mbLower", "mb_strtolower"}, + {"mbFind", "mb_strstr"}, + {"mbIndexOf", "mb_strpos"}, + {"mbLastIndexOf", "mb_strrpos"}, + {"mbILastIndexOf", "mb_strripos"}, + {"mbLastCharIndexOf", "mb_strrchr"}, + {"mbILastCharIndex", "mb_strrichr"}, + {"mbLength", "mb_strlen"}, + {"mbIFind", "mb_stristr"}, + {"mbIIndexOf", "mb_stripos"}, + {"mbCut", "mb_strcut"}, + {"mbRTrim", "mb_rtrim"}, + {"mbLTrim", "mb_ltrim"}, + {"mbDetectEncoding", "mb_detect_encoding"}, + {"mbConvertEncoding", "mb_convert_encoding"}, + {"mbConvertCase", "mb_convert_case"}, + // serialize + {"unserialize", "unserialize"}, + {"unmarshal", "unserialize"}, + {"jsonDecode", "swoole_str_json_decode"}, + {"jsonDecodeToObject", "swoole_str_json_decode_to_object"}, +}; + +static std::unordered_map stream_methods = { + {"write", "fwrite"}, + {"read", "fread"}, + {"close", "fclose"}, + {"dataSync", "fdatasync"}, + {"sync", "fsync"}, + {"truncate", "ftruncate"}, + {"stat", "fstat"}, + {"seek", "fseek"}, + {"tell", "ftell"}, + {"lock", "flock"}, + {"eof", "feof"}, + {"getChar", "fgetc"}, + {"getLine", "fgets"}, +}; + +static void move_first_element(const zval src[], zval dst[], int size, int position) { + zval first = src[0]; + for (int i = 0; i < position; i++) { + dst[i] = src[i + 1]; + } + dst[position] = first; + for (int i = position + 1; i < size; i++) { + dst[i] = src[i]; + } +} + +static void call_func_move_first_arg(zend_function *fn, zend_execute_data *execute_data, zval *retval, int position) { + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const auto argv = static_cast(ecalloc(arg_count, sizeof(zval))); + move_first_element(arg_ptr, argv, arg_count, position); + zend_call_known_function(fn, nullptr, nullptr, retval, arg_count, argv, nullptr); + efree(argv); +} + +static void call_method(const std::unordered_map &method_map, + zend_execute_data *execute_data, + zval *retval) { + const auto call_info = reinterpret_cast(execute_data->run_time_cache); + const auto name = std::string(Z_STRVAL(call_info->func), Z_STRLEN(call_info->func)); + const auto iter = method_map.find(name); + + ON_SCOPE_EXIT { + efree(call_info); + execute_data->run_time_cache = nullptr; + }; + + if (iter == method_map.end()) { + zend_throw_error( + nullptr, "The method `%s` is undefined on %s", name.c_str(), zend_zval_type_name(&call_info->this_)); + return; + } + const auto real_fn = iter->second; + const auto fn = zend::get_function(real_fn.c_str(), real_fn.length()); + if (!fn) { + zend_throw_error(nullptr, "The function `%s` is undefined", real_fn.c_str()); + return; + } + + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const auto argv = static_cast(ecalloc(arg_count + 1, sizeof(zval))); + + argv[0] = call_info->this_; + for (int i = 0; i < arg_count; i++) { + argv[i + 1] = arg_ptr[i]; + } + + zend_call_known_function(fn, nullptr, nullptr, retval, arg_count + 1, argv, nullptr); + if (call_info->op1_type == IS_VAR) { + zval_ptr_dtor(&call_info->this_); + } + efree(argv); +} + +static int opcode_handler_method_call(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + zval *object; + if (opline->op1_type == IS_CONST) { + object = RT_CONSTANT(opline, opline->op1); + } else if (UNEXPECTED(opline->op1_type == IS_UNUSED)) { + return ZEND_USER_OPCODE_DISPATCH; + } else { + object = EX_VAR(opline->op1.var); + } + + auto type = Z_TYPE_P(object); + if (type == IS_REFERENCE) { + type = Z_TYPE_P(Z_REFVAL_P(object)); + } + + if (type == IS_ARRAY || type == IS_STRING || type == IS_RESOURCE) { + auto call_info = static_cast(emalloc(sizeof(CallInfo))); + call_info->func = *RT_CONSTANT(opline, opline->op2); + call_info->this_ = *object; + call_info->op1_type = opline->op1_type; + + zend_function *fbc = nullptr; + switch (type) { + case IS_ARRAY: + fbc = fn_swoole_call_array_method; + break; + case IS_STRING: + fbc = fn_swoole_call_string_method; + break; + default: + fbc = fn_swoole_call_stream_method; + } + + zend_execute_data *call = + zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION, fbc, opline->extended_value, object); + + call->run_time_cache = reinterpret_cast(call_info); + call->prev_execute_data = EX(call); + EX(call) = call; + EX(opline)++; + + return ZEND_USER_OPCODE_CONTINUE; + } + + return ZEND_USER_OPCODE_DISPATCH; +} + +void php_swoole_stdext_minit(int module_number) { + zend_set_user_opcode_handler(ZEND_INIT_METHOD_CALL, opcode_handler_method_call); + zend_set_user_opcode_handler(ZEND_ASSIGN_DIM, opcode_handler_array_assign); + zend_set_user_opcode_handler(ZEND_ASSIGN_DIM_OP, opcode_handler_array_assign_op); + zend_set_user_opcode_handler(ZEND_UNSET_DIM, opcode_handler_array_unset); + zend_set_user_opcode_handler(ZEND_FE_RESET_RW, opcode_handler_foreach_begin); + + fn_swoole_call_array_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_array_method")); + fn_swoole_call_string_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_string_method")); + fn_swoole_call_stream_method = zend::get_function(CG(function_table), ZEND_STRL("swoole_call_stream_method")); + + fn_array_push = zend::get_function(CG(function_table), ZEND_STRL("array_push")); + fn_array_unshift = zend::get_function(CG(function_table), ZEND_STRL("array_unshift")); + fn_array_splice = zend::get_function(CG(function_table), ZEND_STRL("array_splice")); + + ori_handler_array_push = fn_array_push->internal_function.handler; + fn_array_push->internal_function.handler = ZEND_FN(swoole_array_push); + ori_handler_array_unshift = fn_array_unshift->internal_function.handler; + fn_array_unshift->internal_function.handler = ZEND_FN(swoole_array_unshift); + ori_handler_array_splice = fn_array_splice->internal_function.handler; + fn_array_splice->internal_function.handler = ZEND_FN(swoole_array_splice); +} + +#define SW_CREATE_PHP_FUNCTION_WRAPPER(php_func_name, swoole_func_name, position) \ + PHP_FUNCTION(swoole_func_name) { \ + static zend_function *fn_##swoole_func_name = nullptr; \ + if (!fn_##swoole_func_name) { \ + fn_##swoole_func_name = zend::get_function(CG(function_table), ZEND_STRL(#php_func_name)); \ + } \ + call_func_move_first_arg(fn_##swoole_func_name, execute_data, return_value, position); \ + } + +// array +SW_CREATE_PHP_FUNCTION_WRAPPER(array_search, swoole_array_search, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(in_array, swoole_array_contains, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(implode, swoole_array_join, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(array_key_exists, swoole_array_key_exists, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(array_map, swoole_array_map, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_array_replace_str, 2); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_array_ireplace_str, 2); + +// string +SW_CREATE_PHP_FUNCTION_WRAPPER(explode, swoole_str_split, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(hash, swoole_hash, 1); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_replace, swoole_str_replace, 2); +SW_CREATE_PHP_FUNCTION_WRAPPER(str_ireplace, swoole_str_ireplace, 2); + +PHP_FUNCTION(swoole_parse_str) { + char *arg; + size_t arglen; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(arg, arglen) + ZEND_PARSE_PARAMETERS_END(); + + array_init(return_value); + auto res = estrndup(arg, arglen); + sapi_module.treat_data(PARSE_STRING, res, return_value); +} + +PHP_FUNCTION(swoole_str_is_empty) { + zend_string *str; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + RETURN_BOOL(str->len == 0); +} + +PHP_FUNCTION(swoole_array_is_empty) { + zval *array; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + RETURN_BOOL(zend_array_count(Z_ARRVAL_P(array)) == 0); +} + +PHP_FUNCTION(swoole_call_array_method) { + call_method(array_methods, execute_data, return_value); +} + +PHP_FUNCTION(swoole_call_string_method) { + call_method(string_methods, execute_data, return_value); +} + +PHP_FUNCTION(swoole_call_stream_method) { + call_method(stream_methods, execute_data, return_value); +} + +static HashTable *make_typed_array(const uint32_t nSize, const uint32_t nTypeStr) { + const auto ht = static_cast(emalloc(sizeof(HashTable) + sizeof(ArrayTypeInfo) + nTypeStr + 1)); + _zend_hash_init(ht, nSize, ZVAL_PTR_DTOR, false); + HT_FLAGS(ht) |= HASH_FLAG_TYPED_ARRAY; + return ht; +} + +void copy_array_type_info(zval *container, zend_array *src) { + auto src_type_info = get_type_info(src); + zend_array *ht = Z_ARRVAL_P(container); + auto extra_size = sizeof(ArrayTypeInfo) + src_type_info->get_len_of_type_str() + 1; + const auto tmp = static_cast(emalloc(sizeof(HashTable) + extra_size)); + memcpy(tmp, ht, sizeof(HashTable)); + memcpy(reinterpret_cast(tmp) + sizeof(HashTable), src_type_info, extra_size); + HT_FLAGS(tmp) |= HASH_FLAG_TYPED_ARRAY; + Z_ARRVAL_P(container) = tmp; + efree(ht); +} + +static ArrayTypeInfo *get_type_info(zend_array *array) { + return reinterpret_cast(reinterpret_cast(array) + sizeof(HashTable)); +} + +static zend_string *get_array_type_def(const ArrayTypeInfo *info) { + zend_string *result = zend_string_alloc(info->get_len_of_value_type_str() + 16, false); + char *p = result->val; + *p = '<'; + if (info->get_type_of_key() == IS_STRING) { + p++; + strcpy(p, "string,"); + p += 7; + } else if (info->get_type_of_key() == IS_LONG) { + p++; + strcpy(p, "int,"); + p += 4; + } + + memcpy(p, info->get_value_type_str(), info->get_len_of_value_type_str()); + p += info->get_len_of_value_type_str(); + *p = '>'; + p++; + *p = '\0'; + result->len = p - result->val; + + return result; +} + +bool ArrayTypeInfo::check(const zend_array *ht, const zval *key, const zval *value) const { + if (get_type_of_key() > 0) { + if (Z_TYPE_P(key) != get_type_of_key()) { + zend_type_error("Array key type mismatch, expected `%s`, got `%s`", + zend_get_type_by_const(get_type_of_key()), + zend_get_type_by_const(Z_TYPE_P(key))); + return false; + } + } else { + if (Z_TYPE_P(key) == IS_LONG) { + if (Z_LVAL_P(key) > zend_hash_num_elements(ht)) { + zend_throw_error( + nullptr, "Incorrect array key `%ld`, out of the permitted range", (long) Z_LVAL_P(key)); + return false; + } + } else if (!(Z_TYPE_P(key) == IS_UNDEF || Z_TYPE_P(key) == IS_NULL)) { + zend_throw_error(nullptr, "Incorrect array key, must be undef or int"); + return false; + } + } + ZVAL_DEREF(value); + if (value_is_bool() && ZVAL_IS_BOOL(value)) { + return true; + } + if (Z_TYPE_P(value) != get_type_of_value()) { + zend_type_error("Array value type mismatch, expected `%s`, got `%s`", + zend_get_type_by_const(get_type_of_value()), + zend_get_type_by_const(Z_TYPE_P(value))); + return false; + } + if (value_is_object() && !instance_of(value)) { + zend_type_error( + "Array value type mismatch, expected `%s`, got `%s`", value_ce->name->val, Z_OBJCE_P(value)->name->val); + return false; + } + if (value_is_array()) { + const auto element_array_type_info = get_type_info(Z_ARRVAL_P(value)); + const auto element_ht = Z_ARRVAL_P(value); + if (!(HT_FLAGS(element_ht) & HASH_FLAG_TYPED_ARRAY)) { + zend_type_error("Array value type mismatch, expected `%.*s`, got `array`", + get_len_of_value_type_str(), + get_value_type_str()); + return false; + } + if (!element_type_equals(element_array_type_info)) { + const auto element_type_str = get_array_type_def(element_array_type_info); + zend_type_error("Array value type mismatch, expected `%.*s`, got `%.*s`", + get_len_of_value_type_str(), + get_value_type_str(), + (int) ZSTR_LEN(element_type_str), + ZSTR_VAL(element_type_str)); + zend_string_release(element_type_str); + return false; + } + } + return true; +} + +static zval *get_array_on_opline(const zend_op *opline EXECUTE_DATA_DC) { + auto array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); + if (ZVAL_IS_REF(array)) { + array = Z_REFVAL_P(array); + } + if (!ZVAL_IS_ARRAY(array)) { + return nullptr; + } + return array; +} + +#ifdef DEBUG +static void debug_val(const char *tag, int op_type, zval *value) { + printf("[%s] refcount=%d, op1_type=%d, type=%s, refcounted=%d\n", + tag, + Z_REFCOUNTED_P(value) ? Z_REFCOUNT_P(value) : 0, + op_type, + zend_get_type_by_const(Z_TYPE_P(value)), + Z_REFCOUNTED_P(value)); +} +#else +#define debug_val(tag, op_type, value) +#endif + +// In a release version, this function suddenly changes from static to ZEND_API. +// We don't know which version it is. In principle, the ZEND_API should not be changed in the release version, +// but PHP still does so, which is against the R&D specification. We have to copy the code of this function once. +#define zend_cannot_add_element sw_zend_cannot_add_element +static zend_never_inline ZEND_COLD void ZEND_FASTCALL sw_zend_cannot_add_element(void) { + zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied"); +} + +static void array_add_or_update(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { + zval *var_ptr; + HashTable *source = Z_ARRVAL_P(container); + SEPARATE_ARRAY(container); + if (source != Z_ARRVAL_P(container)) { + copy_array_type_info(container, source); + } + HashTable *ht = Z_ARRVAL_P(container); + const zend_op *op_data = opline + 1; + + if (ZVAL_IS_NULL(key)) { + var_ptr = zend_hash_next_index_insert(ht, value); + if (UNEXPECTED(!var_ptr)) { + zend_cannot_add_element(); + goto assign_dim_op_ret_null; + } + } else { + zval *variable_ptr; + if (opline->op2_type == IS_CONST) { + variable_ptr = zend_fetch_dimension_address_inner_W_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } else { + variable_ptr = zend_fetch_dimension_address_inner_W(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } + if (UNEXPECTED(variable_ptr == nullptr)) { + goto assign_dim_op_ret_null; + } + debug_val("1", op_data->op1_type, value); + var_ptr = zend_assign_to_variable(variable_ptr, value, op_data->op1_type, EX_USES_STRICT_TYPES()); + debug_val("2", op_data->op1_type, value); + if (UNEXPECTED(!var_ptr)) { + assign_dim_op_ret_null: + FREE_OP(op_data->op1_type, op_data->op1.var); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + return; + } + } + debug_val("3", op_data->op1_type, value); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), var_ptr); + } + if (op_data->op1_type == IS_VAR) { + Z_TRY_ADDREF_P(value); + } + FREE_OP(op_data->op1_type, op_data->op1.var); + debug_val("4", op_data->op1_type, value); +} + +static void array_op(const zend_op *opline, zval *container, const zval *key, zval *value EXECUTE_DATA_DC) { + HashTable *source = Z_ARRVAL_P(container); + SEPARATE_ARRAY(container); + if (source != Z_ARRVAL_P(container)) { + copy_array_type_info(container, source); + } + const auto type_info = get_type_info(Z_ARRVAL_P(container)); + + zval *variable_ptr; + if ((opline + 1)->op1_type == IS_CONST) { + variable_ptr = zend_fetch_dimension_address_inner_RW_CONST(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } else { + variable_ptr = zend_fetch_dimension_address_inner_RW(Z_ARRVAL_P(container), key EXECUTE_DATA_CC); + } + if (UNEXPECTED(variable_ptr == nullptr)) { + assign_dim_op_ret_null: + FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_NULL(EX_VAR(opline->result.var)); + } + return; + } + do { + if (UNEXPECTED(Z_ISREF_P(variable_ptr))) { + zend_reference *ref = Z_REF_P(variable_ptr); + variable_ptr = Z_REFVAL_P(variable_ptr); + if (UNEXPECTED(ZEND_REF_HAS_TYPE_SOURCES(ref))) { + zend_binary_assign_op_typed_ref(ref, value OPLINE_CC EXECUTE_DATA_CC); + break; + } + } + const auto opcode = opline->extended_value; + if (opcode == ZEND_CONCAT) { + if (!type_info->value_is_string()) { + zend_type_error("Only string support concat operation"); + goto assign_dim_op_ret_null; + } + } else { + if (!type_info->value_is_numeric()) { + zend_type_error("Only int or float support arithmetic operation"); + goto assign_dim_op_ret_null; + } + } + zend_binary_op(variable_ptr, variable_ptr, value OPLINE_CC); + } while (false); + + if (UNEXPECTED(RETURN_VALUE_USED(opline))) { + ZVAL_COPY(EX_VAR(opline->result.var), variable_ptr); + } + FREE_OP((opline + 1)->op1_type, (opline + 1)->op1.var); +} + +typedef std::function ArrayFn; + +static int opcode_handler_array(zend_execute_data *execute_data, const ArrayFn &fn) { + const zend_op *opline = EX(opline); + const zend_op *op_data = opline + 1; + zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); + if (UNEXPECTED(!array)) { + return ZEND_USER_OPCODE_DISPATCH; + } + zend_array *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + return ZEND_USER_OPCODE_DISPATCH; + } + const auto value = get_op_data_zval_ptr_r(op_data->op1_type, op_data->op1); + zval *key; + if (opline->op2_type == IS_CONST) { + key = RT_CONSTANT(opline, opline->op2); + } else if (UNEXPECTED(opline->op2_type == IS_UNUSED)) { + key = &EG(uninitialized_zval); + } else { + key = EX_VAR(opline->op2.var); + } + const auto type_info = get_type_info(ht); + if (!type_info->check(ht, key, value)) { + FREE_OP(op_data->op1_type, op_data->op1.var); + return ZEND_USER_OPCODE_CONTINUE; + } + fn(opline, array, key, value EXECUTE_DATA_CC); + EX(opline) += 2; + return ZEND_USER_OPCODE_CONTINUE; +} + +static int opcode_handler_array_assign(zend_execute_data *execute_data) { + return opcode_handler_array(execute_data, array_add_or_update); +} + +static int opcode_handler_array_assign_op(zend_execute_data *execute_data) { + return opcode_handler_array(execute_data, array_op); +} + +static int opcode_handler_foreach_begin(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + zval *array; + if (opline->op1_type == IS_VAR || opline->op1_type == IS_CV) { + array = _get_zval_ptr_ptr_var(opline->op1.var EXECUTE_DATA_CC); + } else if (opline->op1_type == IS_CONST) { + array = RT_CONSTANT(opline, opline->op1); + } else { + array = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC); + } + ZVAL_DEREF(array); + if (UNEXPECTED(!array || !ZVAL_IS_ARRAY(array))) { + return ZEND_USER_OPCODE_DISPATCH; + } + const zend_array *ht = Z_ARRVAL_P(array); + if (HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY) { + zend_throw_error(nullptr, "The type array do not support using references for element value during iteration"); + ZVAL_UNDEF(EX_VAR(opline->result.var)); + Z_FE_ITER_P(EX_VAR(opline->result.var)) = static_cast(-1); + + return ZEND_USER_OPCODE_CONTINUE; + } + return ZEND_USER_OPCODE_DISPATCH; +} + +static int opcode_handler_array_unset(zend_execute_data *execute_data) { + const zend_op *opline = EX(opline); + const zval *array = get_array_on_opline(opline EXECUTE_DATA_CC); + if (!array) { + return ZEND_USER_OPCODE_DISPATCH; + } + zend_array *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + return ZEND_USER_OPCODE_DISPATCH; + } + const auto type_info = get_type_info(ht); + if (type_info->is_list()) { + zend_throw_error(nullptr, "The typed array list do not support random deletion of elements"); + return ZEND_USER_OPCODE_CONTINUE; + } + return ZEND_USER_OPCODE_DISPATCH; +} + +static void remove_all_spaces(char **val, uint16_t *len) { + if (!*val || *len == 0) { + return; + } + + const char *src = *val; + char *dst = *val; + size_t new_len = 0; + + for (size_t i = 0; i < *len; i++) { + if (!isspace((uchar) *src)) { + *dst = *src; + dst++; + new_len++; + } + src++; + } + + *len = new_len; +} + +static int8_t get_type(const char *val, size_t len) { + if (SW_STRCASEEQ(val, len, "int")) { + return IS_LONG; + } else if (SW_STRCASEEQ(val, len, "float")) { + return IS_DOUBLE; + } else if (SW_STRCASEEQ(val, len, "string")) { + return IS_STRING; + } else if (SW_STRCASEEQ(val, len, "bool")) { + return IS_TRUE; // IS_TRUE or IS_FALSE + } else if (val[0] == '<' && val[len - 1] == '>') { + return IS_ARRAY; + } else if (SW_STRCASEEQ(val, len, "resource")) { + return IS_RESOURCE; + } else if (SW_STRCASEEQ(val, len, "null")) { + return IS_NULL; + } else { + return IS_OBJECT; + } +} + +bool ArrayTypeValue::parse(const char *type_str, const size_t len_of_type_str) { + auto pos = strchr(type_str, ','); + if (pos == nullptr) { + type_of_key = 0; + offset_of_value_type_str = 1; + } else { + type_of_key = get_type(type_str + 1, pos - type_str - 1); + if (type_of_key != IS_STRING && type_of_key != IS_LONG) { + zend_throw_error(nullptr, "The key type of array must be string or int, but got %s", pos + 1); + return false; + } + offset_of_value_type_str = pos - type_str + 1; + } + len_of_value_type_str = len_of_type_str - offset_of_value_type_str - 1; + type_of_value = get_type(type_str + offset_of_value_type_str, len_of_value_type_str); + return true; +} + +bool ArrayTypeInfo::parse(zend_string *type_def) { + if (type_def->len >= 65535) { + zend_throw_error(nullptr, "The type definition string is too long (must be less than 65535 characters)"); + return false; + } + zend_string *lc_type_def = zend_string_tolower(type_def); + memcpy(type_str, lc_type_def->val, lc_type_def->len + 1); + len_of_type_str = lc_type_def->len; + zend_string_release(lc_type_def); + + char *tmp_type_str = type_str; + remove_all_spaces(&tmp_type_str, &len_of_type_str); + tmp_type_str[len_of_type_str] = '\0'; + if (tmp_type_str != type_str) { + memmove(type_str, tmp_type_str, len_of_type_str + 1); + } + + if (type_str[0] != '<' || type_str[len_of_type_str - 1] != '>') { + zend_throw_error(nullptr, "The type definition of typed array must start with '<' and end with '>'"); + return false; + } + if (!self.parse(type_str, len_of_type_str)) { + return false; + } + + if (self.type_of_value == IS_OBJECT) { + zend::String type_str_of_value(type_str + self.offset_of_value_type_str, self.len_of_value_type_str); + value_ce = zend_lookup_class(type_str_of_value.get()); + if (!value_ce) { + zend_throw_error(nullptr, "Class '%s' not found", type_str_of_value.val()); + return false; + } + } + + return true; +} + +PHP_FUNCTION(swoole_typed_array) { + zend_string *type_def; + zval *init_values = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_STR(type_def) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(init_values) + ZEND_PARSE_PARAMETERS_END(); + + auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); + if (!tmp_info->parse(type_def)) { + efree(tmp_info); + RETURN_NULL(); + } + + if (init_values && is_typed_array(init_values)) { + auto type_info = get_type_info(Z_ARRVAL_P(init_values)); + if (tmp_info->equals(type_info)) { + ZVAL_COPY(return_value, init_values); + } else { + zend_throw_error(nullptr, "The type definition of the typed array does not match the initial values"); + } + efree(tmp_info); + return; + } + + auto n = init_values ? zend_array_count(Z_ARRVAL_P(init_values)) : 0; + auto array = make_typed_array(n, tmp_info->len_of_type_str); + ZVAL_ARR(return_value, array); + auto info = get_type_info(array); + memcpy(info, tmp_info, sizeof(ArrayTypeInfo) + tmp_info->len_of_type_str + 1); + efree(tmp_info); + + if (info->self.type_of_value == IS_ARRAY) { + if (!info->element.parse(info->get_value_type_str(), info->get_len_of_value_type_str())) { + zval_ptr_dtor(return_value); + RETURN_NULL(); + } + } + + if (init_values) { + zend_string *str_key; + zend_ulong num_key; + zval *zv; + zval zk; + HashTable *ht = Z_ARRVAL_P(init_values); + + ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, str_key, zv) { + if (str_key) { + ZVAL_STR(&zk, str_key); + } else { + ZVAL_LONG(&zk, num_key); + } + if (!info->check(array, &zk, zv)) { + zval_ptr_dtor(return_value); + RETURN_NULL(); + } + Z_TRY_ADDREF_P(zv); + if (str_key) { + zend_hash_add(array, str_key, zv); + } else { + zend_hash_index_add(array, num_key, zv); + } + } + ZEND_HASH_FOREACH_END(); + } +} + +PHP_FUNCTION(swoole_array_is_typed) { + zend_string *type_def = nullptr; + zval *array; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ARRAY(array) + Z_PARAM_OPTIONAL + Z_PARAM_STR(type_def) + ZEND_PARSE_PARAMETERS_END(); + + HashTable *ht = Z_ARRVAL_P(array); + if (!(HT_FLAGS(ht) & HASH_FLAG_TYPED_ARRAY)) { + RETURN_FALSE; + } + if (!type_def) { + RETURN_TRUE; + } + + const auto tmp_info = static_cast(emalloc(sizeof(ArrayTypeInfo) + ZSTR_LEN(type_def) + 1)); + if (!tmp_info->parse(type_def)) { + efree(tmp_info); + RETURN_FALSE; + } + + const auto info = get_type_info(ht); + RETVAL_BOOL(info->equals(tmp_info)); + efree(tmp_info); +} + +static PHP_FUNCTION(swoole_array_push) { + zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + auto source = Z_ARRVAL_P(array); + auto type_info = get_type_info(source); + for (int i = 1; i < arg_count; i++) { + if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { + return; + } + } + } + ori_handler_array_push(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} + +static PHP_FUNCTION(swoole_array_unshift) { + zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + auto source = Z_ARRVAL_P(array); + auto type_info = get_type_info(source); + for (int i = 1; i < arg_count; i++) { + if (!type_info->check(source, &EG(uninitialized_zval), &arg_ptr[i])) { + return; + } + } + ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); + HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; + } else { + ori_handler_array_unshift(INTERNAL_FUNCTION_PARAM_PASSTHRU); + } +} + +static PHP_FUNCTION(swoole_array_splice) { + const zval *arg_ptr = ZEND_CALL_ARG(execute_data, 1); + const int arg_count = ZEND_CALL_NUM_ARGS(execute_data); + const zval *array = &arg_ptr[0]; + ZVAL_DEREF(array); + if (Z_TYPE_P(array) == IS_ARRAY && is_typed_array(array)) { + if (arg_count > 3) { + auto type_info = get_type_info(Z_ARRVAL_P(array)); + auto values = &arg_ptr[3]; + ZVAL_DEREF(values); + if (Z_TYPE_P(values) == IS_ARRAY) { + zval *zv; + HashTable *ht = Z_ARRVAL_P(values); + ZEND_HASH_FOREACH_VAL(ht, zv) { + if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), zv)) { + return; + } + } + ZEND_HASH_FOREACH_END(); + } else { + if (!type_info->check(Z_ARRVAL_P(array), &EG(uninitialized_zval), values)) { + return; + } + } + } + const auto source = Z_ARRVAL_P(array); + ori_handler_array_splice(execute_data, return_value); + HT_FLAGS(source) |= HASH_FLAG_TYPED_ARRAY; + } else { + ori_handler_array_splice(execute_data, return_value); + } +} + +static void php_do_pcre_match(INTERNAL_FUNCTION_PARAMETERS, int global) /* {{{ */ +{ + /* parameters */ + zend_string *regex; /* Regular expression */ + zend_string *subject; /* String to match against */ + pcre_cache_entry *pce; /* Compiled regular expression */ + zend_long flags = 0; /* Match control flags */ + zend_long start_offset = 0; /* Where the new search starts */ + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STR(subject) + Z_PARAM_STR(regex) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(flags) + Z_PARAM_LONG(start_offset) + ZEND_PARSE_PARAMETERS_END(); + + /* Compile regex or get it from cache. */ + if ((pce = pcre_get_compiled_regex_cache(regex)) == nullptr) { + RETURN_FALSE; + } + + zval count = {}; + php_pcre_pce_incref(pce); +#if PHP_VERSION_ID >= 80400 + php_pcre_match_impl(pce, subject, &count, return_value, global == 1, flags, start_offset); +#else + php_pcre_match_impl(pce, subject, &count, return_value, global, ZEND_NUM_ARGS() >= 3, flags, start_offset); +#endif + php_pcre_pce_decref(pce); +} +/* }}} */ + +PHP_FUNCTION(swoole_str_match) { + php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} + +PHP_FUNCTION(swoole_str_match_all) { + php_do_pcre_match(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} + +static void php_do_json_decode(INTERNAL_FUNCTION_PARAMETERS, bool assoc) /* {{{ */ +{ + char *str; + size_t str_len; + zend_long depth = PHP_JSON_PARSER_DEFAULT_DEPTH; + zend_long options = 0; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_STRING(str, str_len) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(depth) + Z_PARAM_LONG(options) + ZEND_PARSE_PARAMETERS_END(); + + if (assoc) { + options |= PHP_JSON_OBJECT_AS_ARRAY; + } else { + options &= ~PHP_JSON_OBJECT_AS_ARRAY; + } + + zend::json_decode(return_value, str, str_len, options, depth); +} +/* }}} */ + +PHP_FUNCTION(swoole_str_json_decode) { + php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, true); +} + +PHP_FUNCTION(swoole_str_json_decode_to_object) { + php_do_json_decode(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); +} +#endif diff --git a/ext-src/swoole_table.cc b/ext-src/swoole_table.cc index 585faa6383..f083004278 100644 --- a/ext-src/swoole_table.cc +++ b/ext-src/swoole_table.cc @@ -24,11 +24,10 @@ END_EXTERN_C() using namespace swoole; -static inline void php_swoole_table_row2array(Table *table, TableRow *row, zval *return_value) { +static inline void table_row2array(const Table *table, TableRow *row, zval *return_value) { array_init(return_value); - for (auto i = table->column_list->begin(); i != table->column_list->end(); i++) { - TableColumn *col = *i; + for (const auto col : *table->column_list) { if (col->type == TableColumn::TYPE_STRING) { TableStringLength len = 0; char *str = nullptr; @@ -48,9 +47,8 @@ static inline void php_swoole_table_row2array(Table *table, TableRow *row, zval } } -static inline void php_swoole_table_get_field_value( - Table *table, TableRow *row, zval *return_value, char *field, uint16_t field_len) { - TableColumn *col = table->get_column(std::string(field, field_len)); +static inline void table_get_field_value(Table *table, TableRow *row, zval *return_value, const zend_string *field) { + TableColumn *col = table->get_column(std::string(ZSTR_VAL(field), ZSTR_LEN(field))); if (!col) { ZVAL_FALSE(return_value); return; @@ -81,44 +79,40 @@ struct TableObject { zend_object std; }; -static inline TableObject *php_swoole_table_fetch_object(zend_object *obj) { - return (TableObject *) ((char *) obj - swoole_table_handlers.offset); +static TableObject *table_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_table_handlers.offset); } -static inline Table *php_swoole_table_get_ptr(zval *zobject) { - return php_swoole_table_fetch_object(Z_OBJ_P(zobject))->ptr; +static Table *table_get_ptr(const zval *zobject) { + return table_fetch_object(Z_OBJ_P(zobject))->ptr; } -static inline Table *php_swoole_table_get_and_check_ptr(zval *zobject) { - Table *table = php_swoole_table_get_ptr(zobject); - if (!table) { - php_swoole_fatal_error(E_ERROR, "you must call Table constructor first"); +static Table *table_get_and_check_ptr(const zval *zobject) { + Table *table = table_get_ptr(zobject); + if (UNEXPECTED(!table)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); } return table; } -static inline Table *php_swoole_table_get_and_check_ptr2(zval *zobject) { - Table *table = php_swoole_table_get_and_check_ptr(zobject); +static Table *table_get_and_check_ptr2(const zval *zobject) { + Table *table = table_get_and_check_ptr(zobject); if (!table->ready()) { php_swoole_fatal_error(E_ERROR, "table is not created or has been destroyed"); } return table; } -static void inline php_swoole_table_set_ptr(zval *zobject, Table *ptr) { - php_swoole_table_fetch_object(Z_OBJ_P(zobject))->ptr = ptr; +static void table_set_ptr(const zval *zobject, Table *ptr) { + table_fetch_object(Z_OBJ_P(zobject))->ptr = ptr; } -static inline void php_swoole_table_free_object(zend_object *object) { - Table *table = php_swoole_table_fetch_object(object)->ptr; - if (table) { - table->free(); - } +static void table_free_object(zend_object *object) { zend_object_std_dtor(object); } -static inline zend_object *php_swoole_table_create_object(zend_class_entry *ce) { - TableObject *table = (TableObject *) zend_object_alloc(sizeof(TableObject), ce); +static zend_object *table_create_object(zend_class_entry *ce) { + auto *table = static_cast(zend_object_alloc(sizeof(TableObject), ce)); zend_object_std_init(&table->std, ce); object_properties_init(&table->std, ce); table->std.handlers = &swoole_table_handlers; @@ -183,12 +177,9 @@ void php_swoole_table_minit(int module_number) { SW_SET_CLASS_NOT_SERIALIZABLE(swoole_table); SW_SET_CLASS_CLONEABLE(swoole_table, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_table, sw_zend_class_unset_property_deny); - SW_SET_CLASS_CUSTOM_OBJECT( - swoole_table, php_swoole_table_create_object, php_swoole_table_free_object, TableObject, std); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_table, table_create_object, table_free_object, TableObject, std); zend_class_implements(swoole_table_ce, 1, zend_ce_iterator); -#ifdef SW_HAVE_COUNTABLE zend_class_implements(swoole_table_ce, 1, zend_ce_countable); -#endif zend_declare_property_null(swoole_table_ce, ZEND_STRL("size"), ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_table_ce, ZEND_STRL("memorySize"), ZEND_ACC_PUBLIC); @@ -199,9 +190,9 @@ void php_swoole_table_minit(int module_number) { } PHP_METHOD(swoole_table, __construct) { - Table *table = php_swoole_table_get_ptr(ZEND_THIS); + Table *table = table_get_ptr(ZEND_THIS); if (table) { - zend_throw_error(NULL, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); RETURN_FALSE; } @@ -214,7 +205,7 @@ PHP_METHOD(swoole_table, __construct) { Z_PARAM_DOUBLE(conflict_proportion) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - table = Table::make(table_size, conflict_proportion); + table = Table::make(table_size, static_cast(conflict_proportion)); if (table == nullptr) { zend_throw_exception(swoole_exception_ce, "global memory allocation failure", SW_ERROR_MALLOC_FAIL); RETURN_FALSE; @@ -222,11 +213,11 @@ PHP_METHOD(swoole_table, __construct) { table->set_hash_func([](const char *key, size_t len) -> uint64_t { return zend_string_hash_val(zend::fetch_zend_string_by_val((void *) key)); }); - php_swoole_table_set_ptr(ZEND_THIS, table); + table_set_ptr(ZEND_THIS, table); } PHP_METHOD(swoole_table, column) { - Table *table = php_swoole_table_get_and_check_ptr(ZEND_THIS); + Table *table = table_get_and_check_ptr(ZEND_THIS); char *name; size_t len; long type; @@ -246,32 +237,33 @@ PHP_METHOD(swoole_table, column) { php_swoole_fatal_error(E_WARNING, "unable to add column after table has been created"); RETURN_FALSE; } - RETURN_BOOL(table->add_column(std::string(name, len), (enum TableColumn::Type) type, size)); + RETURN_BOOL(table->add_column(std::string(name, len), static_cast(type), size)); } static PHP_METHOD(swoole_table, create) { - Table *table = php_swoole_table_get_and_check_ptr(ZEND_THIS); + Table *table = table_get_and_check_ptr(ZEND_THIS); if (!table->create()) { php_swoole_fatal_error(E_ERROR, "unable to allocate memory"); RETURN_FALSE; } - zend_update_property_long(swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("size"), table->get_size()); zend_update_property_long( - swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("memorySize"), table->get_memory_size()); + swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("size"), (zend_long) table->get_size()); + zend_update_property_long( + swoole_table_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("memorySize"), (zend_long) table->get_memory_size()); RETURN_TRUE; } static PHP_METHOD(swoole_table, destroy) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); table->destroy(); - php_swoole_table_set_ptr(ZEND_THIS, nullptr); + table_set_ptr(ZEND_THIS, nullptr); RETURN_TRUE; } static PHP_METHOD(swoole_table, set) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); zval *array; char *key; size_t keylen; @@ -302,8 +294,7 @@ static PHP_METHOD(swoole_table, set) { HashTable *ht = Z_ARRVAL_P(array); if (out_flags & SW_TABLE_FLAG_NEW_ROW) { - for (auto i = table->column_list->begin(); i != table->column_list->end(); i++) { - TableColumn *col = *i; + for (const auto col : *table->column_list) { zval *zv = zend_hash_str_find(ht, col->name.c_str(), col->name.length()); if (zv == nullptr || ZVAL_IS_NULL(zv)) { col->clear(row); @@ -353,7 +344,7 @@ static PHP_METHOD(swoole_table, set) { } static PHP_METHOD(swoole_table, incr) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t key_len; char *col; @@ -413,7 +404,7 @@ static PHP_METHOD(swoole_table, incr) { } static PHP_METHOD(swoole_table, decr) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t key_len; char *col; @@ -473,32 +464,31 @@ static PHP_METHOD(swoole_table, decr) { } static PHP_METHOD(swoole_table, get) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; - char *field = nullptr; - size_t field_len = 0; + zend_string *field = nullptr; ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) Z_PARAM_STRING(key, keylen) Z_PARAM_OPTIONAL - Z_PARAM_STRING(field, field_len) + Z_PARAM_STR_OR_NULL(field) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); TableRow *_rowlock = nullptr; TableRow *row = table->get(key, keylen, &_rowlock); if (!row) { RETVAL_FALSE; - } else if (field && field_len > 0) { - php_swoole_table_get_field_value(table, row, return_value, field, (uint16_t) field_len); + } else if (field) { + table_get_field_value(table, row, return_value, field); } else { - php_swoole_table_row2array(table, row, return_value); + table_row2array(table, row, return_value); } _rowlock->unlock(); } static PHP_METHOD(swoole_table, exists) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; @@ -509,7 +499,7 @@ static PHP_METHOD(swoole_table, exists) { } static PHP_METHOD(swoole_table, del) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); char *key; size_t keylen; @@ -523,7 +513,7 @@ static PHP_METHOD(swoole_table, del) { static PHP_METHOD(swoole_table, count) { #define COUNT_NORMAL 0 #define COUNT_RECURSIVE 1 - Table *table = php_swoole_table_get_ptr(ZEND_THIS); + Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } @@ -541,7 +531,7 @@ static PHP_METHOD(swoole_table, count) { } static PHP_METHOD(swoole_table, getMemorySize) { - Table *table = php_swoole_table_get_ptr(ZEND_THIS); + Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } else { @@ -550,7 +540,7 @@ static PHP_METHOD(swoole_table, getMemorySize) { } static PHP_METHOD(swoole_table, getSize) { - Table *table = php_swoole_table_get_ptr(ZEND_THIS); + Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_LONG(0); } else { @@ -559,7 +549,7 @@ static PHP_METHOD(swoole_table, getSize) { } static PHP_METHOD(swoole_table, stats) { - Table *table = php_swoole_table_get_ptr(ZEND_THIS); + Table *table = table_get_ptr(ZEND_THIS); if (!table) { RETURN_FALSE; } @@ -567,36 +557,36 @@ static PHP_METHOD(swoole_table, stats) { add_assoc_long(return_value, "num", table->count()); add_assoc_long(return_value, "conflict_count", table->conflict_count); add_assoc_long(return_value, "conflict_max_level", table->conflict_max_level); - add_assoc_long(return_value, "insert_count", table->insert_count); - add_assoc_long(return_value, "update_count", table->update_count); - add_assoc_long(return_value, "delete_count", table->delete_count); + add_assoc_long(return_value, "insert_count", (zend_long) table->insert_count); + add_assoc_long(return_value, "update_count", (zend_long) table->update_count); + add_assoc_long(return_value, "delete_count", (zend_long) table->delete_count); add_assoc_long(return_value, "available_slice_num", table->get_available_slice_num()); add_assoc_long(return_value, "total_slice_num", table->get_total_slice_num()); } static PHP_METHOD(swoole_table, rewind) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); table->rewind(); table->forward(); } static PHP_METHOD(swoole_table, valid) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); auto key = table->current(); RETURN_BOOL(key->key_len != 0); } static PHP_METHOD(swoole_table, current) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); auto row = table->current(); if (row->key_len == 0) { RETURN_NULL(); } - php_swoole_table_row2array(table, row, return_value); + table_row2array(table, row, return_value); } static PHP_METHOD(swoole_table, key) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); auto row = table->current(); if (row->key_len == 0) { RETURN_NULL(); @@ -605,6 +595,6 @@ static PHP_METHOD(swoole_table, key) { } static PHP_METHOD(swoole_table, next) { - Table *table = php_swoole_table_get_and_check_ptr2(ZEND_THIS); + Table *table = table_get_and_check_ptr2(ZEND_THIS); table->forward(); } diff --git a/ext-src/swoole_thread.cc b/ext-src/swoole_thread.cc new file mode 100644 index 0000000000..26b02a8a75 --- /dev/null +++ b/ext-src/swoole_thread.cc @@ -0,0 +1,1201 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" +#include "php_swoole_thread.h" + +#ifdef SW_THREAD + +#include +#include + +#include + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_thread_arginfo.h" +END_EXTERN_C() + +zend_class_entry *swoole_thread_ce; +static zend_object_handlers swoole_thread_handlers; + +zend_class_entry *swoole_thread_error_ce; +static zend_object_handlers swoole_thread_error_handlers; + +static struct { + char *path_translated; + zend_string *argv_serialized; + int argc; +} request_info; + +TSRMLS_CACHE_EXTERN(); + +using swoole::Thread; + +struct PhpThread { + std::shared_ptr thread; + + PhpThread() : thread(std::make_shared()) {} + + bool join() const { + if (!thread->joinable()) { + return false; + } + thread->join(); + return true; + } +}; + +struct ThreadObject { + PhpThread *pt; + zend_object std; +}; + +static void thread_register_stdio_file_handles(bool no_close); + +static thread_local zval thread_argv = {}; +static thread_local JMP_BUF *thread_bailout = nullptr; +static std::atomic thread_num(1); + +static sw_inline ThreadObject *thread_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_handlers.offset); +} + +static sw_inline ThreadObject *thread_fetch_object(const zval *zobj) { + return thread_fetch_object(Z_OBJ_P(zobj)); +} + +static sw_inline PhpThread *thread_get_php_thread(zend_object *obj) { + return thread_fetch_object(obj)->pt; +} + +static sw_inline PhpThread *thread_get_php_thread(const zval *zobj) { + return thread_fetch_object(zobj)->pt; +} + +static void thread_free_object(zend_object *object) { + auto pt = thread_get_php_thread(object); + pt->join(); + delete pt; + zend_object_std_dtor(object); +} + +static zend_object *thread_create_object(zend_class_entry *ce) { + auto to = static_cast(zend_object_alloc(sizeof(ThreadObject), ce)); + zend_object_std_init(&to->std, ce); + object_properties_init(&to->std, ce); + to->pt = new PhpThread(); + to->std.handlers = &swoole_thread_handlers; + return &to->std; +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread, __construct); +static PHP_METHOD(swoole_thread, isAlive); +static PHP_METHOD(swoole_thread, join); +static PHP_METHOD(swoole_thread, joinable); +static PHP_METHOD(swoole_thread, getExitStatus); +static PHP_METHOD(swoole_thread, detach); +static PHP_METHOD(swoole_thread, getArguments); +static PHP_METHOD(swoole_thread, getId); +static PHP_METHOD(swoole_thread, getInfo); +static PHP_METHOD(swoole_thread, activeCount); +static PHP_METHOD(swoole_thread, yield); +static PHP_METHOD(swoole_thread, setName); +#ifdef HAVE_CPU_AFFINITY +static PHP_METHOD(swoole_thread, setAffinity); +static PHP_METHOD(swoole_thread, getAffinity); +#endif +static PHP_METHOD(swoole_thread, setPriority); +static PHP_METHOD(swoole_thread, getPriority); +static PHP_METHOD(swoole_thread, getNativeId); +SW_EXTERN_C_END + +// clang-format off +static const zend_function_entry swoole_thread_methods[] = { + PHP_ME(swoole_thread, __construct, arginfo_class_Swoole_Thread___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, isAlive, arginfo_class_Swoole_Thread_isAlive, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, join, arginfo_class_Swoole_Thread_join, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, joinable, arginfo_class_Swoole_Thread_joinable, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, getExitStatus, arginfo_class_Swoole_Thread_getExitStatus, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, detach, arginfo_class_Swoole_Thread_detach, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread, getArguments, arginfo_class_Swoole_Thread_getArguments, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, getId, arginfo_class_Swoole_Thread_getId, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, getInfo, arginfo_class_Swoole_Thread_getInfo, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, activeCount, arginfo_class_Swoole_Thread_activeCount, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, yield, arginfo_class_Swoole_Thread_yield, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, setName, arginfo_class_Swoole_Thread_setName, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) +#ifdef HAVE_CPU_AFFINITY + PHP_ME(swoole_thread, setAffinity, arginfo_class_Swoole_Thread_setAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, getAffinity, arginfo_class_Swoole_Thread_getAffinity, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) +#endif + PHP_ME(swoole_thread, setPriority, arginfo_class_Swoole_Thread_setPriority, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, getPriority, arginfo_class_Swoole_Thread_getPriority, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_ME(swoole_thread, getNativeId, arginfo_class_Swoole_Thread_getNativeId, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread, "Swoole\\Thread", nullptr, swoole_thread_methods); + swoole_thread_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread, thread_create_object, thread_free_object, ThreadObject, std); + + zend_declare_property_long(swoole_thread_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); + zend_declare_class_constant_long( + swoole_thread_ce, ZEND_STRL("HARDWARE_CONCURRENCY"), std::thread::hardware_concurrency()); + zend_declare_class_constant_string(swoole_thread_ce, ZEND_STRL("API_NAME"), tsrm_api_name()); + + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_OTHER"), SCHED_OTHER); + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_FIFO"), SCHED_FIFO); + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_RR"), SCHED_RR); +#ifdef SCHED_BATCH + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_BATCH"), SCHED_BATCH); +#endif +#ifdef SCHED_ISO + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_ISO"), SCHED_ISO); +#endif +#ifdef SCHED_IDLE + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_IDLE"), SCHED_IDLE); +#endif +#ifdef SCHED_DEADLINE + zend_declare_class_constant_long(swoole_thread_ce, ZEND_STRL("SCHED_DEADLINE"), SCHED_DEADLINE); +#endif + + SW_INIT_CLASS_ENTRY_DATA_OBJECT(swoole_thread_error, "Swoole\\Thread\\Error"); + zend_declare_property_long(swoole_thread_error_ce, ZEND_STRL("code"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); +} + +static PHP_METHOD(swoole_thread, __construct) { + char *script_file; + size_t l_script_file; + zval *args; + int argc = 0; + ZendArray *argv = nullptr; + + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_STRING(script_file, l_script_file) + Z_PARAM_VARIADIC('+', args, argc) + ZEND_PARSE_PARAMETERS_END(); + + if (l_script_file < 1) { + zend_throw_exception(swoole_exception_ce, "exec file name is empty", SW_ERROR_INVALID_PARAMS); + return; + } + + auto pt = thread_get_php_thread(ZEND_THIS); + zend_string *file = zend_string_init(script_file, l_script_file, true); + + if (argc > 0) { + argv = new ZendArray(); + for (int i = 0; i < argc; i++) { + argv->append(&args[i]); + } + } + + try { + pt->thread->start([file, argv, pt]() { php_swoole_thread_start(pt->thread, file, argv); }); + } catch (const std::exception &e) { + zend_throw_exception(swoole_exception_ce, e.what(), SW_ERROR_SYSTEM_CALL_FAIL); + return; + } + + zend::object_set(ZEND_THIS, ZEND_STRL("id"), (zend_long) pt->thread->get_id()); +} + +static PHP_METHOD(swoole_thread, isAlive) { + auto pt = thread_get_php_thread(ZEND_THIS); + RETURN_BOOL(pt->thread->is_alive()); +} + +static PHP_METHOD(swoole_thread, join) { + auto pt = thread_get_php_thread(ZEND_THIS); + RETURN_BOOL(pt->join()); +} + +static PHP_METHOD(swoole_thread, joinable) { + auto pt = thread_get_php_thread(ZEND_THIS); + RETURN_BOOL(pt->thread->joinable()); +} + +static PHP_METHOD(swoole_thread, detach) { + auto pt = thread_get_php_thread(ZEND_THIS); + if (!pt->thread->joinable()) { + RETURN_FALSE; + } + pt->thread->detach(); + RETURN_TRUE; +} + +static PHP_METHOD(swoole_thread, getArguments) { + if (Z_TYPE(thread_argv) == IS_ARRAY) { + RETURN_ZVAL(&thread_argv, 1, 0); + } +} + +static PHP_METHOD(swoole_thread, getId) { + RETURN_LONG((zend_long) pthread_self()); +} + +static PHP_METHOD(swoole_thread, getExitStatus) { + auto pt = thread_get_php_thread(ZEND_THIS); + RETURN_LONG(pt->thread->get_exit_status()); +} + +static PHP_METHOD(swoole_thread, setName) { + char *name; + size_t l_name; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(name, l_name) + ZEND_PARSE_PARAMETERS_END(); + + RETURN_BOOL(swoole_thread_set_name(name)); +} + +#ifdef HAVE_CPU_AFFINITY +static PHP_METHOD(swoole_thread, setAffinity) { + zval *array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + + cpu_set_t cpu_set; + if (!php_swoole_array_to_cpu_set(array, &cpu_set)) { + RETURN_FALSE; + } + + if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set) < 0) { + php_swoole_error(E_WARNING, "pthread_setaffinity_np() failed"); + RETURN_FALSE; + } + RETURN_TRUE; +} + +static PHP_METHOD(swoole_thread, getAffinity) { + cpu_set_t cpu_set; + if (pthread_getaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set) < 0) { + php_swoole_error(E_WARNING, "pthread_getaffinity_np() failed"); + RETURN_FALSE; + } + php_swoole_cpu_set_to_array(return_value, &cpu_set); +} +#endif + +static PHP_METHOD(swoole_thread, setPriority) { + zend_long priority, policy = -1; + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(priority) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(policy) + ZEND_PARSE_PARAMETERS_END(); + + struct sched_param param; + if (policy == -1) { + pthread_setschedparam(pthread_self(), policy, ¶m); + } + + param.sched_priority = priority; + int retval = pthread_setschedparam(pthread_self(), policy, ¶m); + if (retval == 0) { + RETURN_TRUE; + } else { + php_swoole_sys_error(E_WARNING, "pthread_setschedparam() failed"); + RETURN_FALSE; + } +} + +static PHP_METHOD(swoole_thread, getPriority) { + struct sched_param param; + int policy; + if (pthread_getschedparam(pthread_self(), &policy, ¶m) != 0) { + php_swoole_error(E_WARNING, "pthread_getschedparam() failed"); + RETURN_FALSE; + } + + array_init(return_value); + add_assoc_long_ex(return_value, ZEND_STRL("policy"), policy); + add_assoc_long_ex(return_value, ZEND_STRL("priority"), param.sched_priority); +} + +static PHP_METHOD(swoole_thread, getNativeId) { + RETURN_LONG((zend_long) swoole_thread_get_native_id()); +} + +void php_swoole_thread_rinit() { + if (tsrm_is_main_thread()) { + if (SG(request_info).path_translated) { + request_info.path_translated = strdup(SG(request_info).path_translated); + } + // Return reference + zval *global_argv = zend_hash_find_ind(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV)); + if (global_argv) { + request_info.argv_serialized = php_swoole_serialize(global_argv); + request_info.argc = SG(request_info).argc; + } + } +} + +void php_swoole_thread_rshutdown() { + zval_dtor(&thread_argv); + if (!tsrm_is_main_thread()) { + return; + } + if (sw_active_thread_count() > 1) { + swoole_warning("Fatal Error: %zu active threads are running, cannot exit safely.", sw_active_thread_count()); + exit(200); + } + if (request_info.path_translated) { + free((void *) request_info.path_translated); + request_info.path_translated = nullptr; + } + if (request_info.argv_serialized) { + zend_string_release(request_info.argv_serialized); + request_info.argv_serialized = nullptr; + } +} + +static void thread_register_stdio_file_handles(bool no_close) { + php_stream *s_in, *s_out, *s_err; + php_stream_context *sc_in = nullptr, *sc_out = nullptr, *sc_err = nullptr; + zend_constant ic, oc, ec; + + s_in = php_stream_open_wrapper_ex("php://stdin", "rb", 0, NULL, sc_in); + s_out = php_stream_open_wrapper_ex("php://stdout", "wb", 0, NULL, sc_out); + s_err = php_stream_open_wrapper_ex("php://stderr", "wb", 0, NULL, sc_err); + + if (s_in == nullptr || s_out == nullptr || s_err == nullptr) { + if (s_in) php_stream_close(s_in); + if (s_out) php_stream_close(s_out); + if (s_err) php_stream_close(s_err); + return; + } + + if (no_close) { + s_in->flags |= PHP_STREAM_FLAG_NO_CLOSE; + s_out->flags |= PHP_STREAM_FLAG_NO_CLOSE; + s_err->flags |= PHP_STREAM_FLAG_NO_CLOSE; + } + + php_stream_to_zval(s_in, &ic.value); + php_stream_to_zval(s_out, &oc.value); + php_stream_to_zval(s_err, &ec.value); + + ZEND_CONSTANT_SET_FLAGS(&ic, CONST_CS, 0); + ic.name = zend_string_init_interned("STDIN", sizeof("STDIN") - 1, false); + zend_register_constant(&ic); + + ZEND_CONSTANT_SET_FLAGS(&oc, CONST_CS, 0); + oc.name = zend_string_init_interned("STDOUT", sizeof("STDOUT") - 1, false); + zend_register_constant(&oc); + + ZEND_CONSTANT_SET_FLAGS(&ec, CONST_CS, 0); + ec.name = zend_string_init_interned("STDERR", sizeof("STDERR") - 1, false); + zend_register_constant(&ec); +} + +void php_swoole_thread_start(std::shared_ptr thread, zend_string *file, ZendArray *argv) { + thread_num.fetch_add(1); + thread->enter(); + ts_resource(0); +#if defined(COMPILE_DL_SWOOLE) && defined(ZTS) + ZEND_TSRMLS_CACHE_UPDATE(); +#endif + zend_file_handle file_handle{}; + zval global_argc, global_argv; + + PG(expose_php) = false; + PG(auto_globals_jit) = true; + PG(enable_dl) = false; + + swoole_thread_init(false); + + if (php_request_startup() != SUCCESS) { + EG(exit_status) = 1; + goto _startup_error; + } + + PG(during_request_startup) = false; + SG(sapi_started) = false; + SG(headers_sent) = 1; + SG(request_info).no_headers = true; + SG(request_info).path_translated = request_info.path_translated; + SG(request_info).argc = request_info.argc; + +#if PHP_VERSION_ID >= 80500 + refresh_memory_manager(); +#endif + + zend_stream_init_filename(&file_handle, ZSTR_VAL(file)); + file_handle.primary_script = true; + + zend_first_try { + thread_bailout = EG(bailout); + if (request_info.argv_serialized) { + php_swoole_unserialize(request_info.argv_serialized, &global_argv); + ZVAL_LONG(&global_argc, request_info.argc); + zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV), &global_argv); + zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGC), &global_argc); + } + if (argv) { + argv->to_array(&thread_argv); + argv->del_ref(); + } + thread_register_stdio_file_handles(true); + php_execute_script(&file_handle); + } + zend_end_try(); + + zend_destroy_file_handle(&file_handle); + + php_request_shutdown(nullptr); + file_handle.filename = nullptr; + +_startup_error: + zend_string_release(file); + thread->exit(EG(exit_status)); + ts_free_thread(); + swoole_thread_clean(false); + thread_num.fetch_sub(1); +} + +size_t sw_active_thread_count() { + return thread_num.load(); +} + +void php_swoole_thread_bailout() { + if (thread_bailout) { + EG(bailout) = thread_bailout; + } + zend_bailout(); +} + +int php_swoole_thread_stream_cast(zval *zstream) { + php_stream *stream; + int sockfd; + int cast_flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL; + if ((php_stream_from_zval_no_verify(stream, zstream))) { + if (php_stream_cast(stream, cast_flags, (void **) &sockfd, 1) == SUCCESS && sockfd >= 0) { + return dup(sockfd); + } + } + return -1; +} + +int php_swoole_thread_co_socket_cast(zval *zvalue, swSocketType *type) { + SocketImpl *socket = php_swoole_get_socket(zvalue); + if (!socket) { + return -1; + } + int sockfd = socket->get_fd(); + if (sockfd < 0) { + return -1; + } + int newfd = dup(sockfd); + if (newfd < 0) { + return -1; + } + *type = socket->get_type(); + return newfd; +} + +void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd) { + std::string path = "php://fd/" + std::to_string(sockfd); + // The file descriptor will be duplicated once here + php_stream *stream = php_stream_open_wrapper_ex(path.c_str(), "", 0, NULL, NULL); + if (stream) { + php_stream_to_zval(stream, return_value); + } else { + object_init_ex(return_value, swoole_thread_error_ce); + zend::object_set(return_value, ZEND_STRL("code"), errno); + } +} + +void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type) { + int newfd = dup(sockfd); + if (newfd < 0) { + _error: + object_init_ex(return_value, swoole_thread_error_ce); + zend::object_set(return_value, ZEND_STRL("code"), errno); + return; + } + zend_object *sockobj = php_swoole_create_socket_from_fd(newfd, type); + if (sockobj) { + ZVAL_OBJ(return_value, sockobj); + } else { + goto _error; + } +} + +#ifdef SWOOLE_SOCKETS_SUPPORT +void php_swoole_thread_php_socket_create(zval *return_value, zend_long sockfd) { + int newfd = dup(sockfd); + if (newfd < 0) { + _error: + object_init_ex(return_value, swoole_thread_error_ce); + zend::object_set(return_value, ZEND_STRL("code"), errno); + return; + } + object_init_ex(return_value, socket_ce); + auto retsock = Z_SOCKET_P(return_value); + if (!socket_import_file_descriptor(newfd, retsock)) { + goto _error; + } +} +#endif + +static PHP_METHOD(swoole_thread, getInfo) { + array_init(return_value); + add_assoc_bool(return_value, "is_main_thread", tsrm_is_main_thread()); + add_assoc_bool(return_value, "is_shutdown", tsrm_is_shutdown()); + add_assoc_long(return_value, "thread_num", thread_num.load()); +} + +static PHP_METHOD(swoole_thread, activeCount) { + RETURN_LONG(thread_num.load()); +} + +static PHP_METHOD(swoole_thread, yield) { + std::this_thread::yield(); +} + +#define CAST_OBJ_TO_RESOURCE(_name, _type) \ + else if (instanceof_function(Z_OBJCE_P(zvalue), swoole_thread_##_name##_ce)) { \ + value.resource = php_swoole_thread_##_name##_cast(zvalue); \ + value.resource->add_ref(); \ + type = _type; \ + break; \ + } + +void ArrayItem::store(zval *zvalue) { + type = Z_TYPE_P(zvalue); + switch (type) { + case IS_LONG: + value.lval = zval_get_long(zvalue); + break; + case IS_DOUBLE: + value.dval = zval_get_double(zvalue); + break; + case IS_STRING: { + value.str = zend_string_init(Z_STRVAL_P(zvalue), Z_STRLEN_P(zvalue), true); + break; + } + case IS_TRUE: + case IS_FALSE: + case IS_NULL: + break; + case IS_RESOURCE: { + value.socket.fd = php_swoole_thread_stream_cast(zvalue); + type = IS_STREAM_SOCKET; + if (value.socket.fd == -1) { + zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno); + } + break; + } + case IS_ARRAY: { + type = zend_array_is_list(Z_ARRVAL_P(zvalue)) ? IS_ARRAYLIST : IS_MAP; + value.resource = ZendArray::from(Z_ARRVAL_P(zvalue)); + break; + } + case IS_OBJECT: { + if (sw_zval_is_co_socket(zvalue)) { + value.socket.fd = php_swoole_thread_co_socket_cast(zvalue, &value.socket.type); + type = IS_CO_SOCKET; + if (value.socket.fd == -1) { + zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno); + } + break; + } +#ifdef SWOOLE_SOCKETS_SUPPORT + else if (sw_zval_is_php_socket(zvalue)) { + php_socket *php_sock = SW_Z_SOCKET_P(zvalue); + if (php_sock->bsd_socket == -1) { + zend_throw_exception(swoole_exception_ce, "invalid socket fd", EBADF); + break; + } + value.socket.fd = dup(php_sock->bsd_socket); + if (value.socket.fd == -1) { + zend_throw_exception(swoole_exception_ce, "failed to dup socket fd", errno); + } + type = IS_PHP_SOCKET; + break; + } +#endif + CAST_OBJ_TO_RESOURCE(arraylist, IS_ARRAYLIST) + CAST_OBJ_TO_RESOURCE(map, IS_MAP) + CAST_OBJ_TO_RESOURCE(queue, IS_QUEUE) + CAST_OBJ_TO_RESOURCE(lock, IS_LOCK) + CAST_OBJ_TO_RESOURCE(atomic, IS_ATOMIC) + CAST_OBJ_TO_RESOURCE(atomic_long, IS_ATOMIC_LONG) + CAST_OBJ_TO_RESOURCE(barrier, IS_BARRIER) + } + /* no break */ + default: { + auto _serialized_object = php_swoole_serialize(zvalue); + if (!_serialized_object) { + type = IS_UNDEF; + break; + } else { + type = IS_SERIALIZED_OBJECT; + value.serialized_object = _serialized_object; + } + break; + } + } +} + +bool ArrayItem::equals(const zval *zvalue) const { + if (Z_TYPE_P(zvalue) != type) { + return false; + } + switch (type) { + case IS_LONG: + return Z_LVAL_P(zvalue) == value.lval; + case IS_DOUBLE: + return Z_DVAL_P(zvalue) == value.dval; + case IS_TRUE: + case IS_FALSE: + case IS_NULL: + return true; + case IS_STRING: + return zend_string_equals(value.str, Z_STR_P(zvalue)); + default: + return false; + } +} + +#define TYPE_PAIR(t1, t2) (((t1) << 4) | (t2)) +#define ITEM_TYPE(item) (item->type) +#define ITEM_LVAL(item) (item->value.lval) +#define ITEM_DVAL(item) (item->value.dval) +#define ITEM_STR(item) (item->value.str) + +static int compare_long_to_string(zend_long lval, const zend_string *str) /* {{{ */ +{ + zend_long str_lval; + double str_dval; + zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, false); + + if (type == IS_LONG) { + return lval > str_lval ? 1 : lval < str_lval ? -1 : 0; + } + + if (type == IS_DOUBLE) { + double diff = (double) lval - str_dval; + return ZEND_NORMALIZE_BOOL(diff); + } + + zend_string *lval_as_str = zend_long_to_str(lval); + int cmp_result = zend_binary_strcmp(ZSTR_VAL(lval_as_str), ZSTR_LEN(lval_as_str), ZSTR_VAL(str), ZSTR_LEN(str)); + zend_string_release(lval_as_str); + return ZEND_NORMALIZE_BOOL(cmp_result); +} +/* }}} */ + +static int compare_double_to_string(double dval, const zend_string *str) /* {{{ */ +{ + zend_long str_lval; + double str_dval; + zend_uchar type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &str_lval, &str_dval, false); + + if (type == IS_LONG) { + double diff = dval - (double) str_lval; + return ZEND_NORMALIZE_BOOL(diff); + } + + if (type == IS_DOUBLE) { + if (dval == str_dval) { + return 0; + } + return ZEND_NORMALIZE_BOOL(dval - str_dval); + } + + zend_string *dval_as_str = zend_double_to_str(dval); + int cmp_result = zend_binary_strcmp(ZSTR_VAL(dval_as_str), ZSTR_LEN(dval_as_str), ZSTR_VAL(str), ZSTR_LEN(str)); + zend_string_release(dval_as_str); + return ZEND_NORMALIZE_BOOL(cmp_result); +} +/* }}} */ + +int ArrayItem::compare(Bucket *a, Bucket *b) { + const ArrayItem *op1 = static_cast(Z_PTR(a->val)); + const ArrayItem *op2 = static_cast(Z_PTR(b->val)); + + switch (TYPE_PAIR(ITEM_TYPE(op1), ITEM_TYPE(op2))) { + case TYPE_PAIR(IS_LONG, IS_LONG): + return ITEM_LVAL(op1) > ITEM_LVAL(op2) ? 1 : (ITEM_LVAL(op1) < ITEM_LVAL(op2) ? -1 : 0); + + case TYPE_PAIR(IS_DOUBLE, IS_LONG): + return ZEND_NORMALIZE_BOOL(ITEM_DVAL(op1) - (double) ITEM_LVAL(op2)); + + case TYPE_PAIR(IS_LONG, IS_DOUBLE): + return ZEND_NORMALIZE_BOOL((double) ITEM_LVAL(op1) - ITEM_DVAL(op2)); + + case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): + if (ITEM_DVAL(op1) == ITEM_DVAL(op2)) { + return 0; + } else { + return ZEND_NORMALIZE_BOOL(ITEM_DVAL(op1) - ITEM_DVAL(op2)); + } + + case TYPE_PAIR(IS_NULL, IS_NULL): + case TYPE_PAIR(IS_NULL, IS_FALSE): + case TYPE_PAIR(IS_FALSE, IS_NULL): + case TYPE_PAIR(IS_FALSE, IS_FALSE): + case TYPE_PAIR(IS_TRUE, IS_TRUE): + return 0; + + case TYPE_PAIR(IS_NULL, IS_TRUE): + return -1; + + case TYPE_PAIR(IS_TRUE, IS_NULL): + return 1; + + case TYPE_PAIR(IS_STRING, IS_STRING): + if (ITEM_STR(op1) == ITEM_STR(op2)) { + return 0; + } + return zendi_smart_strcmp(ITEM_STR(op1), ITEM_STR(op2)); + + case TYPE_PAIR(IS_NULL, IS_STRING): + return Z_STRLEN_P(op2) == 0 ? 0 : -1; + + case TYPE_PAIR(IS_STRING, IS_NULL): + return Z_STRLEN_P(op1) == 0 ? 0 : 1; + + case TYPE_PAIR(IS_LONG, IS_STRING): + return compare_long_to_string(ITEM_LVAL(op1), ITEM_STR(op2)); + + case TYPE_PAIR(IS_STRING, IS_LONG): + return -compare_long_to_string(ITEM_LVAL(op2), ITEM_STR(op1)); + + case TYPE_PAIR(IS_DOUBLE, IS_STRING): + if (zend_isnan(ITEM_DVAL(op1))) { + return 1; + } + return compare_double_to_string(ITEM_DVAL(op1), ITEM_STR(op2)); + + case TYPE_PAIR(IS_STRING, IS_DOUBLE): + if (zend_isnan(ITEM_DVAL(op2))) { + return 1; + } + return -compare_double_to_string(ITEM_DVAL(op2), ITEM_STR(op1)); + + case TYPE_PAIR(IS_OBJECT, IS_NULL): + return 1; + + case TYPE_PAIR(IS_NULL, IS_OBJECT): + return -1; + + default: + zend_throw_error(nullptr, "Unsupported operand types"); + return 1; + } +} + +void ArrayItem::fetch(zval *return_value) const { + switch (type) { + case IS_LONG: + RETVAL_LONG(value.lval); + break; + case IS_DOUBLE: + RETVAL_DOUBLE(value.dval); + break; + case IS_TRUE: + RETVAL_TRUE; + break; + case IS_FALSE: + RETVAL_FALSE; + break; + case IS_STRING: + RETVAL_NEW_STR(zend_string_init(ZSTR_VAL(value.str), ZSTR_LEN(value.str), 0)); + break; + case IS_ARRAYLIST: + value.resource->add_ref(); + php_swoole_thread_arraylist_create(return_value, value.resource); + break; + case IS_QUEUE: + value.resource->add_ref(); + php_swoole_thread_queue_create(return_value, value.resource); + break; + case IS_LOCK: + value.resource->add_ref(); + php_swoole_thread_lock_create(return_value, value.resource); + break; + case IS_MAP: + value.resource->add_ref(); + php_swoole_thread_map_create(return_value, value.resource); + break; + case IS_BARRIER: + value.resource->add_ref(); + php_swoole_thread_barrier_create(return_value, value.resource); + break; + case IS_ATOMIC: + value.resource->add_ref(); + php_swoole_thread_atomic_create(return_value, value.resource); + break; + case IS_ATOMIC_LONG: + value.resource->add_ref(); + php_swoole_thread_atomic_long_create(return_value, value.resource); + break; + case IS_STREAM_SOCKET: + php_swoole_thread_stream_create(return_value, value.socket.fd); + break; + case IS_CO_SOCKET: + php_swoole_thread_co_socket_create(return_value, value.socket.fd, value.socket.type); + break; +#ifdef SWOOLE_SOCKETS_SUPPORT + case IS_PHP_SOCKET: + php_swoole_thread_php_socket_create(return_value, value.socket.fd); + break; +#endif + case IS_SERIALIZED_OBJECT: + php_swoole_unserialize(value.serialized_object, return_value); + break; + default: + break; + } +} + +void ArrayItem::release() { + if (type == IS_STRING) { + zend_string_release(value.str); + value.str = nullptr; + } else if (type == IS_STREAM_SOCKET || type == IS_CO_SOCKET || type == IS_PHP_SOCKET) { + close(value.socket.fd); + value.socket.fd = -1; + } else if (type == IS_SERIALIZED_OBJECT) { + zend_string_release(value.serialized_object); + value.serialized_object = nullptr; + } else if (type >= IS_ARRAYLIST && type <= IS_ATOMIC_LONG) { + value.resource->del_ref(); + value.resource = nullptr; + } +} + +#define INIT_DECR_VALUE(v) \ + zval rvalue = *v; \ + if (Z_TYPE_P(v) == IS_DOUBLE) { \ + rvalue.value.dval = -rvalue.value.dval; \ + } else { \ + ZVAL_LONG(&rvalue, -zval_get_long(v)); \ + } + +void ZendArray::incr_update(ArrayItem *item, zval *zvalue, zval *return_value) { + if (item->type == IS_DOUBLE) { + item->value.dval += zval_get_double(zvalue); + RETVAL_DOUBLE(item->value.dval); + } else { + item->value.lval += zval_get_long(zvalue); + RETVAL_LONG(item->value.lval); + } +} + +ArrayItem *ZendArray::incr_create(zval *zvalue, zval *return_value) { + zval rvalue = *zvalue; + if (Z_TYPE_P(zvalue) == IS_DOUBLE) { + RETVAL_DOUBLE(rvalue.value.dval); + } else { + ZVAL_LONG(&rvalue, zval_get_long(zvalue)); + RETVAL_LONG(rvalue.value.lval); + } + return new ArrayItem(&rvalue); +} + +void ZendArray::strkey_incr(zval *zkey, zval *zvalue, zval *return_value) { + zend::String skey(zkey); + + lock_.lock(); + auto *item = static_cast(zend_hash_find_ptr(&ht, skey.get())); + if (item) { + incr_update(item, zvalue, return_value); + } else { + item = incr_create(zvalue, return_value); + item->setKey(skey); + zend_hash_update_ptr(&ht, item->key, item); + } + lock_.unlock(); +} + +void ZendArray::intkey_incr(zend_long index, zval *zvalue, zval *return_value) { + lock_.lock(); + auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); + if (item) { + incr_update(item, zvalue, return_value); + } else { + item = incr_create(zvalue, return_value); + item = new ArrayItem(zvalue); + zend_hash_index_update_ptr(&ht, index, item); + } + lock_.unlock(); +} + +void ZendArray::strkey_decr(zval *zkey, zval *zvalue, zval *return_value) { + INIT_DECR_VALUE(zvalue); + strkey_incr(zkey, &rvalue, return_value); +} + +void ZendArray::intkey_decr(zend_long index, zval *zvalue, zval *return_value) { + INIT_DECR_VALUE(zvalue); + intkey_incr(index, &rvalue, return_value); +} + +void ZendArray::strkey_add(zval *zkey, zval *zvalue, zval *return_value) { + zend::String skey(zkey); + lock_.lock(); + if (strkey_exists(skey)) { + RETVAL_FALSE; + } else { + add(skey, zvalue); + RETVAL_TRUE; + } + lock_.unlock(); +} + +void ZendArray::intkey_add(zend_long index, zval *zvalue, zval *return_value) { + lock_.lock(); + if (intkey_exists(index)) { + RETVAL_FALSE; + } else { + add(index, zvalue); + RETVAL_TRUE; + } + lock_.unlock(); +} + +void ZendArray::strkey_update(zval *zkey, zval *zvalue, zval *return_value) { + zend::String skey(zkey); + lock_.lock(); + if (!strkey_exists(skey)) { + RETVAL_FALSE; + } else { + auto item = new ArrayItem(zvalue); + item->setKey(skey); + zend_hash_update_ptr(&ht, item->key, item); + RETVAL_TRUE; + } + lock_.unlock(); +} + +void ZendArray::intkey_update(zend_long index, zval *zvalue, zval *return_value) { + lock_.lock(); + if (!intkey_exists(index)) { + RETVAL_FALSE; + } else { + auto item = new ArrayItem(zvalue); + zend_hash_index_update_ptr(&ht, index, item); + RETVAL_TRUE; + } + lock_.unlock(); +} + +bool ZendArray::index_offsetGet(zend_long index, zval *return_value) { + bool out_of_range = true; + lock_.lock_rd(); + if (index_exists(index)) { + out_of_range = false; + auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); + if (item) { + item->fetch(return_value); + } + } + lock_.unlock(); + return !out_of_range; +} + +bool ZendArray::index_offsetSet(zend_long index, zval *zvalue) { + auto item = new ArrayItem(zvalue); + bool success = true; + lock_.lock(); + if (index > zend_hash_num_elements(&ht)) { + success = false; + delete item; + } else if (index == -1 || index == zend_hash_num_elements(&ht)) { + zend_hash_next_index_insert_ptr(&ht, item); + } else { + zend_hash_index_update_ptr(&ht, index, item); + } + lock_.unlock(); + return success; +} + +void ZendArray::append(zval *zvalue) { + zend_hash_next_index_insert_ptr(&ht, new ArrayItem(zvalue)); +} + +bool ZendArray::index_incr(zval *zkey, zval *zvalue, zval *return_value) { + zend_long index = ZVAL_IS_NULL(zkey) ? -1 : zval_get_long(zkey); + + bool success = true; + lock_.lock(); + if (index > zend_hash_num_elements(&ht)) { + success = false; + } else if (index == -1 || index == zend_hash_num_elements(&ht)) { + auto item = incr_create(zvalue, return_value); + zend_hash_next_index_insert_ptr(&ht, item); + } else { + auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); + incr_update(item, zvalue, return_value); + } + lock_.unlock(); + return success; +} + +void ZendArray::index_offsetExists(zend_long index, zval *return_value) { + lock_.lock_rd(); + RETVAL_BOOL(index_exists(index)); + lock_.unlock(); +} + +void ZendArray::index_offsetUnset(zend_long index) { + lock_.lock(); + zend_long i = index; + zend_long n = zend_hash_num_elements(&ht); + HT_FLAGS(&ht) |= HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS; + auto item = static_cast(zend_hash_index_find_ptr(&ht, index)); + delete item; + while (i < n - 1) { + Z_PTR(ht.arPacked[i]) = Z_PTR(ht.arPacked[i + 1]); + i++; + } + ht.nNumUsed--; + ht.nNumOfElements--; + ht.nNextFreeElement--; + lock_.unlock(); +} + +bool ZendArray::index_decr(zval *zkey, zval *zvalue, zval *return_value) { + INIT_DECR_VALUE(zvalue); + return index_incr(zkey, &rvalue, return_value); +} + +void ZendArray::keys(zval *return_value) { + lock_.lock_rd(); + zend_ulong elem_count = zend_hash_num_elements(&ht); + array_init_size(return_value, elem_count); + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); + zend_ulong num_idx; + zend_string *str_idx; + zval *entry; + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + if (HT_IS_PACKED(&ht) && HT_IS_WITHOUT_HOLES(&ht)) { + /* Optimistic case: range(0..n-1) for vector-like packed array */ + zend_ulong lval = 0; + + for (; lval < elem_count; ++lval) { + ZEND_HASH_FILL_SET_LONG(lval); + ZEND_HASH_FILL_NEXT(); + } + } else { + /* Go through input array and add keys to the return array */ + ZEND_HASH_FOREACH_KEY_VAL(&ht, num_idx, str_idx, entry) { + if (str_idx) { + ZEND_HASH_FILL_SET_STR(zend_string_init(str_idx->val, str_idx->len, 0)); + } else { + ZEND_HASH_FILL_SET_LONG(num_idx); + } + ZEND_HASH_FILL_NEXT(); + } + ZEND_HASH_FOREACH_END(); + } + (void) entry; + } + ZEND_HASH_FILL_END(); + lock_.unlock(); +} + +void ZendArray::values(zval *return_value) { + lock_.lock_rd(); + zend_ulong elem_count = zend_hash_num_elements(&ht); + array_init_size(return_value, elem_count); + void *tmp; + ZEND_HASH_FOREACH_PTR(&ht, tmp) { + zval value; + auto item = static_cast(tmp); + item->fetch(&value); + zend_hash_next_index_insert_new(Z_ARR_P(return_value), &value); + } + ZEND_HASH_FOREACH_END(); + lock_.unlock(); +} + +void ZendArray::to_array(zval *return_value) { + lock_.lock_rd(); + zend_ulong elem_count = zend_hash_num_elements(&ht); + array_init_size(return_value, elem_count); + zend_string *key; + zend_ulong index; + void *tmp; + ZEND_HASH_FOREACH_KEY_PTR(&ht, index, key, tmp) { + zval value; + const auto item = static_cast(tmp); + item->fetch(&value); + if (key) { + zend_hash_str_add(Z_ARR_P(return_value), ZSTR_VAL(key), ZSTR_LEN(key), &value); + } else { + zend_hash_index_add(Z_ARR_P(return_value), index, &value); + } + } + ZEND_HASH_FOREACH_END(); + lock_.unlock(); +} + +void ZendArray::find(const zval *search, zval *return_value) { + lock_.lock_rd(); + zend_string *key; + zend_ulong index; + void *tmp; + ZEND_HASH_FOREACH_KEY_PTR(&ht, index, key, tmp) { + const auto item = static_cast(tmp); + if (item->equals(search)) { + if (key) { + RETVAL_STRINGL(ZSTR_VAL(key), ZSTR_LEN(key)); + } else { + RETVAL_LONG(index); + } + break; + } + } + ZEND_HASH_FOREACH_END(); + lock_.unlock(); +} + +void ZendArray::sort(bool renumber) { + lock_.lock(); + zend_hash_sort(&ht, ArrayItem::compare, renumber); + lock_.unlock(); +} + +ZendArray *ZendArray::from(zend_array *src) { + zend_string *key; + zend_ulong index; + zval *tmp; + auto *result = new ZendArray(); + ZEND_HASH_FOREACH_KEY_VAL(src, index, key, tmp) { + ZVAL_DEREF(tmp); + if (key) { + result->add(key, tmp); + } else { + result->add(index, tmp); + } + } + ZEND_HASH_FOREACH_END(); + return result; +} + +#endif diff --git a/ext-src/swoole_thread_arraylist.cc b/ext-src/swoole_thread_arraylist.cc new file mode 100644 index 0000000000..0eacd33361 --- /dev/null +++ b/ext-src/swoole_thread_arraylist.cc @@ -0,0 +1,241 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" + +#ifdef SW_THREAD +#include "php_swoole_thread.h" + +SW_EXTERN_C_BEGIN +#include "stubs/php_swoole_thread_arraylist_arginfo.h" +SW_EXTERN_C_END + +zend_class_entry *swoole_thread_arraylist_ce; +static zend_object_handlers swoole_thread_arraylist_handlers; + +struct ThreadArrayListObject { + ZendArray *list; + zend_object std; +}; + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_arraylist, __construct); +static PHP_METHOD(swoole_thread_arraylist, offsetGet); +static PHP_METHOD(swoole_thread_arraylist, offsetExists); +static PHP_METHOD(swoole_thread_arraylist, offsetSet); +static PHP_METHOD(swoole_thread_arraylist, offsetUnset); +static PHP_METHOD(swoole_thread_arraylist, find); +static PHP_METHOD(swoole_thread_arraylist, count); +static PHP_METHOD(swoole_thread_arraylist, incr); +static PHP_METHOD(swoole_thread_arraylist, decr); +static PHP_METHOD(swoole_thread_arraylist, clean); +static PHP_METHOD(swoole_thread_arraylist, toArray); +static PHP_METHOD(swoole_thread_arraylist, sort); +SW_EXTERN_C_END + +static sw_inline ThreadArrayListObject *arraylist_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - + swoole_thread_arraylist_handlers.offset); +} + +static void arraylist_free_object(zend_object *object) { + auto ao = arraylist_fetch_object(object); + if (ao->list) { + ao->list->del_ref(); + ao->list = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *arraylist_create_object(zend_class_entry *ce) { + auto ao = static_cast(zend_object_alloc(sizeof(ThreadArrayListObject), ce)); + zend_object_std_init(&ao->std, ce); + object_properties_init(&ao->std, ce); + ao->std.handlers = &swoole_thread_arraylist_handlers; + return &ao->std; +} + +static ThreadArrayListObject *arraylist_fetch_object_check(const zval *zobject) { + auto ao = arraylist_fetch_object(Z_OBJ_P(zobject)); + if (!ao->list) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); + } + return ao; +} + +ThreadResource *php_swoole_thread_arraylist_cast(const zval *zobject) { + return arraylist_fetch_object_check(zobject)->list; +} + +void php_swoole_thread_arraylist_create(zval *return_value, ThreadResource *resource) { + auto obj = arraylist_create_object(swoole_thread_arraylist_ce); + auto ao = arraylist_fetch_object(obj); + ao->list = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +// clang-format off +static const zend_function_entry swoole_thread_arraylist_methods[] = { + PHP_ME(swoole_thread_arraylist, __construct, arginfo_class_Swoole_Thread_ArrayList___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, offsetGet, arginfo_class_Swoole_Thread_ArrayList_offsetGet, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, offsetExists, arginfo_class_Swoole_Thread_ArrayList_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, offsetSet, arginfo_class_Swoole_Thread_ArrayList_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, offsetUnset, arginfo_class_Swoole_Thread_ArrayList_offsetUnset, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, find, arginfo_class_Swoole_Thread_ArrayList_find, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, incr, arginfo_class_Swoole_Thread_ArrayList_incr, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, decr, arginfo_class_Swoole_Thread_ArrayList_decr, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, clean, arginfo_class_Swoole_Thread_ArrayList_clean, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, count, arginfo_class_Swoole_Thread_ArrayList_count, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, toArray, arginfo_class_Swoole_Thread_ArrayList_toArray, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_arraylist, sort, arginfo_class_Swoole_Thread_ArrayList_sort, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_arraylist_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_arraylist, "Swoole\\Thread\\ArrayList", nullptr, swoole_thread_arraylist_methods); + swoole_thread_arraylist_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_arraylist, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_arraylist, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_thread_arraylist, arraylist_create_object, arraylist_free_object, ThreadArrayListObject, std); + + zend_class_implements(swoole_thread_arraylist_ce, 2, zend_ce_arrayaccess, zend_ce_countable); + zend_declare_property_long(swoole_thread_arraylist_ce, ZEND_STRL("id"), 0, ZEND_ACC_PUBLIC | ZEND_ACC_READONLY); +} + +static PHP_METHOD(swoole_thread_arraylist, __construct) { + zend_array *array = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT_OR_NULL(array) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (ao->list != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + return; + } + + if (array) { + if (!zend_array_is_list(array)) { + zend_throw_error(nullptr, "the parameter $array must be an array of type list"); + return; + } + ao->list = ZendArray::from(array); + } else { + ao->list = new ZendArray(); + } +} + +static PHP_METHOD(swoole_thread_arraylist, offsetGet) { + zend_long index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object_check(ZEND_THIS); + if (!ao->list->index_offsetGet(index, return_value)) { + zend_throw_exception(swoole_exception_ce, "out of range", -1); + } +} + +static PHP_METHOD(swoole_thread_arraylist, offsetExists) { + zend_long index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->index_offsetExists(index, return_value); +} + +static PHP_METHOD(swoole_thread_arraylist, offsetSet) { + zval *zkey; + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zkey) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object_check(ZEND_THIS); + zend_long index = ZVAL_IS_NULL(zkey) ? -1 : zval_get_long(zkey); + if (!ao->list->index_offsetSet(index, zvalue)) { + zend_throw_exception(swoole_exception_ce, "out of range", -1); + } +} + +static PHP_METHOD(swoole_thread_arraylist, incr) { + INIT_ARRAY_INCR_PARAMS + auto ao = arraylist_fetch_object_check(ZEND_THIS); + if (!ao->list->index_incr(zkey, zvalue, return_value)) { + zend_throw_exception(swoole_exception_ce, "out of range", -1); + } +} + +static PHP_METHOD(swoole_thread_arraylist, decr) { + INIT_ARRAY_INCR_PARAMS + auto ao = arraylist_fetch_object_check(ZEND_THIS); + if (!ao->list->index_decr(zkey, zvalue, return_value)) { + zend_throw_exception(swoole_exception_ce, "out of range", -1); + } +} + +static PHP_METHOD(swoole_thread_arraylist, offsetUnset) { + zend_long index; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(index) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->index_offsetUnset(index); +} + +static PHP_METHOD(swoole_thread_arraylist, find) { + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->find(zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_arraylist, count) { + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->count(return_value); +} + +static PHP_METHOD(swoole_thread_arraylist, clean) { + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->clean(); +} + +static PHP_METHOD(swoole_thread_arraylist, toArray) { + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->to_array(return_value); +} + +static PHP_METHOD(swoole_thread_arraylist, sort) { + auto ao = arraylist_fetch_object_check(ZEND_THIS); + ao->list->sort(true); +} +#endif diff --git a/ext-src/swoole_thread_atomic.cc b/ext-src/swoole_thread_atomic.cc new file mode 100644 index 0000000000..ce6fafd9bc --- /dev/null +++ b/ext-src/swoole_thread_atomic.cc @@ -0,0 +1,356 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" +#include "php_swoole_thread.h" + +#ifdef SW_THREAD + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_thread_atomic_arginfo.h" +END_EXTERN_C() + +zend_class_entry *swoole_thread_atomic_ce; +static zend_object_handlers swoole_thread_atomic_handlers; + +zend_class_entry *swoole_thread_atomic_long_ce; +static zend_object_handlers swoole_thread_atomic_long_handlers; + +struct AtomicResource : ThreadResource { + sw_atomic_t value; + + explicit AtomicResource(zend_long _value) { + value = _value; + } + + ~AtomicResource() override = default; +}; + +struct AtomicObject { + AtomicResource *atomic; + zend_object std; +}; + +static sw_inline AtomicObject *atomic_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_atomic_handlers.offset); +} + +static sw_atomic_t *atomic_get_ptr(const zval *zobject) { + return &atomic_fetch_object(Z_OBJ_P(zobject))->atomic->value; +} + +static void atomic_free_object(zend_object *object) { + AtomicObject *o = atomic_fetch_object(object); + if (o->atomic) { + o->atomic->del_ref(); + o->atomic = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *atomic_create_object(zend_class_entry *ce) { + const auto atomic = static_cast(zend_object_alloc(sizeof(AtomicObject), ce)); + zend_object_std_init(&atomic->std, ce); + object_properties_init(&atomic->std, ce); + atomic->std.handlers = &swoole_thread_atomic_handlers; + + return &atomic->std; +} + +struct AtomicLongResource : ThreadResource { + sw_atomic_long_t value; + + explicit AtomicLongResource(zend_long _value) { + value = _value; + } + + ~AtomicLongResource() override = default; +}; + +struct AtomicLongObject { + AtomicLongResource *atomic; + zend_object std; +}; + +static sw_inline AtomicLongObject *atomic_long_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - + swoole_thread_atomic_long_handlers.offset); +} + +static sw_atomic_long_t *atomic_long_get_ptr(const zval *zobject) { + return &atomic_long_fetch_object(Z_OBJ_P(zobject))->atomic->value; +} + +static void atomic_long_free_object(zend_object *object) { + AtomicLongObject *o = atomic_long_fetch_object(object); + if (o->atomic) { + o->atomic->del_ref(); + o->atomic = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *atomic_long_create_object(zend_class_entry *ce) { + auto atomic_long = static_cast(zend_object_alloc(sizeof(AtomicLongObject), ce)); + zend_object_std_init(&atomic_long->std, ce); + object_properties_init(&atomic_long->std, ce); + atomic_long->std.handlers = &swoole_thread_atomic_long_handlers; + return &atomic_long->std; +} + +ThreadResource *php_swoole_thread_atomic_cast(const zval *zobject) { + return atomic_fetch_object(Z_OBJ_P(zobject))->atomic; +} + +ThreadResource *php_swoole_thread_atomic_long_cast(const zval *zobject) { + return atomic_long_fetch_object(Z_OBJ_P(zobject))->atomic; +} + +void php_swoole_thread_atomic_create(zval *return_value, ThreadResource *resource) { + auto obj = atomic_create_object(swoole_thread_atomic_ce); + auto ao = atomic_fetch_object(obj); + ao->atomic = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +void php_swoole_thread_atomic_long_create(zval *return_value, ThreadResource *resource) { + auto obj = atomic_long_create_object(swoole_thread_atomic_long_ce); + auto ao = atomic_long_fetch_object(obj); + ao->atomic = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_atomic, __construct); +static PHP_METHOD(swoole_thread_atomic, add); +static PHP_METHOD(swoole_thread_atomic, sub); +static PHP_METHOD(swoole_thread_atomic, get); +static PHP_METHOD(swoole_thread_atomic, set); +static PHP_METHOD(swoole_thread_atomic, cmpset); +static PHP_METHOD(swoole_thread_atomic, wait); +static PHP_METHOD(swoole_thread_atomic, wakeup); + +static PHP_METHOD(swoole_thread_atomic_long, __construct); +static PHP_METHOD(swoole_thread_atomic_long, add); +static PHP_METHOD(swoole_thread_atomic_long, sub); +static PHP_METHOD(swoole_thread_atomic_long, get); +static PHP_METHOD(swoole_thread_atomic_long, set); +static PHP_METHOD(swoole_thread_atomic_long, cmpset); +SW_EXTERN_C_END + +// clang-format off +static const zend_function_entry swoole_thread_atomic_methods[] = +{ + PHP_ME(swoole_thread_atomic, __construct, arginfo_class_Swoole_Thread_Atomic___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, add, arginfo_class_Swoole_Thread_Atomic_add, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, sub, arginfo_class_Swoole_Thread_Atomic_sub, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, get, arginfo_class_Swoole_Thread_Atomic_get, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, set, arginfo_class_Swoole_Thread_Atomic_set, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, wait, arginfo_class_Swoole_Thread_Atomic_wait, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, wakeup, arginfo_class_Swoole_Thread_Atomic_wakeup, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic, cmpset, arginfo_class_Swoole_Thread_Atomic_cmpset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; + +static const zend_function_entry swoole_thread_atomic_long_methods[] = +{ + PHP_ME(swoole_thread_atomic_long, __construct, arginfo_class_Swoole_Thread_Atomic_Long___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic_long, add, arginfo_class_Swoole_Thread_Atomic_Long_add, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic_long, sub, arginfo_class_Swoole_Thread_Atomic_Long_sub, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic_long, get, arginfo_class_Swoole_Thread_Atomic_Long_get, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic_long, set, arginfo_class_Swoole_Thread_Atomic_Long_set, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_atomic_long, cmpset, arginfo_class_Swoole_Thread_Atomic_Long_cmpset, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_atomic_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_atomic, "Swoole\\Thread\\Atomic", nullptr, swoole_thread_atomic_methods); + swoole_thread_atomic_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_atomic, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_atomic, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_atomic, atomic_create_object, atomic_free_object, AtomicObject, std); + + SW_INIT_CLASS_ENTRY( + swoole_thread_atomic_long, "Swoole\\Thread\\Atomic\\Long", nullptr, swoole_thread_atomic_long_methods); + swoole_thread_atomic_long_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_atomic_long, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_atomic_long, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_thread_atomic_long, atomic_long_create_object, atomic_long_free_object, AtomicLongObject, std); +} + +PHP_METHOD(swoole_thread_atomic, __construct) { + auto o = atomic_fetch_object(Z_OBJ_P(ZEND_THIS)); + zend_long value = 0; + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (o->atomic) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + return; + } + o->atomic = new AtomicResource(value); +} + +PHP_METHOD(swoole_thread_atomic, add) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + zend_long add_value = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(add_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_LONG(sw_atomic_add_fetch(atomic, (uint32_t) add_value)); +} + +PHP_METHOD(swoole_thread_atomic, sub) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + zend_long sub_value = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(sub_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_LONG(sw_atomic_sub_fetch(atomic, (uint32_t) sub_value)); +} + +PHP_METHOD(swoole_thread_atomic, get) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + RETURN_LONG(*atomic); +} + +PHP_METHOD(swoole_thread_atomic, set) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + zend_long set_value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(set_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + *atomic = (uint32_t) set_value; +} + +PHP_METHOD(swoole_thread_atomic, cmpset) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + zend_long cmp_value, set_value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(cmp_value) + Z_PARAM_LONG(set_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_BOOL(sw_atomic_cmp_set(atomic, (sw_atomic_t) cmp_value, (sw_atomic_t) set_value)); +} + +PHP_METHOD(swoole_thread_atomic, wait) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + double timeout = 1.0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + SW_CHECK_RETURN(sw_atomic_futex_wait(atomic, timeout)); +} + +PHP_METHOD(swoole_thread_atomic, wakeup) { + sw_atomic_t *atomic = atomic_get_ptr(ZEND_THIS); + zend_long n = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(n) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + SW_CHECK_RETURN(sw_atomic_futex_wakeup(atomic, (int) n)); +} + +PHP_METHOD(swoole_thread_atomic_long, __construct) { + auto o = atomic_long_fetch_object(Z_OBJ_P(ZEND_THIS)); + zend_long value = 0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (o->atomic) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + RETURN_FALSE; + } + o->atomic = new AtomicLongResource(value); +} + +PHP_METHOD(swoole_thread_atomic_long, add) { + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); + zend_long add_value = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(add_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_LONG(sw_atomic_add_fetch(atomic_long, (sw_atomic_long_t) add_value)); +} + +PHP_METHOD(swoole_thread_atomic_long, sub) { + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); + zend_long sub_value = 1; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(sub_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_LONG(sw_atomic_sub_fetch(atomic_long, (sw_atomic_long_t) sub_value)); +} + +PHP_METHOD(swoole_thread_atomic_long, get) { + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); + RETURN_LONG(*atomic_long); +} + +PHP_METHOD(swoole_thread_atomic_long, set) { + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); + zend_long set_value; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(set_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + *atomic_long = (sw_atomic_long_t) set_value; +} + +PHP_METHOD(swoole_thread_atomic_long, cmpset) { + sw_atomic_long_t *atomic_long = atomic_long_get_ptr(ZEND_THIS); + zend_long cmp_value, set_value; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_LONG(cmp_value) + Z_PARAM_LONG(set_value) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + RETURN_BOOL(sw_atomic_cmp_set(atomic_long, (sw_atomic_long_t) cmp_value, (sw_atomic_long_t) set_value)); +} + +#endif diff --git a/ext-src/swoole_thread_barrier.cc b/ext-src/swoole_thread_barrier.cc new file mode 100644 index 0000000000..712d8aa092 --- /dev/null +++ b/ext-src/swoole_thread_barrier.cc @@ -0,0 +1,144 @@ +/* + +----------------------------------------------------------------------+ + | 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_private.h" +#include "php_swoole_thread.h" +#include "swoole_lock.h" + +#ifdef SW_THREAD + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_thread_barrier_arginfo.h" +END_EXTERN_C() + +using swoole::Barrier; + +zend_class_entry *swoole_thread_barrier_ce; +static zend_object_handlers swoole_thread_barrier_handlers; + +struct BarrierResource : ThreadResource { + Barrier barrier_; + explicit BarrierResource(int count) { + barrier_.init(false, count); + } + void wait() { + barrier_.wait(); + } + ~BarrierResource() override { + barrier_.destroy(); + } +}; + +struct BarrierObject { + BarrierResource *barrier; + zend_object std; +}; + +static sw_inline BarrierObject *barrier_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_barrier_handlers.offset); +} + +static BarrierResource *barrier_get_ptr(const zval *zobject) { + return barrier_fetch_object(Z_OBJ_P(zobject))->barrier; +} + +static BarrierResource *barrier_get_and_check_ptr(const zval *zobject) { + BarrierResource *barrier = barrier_get_ptr(zobject); + if (UNEXPECTED(!barrier)) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, "must call constructor first"); + } + return barrier; +} + +static void barrier_free_object(zend_object *object) { + auto bo = barrier_fetch_object(object); + if (bo->barrier) { + bo->barrier->del_ref(); + bo->barrier = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *barrier_create_object(zend_class_entry *ce) { + auto bo = static_cast(zend_object_alloc(sizeof(BarrierObject), ce)); + zend_object_std_init(&bo->std, ce); + object_properties_init(&bo->std, ce); + bo->std.handlers = &swoole_thread_barrier_handlers; + return &bo->std; +} + +ThreadResource *php_swoole_thread_barrier_cast(const zval *zobject) { + return barrier_fetch_object(Z_OBJ_P(zobject))->barrier; +} + +void php_swoole_thread_barrier_create(zval *return_value, ThreadResource *resource) { + auto obj = barrier_create_object(swoole_thread_barrier_ce); + auto bo = barrier_fetch_object(obj); + bo->barrier = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_barrier, __construct); +static PHP_METHOD(swoole_thread_barrier, wait); +SW_EXTERN_C_END + +// clang-format off +static constexpr zend_function_entry swoole_thread_barrier_methods[] = +{ + PHP_ME(swoole_thread_barrier, __construct, arginfo_class_Swoole_Thread_Barrier___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_barrier, wait, arginfo_class_Swoole_Thread_Barrier_wait, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_barrier_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_barrier, "Swoole\\Thread\\Barrier", nullptr, swoole_thread_barrier_methods); + swoole_thread_barrier_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_barrier, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_barrier, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_barrier, barrier_create_object, barrier_free_object, BarrierObject, std); +} + +static PHP_METHOD(swoole_thread_barrier, __construct) { + auto bo = barrier_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (bo->barrier != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + return; + } + + zend_long count; + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(count) + ZEND_PARSE_PARAMETERS_END(); + + if (count < 2) { + zend_throw_exception( + swoole_exception_ce, "The parameter $count must be greater than 1", SW_ERROR_INVALID_PARAMS); + return; + } + + bo->barrier = new BarrierResource(count); +} + +static PHP_METHOD(swoole_thread_barrier, wait) { + BarrierResource *barrier = barrier_get_and_check_ptr(ZEND_THIS); + if (barrier) { + barrier->wait(); + } +} + +#endif diff --git a/ext-src/swoole_thread_lock.cc b/ext-src/swoole_thread_lock.cc new file mode 100644 index 0000000000..eb323fa34b --- /dev/null +++ b/ext-src/swoole_thread_lock.cc @@ -0,0 +1,184 @@ +/* + +----------------------------------------------------------------------+ + | 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_private.h" +#include "php_swoole_thread.h" +#include "swoole_lock.h" +#include "swoole_timer.h" + +#ifdef SW_THREAD + +BEGIN_EXTERN_C() +#include "stubs/php_swoole_thread_lock_arginfo.h" +END_EXTERN_C() + +using swoole::Lock; +using swoole::Mutex; +#ifdef HAVE_SPINLOCK +using swoole::SpinLock; +#endif +#ifdef HAVE_RWLOCK +using swoole::RWLock; +#endif + +zend_class_entry *swoole_thread_lock_ce; +static zend_object_handlers swoole_thread_lock_handlers; + +struct LockResource : public ThreadResource { + Lock *lock_; + LockResource(int type) : ThreadResource() { + switch (type) { +#ifdef HAVE_SPINLOCK + case Lock::SPIN_LOCK: + lock_ = new SpinLock(false); + break; +#endif +#ifdef HAVE_RWLOCK + case Lock::RW_LOCK: + lock_ = new RWLock(false); + break; +#endif + case Lock::MUTEX: + default: + lock_ = new Mutex(false); + break; + } + } + ~LockResource() override { + delete lock_; + } +}; + +struct ThreadLockObject { + LockResource *lock; + zend_object std; +}; + +static sw_inline ThreadLockObject *thread_lock_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_lock_handlers.offset); +} + +static Lock *thread_lock_get_ptr(const zval *zobject) { + return thread_lock_fetch_object(Z_OBJ_P(zobject))->lock->lock_; +} + +static Lock *thread_lock_get_and_check_ptr(const zval *zobject) { + Lock *lock = thread_lock_get_ptr(zobject); + if (!lock) { + php_swoole_fatal_error(E_ERROR, "must call constructor first"); + } + return lock; +} + +static void thread_lock_free_object(zend_object *object) { + ThreadLockObject *o = thread_lock_fetch_object(object); + if (o->lock) { + o->lock->del_ref(); + o->lock = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *thread_lock_create_object(zend_class_entry *ce) { + auto lock = static_cast(zend_object_alloc(sizeof(ThreadLockObject), ce)); + zend_object_std_init(&lock->std, ce); + object_properties_init(&lock->std, ce); + lock->std.handlers = &swoole_thread_lock_handlers; + return &lock->std; +} + +ThreadResource *php_swoole_thread_lock_cast(const zval *zobject) { + return thread_lock_fetch_object(Z_OBJ_P(zobject))->lock; +} + +void php_swoole_thread_lock_create(zval *return_value, ThreadResource *resource) { + auto obj = thread_lock_create_object(swoole_thread_lock_ce); + auto lo = thread_lock_fetch_object(obj); + lo->lock = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_lock, __construct); +static PHP_METHOD(swoole_thread_lock, lock); +static PHP_METHOD(swoole_thread_lock, unlock); +SW_EXTERN_C_END + +// clang-format off +static constexpr zend_function_entry swoole_thread_lock_methods[] = +{ + PHP_ME(swoole_thread_lock, __construct, arginfo_class_Swoole_Thread_Lock___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_lock, lock, arginfo_class_Swoole_Thread_Lock_lock, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_lock, unlock, arginfo_class_Swoole_Thread_Lock_unlock, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_lock_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_lock, "Swoole\\Thread\\Lock", nullptr, swoole_thread_lock_methods); + swoole_thread_lock_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_lock, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_lock, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT( + swoole_thread_lock, thread_lock_create_object, thread_lock_free_object, ThreadLockObject, std); + + zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("MUTEX"), Lock::MUTEX); +#ifdef HAVE_RWLOCK + zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("RWLOCK"), Lock::RW_LOCK); +#endif +#ifdef HAVE_SPINLOCK + zend_declare_class_constant_long(swoole_thread_lock_ce, ZEND_STRL("SPINLOCK"), Lock::SPIN_LOCK); +#endif + zend_declare_property_long(swoole_thread_lock_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); +} + +static PHP_METHOD(swoole_thread_lock, __construct) { + auto o = thread_lock_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (o->lock != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + RETURN_FALSE; + } + + zend_long type = Lock::MUTEX; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(type) + ZEND_PARSE_PARAMETERS_END(); + + o->lock = new LockResource(type); +} + +static PHP_METHOD(swoole_thread_lock, lock) { + zend_long operation = LOCK_EX; + double timeout = -1; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(operation) + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + Lock *lock = thread_lock_get_and_check_ptr(ZEND_THIS); + SW_LOCK_CHECK_RETURN(lock->lock(operation, swoole::sec2msec(timeout))); +} + +static PHP_METHOD(swoole_thread_lock, unlock) { + Lock *lock = thread_lock_get_and_check_ptr(ZEND_THIS); + SW_LOCK_CHECK_RETURN(lock->unlock()); +} + +#endif diff --git a/ext-src/swoole_thread_map.cc b/ext-src/swoole_thread_map.cc new file mode 100644 index 0000000000..2de07e8f31 --- /dev/null +++ b/ext-src/swoole_thread_map.cc @@ -0,0 +1,312 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" + +#ifdef SW_THREAD +#include "php_swoole_thread.h" + +SW_EXTERN_C_BEGIN +#include "stubs/php_swoole_thread_map_arginfo.h" +SW_EXTERN_C_END + +zend_class_entry *swoole_thread_map_ce; +static zend_object_handlers swoole_thread_map_handlers; + +struct ThreadMapObject { + ZendArray *map; + zend_object std; +}; + +static sw_inline ThreadMapObject *map_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_map_handlers.offset); +} + +static void map_free_object(zend_object *object) { + auto mo = map_fetch_object(object); + if (mo->map) { + mo->map->del_ref(); + mo->map = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *map_create_object(zend_class_entry *ce) { + auto mo = static_cast(zend_object_alloc(sizeof(ThreadMapObject), ce)); + zend_object_std_init(&mo->std, ce); + object_properties_init(&mo->std, ce); + mo->std.handlers = &swoole_thread_map_handlers; + return &mo->std; +} + +static ThreadMapObject *map_fetch_object_check(const zval *zobject) { + ThreadMapObject *map = map_fetch_object(Z_OBJ_P(zobject)); + if (!map->map) { + php_swoole_fatal_error(E_ERROR, "must call constructor first"); + } + return map; +} + +ThreadResource *php_swoole_thread_map_cast(const zval *zobject) { + return map_fetch_object(Z_OBJ_P(zobject))->map; +} + +void php_swoole_thread_map_create(zval *return_value, ThreadResource *resource) { + auto obj = map_create_object(swoole_thread_map_ce); + auto mo = map_fetch_object(obj); + mo->map = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_map, __construct); +static PHP_METHOD(swoole_thread_map, offsetGet); +static PHP_METHOD(swoole_thread_map, offsetExists); +static PHP_METHOD(swoole_thread_map, offsetSet); +static PHP_METHOD(swoole_thread_map, offsetUnset); +static PHP_METHOD(swoole_thread_map, find); +static PHP_METHOD(swoole_thread_map, count); +static PHP_METHOD(swoole_thread_map, keys); +static PHP_METHOD(swoole_thread_map, values); +static PHP_METHOD(swoole_thread_map, incr); +static PHP_METHOD(swoole_thread_map, decr); +static PHP_METHOD(swoole_thread_map, add); +static PHP_METHOD(swoole_thread_map, update); +static PHP_METHOD(swoole_thread_map, clean); +static PHP_METHOD(swoole_thread_map, toArray); +static PHP_METHOD(swoole_thread_map, sort); +SW_EXTERN_C_END + +// clang-format off +static const zend_function_entry swoole_thread_map_methods[] = { + PHP_ME(swoole_thread_map, __construct, arginfo_class_Swoole_Thread_Map___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, offsetGet, arginfo_class_Swoole_Thread_Map_offsetGet, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, offsetExists, arginfo_class_Swoole_Thread_Map_offsetExists, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, offsetSet, arginfo_class_Swoole_Thread_Map_offsetSet, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, offsetUnset, arginfo_class_Swoole_Thread_Map_offsetUnset, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, find, arginfo_class_Swoole_Thread_Map_find, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, count, arginfo_class_Swoole_Thread_Map_count, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, incr, arginfo_class_Swoole_Thread_Map_incr, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, decr, arginfo_class_Swoole_Thread_Map_decr, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, add, arginfo_class_Swoole_Thread_Map_add, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, update, arginfo_class_Swoole_Thread_Map_update, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, clean, arginfo_class_Swoole_Thread_Map_clean, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, keys, arginfo_class_Swoole_Thread_Map_keys, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, values, arginfo_class_Swoole_Thread_Map_values, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, toArray, arginfo_class_Swoole_Thread_Map_toArray, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_map, sort, arginfo_class_Swoole_Thread_Map_sort, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_map_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_map, "Swoole\\Thread\\Map", nullptr, swoole_thread_map_methods); + swoole_thread_map_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_map, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_map, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_map, map_create_object, map_free_object, ThreadMapObject, std); + + zend_class_implements(swoole_thread_map_ce, 2, zend_ce_arrayaccess, zend_ce_countable); +} + +static PHP_METHOD(swoole_thread_map, __construct) { + zend_array *array = nullptr; + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT_OR_NULL(array) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (mo->map != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + return; + } + + if (array) { + mo->map = ZendArray::from(array); + } else { + mo->map = new ZendArray(); + } +} + +static int handle_array_key(const zval *key, zend_ulong *idx) { + switch (Z_TYPE_P(key)) { + case IS_STRING: + return _zend_handle_numeric_str(Z_STRVAL_P(key), Z_STRLEN_P(key), idx) ? IS_LONG : IS_STRING; + case IS_LONG: + *idx = Z_LVAL_P(key); + return IS_LONG; + case IS_NULL: + return IS_NULL; + case IS_DOUBLE: + *idx = zend_dval_to_lval_safe(Z_DVAL_P(key)); + return IS_LONG; + case IS_FALSE: + *idx = 0; + return IS_LONG; + case IS_TRUE: + *idx = 1; + return IS_LONG; + case IS_RESOURCE: + zend_use_resource_as_offset(key); + *idx = Z_RES_HANDLE_P(key); + return IS_LONG; + default: + zend_argument_type_error(1, "Illegal offset type"); + return IS_UNDEF; + } +} + +#define ZEND_ARRAY_CALL_METHOD(array, method, zkey, ...) \ + zend_ulong idx; \ + int type_of_key = handle_array_key(zkey, &idx); \ + if (type_of_key == IS_LONG) { \ + array->intkey_##method(idx, ##__VA_ARGS__); \ + } else if (type_of_key == IS_STRING) { \ + array->strkey_##method(zkey, ##__VA_ARGS__); \ + } else if (type_of_key == IS_NULL) { \ + zval empty_str; \ + ZVAL_EMPTY_STRING(&empty_str); \ + array->strkey_##method(&empty_str, ##__VA_ARGS__); \ + } else { \ + zend_type_error("Illegal offset type"); \ + } + +static PHP_METHOD(swoole_thread_map, offsetGet) { + zval *zkey; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zkey) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, offsetGet, zkey, return_value); +} + +static PHP_METHOD(swoole_thread_map, offsetExists) { + zval *zkey; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zkey) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, offsetExists, zkey, return_value); +} + +static PHP_METHOD(swoole_thread_map, offsetSet) { + zval *zkey; + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zkey) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, offsetSet, zkey, zvalue); +} + +static PHP_METHOD(swoole_thread_map, incr) { + INIT_ARRAY_INCR_PARAMS + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, incr, zkey, zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_map, decr) { + INIT_ARRAY_INCR_PARAMS + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, decr, zkey, zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_map, add) { + zval *zkey; + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zkey) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, add, zkey, zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_map, update) { + zval *zkey; + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_ZVAL(zkey) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, update, zkey, zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_map, offsetUnset) { + zval *zkey; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zkey) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + ZEND_ARRAY_CALL_METHOD(mo->map, offsetUnset, zkey); +} + +static PHP_METHOD(swoole_thread_map, find) { + zval *zvalue; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->find(zvalue, return_value); +} + +static PHP_METHOD(swoole_thread_map, count) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->count(return_value); +} + +static PHP_METHOD(swoole_thread_map, keys) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->keys(return_value); +} + +static PHP_METHOD(swoole_thread_map, values) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->values(return_value); +} + +static PHP_METHOD(swoole_thread_map, toArray) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->to_array(return_value); +} + +static PHP_METHOD(swoole_thread_map, clean) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->clean(); +} + +static PHP_METHOD(swoole_thread_map, sort) { + auto mo = map_fetch_object_check(ZEND_THIS); + mo->map->sort(false); +} +#endif diff --git a/ext-src/swoole_thread_queue.cc b/ext-src/swoole_thread_queue.cc new file mode 100644 index 0000000000..394d449eb8 --- /dev/null +++ b/ext-src/swoole_thread_queue.cc @@ -0,0 +1,249 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" + +#ifdef SW_THREAD +#include "php_swoole_thread.h" +#include "stubs/php_swoole_thread_queue_arginfo.h" + +#include +#include + +zend_class_entry *swoole_thread_queue_ce; +static zend_object_handlers swoole_thread_queue_handlers; + +struct Queue : ThreadResource { + std::queue queue; + std::mutex lock_; + std::condition_variable cv_; + + enum { + NOTIFY_NONE = 0, + NOTIFY_ONE = 1, + NOTIFY_ALL = 2, + }; + + Queue() = default; + + ~Queue() override { + clean(); + } + + void push(zval *zvalue) { + auto item = new ArrayItem(zvalue); + lock_.lock(); + queue.push(item); + lock_.unlock(); + } + + void pop(zval *return_value) { + ArrayItem *item = nullptr; + lock_.lock(); + if (!queue.empty()) { + item = queue.front(); + queue.pop(); + } + lock_.unlock(); + if (item) { + item->fetch(return_value); + delete item; + } + } + + void push_notify(zval *zvalue, bool notify_all) { + push(zvalue); + if (notify_all) { + cv_.notify_all(); + } else { + cv_.notify_one(); + } + } + + void pop_wait(zval *return_value, double timeout) { + ArrayItem *item = nullptr; + std::unique_lock _lock(lock_); + + if (timeout > 0) { + cv_.wait_for(_lock, std::chrono::duration(timeout), [this] { return !queue.empty(); }); + } else { + cv_.wait(_lock, [this] { return !queue.empty(); }); + } + + if (!queue.empty()) { + item = queue.front(); + queue.pop(); + } else { + // All threads have been awakened, + // but the data has already been acquired by other thread, returning NULL. + RETVAL_NULL(); + swoole_set_last_error(SW_ERROR_NO_PAYLOAD); + } + + if (item) { + item->fetch(return_value); + delete item; + } + } + + void count(zval *return_value) { + lock_.lock(); + RETVAL_LONG(queue.size()); + lock_.unlock(); + } + + void clean() { + lock_.lock(); + while (!queue.empty()) { + const ArrayItem *item = queue.front(); + delete item; + queue.pop(); + } + lock_.unlock(); + } +}; + +struct ThreadQueueObject { + Queue *queue; + zend_object std; +}; + +static sw_inline ThreadQueueObject *queue_fetch_object(zend_object *obj) { + return reinterpret_cast(reinterpret_cast(obj) - swoole_thread_queue_handlers.offset); +} + +static void queue_free_object(zend_object *object) { + ThreadQueueObject *qo = queue_fetch_object(object); + if (qo->queue) { + qo->queue->del_ref(); + qo->queue = nullptr; + } + zend_object_std_dtor(object); +} + +static zend_object *queue_create_object(zend_class_entry *ce) { + const auto qo = static_cast(zend_object_alloc(sizeof(ThreadQueueObject), ce)); + zend_object_std_init(&qo->std, ce); + object_properties_init(&qo->std, ce); + qo->std.handlers = &swoole_thread_queue_handlers; + return &qo->std; +} + +ThreadQueueObject *queue_fetch_object_check(const zval *zobject) { + ThreadQueueObject *qo = queue_fetch_object(Z_OBJ_P(zobject)); + if (!qo->queue) { + php_swoole_fatal_error(E_ERROR, "must call constructor first"); + } + return qo; +} + +ThreadResource *php_swoole_thread_queue_cast(const zval *zobject) { + return queue_fetch_object(Z_OBJ_P(zobject))->queue; +} + +void php_swoole_thread_queue_create(zval *return_value, ThreadResource *resource) { + auto obj = queue_create_object(swoole_thread_queue_ce); + auto qo = queue_fetch_object(obj); + qo->queue = dynamic_cast(resource); + ZVAL_OBJ(return_value, obj); +} + +SW_EXTERN_C_BEGIN +static PHP_METHOD(swoole_thread_queue, __construct); +static PHP_METHOD(swoole_thread_queue, push); +static PHP_METHOD(swoole_thread_queue, pop); +static PHP_METHOD(swoole_thread_queue, count); +static PHP_METHOD(swoole_thread_queue, clean); +SW_EXTERN_C_END + +// clang-format off +static const zend_function_entry swoole_thread_queue_methods[] = { + PHP_ME(swoole_thread_queue, __construct, arginfo_class_Swoole_Thread_Queue___construct, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_queue, push, arginfo_class_Swoole_Thread_Queue_push, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_queue, pop, arginfo_class_Swoole_Thread_Queue_pop, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_queue, clean, arginfo_class_Swoole_Thread_Queue_clean, ZEND_ACC_PUBLIC) + PHP_ME(swoole_thread_queue, count, arginfo_class_Swoole_Thread_Queue_count, ZEND_ACC_PUBLIC) + PHP_FE_END +}; +// clang-format on + +void php_swoole_thread_queue_minit(int module_number) { + SW_INIT_CLASS_ENTRY(swoole_thread_queue, "Swoole\\Thread\\Queue", nullptr, swoole_thread_queue_methods); + swoole_thread_queue_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NOT_SERIALIZABLE; + SW_SET_CLASS_CLONEABLE(swoole_thread_queue, sw_zend_class_clone_deny); + SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_thread_queue, sw_zend_class_unset_property_deny); + SW_SET_CLASS_CUSTOM_OBJECT(swoole_thread_queue, queue_create_object, queue_free_object, ThreadQueueObject, std); + + zend_class_implements(swoole_thread_queue_ce, 1, zend_ce_countable); + + zend_declare_class_constant_long(swoole_thread_queue_ce, ZEND_STRL("NOTIFY_ONE"), Queue::NOTIFY_ONE); + zend_declare_class_constant_long(swoole_thread_queue_ce, ZEND_STRL("NOTIFY_ALL"), Queue::NOTIFY_ALL); +} + +static PHP_METHOD(swoole_thread_queue, __construct) { + auto qo = queue_fetch_object(Z_OBJ_P(ZEND_THIS)); + if (qo->queue != nullptr) { + zend_throw_error(nullptr, "Constructor of %s can only be called once", SW_Z_OBJCE_NAME_VAL_P(ZEND_THIS)); + return; + } + qo->queue = new Queue(); +} + +static PHP_METHOD(swoole_thread_queue, push) { + zval *zvalue; + zend_long notify_which = 0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_ZVAL(zvalue) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(notify_which) + ZEND_PARSE_PARAMETERS_END(); + + auto qo = queue_fetch_object_check(ZEND_THIS); + if (notify_which > 0) { + qo->queue->push_notify(zvalue, notify_which == Queue::NOTIFY_ALL); + } else { + qo->queue->push(zvalue); + } +} + +static PHP_METHOD(swoole_thread_queue, pop) { + double timeout = 0; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END(); + + auto qo = queue_fetch_object_check(ZEND_THIS); + if (timeout == 0) { + qo->queue->pop(return_value); + } else { + qo->queue->pop_wait(return_value, timeout); + } +} + +static PHP_METHOD(swoole_thread_queue, count) { + auto qo = queue_fetch_object_check(ZEND_THIS); + qo->queue->count(return_value); +} + +static PHP_METHOD(swoole_thread_queue, clean) { + auto qo = queue_fetch_object_check(ZEND_THIS); + qo->queue->clean(); +} + +#endif diff --git a/ext-src/swoole_timer.cc b/ext-src/swoole_timer.cc index df83a713a5..e4989b19b0 100644 --- a/ext-src/swoole_timer.cc +++ b/ext-src/swoole_timer.cc @@ -17,6 +17,7 @@ */ #include "php_swoole_cxx.h" +#include "php_swoole_process.h" #include "swoole_server.h" @@ -36,7 +37,6 @@ static zend_object_handlers swoole_timer_handlers; static zend_class_entry *swoole_timer_iterator_ce; SW_EXTERN_C_BEGIN -static PHP_FUNCTION(swoole_timer_set); static PHP_FUNCTION(swoole_timer_after); static PHP_FUNCTION(swoole_timer_tick); static PHP_FUNCTION(swoole_timer_exists); @@ -50,7 +50,6 @@ SW_EXTERN_C_END // clang-format off static const zend_function_entry swoole_timer_methods[] = { - ZEND_FENTRY(set, ZEND_FN(swoole_timer_set), arginfo_swoole_timer_set, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED) ZEND_FENTRY(tick, ZEND_FN(swoole_timer_tick), arginfo_swoole_timer_tick, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(after, ZEND_FN(swoole_timer_after), arginfo_swoole_timer_after, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) ZEND_FENTRY(exists, ZEND_FN(swoole_timer_exists), arginfo_swoole_timer_exists, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) @@ -69,15 +68,35 @@ void php_swoole_timer_minit(int module_number) { SW_INIT_CLASS_ENTRY_BASE(swoole_timer_iterator, "Swoole\\Timer\\Iterator", nullptr, nullptr, spl_ce_ArrayIterator); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "set", CG(function_table), "swoole_timer_set"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "after", CG(function_table), "swoole_timer_after"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "tick", CG(function_table), "swoole_timer_tick"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "exists", CG(function_table), "swoole_timer_exists"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "info", CG(function_table), "swoole_timer_info"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "stats", CG(function_table), "swoole_timer_stats"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "list", CG(function_table), "swoole_timer_list"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "clear", CG(function_table), "swoole_timer_clear"); - SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, "clearAll", CG(function_table), "swoole_timer_clear_all"); + SW_FUNCTION_ALIAS( + &swoole_timer_ce->function_table, "after", CG(function_table), "swoole_timer_after", arginfo_swoole_timer_tick); + SW_FUNCTION_ALIAS( + &swoole_timer_ce->function_table, "tick", CG(function_table), "swoole_timer_tick", arginfo_swoole_timer_after); + SW_FUNCTION_ALIAS( + &swoole_timer_ce->function_table, "info", CG(function_table), "swoole_timer_info", arginfo_swoole_timer_info); + SW_FUNCTION_ALIAS( + &swoole_timer_ce->function_table, "list", CG(function_table), "swoole_timer_list", arginfo_swoole_timer_list); + + SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, + "exists", + CG(function_table), + "swoole_timer_exists", + arginfo_swoole_timer_exists); + SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, + "stats", + CG(function_table), + "swoole_timer_stats", + arginfo_swoole_timer_stats); + SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, + "clear", + CG(function_table), + "swoole_timer_clear", + arginfo_swoole_timer_clear); + SW_FUNCTION_ALIAS(&swoole_timer_ce->function_table, + "clearAll", + CG(function_table), + "swoole_timer_clear_all", + arginfo_swoole_timer_clear_all); SW_REGISTER_LONG_CONSTANT("SWOOLE_TIMER_MIN_MS", SW_TIMER_MIN_MS); SW_REGISTER_DOUBLE_CONSTANT("SWOOLE_TIMER_MIN_SEC", SW_TIMER_MIN_SEC); @@ -85,8 +104,12 @@ void php_swoole_timer_minit(int module_number) { SW_REGISTER_DOUBLE_CONSTANT("SWOOLE_TIMER_MAX_SEC", SW_TIMER_MAX_SEC); } +void php_swoole_timer_rshutdown() { + php_swoole_timer_clear_all(); +} + static void timer_dtor(TimerNode *tnode) { - Function *fci = (Function *) tnode->data; + auto *fci = static_cast(tnode->data); sw_zend_fci_params_discard(&fci->fci); sw_zend_fci_cache_discard(&fci->fci_cache); efree(fci); @@ -97,13 +120,13 @@ bool php_swoole_timer_clear(TimerNode *tnode) { } bool php_swoole_timer_clear_all() { - if (UNEXPECTED(!SwooleTG.timer)) { + if (UNEXPECTED(!swoole_timer_is_available())) { return false; } - size_t num = SwooleTG.timer->count(), index = 0; - TimerNode **list = (TimerNode **) emalloc(num * sizeof(TimerNode *)); - for (auto &kv : SwooleTG.timer->get_map()) { + size_t num = sw_timer()->count(), index = 0; + auto **list = static_cast(emalloc(num * sizeof(TimerNode *))); + for (auto &kv : sw_timer()->get_map()) { TimerNode *tnode = kv.second; if (tnode->type == TimerNode::TYPE_PHP) { list[index++] = tnode; @@ -120,19 +143,28 @@ bool php_swoole_timer_clear_all() { } static void timer_callback(Timer *timer, TimerNode *tnode) { - Function *fci = (Function *) tnode->data; + auto *fci = static_cast(tnode->data); if (UNEXPECTED(!fci->call(nullptr, php_swoole_is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onTimeout handler error", ZSTR_VAL(swoole_timer_ce->name)); } - if (!tnode->interval || tnode->removed) { - timer_dtor(tnode); +} + +static bool timer_if_use_reactor() { + auto server = sw_server(); + if (server) { + return server->is_user_worker() || (server->is_task_worker() && server->task_enable_coroutine); + } + auto process_pool = sw_process_pool(); + if (process_pool) { + return !process_pool->is_master(); } + return true; } static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) { zend_long ms; - Function *fci = (Function *) ecalloc(1, sizeof(Function)); + auto *fci = static_cast(ecalloc(1, sizeof(Function))); TimerNode *tnode; ZEND_PARSE_PARAMETERS_START(2, -1) @@ -148,13 +180,15 @@ static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) { RETURN_FALSE; } - // no server || user worker || task process with async mode - if (!sw_server() || sw_server()->is_user_worker() || - (sw_server()->is_task_worker() && sw_server()->task_enable_coroutine)) { + if (UNEXPECTED(!sw_reactor() && timer_if_use_reactor())) { php_swoole_check_reactor(); } - tnode = swoole_timer_add(ms, persistent, timer_callback, fci); + /** + * In certain systems, such as macOS, zend_long is the long long type, + * and it must be explicitly converted to long. + */ + tnode = swoole_timer_add((long) ms, persistent, timer_callback, fci); if (UNEXPECTED(!tnode)) { php_swoole_fatal_error(E_WARNING, "add timer failed"); goto _failed; @@ -163,14 +197,13 @@ static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) { tnode->destructor = timer_dtor; if (persistent) { if (fci->fci.param_count > 0) { - uint32_t i; - zval *params = (zval *) ecalloc(fci->fci.param_count + 1, sizeof(zval)); - for (i = 0; i < fci->fci.param_count; i++) { + auto params = static_cast(ecalloc(fci->fci.param_count + 1, sizeof(zval))); + for (uint32_t i = 0; i < fci->fci.param_count; i++) { ZVAL_COPY(¶ms[i + 1], &fci->fci.params[i]); } fci->fci.params = params; } else { - fci->fci.params = (zval *) emalloc(sizeof(zval)); + fci->fci.params = static_cast(emalloc(sizeof(zval))); } fci->fci.param_count += 1; ZVAL_LONG(fci->fci.params, tnode->id); @@ -181,21 +214,6 @@ static void timer_add(INTERNAL_FUNCTION_PARAMETERS, bool persistent) { RETURN_LONG(tnode->id); } -static PHP_FUNCTION(swoole_timer_set) { - zval *zset = nullptr; - zval *ztmp; - - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_ARRAY(zset) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - - HashTable *vht = Z_ARRVAL_P(zset); - - if (php_swoole_array_get_value(vht, "enable_coroutine", ztmp)) { - SWOOLE_G(enable_coroutine) = zval_is_true(ztmp); - } -} - static PHP_FUNCTION(swoole_timer_after) { timer_add(INTERNAL_FUNCTION_PARAM_PASSTHRU, false); } @@ -205,48 +223,45 @@ static PHP_FUNCTION(swoole_timer_tick) { } static PHP_FUNCTION(swoole_timer_exists) { - if (UNEXPECTED(!SwooleTG.timer)) { + if (UNEXPECTED(!swoole_timer_is_available())) { RETURN_FALSE; - } else { - zend_long id; - TimerNode *tnode; + } + zend_long id; - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_LONG(id) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - tnode = swoole_timer_get(id); - RETURN_BOOL(tnode && !tnode->removed); - } + auto *tnode = swoole_timer_get(id); + RETURN_BOOL(tnode && !tnode->removed); } static PHP_FUNCTION(swoole_timer_info) { - if (UNEXPECTED(!SwooleTG.timer)) { + if (UNEXPECTED(!swoole_timer_is_available())) { RETURN_FALSE; - } else { - zend_long id; - TimerNode *tnode; + } - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_LONG(id) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + zend_long id; - tnode = swoole_timer_get(id); - if (UNEXPECTED(!tnode)) { - RETURN_NULL(); - } - array_init(return_value); - add_assoc_long(return_value, "exec_msec", tnode->exec_msec); - add_assoc_long(return_value, "exec_count", tnode->exec_count); - add_assoc_long(return_value, "interval", tnode->interval); - add_assoc_long(return_value, "round", tnode->round); - add_assoc_bool(return_value, "removed", tnode->removed); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + auto *tnode = swoole_timer_get(id); + if (UNEXPECTED(!tnode)) { + RETURN_NULL(); } + array_init(return_value); + add_assoc_long(return_value, "exec_msec", tnode->exec_msec); + add_assoc_long(return_value, "exec_count", tnode->exec_count); + add_assoc_long(return_value, "interval", tnode->interval); + add_assoc_long(return_value, "round", tnode->round); + add_assoc_bool(return_value, "removed", tnode->removed); } static PHP_FUNCTION(swoole_timer_stats) { array_init(return_value); - if (SwooleTG.timer) { + if (swoole_timer_is_available()) { add_assoc_bool(return_value, "initialized", 1); add_assoc_long(return_value, "num", SwooleTG.timer->count()); add_assoc_long(return_value, "round", SwooleTG.timer->get_round()); diff --git a/ext-src/swoole_tracer.cc b/ext-src/swoole_tracer.cc new file mode 100644 index 0000000000..cfb3ce5a5e --- /dev/null +++ b/ext-src/swoole_tracer.cc @@ -0,0 +1,758 @@ +/* + +----------------------------------------------------------------------+ + | 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_cxx.h" +#include "php_swoole_api.h" +#include "php_swoole_coroutine.h" + +#include "swoole_thread.h" + +#include +#include +#include +#include +#include +#include +#include + +using swoole::Coroutine; +using swoole::PHPContext; +using swoole::PHPCoroutine; + +BEGIN_EXTERN_C() +#include "zend_builtin_functions.h" +#include "zend_observer.h" +#include "ext/standard/php_math.h" +#include "ext/json/php_json.h" +END_EXTERN_C() + +struct Prof { + std::string fname; + std::string file_pos; + long began_at; + long cid; +}; + +struct ProfContext { + std::array profs; + uint8_t call_stack_level; +}; + +struct AllocPoint { + size_t size; + size_t count; + std::string at; +}; + +struct AllocStat { + size_t total_bytes; + size_t count; +}; + +struct BlockingDetectionSpan { + zend_long began_at; + size_t switch_count; + PHPContext::SwapCallback swap_callback; +}; + +static SW_THREAD_LOCAL struct { + std::unordered_map points; + std::unordered_set debug_points; + std::unordered_map backtraces; + std::unordered_map counters; + size_t loop; + zend_long threshold; + bool profiling; + std::unordered_map co_prof; + ProfContext main_co_prof; + std::string prof_root_path; + zval prof_events; + size_t fcall_count; + size_t return_count; + pid_t pid; +} TracerG; + +#define DEBUG 0 +#define DEBUG_LINE 16 + +constexpr int blocking_detection_func_reserve_index = 4; + +#if PHP_VERSION_ID < 80400 +#define MM_LINE_DC +#define MM_LINE_ORIG_DC +#define MM_LINE_CC +#define MM_LINE_ORIG_RELAY_CC +#else +#define MM_LINE_DC ZEND_FILE_LINE_DC +#define MM_LINE_ORIG_DC ZEND_FILE_LINE_ORIG_DC +#define MM_LINE_CC ZEND_FILE_LINE_RELAY_CC +#define MM_LINE_ORIG_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC +#endif + +static void *(*ori_malloc)(size_t MM_LINE_DC MM_LINE_ORIG_DC); +static void (*ori_free)(void *MM_LINE_DC MM_LINE_ORIG_DC); +static void *(*ori_realloc)(void *, size_t MM_LINE_DC MM_LINE_ORIG_DC); + +static void *tracer_malloc(size_t size MM_LINE_DC MM_LINE_ORIG_DC); +static void tracer_free(void *ptr MM_LINE_DC MM_LINE_ORIG_DC); +static void *tracer_realloc(void *ptr, size_t size MM_LINE_DC MM_LINE_ORIG_DC); + +static void hook_emalloc(); +static void unhook_emalloc(); + +static long tracer_get_time_us() { + return swoole::time(true); +} + +static std::string zstr_to_std_string(zend_string *zs) { + return std::string(ZSTR_VAL(zs), ZSTR_LEN(zs)); +} + +static bool str_starts_with(const std::string &str, const std::string &prefix) { + return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0; +} + +static bool str_ends_with(const std::string &str, const std::string &suffix) { + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static void debug(const char *label, void *ptr, size_t size, uint32_t lineno) { +#if DEBUG + auto iter = TrackerG.points.find(ptr); + if (iter != TrackerG.points.end() && str_ends_with(iter->second.at, ":" + std::to_string(lineno))) { + printf("[%s]\tptr=%p, size=%lu, count=%lu, lineno=%u, at=%s\n", + label, + ptr, + size, + iter->second.count, + lineno, + iter->second.at.c_str()); + if (strcmp(label, "update")) { + TrackerG.debug_points.insert(ptr); + } + } + + if (strcmp(label, "free") && TrackerG.debug_points.find(ptr) != TrackerG.debug_points.end()) { + TrackerG.debug_points.erase(ptr); + printf("free ptr=%p\n", ptr); + } +#endif +} + +static uint32_t tracer_get_executed_lineno(void) { + zend_execute_data *ex = EG(current_execute_data); + while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) { + ex = ex->prev_execute_data; + } + + if (ex && ex->opline) { + if (EG(exception) && ex->opline->opcode == ZEND_HANDLE_EXCEPTION && ex->opline->lineno == 0 && + EG(opline_before_exception)) { + return EG(opline_before_exception)->lineno; + } + return ex->opline->lineno; + } else { + return 0; + } +} + +static zend_string *tracer_get_file_and_line() { + const char *file = zend_get_executed_filename(); + uint32_t lineno = tracer_get_executed_lineno(); + char file_line_buf[1024]; + if (lineno == 0 && strcmp(file, "[no active file]") == 0) { + return nullptr; + } + size_t file_len = strlen(file); + if (UNEXPECTED(file_len + 100 > sizeof(file_line_buf))) { + char *tmp_buf = (char *) malloc(file_len + 100); + if (!tmp_buf) { + php_printf("tracker out of memory\n"); + zend_bailout(); + } + sprintf(tmp_buf, "%s:%u", file, lineno); + zend_string *ret = zend_string_init(tmp_buf, strlen(tmp_buf), 1); + free(tmp_buf); + return ret; + } else { + sprintf(file_line_buf, "%s:%u", file, lineno); + return zend_string_init(file_line_buf, strlen(file_line_buf), 1); + } +} + +static zend_string *tracer_get_backtrace() { + zval backtrace; + zend_fetch_debug_backtrace(&backtrace, 0, 0, 0); + auto backtrace_str = zend_trace_to_string(Z_ARRVAL(backtrace), false); + zval_ptr_dtor(&backtrace); + return backtrace_str; +} + +static long tracer_get_pid() { + return swoole_thread_get_native_id(); +} + +static long tracer_get_tid() { + return swoole_coroutine_get_id(); +} + +static ProfContext &tracer_get_ctx(long tid) { + if (tid == TracerG.pid) { + return TracerG.main_co_prof; + } else { + if (TracerG.co_prof.find(tid) == TracerG.co_prof.end()) { + TracerG.co_prof[tid] = {}; + } + return TracerG.co_prof[tid]; + } +} + +static zend_string *tracer_format_number(long num) { +#if PHP_VERSION_ID >= 80300 + return _php_math_number_format_long(num, 0, ".", 1, ",", 1); +#else + return _php_math_number_format((double) num, 0, '.', ','); +#endif +} + +static std::string format_bytes(uint64_t size) { + const char *units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; + int unitIndex = 0; + double adjustedSize = static_cast(size); + + while (adjustedSize >= 1024.0 && unitIndex < 8) { + adjustedSize /= 1024.0; + unitIndex++; + } + + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << adjustedSize << " " << units[unitIndex]; + return oss.str(); +} + +static void tracer_leak_clear_stat(const std::string &at) { + for (auto iter = TracerG.points.begin(); iter != TracerG.points.end(); iter++) { + if (iter->second.at == at) { + iter = TracerG.points.erase(iter); + if (iter == TracerG.points.end()) { + break; + } + } + } +} + +static void save_backtrace(const std::string &alloc_at) { + if (TracerG.backtraces.find(alloc_at) == TracerG.backtraces.end()) { + zval bt; + zend_fetch_debug_backtrace(&bt, 0, DEBUG_BACKTRACE_IGNORE_ARGS, 0); + TracerG.backtraces[alloc_at] = bt; + } +} + +static void add_point(void *ptr, size_t size, zend_string *current_file_lineno) { + std::string alloc_at = zstr_to_std_string(current_file_lineno); + TracerG.points[ptr] = { + size, + 1, + alloc_at, + }; + + auto iter = TracerG.counters.find(alloc_at); + if (iter == TracerG.counters.end()) { + TracerG.counters[alloc_at] = 1; + } else { + iter->second++; + if (iter->second >= TracerG.threshold - 1) { + save_backtrace(alloc_at); + } + } + + debug("new", ptr, size, DEBUG_LINE); +} + +static void update_point(AllocPoint &point, void *new_ptr, size_t new_size) { + TracerG.points[new_ptr] = { + new_size, + point.count + 1, + point.at, + }; + + auto counter_iter = TracerG.counters.find(point.at); + if (counter_iter != TracerG.counters.end()) { + counter_iter->second++; + if (counter_iter->second >= TracerG.threshold - 1) { + save_backtrace(point.at); + } + } + + debug("update", new_ptr, new_size, DEBUG_LINE); +} + +static void del_point(void *ptr, decltype(TracerG.points)::iterator &iter) { + debug("free", ptr, 0, DEBUG_LINE); + const auto &alloc_at = iter->second.at; + auto counter_iter = TracerG.counters.find(alloc_at); + if (counter_iter != TracerG.counters.end()) { + counter_iter->second--; + } + TracerG.points.erase(iter); +} + +static void *tracer_malloc(size_t size MM_LINE_DC MM_LINE_ORIG_DC) { + void *ptr; + if (ori_malloc) { + ptr = ori_malloc(size MM_LINE_CC MM_LINE_ORIG_RELAY_CC); + } else { + zend_mm_heap *heap = zend_mm_get_heap(); + ptr = zend_mm_alloc(heap, size); + } + + unhook_emalloc(); + + zend_string *current_file_lineno = tracer_get_file_and_line(); + if (current_file_lineno) { + add_point(ptr, size, current_file_lineno); + zend_string_release(current_file_lineno); + } + + hook_emalloc(); + + return ptr; +} + +static void *tracer_realloc(void *ptr, size_t size MM_LINE_DC MM_LINE_ORIG_DC) { + void *new_ptr; + + if (ori_realloc) { + new_ptr = ori_realloc(ptr, size MM_LINE_CC MM_LINE_ORIG_RELAY_CC); + } else { + zend_mm_heap *heap = zend_mm_get_heap(); + new_ptr = zend_mm_realloc(heap, ptr, size); + } + + unhook_emalloc(); + + zend_string *current_file_lineno = tracer_get_file_and_line(); + if (current_file_lineno) { + auto iter = TracerG.points.find(ptr); + if (iter != TracerG.points.end()) { + update_point(iter->second, new_ptr, size); + if (new_ptr != ptr) { + TracerG.points.erase(iter); + } + } else { + add_point(new_ptr, size, current_file_lineno); + } + zend_string_release(current_file_lineno); + } + + hook_emalloc(); + + return new_ptr; +} + +static void tracer_free(void *ptr MM_LINE_DC MM_LINE_ORIG_DC) { + if (ori_free) { + ori_free(ptr MM_LINE_CC MM_LINE_ORIG_RELAY_CC); + } else { + zend_mm_heap *heap = zend_mm_get_heap(); + zend_mm_free(heap, ptr); + } + + auto iter = TracerG.points.find(ptr); + if (iter != TracerG.points.end()) { + del_point(ptr, iter); + } +} + +static void hook_emalloc() { + zend_mm_heap *heap = zend_mm_get_heap(); + zend_mm_get_custom_handlers(heap, &ori_malloc, &ori_free, &ori_realloc); + zend_mm_set_custom_handlers(heap, &tracer_malloc, &tracer_free, &tracer_realloc); +} + +static void unhook_emalloc() { + zend_mm_heap *heap = zend_mm_get_heap(); + if (ori_malloc || ori_free || ori_realloc) { + zend_mm_set_custom_handlers(heap, ori_malloc, ori_free, ori_realloc); + ori_malloc = NULL; + ori_free = NULL; + ori_realloc = NULL; + } else { + *((int *) heap) = 0; + } +} + +static void profiling_begin(zend_string *root_symbol, zend_execute_data *execute_data) { + zend_function *fbc = execute_data->func; + auto type = fbc->type; + + if (!TracerG.profiling || type == ZEND_INTERNAL_FUNCTION) { + return; + } + + auto ts = tracer_get_time_us(); + zend_string *fn_name = fbc->common.function_name; + std::string fn; + std::string file_pos; + + if (fbc->common.scope) { + zend_string *class_name = fbc->common.scope->name; + fn = std::string(ZSTR_VAL(class_name)) + "::" + std::string(ZSTR_VAL(fn_name)); + } else { + fn = std::string(ZSTR_VAL(fn_name)); + } + +#if DEBUG + printf("fn=%s, level=%d \n", fn.c_str(), TracerG.call_stack_level); +#endif + + zend_string *current_file_lineno = tracer_get_file_and_line(); + if (current_file_lineno) { + file_pos = zstr_to_std_string(current_file_lineno); + if (str_starts_with(file_pos, TracerG.prof_root_path)) { + file_pos = + file_pos.substr(TracerG.prof_root_path.length(), file_pos.length() - TracerG.prof_root_path.length()); + } + zend_string_release(current_file_lineno); + } + + auto tid = tracer_get_tid(); + + Prof prof{ + fn, + file_pos, + ts, + tid, + }; + + auto &ctx = tracer_get_ctx(tid); + ctx.profs[ctx.call_stack_level++] = prof; +} + +static void profiling_clear() { + zval_ptr_dtor(&TracerG.prof_events); + ZVAL_NULL(&TracerG.prof_events); + TracerG.main_co_prof = {}; + TracerG.co_prof.clear(); + TracerG.profiling = false; +} + +static void profiling_end() { + auto tid = tracer_get_tid(); + auto &ctx = tracer_get_ctx(tid); + + assert(ctx.call_stack_level > 0); + + ctx.call_stack_level--; + + auto &prof = ctx.profs[ctx.call_stack_level]; + +#if DEBUG + printf("return level=%d, count=%zu, func=%p\n", + TracerG.call_stack_level, + array_count(&TracerG.prof_events), + prof.fname.c_str()); +#endif + + auto te = tracer_get_time_us(); + + zval event; + array_init(&event); + + std::string name; + if (prof.file_pos.length() > 0) { + name = prof.fname + " (" + prof.file_pos + ")"; + } else { + name = prof.fname; + } + + add_assoc_stringl_ex(&event, ZEND_STRL("name"), name.c_str(), name.length()); + add_assoc_string_ex(&event, ZEND_STRL("ph"), "X"); + add_assoc_string_ex(&event, ZEND_STRL("cat"), "FEE"); + add_assoc_double_ex(&event, ZEND_STRL("ts"), prof.began_at); + add_assoc_double_ex(&event, ZEND_STRL("dur"), te - prof.began_at); + add_assoc_long_ex(&event, ZEND_STRL("pid"), tracer_get_pid()); + add_assoc_long_ex(&event, ZEND_STRL("tid"), tid); + + add_next_index_zval(&TracerG.prof_events, &event); + +#if DEBUG + printf("fn=%s, begun_at=%f, dr=%f, at=%s\n", + prof.fname.c_str(), + prof.began_at, + te - prof.began_at, + prof.file_pos.c_str()); +#endif +} + +static void blocking_detection_begin(zend_execute_data *execute_data) { + auto ctx = (PHPContext *) swoole::Coroutine::get_current_task(); + if (!ctx) { + return; + } + + auto span = new BlockingDetectionSpan; + span->began_at = tracer_get_time_us(); + span->switch_count = ctx->switch_count = 0; + span->swap_callback = [](PHPContext *ctx) { ctx->switch_count++; }; + ctx->on_resume = &span->swap_callback; + ctx->on_yield = &span->swap_callback; + execute_data->func->internal_function.reserved[blocking_detection_func_reserve_index] = span; +} + +static void blocking_detection_end(zend_execute_data *execute_data) { + PHPContext *ctx = PHPCoroutine::get_context(); + if (!ctx) { + return; + } + + ctx->on_resume = nullptr; + ctx->on_yield = nullptr; + + auto fn = &execute_data->func->internal_function; + auto span = (BlockingDetectionSpan *) fn->reserved[blocking_detection_func_reserve_index]; + if (!span) { + return; + } + fn->reserved[blocking_detection_func_reserve_index] = nullptr; + + auto now = tracer_get_time_us(); + auto duration = now - span->began_at; + if (span->switch_count == ctx->switch_count && duration > SWOOLE_G(blocking_threshold)) { + auto duration_str = tracer_format_number(duration); + auto backtrace_str = tracer_get_backtrace(); + + const char *scope = nullptr; + if (execute_data->func->common.scope) { + scope = ZSTR_VAL(execute_data->func->common.scope->name); + } + + sw_printf(" >>> [Detected blocking I/O in Coroutine#%ld, internal function `%s%s%s()` blocked for %s us]\n%s", + PHPCoroutine::get_cid(), + scope ? scope : "", + scope ? "::" : "", + fn->function_name->val, + duration_str->val, + backtrace_str->val); + + zend_string_release(duration_str); + zend_string_release(backtrace_str); + } + + swoole_event_defer( + [](void *ptr) { + auto span = (BlockingDetectionSpan *) ptr; + delete span; + }, + span); +} + +static bool tracer_observer_if_enable_profile(zend_execute_data *execute_data) { + return SWOOLE_G(profile) && TracerG.profiling && execute_data->func->type != ZEND_INTERNAL_FUNCTION; +} + +static bool tracer_observer_if_enable_blocking_detection(zend_execute_data *execute_data) { + return SWOOLE_G(blocking_detection) && execute_data->func->type == ZEND_INTERNAL_FUNCTION; +} + +static void tracer_observer_begin(zend_execute_data *execute_data) { + if (tracer_observer_if_enable_profile(execute_data)) { + profiling_begin(NULL, execute_data); + } + + if (tracer_observer_if_enable_blocking_detection(execute_data)) { + blocking_detection_begin(execute_data); + } +} + +static void tracer_observer_end(zend_execute_data *execute_data, zval *return_value) { + if (tracer_observer_if_enable_profile(execute_data)) { + profiling_end(); + } + + if (tracer_observer_if_enable_blocking_detection(execute_data)) { + blocking_detection_end(execute_data); + } +} + +static zend_observer_fcall_handlers tracer_observer(zend_execute_data *execute_data) { + zend_observer_fcall_handlers empty_handlers = {nullptr, nullptr}; + + if (!execute_data->func || !execute_data->func->common.function_name) { + return empty_handlers; + } + + if (!tracer_observer_if_enable_profile(execute_data) && + !tracer_observer_if_enable_blocking_detection(execute_data)) { + return empty_handlers; + } + + return {tracer_observer_begin, tracer_observer_end}; +} + +PHP_FUNCTION(swoole_tracer_leak_detect) { + if (!SWOOLE_G(leak_detection)) { + return; + } + + zend_long threshold = 64; + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(threshold) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + std::unordered_map stats; + unhook_emalloc(); + for (auto &point : TracerG.points) { + auto iter = stats.find(point.second.at); + if (iter == stats.end()) { + stats[point.second.at] = { + point.second.size, + point.second.count, + }; + } else { + iter->second.count += point.second.count; + iter->second.total_bytes += point.second.size; + } + } + + for (auto &stat : stats) { + if (stat.second.count >= (size_t) threshold) { + php_printf("[Round#%lu] leak %s bytes, alloc %lu times at %s\n", + TracerG.loop, + format_bytes(stat.second.total_bytes).c_str(), + stat.second.count, + stat.first.c_str()); + + auto bt_iter = TracerG.backtraces.find(stat.first); + if (bt_iter != TracerG.backtraces.end()) { + zend_string *str = zend_trace_to_string(Z_ARR(bt_iter->second), false); + ZEND_WRITE(ZSTR_VAL(str), ZSTR_LEN(str)); + zend_string_release(str); + } + tracer_leak_clear_stat(stat.first); + php_printf("\n"); + } + } + + TracerG.threshold = threshold; + TracerG.loop++; + hook_emalloc(); +} + +PHP_FUNCTION(swoole_tracer_prof_begin) { + if (!SWOOLE_G(profile) || TracerG.profiling) { + RETURN_FALSE; + } + + array_init(&TracerG.prof_events); + TracerG.profiling = true; + + zval *options = NULL; /* optional array arg: for future use */ + + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(options) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + if (options) { + zval *pzval = zend_hash_str_find(Z_ARRVAL_P(options), ZEND_STRL("root_path")); + if (pzval) { + auto tmp = zval_get_string(pzval); + TracerG.prof_root_path = zstr_to_std_string(tmp); + if (TracerG.prof_root_path.at(TracerG.prof_root_path.length() - 1) != '/') { + TracerG.prof_root_path.append("/"); + } + zend_string_release(tmp); + } + } + + TracerG.pid = getpid(); + + RETURN_TRUE; +} + +PHP_FUNCTION(swoole_tracer_prof_end) { + if (!SWOOLE_G(profile) || !TracerG.profiling) { + RETURN_FALSE; + } + + zend_string *file; + ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 1) + Z_PARAM_STR(file) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + zval json; + array_init(&json); + + zend::array_set(&json, ZEND_STRL("traceEvents"), &TracerG.prof_events); + + zval metadata; + array_init(&metadata); + + zend::array_set(&metadata, ZEND_STRL("version"), "0.17.1"); + zend::array_set(&metadata, ZEND_STRL("overflow"), false); + zend::array_set(&json, ZEND_STRL("viztracer_metadata"), &metadata); + + smart_str buf = {}; + if (php_json_encode(&buf, &json, 0) == FAILURE) { + _fail: + zval_ptr_dtor(&TracerG.prof_events); + RETURN_FALSE; + } + + std::ofstream outputFile(ZSTR_VAL(file), std::ios::binary); + if (!outputFile.is_open()) { + goto _fail; + } + + outputFile.write(buf.s->val, buf.s->len); + outputFile.close(); + + zval_ptr_dtor(&metadata); + zval_ptr_dtor(&json); + + smart_str_free(&buf); + profiling_clear(); + + RETURN_TRUE; +} + +void php_swoole_tracer_minit(int module_number) { + if (SWOOLE_G(blocking_detection) || SWOOLE_G(profile)) { + zend_observer_fcall_register(tracer_observer); + SWOOLE_G(enable_fiber_mock) = true; + } +} + +void php_swoole_tracer_rinit() { + if (SWOOLE_G(leak_detection)) { + hook_emalloc(); + } +} + +void php_swoole_tracer_rshutdown() { + if (SWOOLE_G(leak_detection)) { + unhook_emalloc(); + } + + for (auto &iter : TracerG.backtraces) { + zval_ptr_dtor(&iter.second); + } + + profiling_clear(); +} diff --git a/ext-src/swoole_websocket_server.cc b/ext-src/swoole_websocket_server.cc index 81ed6f1662..ed29431410 100644 --- a/ext-src/swoole_websocket_server.cc +++ b/ext-src/swoole_websocket_server.cc @@ -15,6 +15,7 @@ */ #include "php_swoole_http_server.h" +#include "php_swoole_websocket.h" SW_EXTERN_C_BEGIN #include "ext/standard/sha1.h" @@ -25,11 +26,14 @@ SW_EXTERN_C_END using swoole::Connection; using swoole::ListenPort; +using swoole::make_string; using swoole::RecvData; using swoole::Server; using swoole::SessionId; using swoole::String; -using swoole::coroutine::Socket; +using swoole::WebSocketSettings; +using swoole::websocket::Frame; +using swoole::websocket::FrameObject; using HttpContext = swoole::http::Context; @@ -44,14 +48,13 @@ static zend_object_handlers swoole_websocket_frame_handlers; static zend_class_entry *swoole_websocket_closeframe_ce; static zend_object_handlers swoole_websocket_closeframe_handlers; -static String *swoole_websocket_buffer = nullptr; - SW_EXTERN_C_BEGIN static PHP_METHOD(swoole_websocket_server, push); static PHP_METHOD(swoole_websocket_server, isEstablished); static PHP_METHOD(swoole_websocket_server, pack); static PHP_METHOD(swoole_websocket_server, unpack); static PHP_METHOD(swoole_websocket_server, disconnect); +static PHP_METHOD(swoole_websocket_server, ping); static PHP_METHOD(swoole_websocket_frame, __toString); SW_EXTERN_C_END @@ -61,13 +64,14 @@ const zend_function_entry swoole_websocket_server_methods[] = { PHP_ME(swoole_websocket_server, push, arginfo_class_Swoole_WebSocket_Server_push, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, disconnect, arginfo_class_Swoole_WebSocket_Server_disconnect, ZEND_ACC_PUBLIC) + PHP_ME(swoole_websocket_server, ping, arginfo_class_Swoole_WebSocket_Server_ping, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, isEstablished, arginfo_class_Swoole_WebSocket_Server_isEstablished, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, pack, arginfo_class_Swoole_WebSocket_Server_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_ME(swoole_websocket_server, unpack, arginfo_class_Swoole_WebSocket_Server_unpack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) PHP_FE_END }; -const zend_function_entry swoole_websocket_frame_methods[] = +static constexpr zend_function_entry swoole_websocket_frame_methods[] = { PHP_ME(swoole_websocket_frame, __toString, arginfo_class_Swoole_WebSocket_Frame___toString, ZEND_ACC_PUBLIC) PHP_ME(swoole_websocket_server, pack, arginfo_class_Swoole_WebSocket_Frame_pack, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) @@ -76,13 +80,8 @@ const zend_function_entry swoole_websocket_frame_methods[] = }; // clang-format on -#ifdef SW_HAVE_ZLIB -static bool websocket_message_compress(String *buffer, const char *data, size_t length, int level); -static bool websocket_message_uncompress(String *buffer, const char *in, size_t in_len); -#endif - -static void php_swoole_websocket_construct_frame(zval *zframe, zend_long opcode, zval *zpayload, uint8_t flags) { - if (opcode == WebSocket::OPCODE_CLOSE) { +void WebSocket::construct_frame(zval *zframe, zend_long opcode, zval *zpayload, uint8_t flags) { + if (opcode == OPCODE_CLOSE) { const char *payload = Z_STRVAL_P(zpayload); size_t payload_length = Z_STRLEN_P(zpayload); object_init_ex(zframe, swoole_websocket_closeframe_ce); @@ -105,162 +104,131 @@ static void php_swoole_websocket_construct_frame(zval *zframe, zend_long opcode, object_init_ex(zframe, swoole_websocket_frame_ce); zend_update_property(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("data"), zpayload); } + if (flags & FLAG_RSV1) { + flags |= FLAG_COMPRESS; + } zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("opcode"), opcode); zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("flags"), flags); - /* BC */ - zend_update_property_bool( - swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("finish"), flags & WebSocket::FLAG_FIN); + zend_update_property_bool(swoole_websocket_frame_ce, SW_Z8_OBJ_P(zframe), ZEND_STRL("finish"), flags & FLAG_FIN); } -void php_swoole_websocket_frame_unpack_ex(String *data, zval *zframe, uchar uncompress) { - WebSocket::Frame frame; - zval zpayload; - uint8_t flags; - - if (data->length < sizeof(frame.header)) { - swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); - ZVAL_FALSE(zframe); - return; - } - - if (!WebSocket::decode(&frame, data)) { - swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); - ZVAL_FALSE(zframe); - return; - } - - flags = WebSocket::get_flags(&frame); -#ifdef SW_HAVE_ZLIB - if (uncompress && frame.header.RSV1) { - String *zlib_buffer = sw_tg_buffer(); - zlib_buffer->clear(); - if (!websocket_message_uncompress(zlib_buffer, frame.payload, frame.payload_length)) { - swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); - ZVAL_FALSE(zframe); - return; - } - frame.payload = zlib_buffer->str; - frame.payload_length = zlib_buffer->length; - flags ^= (WebSocket::FLAG_RSV1 | WebSocket::FLAG_COMPRESS); +bool FrameObject::uncompress(zval *zpayload, const char *data, size_t length) { +#ifndef SW_HAVE_ZLIB + swoole_warning("The compressed websocket data frame is received, the `zlib` supports is required"); + return false; +#else + String zlib_buffer(length + SW_WEBSOCKET_DEFAULT_PAYLOAD_SIZE, sw_zend_string_allocator()); + if (sw_likely(WebSocket::message_uncompress(&zlib_buffer, data, length))) { + zend::assign_zend_string_by_val(zpayload, zlib_buffer.str, zlib_buffer.length); + zlib_buffer.release(); + return true; + } else { + return false; } #endif - /* TODO: optimize memory copy */ - ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); - php_swoole_websocket_construct_frame(zframe, frame.header.OPCODE, &zpayload, flags); - zval_ptr_dtor(&zpayload); } -void php_swoole_websocket_frame_unpack(String *data, zval *zframe) { - return php_swoole_websocket_frame_unpack_ex(data, zframe, 0); -} - -static sw_inline int php_swoole_websocket_frame_pack_ex(String *buffer, - zval *zdata, - zend_long opcode, - zend_long code, - uint8_t flags, - zend_bool mask, - zend_bool allow_compress) { - char *data = nullptr; - size_t length = 0; +bool FrameObject::pack(String *buffer) { + const char *ptr = nullptr; + size_t len = 0; if (sw_unlikely(opcode > SW_WEBSOCKET_OPCODE_MAX)) { php_swoole_fatal_error(E_WARNING, "the maximum value of opcode is %d", SW_WEBSOCKET_OPCODE_MAX); - return SW_ERR; + return false; } zend::String str_zdata; - if (zdata && !ZVAL_IS_NULL(zdata)) { - str_zdata = zdata; - data = str_zdata.val(); - length = str_zdata.len(); - } - - if (mask) { - flags |= WebSocket::FLAG_MASK; - } - -#ifdef SW_HAVE_ZLIB - if (flags & WebSocket::FLAG_COMPRESS) { - if (!allow_compress) { - flags ^= WebSocket::FLAG_COMPRESS; - } else if (length > 0) { - String *zlib_buffer = sw_tg_buffer(); - zlib_buffer->clear(); - if (websocket_message_compress(zlib_buffer, data, length, Z_DEFAULT_COMPRESSION)) { - data = zlib_buffer->str; - length = zlib_buffer->length; - flags |= WebSocket::FLAG_RSV1; - } + if (data && !ZVAL_IS_NULL(data)) { + str_zdata = data; + ptr = str_zdata.val(); + len = str_zdata.len(); + } + +#ifndef SW_HAVE_ZLIB + swoole_warning("Unable to compress websocket data frame, the `zlib` supports is required"); + return false; +#else + bool need_compress = ((flags & WebSocket::FLAG_COMPRESS) && len > 0); + if (opcode == WebSocket::OPCODE_CLOSE || opcode == WebSocket::OPCODE_PING || opcode == WebSocket::OPCODE_PONG) { + sw_unset_bit(flags, WebSocket::FLAG_COMPRESS | WebSocket::FLAG_RSV1); + sw_set_bit(flags, WebSocket::FLAG_FIN); + need_compress = false; + } else if (opcode == WebSocket::OPCODE_CONTINUATION || !(flags & WebSocket::FLAG_FIN)) { + // Continuous frames and WebSocket message frames without the FLAG_FIN flag do not require compression. + need_compress = false; + } + + if (need_compress) { + String *zlib_buffer = sw_tg_buffer(); + zlib_buffer->clear(); + if (WebSocket::message_compress(zlib_buffer, ptr, len, Z_DEFAULT_COMPRESSION)) { + ptr = zlib_buffer->str; + len = zlib_buffer->length; + sw_set_bit(flags, WebSocket::FLAG_RSV1); + } else { + sw_unset_bit(flags, WebSocket::FLAG_RSV1); } } #endif - // TODO: send data in zlib_buffer directly, do not copy to http_buffer - switch (opcode) { - case WebSocket::OPCODE_CLOSE: - return WebSocket::pack_close_frame(buffer, code, data, length, flags); - default: - return WebSocket::encode(buffer, data, length, opcode, flags) ? SW_OK : SW_ERR; + buffer->clear(); + if (UNEXPECTED(opcode == WebSocket::OPCODE_CLOSE)) { + return WebSocket::pack_close_frame(buffer, code, ptr, len, flags); + } else { + return WebSocket::encode(buffer, ptr, len, opcode, flags); } } -int php_swoole_websocket_frame_pack_ex( - String *buffer, zval *zdata, zend_long opcode, uint8_t flags, zend_bool mask, zend_bool allow_compress) { - return php_swoole_websocket_frame_pack_ex( - buffer, zdata, opcode, WebSocket::CLOSE_NORMAL, flags, mask, allow_compress); -} - -int php_swoole_websocket_frame_object_pack_ex(String *buffer, zval *zdata, zend_bool mask, zend_bool allow_compress) { - zval *zframe = zdata; - zend_long opcode = WebSocket::OPCODE_TEXT; - zend_long code = WebSocket::CLOSE_NORMAL; - zend_long flags = WebSocket::FLAG_FIN; - zval *ztmp = nullptr; - - zdata = nullptr; - if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_OPCODE), 1))) { - opcode = zval_get_long(ztmp); - } - if (opcode == WebSocket::OPCODE_CLOSE) { - if ((ztmp = sw_zend_read_property_not_null_ex( - swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_CODE), 1))) { - code = zval_get_long(ztmp); +FrameObject::FrameObject(zval *zdata, zend_long _opcode, zend_long _flags, zend_long _code) { + if (Z_TYPE_P(zdata) == IS_OBJECT && instanceof_function(Z_OBJCE_P(zdata), swoole_websocket_frame_ce)) { + zval *ztmp = nullptr; + if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_OPCODE), 1))) { + opcode = zval_get_long(ztmp); + } else { + opcode = OPCODE_TEXT; } - if ((ztmp = sw_zend_read_property_not_null_ex( - swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_REASON), 1))) { - zdata = ztmp; + if (opcode == OPCODE_CLOSE) { + if ((ztmp = sw_zend_read_property_not_null_ex( + swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_CODE), 1))) { + code = zval_get_long(ztmp); + } else { + code = CLOSE_NORMAL; + } + data = sw_zend_read_property_not_null_ex( + swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_REASON), 1); + } else { + data = + sw_zend_read_property_not_null_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 1); } - } - if (!zdata && - (ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 1))) { - zdata = ztmp; - } - if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_FLAGS), 1))) { - flags = zval_get_long(ztmp) & WebSocket::FLAGS_ALL; - } - if ((ztmp = sw_zend_read_property_not_null_ex( - swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_FINISH), 1))) { - if (zval_is_true(ztmp)) { - flags |= WebSocket::FLAG_FIN; + if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_FLAGS), 1))) { + flags = zval_get_long(ztmp) & FLAGS_ALL; } else { - flags &= ~WebSocket::FLAG_FIN; + flags = 0; + } + if ((ztmp = sw_zend_read_property_not_null_ex( + swoole_websocket_frame_ce, zdata, SW_ZSTR_KNOWN(SW_ZEND_STR_FINISH), 1))) { + if (zval_is_true(ztmp)) { + sw_set_bit(flags, WebSocket::FLAG_FIN); + } else { + sw_unset_bit(flags, WebSocket::FLAG_FIN); + } } + } else { + opcode = _opcode; + flags = _flags & FLAGS_ALL; + code = _code; + data = zdata; } - - return php_swoole_websocket_frame_pack_ex( - buffer, zdata, opcode, code, flags & WebSocket::FLAGS_ALL, mask, allow_compress); } void swoole_websocket_onBeforeHandshakeResponse(Server *serv, int server_fd, HttpContext *ctx) { - zend_fcall_info_cache *fci_cache = - php_swoole_server_get_fci_cache(serv, server_fd, SW_SERVER_CB_onBeforeHandShakeResponse); - if (fci_cache) { + auto cb = php_swoole_server_get_callback(serv, server_fd, SW_SERVER_CB_onBeforeHandshakeResponse); + if (cb) { zval args[3]; - args[0] = *((zval *) serv->private_data_2); + args[0] = *php_swoole_server_zval_ptr(serv); args[1] = *ctx->request.zobject; args[2] = *ctx->response.zobject; - if (UNEXPECTED(!zend::function::call(fci_cache, 3, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, 3, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error( E_WARNING, "%s->onBeforeHandshakeResponse handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(ctx->fd, false); @@ -268,18 +236,18 @@ void swoole_websocket_onBeforeHandshakeResponse(Server *serv, int server_fd, Htt } } -void swoole_websocket_onOpen(Server *serv, HttpContext *ctx) { +void swoole_websocket_onOpen(Server *serv, const HttpContext *ctx) { Connection *conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED, "session[%ld] is closed", ctx->fd); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd); return; } - zend_fcall_info_cache *fci_cache = php_swoole_server_get_fci_cache(serv, conn->server_fd, SW_SERVER_CB_onOpen); - if (fci_cache) { + auto cb = php_swoole_server_get_callback(serv, conn->server_fd, SW_SERVER_CB_onOpen); + if (cb) { zval args[2]; - args[0] = *((zval *) serv->private_data_2); + args[0] = *php_swoole_server_zval_ptr(serv); args[1] = *ctx->request.zobject; - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onOpen handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(ctx->fd, false); } @@ -298,7 +266,7 @@ void swoole_websocket_onRequest(HttpContext *ctx) { "Server: " SW_HTTP_SERVER_SOFTWARE "\r\n\r\n" "

HTTP 400 Bad Request


Powered by Swoole"; - ctx->send(ctx, (char *) bad_request, strlen(bad_request)); + ctx->send(ctx, bad_request, strlen(bad_request)); ctx->end_ = 1; ctx->close(ctx); } @@ -317,7 +285,7 @@ bool swoole_websocket_handshake(HttpContext *ctx) { zval *pData; zval retval; - if (!(pData = zend_hash_str_find(ht, ZEND_STRL("sec-websocket-key")))) { + if (!((pData = zend_hash_str_find(ht, ZEND_STRL("sec-websocket-key"))))) { _bad_request: ctx->response.status = SW_HTTP_BAD_REQUEST; ctx->end(nullptr, &retval); @@ -347,11 +315,11 @@ bool swoole_websocket_handshake(HttpContext *ctx) { Server *serv = nullptr; Connection *conn = nullptr; - if (!ctx->co_socket) { - serv = (Server *) ctx->private_data; + if (!ctx->is_co_socket()) { + serv = ctx->get_async_server(); conn = serv->get_connection_by_session_id(ctx->fd); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED, "session[%ld] is closed", ctx->fd); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", ctx->fd); return false; } } @@ -359,16 +327,13 @@ bool swoole_websocket_handshake(HttpContext *ctx) { if (conn) { conn->websocket_status = WebSocket::STATUS_ACTIVE; ListenPort *port = serv->get_port_by_server_fd(conn->server_fd); - if (port && !port->websocket_subprotocol.empty()) { - ctx->set_header(ZEND_STRL("Sec-WebSocket-Protocol"), - port->websocket_subprotocol.c_str(), - port->websocket_subprotocol.length(), - false); + if (port && !port->websocket_settings.protocol.empty()) { + ctx->set_header(ZEND_STRL("Sec-WebSocket-Protocol"), port->websocket_settings.protocol, false); } swoole_websocket_onBeforeHandshakeResponse(serv, conn->server_fd, ctx); } else { - Socket *sock = (Socket *) ctx->private_data; - sock->open_length_check = 1; + auto sock = ctx->get_co_socket(); + sock->open_length_check = true; sock->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN; sock->protocol.package_length_offset = 0; sock->protocol.package_body_offset = 0; @@ -383,7 +348,7 @@ bool swoole_websocket_handshake(HttpContext *ctx) { } #ifdef SW_HAVE_ZLIB -static bool websocket_message_uncompress(String *buffer, const char *in, size_t in_len) { +bool WebSocket::message_uncompress(String *buffer, const char *in, size_t in_len) { z_stream zstream; int status; bool ret = false; @@ -402,7 +367,7 @@ static bool websocket_message_uncompress(String *buffer, const char *in, size_t zstream.avail_in = in_len; zstream.total_in = 0; - while (1) { + while (true) { zstream.avail_out = buffer->size - buffer->length; zstream.next_out = (Bytef *) (buffer->str + buffer->length); status = inflate(&zstream, Z_SYNC_FLUSH); @@ -417,22 +382,19 @@ static bool websocket_message_uncompress(String *buffer, const char *in, size_t break; } if (buffer->length + (SW_BUFFER_SIZE_STD / 2) >= buffer->size) { - if (!buffer->extend()) { - status = Z_MEM_ERROR; - break; - } + buffer->extend(); } } inflateEnd(&zstream); if (!ret) { - swoole_warning("inflate() failed, Error: %s[%d]", zError(status), status); + php_swoole_fatal_error(E_WARNING, "inflate() failed, Error: %s[%d]", zError(status), status); return false; } return true; } -static bool websocket_message_compress(String *buffer, const char *data, size_t length, int level) { +bool WebSocket::message_compress(String *buffer, const char *data, size_t length, int level) { // ==== ZLIB ==== if (level == Z_NO_COMPRESSION) { level = Z_DEFAULT_COMPRESSION; @@ -446,9 +408,9 @@ static bool websocket_message_compress(String *buffer, const char *data, size_t zstream.zalloc = php_zlib_alloc; zstream.zfree = php_zlib_free; - status = deflateInit2(&zstream, level, Z_DEFLATED, SW_ZLIB_ENCODING_RAW, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + status = deflateInit2(&zstream, level, Z_DEFLATED, SW_ZLIB_ENCODING_RAW, SW_ZLIB_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (status != Z_OK) { - swoole_warning("deflateInit2() failed, Error: [%d]", status); + php_swoole_fatal_error(E_WARNING, "deflateInit2() failed, Error: [%d]", status); return false; } @@ -458,9 +420,7 @@ static bool websocket_message_compress(String *buffer, const char *data, size_t size_t max_length = deflateBound(&zstream, length); if (max_length > buffer->size) { - if (!buffer->extend(max_length)) { - return false; - } + buffer->extend(max_length); } size_t bytes_written = 0; @@ -491,12 +451,12 @@ static bool websocket_message_compress(String *buffer, const char *data, size_t deflateEnd(&zstream); if (result != Z_BUF_ERROR || bytes_written < 4) { - swoole_warning("Failed to compress outgoing frame"); + php_swoole_fatal_error(E_WARNING, "Failed to compress outgoing frame"); return false; } if (status != Z_OK) { - swoole_warning("deflate() failed, Error: [%d]", status); + php_swoole_fatal_error(E_WARNING, "deflate() failed, Error: [%d]", status); return false; } @@ -509,63 +469,49 @@ static bool websocket_message_compress(String *buffer, const char *data, size_t int swoole_websocket_onMessage(Server *serv, RecvData *req) { SessionId fd = req->info.fd; uchar flags = 0; - zend_long opcode = 0; + uchar opcode = 0; auto port = serv->get_port_by_session_id(fd); if (!port) { return SW_ERR; } zval zdata; - char frame_header[2]; - memcpy(frame_header, &req->info.ext_flags, sizeof(frame_header)); - php_swoole_get_recv_data(serv, &zdata, req); - // frame info has already decoded in websocket::dispatch_frame - flags = frame_header[0]; - opcode = frame_header[1]; + WebSocket::parse_ext_flags(req->info.ext_flags, &opcode, &flags); - if ((opcode == WebSocket::OPCODE_CLOSE && !port->open_websocket_close_frame) || - (opcode == WebSocket::OPCODE_PING && !port->open_websocket_ping_frame) || - (opcode == WebSocket::OPCODE_PONG && !port->open_websocket_pong_frame)) { + if ((opcode == WebSocket::OPCODE_CLOSE && !port->websocket_settings.open_close_frame) || + (opcode == WebSocket::OPCODE_PING && !port->websocket_settings.open_ping_frame) || + (opcode == WebSocket::OPCODE_PONG && !port->websocket_settings.open_pong_frame)) { if (opcode == WebSocket::OPCODE_PING) { - String send_frame = {}; - char buf[SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_CLOSE_CODE_LEN + SW_WEBSOCKET_CLOSE_REASON_MAX_LEN]; - send_frame.str = buf; - send_frame.size = sizeof(buf); - WebSocket::encode(&send_frame, req->data, req->info.len, WebSocket::OPCODE_PONG, WebSocket::FLAG_FIN); - serv->send(fd, send_frame.str, send_frame.length); + String frame(SW_WEBSOCKET_FRAME_HEADER_SIZE + req->info.len, sw_php_allocator()); + WebSocket::encode(&frame, req->data, req->info.len, WebSocket::OPCODE_PONG, WebSocket::FLAG_FIN); + serv->send(fd, frame.str, frame.length); } zval_ptr_dtor(&zdata); return SW_OK; } -#ifdef SW_HAVE_ZLIB - /** - * RFC 7692 - */ - if (serv->websocket_compression && (flags & WebSocket::FLAG_RSV1)) { - String *zlib_buffer = sw_tg_buffer(); - zlib_buffer->clear(); - if (!websocket_message_uncompress(zlib_buffer, Z_STRVAL(zdata), Z_STRLEN(zdata))) { - zval_ptr_dtor(&zdata); + // RFC 7692: uncompress websocket data + // See https://datatracker.ietf.org/doc/html/rfc7692 + if (flags & WebSocket::FLAG_RSV1) { + zval zpayload; + auto rs = FrameObject::uncompress(&zpayload, Z_STRVAL(zdata), Z_STRLEN(zdata)); + zval_ptr_dtor(&zdata); + if (!rs) { return SW_OK; } - zval_ptr_dtor(&zdata); - ZVAL_STRINGL(&zdata, zlib_buffer->str, zlib_buffer->length); - flags ^= (WebSocket::FLAG_RSV1 | WebSocket::FLAG_COMPRESS); + zdata = zpayload; } -#endif - zend_fcall_info_cache *fci_cache = - php_swoole_server_get_fci_cache(serv, req->info.server_fd, SW_SERVER_CB_onMessage); + auto cb = php_swoole_server_get_callback(serv, req->info.server_fd, SW_SERVER_CB_onMessage); zval args[2]; - args[0] = *(zval *) serv->private_data_2; - php_swoole_websocket_construct_frame(&args[1], opcode, &zdata, flags); + args[0] = *php_swoole_server_zval_ptr(serv); + WebSocket::construct_frame(&args[1], opcode, &zdata, flags); zend_update_property_long(swoole_websocket_frame_ce, SW_Z8_OBJ_P(&args[1]), ZEND_STRL("fd"), fd); - if (UNEXPECTED(!zend::function::call(fci_cache, 2, args, nullptr, serv->is_enable_coroutine()))) { + if (UNEXPECTED(!zend::function::call(cb, 2, args, nullptr, serv->is_enable_coroutine()))) { php_swoole_error(E_WARNING, "%s->onMessage handler error", ZSTR_VAL(swoole_websocket_server_ce->name)); serv->close(fd, false); } @@ -593,11 +539,13 @@ void php_swoole_websocket_server_minit(int module_number) { nullptr, swoole_websocket_server_methods, swoole_http_server); +#ifndef SW_THREAD SW_SET_CLASS_NOT_SERIALIZABLE(swoole_websocket_server); +#endif SW_SET_CLASS_CLONEABLE(swoole_websocket_server, sw_zend_class_clone_deny); SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_websocket_server, sw_zend_class_unset_property_deny); - SW_INIT_CLASS_ENTRY(swoole_websocket_frame, "Swoole\\WebSocket\\Frame", nullptr, swoole_websocket_frame_methods); + SW_INIT_CLASS_ENTRY(swoole_websocket_frame, R"(Swoole\WebSocket\Frame)", nullptr, swoole_websocket_frame_methods); zend_class_implements(swoole_websocket_frame_ce, 1, zend_ce_stringable); zend_declare_property_long(swoole_websocket_frame_ce, ZEND_STRL("fd"), 0, ZEND_ACC_PUBLIC); zend_declare_property_string(swoole_websocket_frame_ce, ZEND_STRL("data"), "", ZEND_ACC_PUBLIC); @@ -645,6 +593,9 @@ void php_swoole_websocket_server_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_MESSAGE_TOO_BIG", WebSocket::CLOSE_MESSAGE_TOO_BIG); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_EXTENSION_MISSING", WebSocket::CLOSE_EXTENSION_MISSING); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_SERVER_ERROR", WebSocket::CLOSE_SERVER_ERROR); + SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_CLOSE_SERVICE_RESTART", WebSocket::CLOSE_SERVICE_RESTART); + SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_TRY_AGAIN_LATER", WebSocket::CLOSE_TRY_AGAIN_LATER); + SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_BAD_GATEWAY", WebSocket::CLOSE_BAD_GATEWAY); SW_REGISTER_LONG_CONSTANT("SWOOLE_WEBSOCKET_CLOSE_TLS", WebSocket::CLOSE_TLS); /* swoole namespace }}} */ @@ -655,6 +606,7 @@ void php_swoole_websocket_server_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_FRAME", WebSocket::STATUS_ACTIVE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_ACTIVE", WebSocket::STATUS_ACTIVE); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_CLOSING", WebSocket::STATUS_CLOSING); + SW_REGISTER_LONG_CONSTANT("WEBSOCKET_STATUS_HANDSHAKE_FAILED", WebSocket::STATUS_HANDSHAKE_FAILED); // all opcodes SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_CONTINUATION", WebSocket::OPCODE_CONTINUATION); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_OPCODE_TEXT", WebSocket::OPCODE_TEXT); @@ -674,18 +626,41 @@ void php_swoole_websocket_server_minit(int module_number) { SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_MESSAGE_TOO_BIG", WebSocket::CLOSE_MESSAGE_TOO_BIG); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_EXTENSION_MISSING", WebSocket::CLOSE_EXTENSION_MISSING); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_SERVER_ERROR", WebSocket::CLOSE_SERVER_ERROR); + SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_CLOSE_SERVICE_RESTART", WebSocket::CLOSE_SERVICE_RESTART); + SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_TRY_AGAIN_LATER", WebSocket::CLOSE_TRY_AGAIN_LATER); + SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_BAD_GATEWAY", WebSocket::CLOSE_BAD_GATEWAY); SW_REGISTER_LONG_CONSTANT("WEBSOCKET_CLOSE_TLS", WebSocket::CLOSE_TLS); +} - if (swoole_websocket_buffer == nullptr) { - swoole_websocket_buffer = swoole::make_string(SW_BUFFER_SIZE_BIG); - } +void php_swoole_server_set_websocket_option(ListenPort *port, zend_array *vht) { + WebSocket::apply_setting(port->websocket_settings, vht, true); } -void php_swoole_websocket_server_mshutdown() { - if (swoole_websocket_buffer) { - delete swoole_websocket_buffer; - swoole_websocket_buffer = nullptr; +void WebSocket::apply_setting(WebSocketSettings &settings, zend_array *vht, bool in_server) { + zval *ztmp; + if (php_swoole_array_get_value(vht, "websocket_subprotocol", ztmp)) { + settings.protocol = zend::String(ztmp).to_std_string(); } + if (php_swoole_array_get_value(vht, "websocket_mask", ztmp)) { + settings.mask = zval_is_true(ztmp); + } else { + settings.mask = !in_server; + } +#ifdef SW_HAVE_ZLIB + if (php_swoole_array_get_value(vht, "websocket_compression", ztmp)) { + settings.compression = zval_is_true(ztmp); + } +#endif + if (php_swoole_array_get_value(vht, "open_websocket_close_frame", ztmp)) { + settings.open_close_frame = zval_is_true(ztmp); + } + if (php_swoole_array_get_value(vht, "open_websocket_ping_frame", ztmp)) { + settings.open_ping_frame = zval_is_true(ztmp); + } + if (php_swoole_array_get_value(vht, "open_websocket_pong_frame", ztmp)) { + settings.open_pong_frame = zval_is_true(ztmp); + } + settings.in_server = in_server; } static sw_inline bool swoole_websocket_server_push(Server *serv, SessionId fd, String *buffer) { @@ -702,17 +677,13 @@ static sw_inline bool swoole_websocket_server_push(Server *serv, SessionId fd, S return false; } - bool ret = serv->send(fd, buffer->str, buffer->length); - if (!ret && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { - zval _return_value; - zval *return_value = &_return_value; - zval _yield_data; - ZVAL_STRINGL(&_yield_data, buffer->str, buffer->length); - ZVAL_FALSE(return_value); - php_swoole_server_send_yield(serv, fd, &_yield_data, return_value); - ret = Z_BVAL_P(return_value); + bool rv = serv->send(fd, buffer->str, buffer->length); + if (!rv && swoole_get_last_error() == SW_ERROR_OUTPUT_SEND_YIELD) { + auto sdata = zend_string_init(buffer->str, buffer->length, false); + rv = php_swoole_server_send_yield(serv, fd, sdata); + zend_string_release(sdata); } - return ret; + return rv; } static sw_inline bool swoole_websocket_server_close(Server *serv, SessionId fd, String *buffer, bool real_close) { @@ -731,6 +702,21 @@ static sw_inline bool swoole_websocket_server_close(Server *serv, SessionId fd, } } +static inline void swoole_websocket_server_pack(zval *zdata, zend_long opcode, zend_long flags, zval *return_value) { + FrameObject frame{zdata, opcode, flags}; + String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); + + if (sw_unlikely(!frame.pack(&buffer))) { + RETURN_EMPTY_STRING(); + } + + auto packed_str = zend::fetch_zend_string_by_val(buffer.str); + ZSTR_VAL(packed_str)[buffer.length] = '\0'; + ZSTR_LEN(packed_str) = buffer.length; + buffer.release(); + RETURN_STR(packed_str); +} + static PHP_METHOD(swoole_websocket_server, disconnect) { Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); if (sw_unlikely(!serv->is_started())) { @@ -743,14 +729,47 @@ static PHP_METHOD(swoole_websocket_server, disconnect) { char *data = nullptr; size_t length = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|ls", &fd, &code, &data, &length) == FAILURE) { + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_LONG(fd) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(code) + Z_PARAM_STRING(data, length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + length + 2, sw_zend_string_allocator()); + if (!WebSocket::pack_close_frame(&buffer, code, data, length, 0)) { RETURN_FALSE; } - swoole_websocket_buffer->clear(); - if (WebSocket::pack_close_frame(swoole_websocket_buffer, code, data, length, 0) < 0) { + RETURN_BOOL(swoole_websocket_server_close(serv, fd, &buffer, 1)); +} + +static PHP_METHOD(swoole_websocket_server, ping) { + Server *serv = php_swoole_server_get_and_check_server(ZEND_THIS); + if (sw_unlikely(!serv->is_started())) { + php_swoole_fatal_error(E_WARNING, "server is not running"); + RETURN_FALSE; + } + + zend_long fd = 0; + zend_string *zpayload = zend_empty_string; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_LONG(fd) + Z_PARAM_OPTIONAL + Z_PARAM_STR(zpayload) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + + zval zdata = {}; + ZVAL_STR(&zdata, zpayload); + FrameObject frame{&zdata, WebSocket::OPCODE_PING, WebSocket::FLAG_FIN}; + + String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); + if (sw_unlikely(!frame.pack(&buffer))) { + swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); RETURN_FALSE; } - RETURN_BOOL(swoole_websocket_server_close(serv, fd, swoole_websocket_buffer, 1)); + + RETURN_BOOL(swoole_websocket_server_push(serv, fd, &buffer)); } static PHP_METHOD(swoole_websocket_server, push) { @@ -765,9 +784,6 @@ static PHP_METHOD(swoole_websocket_server, push) { zend_long opcode = WebSocket::OPCODE_TEXT; zval *zflags = nullptr; zend_long flags = WebSocket::FLAG_FIN; -#ifdef SW_HAVE_ZLIB - zend_bool allow_compress = 0; -#endif ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_LONG(fd) @@ -781,115 +797,85 @@ static PHP_METHOD(swoole_websocket_server, push) { flags = zval_get_long(zflags); } -#ifdef SW_HAVE_ZLIB Connection *conn = serv->get_connection_verify(fd); - if (!conn) { + if (sw_unlikely(!conn)) { swoole_set_last_error(SW_ERROR_SESSION_NOT_EXIST); php_swoole_fatal_error(E_WARNING, "session#" ZEND_LONG_FMT " does not exists", fd); RETURN_FALSE; } - allow_compress = conn->websocket_compression; -#endif - swoole_websocket_buffer->clear(); - if (php_swoole_websocket_frame_is_object(zdata)) { - if (php_swoole_websocket_frame_object_pack(swoole_websocket_buffer, zdata, 0, allow_compress) < 0) { - swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); - RETURN_FALSE; - } - } else { - if (php_swoole_websocket_frame_pack( - swoole_websocket_buffer, zdata, opcode, flags & WebSocket::FLAGS_ALL, 0, allow_compress) < 0) { - swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); - RETURN_FALSE; - } - } + FrameObject frame{zdata, opcode, flags}; - switch (opcode) { - case WebSocket::OPCODE_CLOSE: - RETURN_BOOL(swoole_websocket_server_close(serv, fd, swoole_websocket_buffer, flags & WebSocket::FLAG_FIN)); - break; - default: - RETURN_BOOL(swoole_websocket_server_push(serv, fd, swoole_websocket_buffer)); + if (conn->websocket_compression) { + sw_set_bit(frame.flags, WebSocket::FLAG_COMPRESS); } -} -static size_t websocket_get_frame_data_size(zval *zframe) { - zend_long opcode = WebSocket::OPCODE_TEXT; - zval *ztmp = nullptr; - zval *zdata = nullptr; + // WebSocket server must not set data mask + sw_unset_bit(frame.flags, WebSocket::FLAG_MASK); - if ((ztmp = sw_zend_read_property_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_OPCODE), 1))) { - opcode = zval_get_long(ztmp); - } - if (opcode == WebSocket::OPCODE_CLOSE) { - zdata = - sw_zend_read_property_not_null_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_REASON), 1); - } else { - zdata = sw_zend_read_property_ex(swoole_websocket_frame_ce, zframe, SW_ZSTR_KNOWN(SW_ZEND_STR_DATA), 1); + String buffer(SW_WEBSOCKET_FRAME_HEADER_SIZE + frame.get_data_size(), sw_zend_string_allocator()); + + if (sw_unlikely(!frame.pack(&buffer))) { + swoole_set_last_error(SW_ERROR_WEBSOCKET_PACK_FAILED); + RETURN_FALSE; } - return (zdata && ZVAL_IS_STRING(zdata)) ? Z_STRLEN_P(zdata) : 0; -} -#define FRAME_HEADER_LEN 16 + RETURN_BOOL(swoole_websocket_server_push(serv, fd, &buffer)); +} static PHP_METHOD(swoole_websocket_server, pack) { zval *zdata; zend_long opcode = WebSocket::OPCODE_TEXT; - zval *zflags = nullptr; zend_long flags = WebSocket::FLAG_FIN; ZEND_PARSE_PARAMETERS_START(1, 3) Z_PARAM_ZVAL(zdata) Z_PARAM_OPTIONAL Z_PARAM_LONG(opcode) - Z_PARAM_ZVAL_EX(zflags, 1, 0) + Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (zflags != nullptr) { - flags = zval_get_long(zflags); - } - - String *buffer; - int result; - if (php_swoole_websocket_frame_is_object(zdata)) { - buffer = - swoole::make_string(websocket_get_frame_data_size(zdata) + FRAME_HEADER_LEN, sw_zend_string_allocator()); - result = php_swoole_websocket_frame_object_pack(buffer, zdata, 0, 1); - } else { - buffer = swoole::make_string(Z_STRLEN_P(zdata) + FRAME_HEADER_LEN, sw_zend_string_allocator()); - result = php_swoole_websocket_frame_pack(buffer, zdata, opcode, flags & WebSocket::FLAGS_ALL, 0, 1); - } - - if (result < 0) { - delete buffer; - RETURN_EMPTY_STRING(); - } - - auto packed_str = zend::fetch_zend_string_by_val(buffer->str); - ZSTR_VAL(packed_str)[buffer->length] = '\0'; - ZSTR_LEN(packed_str) = buffer->length; - RETURN_STR(packed_str); + swoole_websocket_server_pack(zdata, opcode, flags, return_value); } static PHP_METHOD(swoole_websocket_frame, __toString) { - String *buffer = sw_tg_buffer(); - buffer->clear(); - - if (php_swoole_websocket_frame_object_pack(buffer, ZEND_THIS, 0, 1) < 0) { - RETURN_EMPTY_STRING(); - } - RETURN_STRINGL(buffer->str, buffer->length); + swoole_websocket_server_pack(ZEND_THIS, 0, 0, return_value); } static PHP_METHOD(swoole_websocket_server, unpack) { - String buffer = {}; + char *data; + size_t length; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STRING(data, length) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &buffer.str, &buffer.length) == FAILURE) { + WebSocket::Frame frame; + + if (length < sizeof(frame.header)) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); RETURN_FALSE; } - php_swoole_websocket_frame_unpack(&buffer, return_value); + if (!WebSocket::decode(&frame, data, length)) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_FALSE; + } + + zval zpayload{}; + uint8_t flags = frame.get_flags(); + + if (frame.compressed()) { + if (sw_unlikely(!FrameObject::uncompress(&zpayload, frame.payload, frame.payload_length))) { + swoole_set_last_error(SW_ERROR_PROTOCOL_ERROR); + RETURN_FALSE; + } + } else { + ZVAL_STRINGL(&zpayload, frame.payload, frame.payload_length); + } + + WebSocket::construct_frame(return_value, frame.header.OPCODE, &zpayload, flags); + zval_ptr_dtor(&zpayload); } static PHP_METHOD(swoole_websocket_server, isEstablished) { @@ -901,9 +887,9 @@ static PHP_METHOD(swoole_websocket_server, isEstablished) { zend_long session_id; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &session_id) == FAILURE) { - RETURN_FALSE; - } + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(session_id) + ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); Connection *conn = serv->get_connection_verify(session_id); // not isEstablished diff --git a/include/swoole.h b/include/swoole.h index 904edf85ea..25da552036 100644 --- a/include/swoole.h +++ b/include/swoole.h @@ -40,41 +40,29 @@ #endif /*--- C standard library ---*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include /* sched_yield() */ #include +#include +#include #include -#include #include #include #include #include +#include typedef unsigned long ulong_t; -#ifndef PRId64 -#define PRId64 "lld" -#endif - -#ifndef PRIu64 -#define PRIu64 "llu" -#endif - -#ifndef PRIx64 -#define PRIx64 "llx" -#endif - #if defined(__GNUC__) #if __GNUC__ >= 3 #define sw_inline inline __attribute__((always_inline)) @@ -118,6 +106,9 @@ typedef unsigned long ulong_t; #define SW_ECHO_LEN_CYAN "\e[36m%.*s\e[0m" #define SW_ECHO_LEN_WHITE "\e[37m%.*s\e[0m" +#define SW_ECHO_RED_BG "\e[41m%s\e[0m" +#define SW_ECHO_GREEN_BG "\e[42m%s\e[0m" + #define SW_COLOR_RED 1 #define SW_COLOR_GREEN 2 #define SW_COLOR_YELLOW 3 @@ -144,6 +135,14 @@ typedef unsigned long ulong_t; #define SW_LOOP_N(n) for (decltype(n) i = 0; i < n; i++) #define SW_LOOP for (;;) +#ifndef MAYBE_UNUSED +#ifdef __GNUC__ +#define MAYBE_UNUSED __attribute__((used)) +#else +#define MAYBE_UNUSED +#endif +#endif + #ifndef MAX #define MAX(A, B) SW_MAX(A, B) #endif @@ -168,7 +167,21 @@ typedef unsigned long ulong_t; #define SW_ASSERT(e) #define SW_ASSERT_1BYTE(v) #endif -#define SW_START_SLEEP usleep(100000) // sleep 1s,wait fork and pthread_create +#define SW_START_SLEEP usleep(100000) // sleep 0.1s, wait fork and pthread_create + +#ifdef SW_THREAD +#define SW_THREAD_LOCAL thread_local +extern std::mutex sw_thread_lock; +#else +#define SW_THREAD_LOCAL +#endif + +/** + * API naming rules + * ----------------------------------- + * - starts with swoole_, means it is ready or has been used as an external API + * - starts with sw_, internal use only + */ /*-----------------------------------Memory------------------------------------*/ void *sw_malloc(size_t size); @@ -192,11 +205,16 @@ class Timer; struct TimerNode; struct Event; class Pipe; +class MessageBus; +class Server; namespace network { struct Socket; struct Address; } // namespace network class AsyncThreads; +#ifdef SW_USE_IOURING +class Iouring; +#endif namespace async { class ThreadPool; } @@ -208,14 +226,7 @@ typedef std::function Callback; typedef std::function TimerCallback; } // namespace swoole -typedef swoole::Reactor swReactor; -typedef swoole::String swString; -typedef swoole::Timer swTimer; -typedef swoole::network::Socket swSocket; -typedef swoole::Protocol swProtocol; -typedef swoole::EventData swEventData; typedef swoole::DataHead swDataHead; -typedef swoole::Event swEvent; /*----------------------------------String-------------------------------------*/ @@ -225,9 +236,8 @@ typedef swoole::Event swEvent; #define SW_STREQ(str, len, const_str) swoole_streq(str, len, SW_STRL(const_str)) #define SW_STRCASEEQ(str, len, const_str) swoole_strcaseeq(str, len, SW_STRL(const_str)) -/* string contain */ -#define SW_STRCT(str, len, const_sub_str) swoole_strct(str, len, SW_STRL(const_sub_str)) -#define SW_STRCASECT(str, len, const_sub_str) swoole_strcasect(str, len, SW_STRL(const_sub_str)) +#define SW_STR_STARTS_WITH(str, len, const_sub_str) swoole_str_starts_with(str, len, SW_STRL(const_sub_str)) +#define SW_STR_ISTARTS_WITH(str, len, const_sub_str) swoole_str_istarts_with(str, len, SW_STRL(const_sub_str)) #if defined(SW_USE_JEMALLOC) || defined(SW_USE_TCMALLOC) #define sw_strdup swoole_strdup @@ -240,10 +250,25 @@ typedef swoole::Event swEvent; /** always return less than size, zero termination */ size_t sw_snprintf(char *buf, size_t size, const char *format, ...) __attribute__((format(printf, 3, 4))); size_t sw_vsnprintf(char *buf, size_t size, const char *format, va_list args); +int sw_printf(const char *format, ...); +bool sw_wait_for(const std::function &fn, int timeout_ms); + +static inline long sw_atol(const char *str) { + return std::strtol(str, nullptr, 10); +} + +static inline int sw_atoi(const char *str) { + return static_cast(sw_atol(str)); +} + +static inline void sw_memset_zero(void *s, size_t n) { + memset(s, '\0', n); +} -#define sw_memset_zero(s, n) memset(s, '\0', n) +#define sw_unset_bit(val, bit) val &= ~bit +#define sw_set_bit(val, bit) val |= bit -static sw_inline int sw_mem_equal(const void *v1, size_t s1, const void *v2, size_t s2) { +static inline int sw_mem_equal(const void *v1, size_t s1, const void *v2, size_t s2) { return s1 == s2 && memcmp(v1, v2, s2) == 0; } @@ -259,15 +284,15 @@ static inline size_t swoole_strlcpy(char *dest, const char *src, size_t size) { static inline char *swoole_strdup(const char *s) { size_t l = strlen(s) + 1; - char *p = (char *) sw_malloc(l); + char *p = static_cast(sw_malloc(l)); if (sw_likely(p)) { memcpy(p, s, l); } return p; } -static inline char *swoole_strndup(const char *s, size_t n) { - char *p = (char *) sw_malloc(n + 1); +static inline char *swoole_strndup(const char *s, const size_t n) { + char *p = static_cast(sw_malloc(n + 1)); if (sw_likely(p)) { strncpy(p, s, n)[n] = '\0'; } @@ -283,11 +308,11 @@ static inline unsigned int swoole_strcaseeq(const char *str1, size_t len1, const return (len1 == len2) && (strncasecmp(str1, str2, len1) == 0); } -static inline unsigned int swoole_strct(const char *pstr, size_t plen, const char *sstr, size_t slen) { +static inline unsigned int swoole_str_starts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) { return (plen >= slen) && (strncmp(pstr, sstr, slen) == 0); } -static inline unsigned int swoole_strcasect(const char *pstr, size_t plen, const char *sstr, size_t slen) { +static inline unsigned int swoole_str_istarts_with(const char *pstr, size_t plen, const char *sstr, size_t slen) { return (plen >= slen) && (strncasecmp(pstr, sstr, slen) == 0); } @@ -296,10 +321,9 @@ static inline const char *swoole_strnstr(const char *haystack, const char *needle, uint32_t needle_length) { assert(needle_length > 0); - uint32_t i; if (sw_likely(needle_length <= haystack_length)) { - for (i = 0; i < haystack_length - needle_length + 1; i++) { + for (uint32_t i = 0; i < haystack_length - needle_length + 1; i++) { if ((haystack[0] == needle[0]) && (0 == memcmp(haystack, needle, needle_length))) { return haystack; } @@ -307,7 +331,25 @@ static inline const char *swoole_strnstr(const char *haystack, } } - return NULL; + return nullptr; +} + +static inline const char *swoole_strncasestr(const char *haystack, + uint32_t haystack_length, + const char *needle, + uint32_t needle_length) { + assert(needle_length > 0); + + if (sw_likely(needle_length <= haystack_length)) { + for (uint32_t i = 0; i < haystack_length - needle_length + 1; i++) { + if ((haystack[0] == needle[0]) && (0 == strncasecmp(haystack, needle, needle_length))) { + return haystack; + } + haystack++; + } + } + + return nullptr; } static inline ssize_t swoole_strnpos(const char *haystack, @@ -315,19 +357,17 @@ static inline ssize_t swoole_strnpos(const char *haystack, const char *needle, uint32_t needle_length) { assert(needle_length > 0); - const char *pos; - pos = swoole_strnstr(haystack, haystack_length, needle, needle_length); - return pos == NULL ? -1 : pos - haystack; + const char *pos = swoole_strnstr(haystack, haystack_length, needle, needle_length); + return pos == nullptr ? -1 : pos - haystack; } static inline ssize_t swoole_strrnpos(const char *haystack, const char *needle, uint32_t length) { uint32_t needle_length = strlen(needle); assert(needle_length > 0); - uint32_t i; haystack += (length - needle_length); - for (i = length - needle_length; i > 0; i--) { + for (uint32_t i = length - needle_length; i > 0; i--) { if ((haystack[0] == needle[0]) && (0 == memcmp(haystack, needle, needle_length))) { return i; } @@ -337,14 +377,12 @@ static inline ssize_t swoole_strrnpos(const char *haystack, const char *needle, return -1; } -static inline void swoole_strtolower(char *str, int length) { - char *c, *e; - - c = str; - e = c + length; +static inline void swoole_strtolower(char *str, const int length) { + char *c = str; + const char *e = c + length; while (c < e) { - *c = tolower(*c); + *c = static_cast(tolower(*c)); c++; } } @@ -356,6 +394,7 @@ enum swResultCode { }; enum swReturnCode { + SW_SUCCESS = 0, SW_CONTINUE = 1, SW_WAIT = 2, SW_CLOSE = 3, @@ -390,6 +429,10 @@ enum swFdType { SW_FD_SIGNAL, SW_FD_DNS_RESOLVER, SW_FD_CARES, + /** + * io_uring + */ + SW_FD_IOURING, /** * SW_FD_USER or SW_FD_USER+n: for custom event */ @@ -412,6 +455,16 @@ enum swSocketType { SW_SOCK_UNIX_STREAM = 5, // unix sock stream SW_SOCK_UNIX_DGRAM = 6, // unix sock dgram SW_SOCK_RAW = 7, + SW_SOCK_RAW6 = 8, +}; + +enum swTimeoutType { + SW_TIMEOUT_DNS = 1 << 0, + SW_TIMEOUT_CONNECT = 1 << 1, + SW_TIMEOUT_READ = 1 << 2, + SW_TIMEOUT_WRITE = 1 << 3, + SW_TIMEOUT_RDWR = SW_TIMEOUT_READ | SW_TIMEOUT_WRITE, + SW_TIMEOUT_ALL = SW_TIMEOUT_DNS | SW_TIMEOUT_CONNECT | SW_TIMEOUT_RDWR, }; enum swEventType { @@ -437,9 +490,6 @@ enum swTraverseOperation { SW_TRAVERSE_STOP = 2, }; -//------------------------------------------------------------------------------- -#define sw_yield() sched_yield() - //------------------------------Base-------------------------------- #ifndef uchar typedef unsigned char uchar; @@ -448,20 +498,26 @@ typedef unsigned char uchar; #define swoole_tolower(c) (uchar)((c >= 'A' && c <= 'Z') ? (c | 0x20) : c) #define swoole_toupper(c) (uchar)((c >= 'a' && c <= 'z') ? (c & ~0x20) : c) -void swoole_random_string(char *buf, size_t size); +/** + * This function appends a '\0' at the end of the string, + * so the allocated memory buffer must be len + 1. + */ +void swoole_random_string(char *buf, size_t len); +void swoole_random_string(std::string &str, size_t len); +uint64_t swoole_random_int(); size_t swoole_random_bytes(char *buf, size_t size); -static sw_inline char *swoole_strlchr(char *p, char *last, char c) { +static inline char *swoole_strlchr(char *p, const char *last, char c) { while (p < last) { if (*p == c) { return p; } p++; } - return NULL; + return nullptr; } -static sw_inline size_t swoole_size_align(size_t size, int pagesize) { +static inline size_t swoole_size_align(size_t size, int pagesize) { return size + (pagesize - (size % pagesize)); } @@ -491,20 +547,7 @@ enum swDNSLookupFlag { SW_DNS_LOOKUP_RANDOM = (1u << 11), }; -#ifdef __MACH__ -char *sw_error_(); -#define sw_error sw_error_() -#else -extern __thread char sw_error[SW_ERROR_MSG_SIZE]; -#endif - -enum swProcessType { - SW_PROCESS_MASTER = 1, - SW_PROCESS_WORKER = 2, - SW_PROCESS_MANAGER = 3, - SW_PROCESS_TASKWORKER = 4, - SW_PROCESS_USERWORKER = 5, -}; +extern thread_local char sw_error[SW_ERROR_MSG_SIZE]; enum swPipeType { SW_PIPE_WORKER = 0, @@ -521,22 +564,34 @@ uint32_t swoole_common_divisor(uint32_t u, uint32_t v); int swoole_itoa(char *buf, long value); bool swoole_mkdir_recursive(const std::string &dir); +int swoole_rand(); int swoole_rand(int min, int max); int swoole_system_random(int min, int max); int swoole_version_compare(const char *version1, const char *version2); -void swoole_print_backtrace(void); +void swoole_print_backtrace(); +void swoole_print_backtrace_on_error(); char *swoole_string_format(size_t n, const char *format, ...); bool swoole_get_env(const char *name, int *value); int swoole_get_systemd_listen_fds(); -void swoole_init(void); -void swoole_clean(void); +void swoole_init(); +void swoole_clean(); +void swoole_exit(int _status); pid_t swoole_fork(int flags); +pid_t swoole_fork_exec(const std::function &child_fn); +pid_t swoole_waitpid(pid_t _pid, int *_stat_loc, int _options); +void swoole_thread_init(bool main_thread = false); +void swoole_thread_clean(bool main_thread = false); void swoole_redirect_stdout(int new_fd); +void swoole_redirect_stdout(const char *file); int swoole_shell_exec(const char *command, pid_t *pid, bool get_error_stream); int swoole_daemon(int nochdir, int noclose); +bool swoole_is_root_user(); +void swoole_set_isolation(const std::string &group_, const std::string &user_, const std::string &chroot_); bool swoole_set_task_tmpdir(const std::string &dir); +void swoole_set_process_death_signal(int signal); +const std::string &swoole_get_task_tmpdir(); int swoole_tmpfile(char *filename); #ifdef HAVE_CPU_AFFINITY @@ -547,47 +602,20 @@ int swoole_tmpfile(char *filename); typedef cpuset_t cpu_set_t; #endif int swoole_set_cpu_affinity(cpu_set_t *set); +int swoole_get_cpu_affinity(cpu_set_t *set); #endif -#if defined(_POSIX_TIMERS) && ((_POSIX_TIMERS > 0) || defined(__OpenBSD__)) && defined(_POSIX_MONOTONIC_CLOCK) && defined(CLOCK_MONOTONIC) -#ifndef HAVE_CLOCK_GETTIME -#define HAVE_CLOCK_GETTIME -#endif -#define swoole_clock_realtime(t) clock_gettime(CLOCK_REALTIME, t) -#elif defined(__APPLE__) -int swoole_clock_realtime(struct timespec *t); -#endif - -static inline struct timespec swoole_time_until(int milliseconds) { - struct timespec t; - swoole_clock_realtime(&t); - - int sec = milliseconds / 1000; - int msec = milliseconds - (sec * 1000); - - t.tv_sec += sec; - t.tv_nsec += msec * 1000 * 1000; - - if (t.tv_nsec > SW_NUM_BILLION) { - int _sec = t.tv_nsec / SW_NUM_BILLION; - t.tv_sec += _sec; - t.tv_nsec -= _sec * SW_NUM_BILLION; - } - - return t; -} - namespace swoole { - typedef long SessionId; typedef long TaskId; typedef uint8_t ReactorId; typedef uint32_t WorkerId; -typedef enum swEventType EventType; -typedef enum swSocketType SocketType; -typedef enum swFdType FdType; -typedef enum swReturnCode ReturnCode; -typedef enum swResultCode ResultCode; +typedef swEventType EventType; +typedef swSocketType SocketType; +typedef swTimeoutType TimeoutType; +typedef swFdType FdType; +typedef swReturnCode ReturnCode; +typedef swResultCode ResultCode; struct Event { int fd; @@ -614,28 +642,30 @@ struct DataHead { struct EventData { DataHead info; char data[SW_IPC_BUFFER_SIZE]; -}; -struct SendData { - DataHead info; - const char *data; -}; + uint32_t size() const { + return sizeof(info) + len(); + } -struct RecvData { - DataHead info; - const char *data; + uint32_t len() const { + return info.len; + } }; struct ThreadGlobal { uint16_t id; uint8_t type; + bool main_thread; + int32_t error; String *buffer_stack; Reactor *reactor; Timer *timer; + MessageBus *message_bus; AsyncThreads *async_threads; - uint32_t signal_listener_num; - uint32_t co_signal_listener_num; - int error; +#ifdef SW_USE_IOURING + Iouring *iouring; +#endif + bool signal_blocking_all; }; struct Allocator { @@ -668,7 +698,12 @@ struct NameResolver { }; std::function resolve; void *private_data; - enum Type type; + Type type; +}; + +struct DnsServer { + std::string host; + int port; }; struct Global { @@ -676,43 +711,62 @@ struct Global { uchar running : 1; uchar wait_signal : 1; uchar enable_signalfd : 1; - uchar socket_dontwait : 1; + /** + * Under macOS or FreeBSD, kqueue does not support listening for writable events on pipes. When a large amount of + * data is written to a pipe in process A, and the buffer becomes full, listening for writable events will not work. + * In process B, even after consuming the data from the pipe, the writable event in process A cannot be triggered. + * As a result, the functionality of Task and Process Server cannot be supported, making all scenarios relying on + * pipes for inter-process communication unable to function properly. + */ + uchar enable_kqueue : 1; uchar dns_lookup_random : 1; uchar use_async_resolver : 1; uchar use_name_resolver : 1; + uchar enable_coroutine : 1; + uchar print_backtrace_on_error : 1; - int process_type; - uint32_t process_id; TaskId current_task_id; - pid_t pid; int signal_fd; bool signal_alarm; + bool signal_dispatch; + uint32_t signal_listener_num; + uint32_t signal_async_listener_num; long trace_flags; void (*fatal_error)(int code, const char *str, ...); + void (*print_backtrace)(); //-----------------------[System]-------------------------- uint16_t cpu_num; uint32_t pagesize; struct utsname uname; uint32_t max_sockets; + uint32_t max_file_content; //-----------------------[Memory]-------------------------- MemoryPool *memory_pool; Allocator std_allocator; std::string task_tmpfile; - //-----------------------[DNS]-------------------------- - std::string dns_server_host; - int dns_server_port; + //------------------[Single Instance]---------------------- + Logger *logger; + Server *server; + FILE *stdout_; + //-----------------------[DNS]----------------------------- + DnsServer dns_server; double dns_cache_refresh_time; int dns_tries; std::string dns_resolvconf_path; std::string dns_hosts_path; std::list name_resolvers; - //-----------------------[AIO]-------------------------- + //-----------------------[AIO]---------------------------- uint32_t aio_core_worker_num; uint32_t aio_worker_num; +#ifdef SW_USE_IOURING + uint32_t iouring_entries = 0; + uint32_t iouring_workers = 0; + uint32_t iouring_flag = 0; +#endif double aio_max_wait_time; double aio_max_idle_time; network::Socket *aio_default_socket; @@ -724,13 +778,15 @@ struct Global { }; std::string dirname(const std::string &file); -int hook_add(void **hooks, int type, const Callback &func, int push_back); +void hook_add(void **hooks, int type, const Callback &func, int push_back); void hook_call(void **hooks, int type, void *arg); -double microtime(void); +double microtime(); +void realtime_get(timespec *time); +void realtime_add(timespec *time, int64_t add_msec); } // namespace swoole -extern swoole::Global SwooleG; // Local Global Variable -extern __thread swoole::ThreadGlobal SwooleTG; // Thread Global Variable +extern swoole::Global SwooleG; // Local Global Variable +extern thread_local swoole::ThreadGlobal SwooleTG; // Thread Global Variable #define SW_CPU_NUM (SwooleG.cpu_num) @@ -742,16 +798,27 @@ static inline int swoole_get_last_error() { return SwooleTG.error; } +static inline void swoole_clear_last_error() { + SwooleTG.error = 0; +} + +void swoole_clear_last_error_msg(); +const char *swoole_get_last_error_msg(); + static inline int swoole_get_thread_id() { return SwooleTG.id; } -static inline int swoole_get_process_type() { - return SwooleG.process_type; +static inline int swoole_get_thread_type() { + return SwooleTG.type; } -static inline int swoole_get_process_id() { - return SwooleG.process_id; +static inline void swoole_set_thread_id(uint16_t id) { + SwooleTG.id = id; +} + +static inline void swoole_set_thread_type(uint8_t type) { + SwooleTG.type = type; } static inline uint32_t swoole_pagesize() { @@ -762,38 +829,68 @@ SW_API const char *swoole_strerror(int code); SW_API void swoole_throw_error(int code); SW_API void swoole_ignore_error(int code); SW_API bool swoole_is_ignored_error(int code); +SW_API bool swoole_is_main_thread(); SW_API void swoole_set_log_level(int level); -SW_API void swoole_set_trace_flags(int flags); +SW_API void swoole_set_log_file(const char *file); +SW_API void swoole_set_trace_flags(long flags); +SW_API void swoole_set_print_backtrace_on_error(bool enable = true); +SW_API void swoole_set_stdout_stream(FILE *fp); SW_API void swoole_set_dns_server(const std::string &server); SW_API void swoole_set_hosts_path(const std::string &hosts_file); -SW_API std::pair swoole_get_dns_server(); +SW_API swoole::DnsServer swoole_get_dns_server(); SW_API bool swoole_load_resolv_conf(); SW_API void swoole_name_resolver_add(const swoole::NameResolver &resolver, bool append = true); SW_API void swoole_name_resolver_each( const std::function::iterator &iter)> &fn); SW_API std::string swoole_name_resolver_lookup(const std::string &host_name, swoole::NameResolver::Context *ctx); +SW_API int swoole_get_log_level(); +SW_API FILE *swoole_get_stdout_stream(); -//----------------------------------------------- -static sw_inline void sw_spinlock(sw_atomic_t *lock) { - uint32_t i, n; - while (1) { - if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { - return; - } - if (SW_CPU_NUM > 1) { - for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1) { - for (i = 0; i < n; i++) { - sw_atomic_cpu_pause(); - } - - if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { - return; - } - } - } - sw_yield(); - } -} +enum swEventInitFlag { + SW_EVENTLOOP_WAIT_EXIT = 1, +}; + +/** + * manually_trigger: + * Once enabled, the timer will no longer be triggered by event polling or the operating system's timer; + * instead, it will be managed directly at the user space. + */ +SW_API swoole::Timer *swoole_timer_create(bool manually_trigger = false); +SW_API long swoole_timer_after(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); +SW_API long swoole_timer_tick(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); +SW_API swoole::TimerNode *swoole_timer_add(double ms, + bool persistent, + const swoole::TimerCallback &callback, + void *private_data = nullptr); +SW_API swoole::TimerNode *swoole_timer_add(long ms, + bool persistent, + const swoole::TimerCallback &callback, + void *private_data = nullptr); +SW_API bool swoole_timer_del(swoole::TimerNode *tnode); +SW_API bool swoole_timer_exists(long timer_id); +SW_API void swoole_timer_delay(swoole::TimerNode *tnode, long delay_ms); +SW_API swoole::TimerNode *swoole_timer_get(long timer_id); +SW_API bool swoole_timer_clear(long timer_id); +SW_API void swoole_timer_free(); +SW_API void swoole_timer_select(); +SW_API int64_t swoole_timer_get_next_msec(); +SW_API bool swoole_timer_is_available(); + +SW_API int swoole_event_init(int flags); +SW_API int swoole_event_add(swoole::network::Socket *socket, int events); +SW_API int swoole_event_set(swoole::network::Socket *socket, int events); +SW_API int swoole_event_add_or_update(swoole::network::Socket *socket, int event); +SW_API int swoole_event_del(swoole::network::Socket *socket); +SW_API void swoole_event_defer(const swoole::Callback &cb, void *private_data); +SW_API ssize_t swoole_event_write(swoole::network::Socket *socket, const void *data, size_t len); +SW_API ssize_t swoole_event_writev(swoole::network::Socket *socket, const iovec *iov, size_t iovcnt); +SW_API swoole::network::Socket *swoole_event_get_socket(int fd); +SW_API int swoole_event_wait(); +SW_API int swoole_event_free(); +SW_API void swoole_event_set_handler(int fd_type, int event, swoole::ReactorHandler handler); +SW_API bool swoole_event_isset_handler(int fd_type, int event); +SW_API bool swoole_event_is_available(); +SW_API bool swoole_event_is_running(); static sw_inline swoole::String *sw_tg_buffer() { return SwooleTG.buffer_stack; @@ -806,3 +903,11 @@ static sw_inline swoole::MemoryPool *sw_mem_pool() { static sw_inline const swoole::Allocator *sw_std_allocator() { return &SwooleG.std_allocator; } + +static sw_inline swoole::Reactor *sw_reactor() { + return SwooleTG.reactor; +} + +static sw_inline swoole::Timer *sw_timer() { + return SwooleTG.timer; +} diff --git a/include/swoole_api.h b/include/swoole_api.h index 9288e1e1f3..3dfc6dc4da 100644 --- a/include/swoole_api.h +++ b/include/swoole_api.h @@ -1,62 +1,57 @@ /* - +----------------------------------------------------------------------+ - | Swoole | - +----------------------------------------------------------------------+ - | Copyright (c) 2012-2018 The Swoole Group | - +----------------------------------------------------------------------+ - | 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 | - +----------------------------------------------------------------------+ - */ - -#pragma once - -#include "swoole.h" -#include "swoole_coroutine_c_api.h" - -enum swEventInitFlag { - SW_EVENTLOOP_WAIT_EXIT = 1, + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#ifndef SW_C_API_H_ +#define SW_C_API_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "swoole_config.h" + +enum swGlobalHookType { + SW_GLOBAL_HOOK_BEFORE_SERVER_START, + SW_GLOBAL_HOOK_BEFORE_CLIENT_START, + SW_GLOBAL_HOOK_BEFORE_WORKER_START, + SW_GLOBAL_HOOK_ON_CORO_START, + SW_GLOBAL_HOOK_ON_CORO_STOP, + SW_GLOBAL_HOOK_ON_REACTOR_CREATE, + SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, + SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN, + SW_GLOBAL_HOOK_BEFORE_WORKER_STOP, + SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, + SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE, + SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, + SW_GLOBAL_HOOK_AFTER_FORK, + SW_GLOBAL_HOOK_USER = 24, + SW_GLOBAL_HOOK_END = SW_MAX_HOOK_TYPE - 1, }; -SW_API long swoole_timer_after(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); -SW_API long swoole_timer_tick(long ms, const swoole::TimerCallback &callback, void *private_data = nullptr); -SW_API swoole::TimerNode *swoole_timer_add(long ms, bool persistent, const swoole::TimerCallback &callback, - void *private_data = nullptr); -SW_API bool swoole_timer_del(swoole::TimerNode *tnode); -SW_API bool swoole_timer_exists(long timer_id); -SW_API void swoole_timer_delay(swoole::TimerNode *tnode, long delay_ms); -SW_API swoole::TimerNode *swoole_timer_get(long timer_id); -SW_API bool swoole_timer_clear(long timer_id); -SW_API void swoole_timer_free(); -SW_API int swoole_timer_select(); -SW_API bool swoole_timer_is_available(); - -SW_API int swoole_event_init(int flags); -SW_API int swoole_event_add(swoole::network::Socket *socket, int events); -SW_API int swoole_event_set(swoole::network::Socket *socket, int events); -SW_API int swoole_event_add_or_update(swoole::network::Socket *socket, int event); -SW_API int swoole_event_del(swoole::network::Socket *socket); -SW_API void swoole_event_defer(swoole::Callback cb, void *private_data); -SW_API ssize_t swoole_event_write(swoole::network::Socket *socket, const void *data, size_t len); -SW_API ssize_t swoole_event_writev(swoole::network::Socket *socket, const iovec *iov, size_t iovcnt); -SW_API swoole::network::Socket *swoole_event_get_socket(int fd); -SW_API int swoole_event_wait(); -SW_API int swoole_event_free(); -SW_API bool swoole_event_set_handler(int fdtype, swoole::ReactorHandler handler); -SW_API bool swoole_event_isset_handler(int fdtype); -SW_API bool swoole_event_is_available(); - -#ifdef __MACH__ -swoole::Reactor *sw_reactor(); -swoole::Timer *sw_timer(); -#else -#define sw_reactor() (SwooleTG.reactor) -#define sw_timer() (SwooleTG.timer) +typedef void (*swHookFunc)(void *data); + +void swoole_add_hook(swGlobalHookType type, swHookFunc cb, int push_back); +void swoole_call_hook(swGlobalHookType type, void *arg); +bool swoole_isset_hook(swGlobalHookType type); + +const char *swoole_version(void); +int swoole_version_id(void); +int swoole_api_version_id(void); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif #endif diff --git a/include/swoole_asm_context.h b/include/swoole_asm_context.h index 3ba14af593..2a73f87933 100644 --- a/include/swoole_asm_context.h +++ b/include/swoole_asm_context.h @@ -29,6 +29,11 @@ SW_EXTERN_C_BEGIN typedef void *fcontext_t; +struct transfer_t { + fcontext_t fctx; + void *data; +}; + #ifdef __GNUC__ #define SW_GCC_VERSION (__GNUC__ * 1000 + __GNUC_MINOR__) #else @@ -41,8 +46,18 @@ typedef void *fcontext_t; #define SW_INDIRECT_RETURN #endif -intptr_t swoole_jump_fcontext(fcontext_t *ofc, fcontext_t nfc, intptr_t vp, bool preserve_fpu = false); -SW_INDIRECT_RETURN fcontext_t swoole_make_fcontext(void *sp, size_t size, void (*fn)(intptr_t)); +#undef SWOOLE_CONTEXT_CALLDECL +#if (defined(i386) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i586__) || \ + defined(__i686__) || defined(__X86__) || defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || \ + defined(__INTEL__) || defined(__IA32__) || defined(_M_IX86) || defined(_I86_)) && \ + defined(BOOST_WINDOWS) +#define SWOOLE_CONTEXT_CALLDECL __cdecl +#else +#define SWOOLE_CONTEXT_CALLDECL +#endif + +transfer_t SWOOLE_CONTEXT_CALLDECL swoole_jump_fcontext(fcontext_t to, void *vp); +fcontext_t SWOOLE_CONTEXT_CALLDECL swoole_make_fcontext(void *stack, size_t stack_size, void (*fn)(transfer_t)); SW_EXTERN_C_END diff --git a/include/swoole_async.h b/include/swoole_async.h index 3386f0ad91..a60c8254f8 100644 --- a/include/swoole_async.h +++ b/include/swoole_async.h @@ -18,10 +18,9 @@ #pragma once +#include "swoole_socket.h" + #include -#include -#include -#include #ifndef O_DIRECT #define O_DIRECT 040000 @@ -34,24 +33,22 @@ enum AsyncFlag { SW_AIO_EOF = 1u << 2, }; +struct AsyncRequest { + virtual ~AsyncRequest() = default; +}; + struct AsyncEvent { - int fd; size_t task_id; - uint8_t lock; uint8_t canceled; + int error; /** * input & output */ - uint16_t flags; - off_t offset; - size_t nbytes; - void *buf; - void *req; + std::shared_ptr data; /** * output */ ssize_t retval; - int error; /** * internal use only */ @@ -61,34 +58,56 @@ struct AsyncEvent { void (*handler)(AsyncEvent *event); void (*callback)(AsyncEvent *event); - bool catch_error() { + bool catch_error() const { return (error == SW_ERROR_AIO_TIMEOUT || error == SW_ERROR_AIO_CANCELED); } }; +struct GethostbynameRequest : AsyncRequest { + std::string name; + int family; + std::string addr; + + GethostbynameRequest(std::string _name, int _family); + ~GethostbynameRequest() override = default; +}; + +struct GetaddrinfoRequest : public AsyncRequest { + std::string hostname; + std::string service; + int family; + int socktype; + int protocol; + int error; + std::vector results; + int count; + + void parse_result(std::vector &retval) const; + + GetaddrinfoRequest(std::string _hostname, int _family, int _socktype, int _protocol, std::string _service); + ~GetaddrinfoRequest() override = default; +}; + class AsyncThreads { public: - bool schedule = false; size_t task_num = 0; Pipe *pipe = nullptr; - async::ThreadPool *pool = nullptr; + std::shared_ptr pool; network::Socket *read_socket = nullptr; network::Socket *write_socket = nullptr; AsyncThreads(); ~AsyncThreads(); - size_t get_task_num() { + size_t get_task_num() const { return task_num; } - size_t get_queue_size(); - size_t get_worker_num(); - void notify_one(); + size_t get_queue_size() const; + size_t get_worker_num() const; + void notify_one() const; static int callback(Reactor *reactor, Event *event); - private: - std::mutex init_lock; }; namespace async { @@ -102,3 +121,5 @@ void handler_getaddrinfo(AsyncEvent *event); } // namespace async }; // namespace swoole + +swoole::AsyncThreads *sw_async_threads(); diff --git a/include/swoole_atomic.h b/include/swoole_atomic.h index a18929ac7e..222c3505d2 100644 --- a/include/swoole_atomic.h +++ b/include/swoole_atomic.h @@ -19,32 +19,22 @@ typedef volatile int32_t sw_atomic_int32_t; typedef volatile uint32_t sw_atomic_uint32_t; - -#if defined(__x86_64__) || defined(__aarch64__) typedef volatile int64_t sw_atomic_int64_t; typedef volatile uint64_t sw_atomic_uint64_t; -#endif -#if defined(__x86_64__) || defined(__aarch64__) typedef sw_atomic_int64_t sw_atomic_long_t; typedef sw_atomic_uint64_t sw_atomic_ulong_t; -#else -typedef sw_atomic_int32_t sw_atomic_long_t; -typedef sw_atomic_uint32_t sw_atomic_ulong_t; -#endif - typedef sw_atomic_uint32_t sw_atomic_t; #define sw_atomic_cmp_set(lock, old, set) __sync_bool_compare_and_swap(lock, old, set) +#define sw_atomic_value_cmp_set(value, expected, set) __sync_val_compare_and_swap(value, expected, set) #define sw_atomic_fetch_add(value, add) __sync_fetch_and_add(value, add) #define sw_atomic_fetch_sub(value, sub) __sync_fetch_and_sub(value, sub) #define sw_atomic_memory_barrier() __sync_synchronize() #define sw_atomic_add_fetch(value, add) __sync_add_and_fetch(value, add) #define sw_atomic_sub_fetch(value, sub) __sync_sub_and_fetch(value, sub) -#ifdef __arm__ -#define sw_atomic_cpu_pause() __asm__ __volatile__("NOP"); -#elif defined(__x86_64__) +#if defined(__x86_64__) #define sw_atomic_cpu_pause() __asm__ __volatile__("pause") #elif defined(__aarch64__) #define sw_atomic_cpu_pause() __asm__ __volatile__("yield") @@ -52,8 +42,7 @@ typedef sw_atomic_uint32_t sw_atomic_t; #define sw_atomic_cpu_pause() #endif -#if 0 +void sw_spinlock(sw_atomic_t *lock); #define sw_spinlock_release(lock) __sync_lock_release(lock) -#else -#define sw_spinlock_release(lock) *(lock) = 0 -#endif +int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout); +int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n); diff --git a/include/swoole_base64.h b/include/swoole_base64.h index eadc13e06d..ec2db3a349 100644 --- a/include/swoole_base64.h +++ b/include/swoole_base64.h @@ -16,14 +16,14 @@ #pragma once -#include -#include -#include +#include +#include +#include #define BASE64_ENCODE_OUT_SIZE(s) (((s) + 2) / 3 * 4) #define BASE64_DECODE_OUT_SIZE(s) (((s)) / 4 * 3) namespace swoole { - size_t base64_encode(const unsigned char *in, size_t inlen, char *out); - size_t base64_decode(const char *in, size_t inlen, char *out); -} +size_t base64_encode(const unsigned char *in, size_t inlen, char *out); +size_t base64_decode(const char *in, size_t inlen, char *out); +} // namespace swoole diff --git a/include/swoole_buffer.h b/include/swoole_buffer.h index 4219c57f3e..5d0dd58008 100644 --- a/include/swoole_buffer.h +++ b/include/swoole_buffer.h @@ -29,54 +29,55 @@ struct BufferChunk { }; Type type; - uint32_t length; - uint32_t offset; + uint32_t length = 0; + uint32_t offset = 0; union { - char *ptr; - void *object; - struct { - uint32_t val1; - uint32_t val2; - } data; - } value; - uint32_t size; - void (*destroy)(BufferChunk *chunk); + char *str; + void *ptr; + uint32_t u32; + uint64_t u64; + } value{}; + uint32_t size = 0; + + BufferChunk(Type type, uint32_t size); + ~BufferChunk(); + + void (*destroy)(BufferChunk *chunk) = nullptr; }; class Buffer { - private: - // 0: donot use chunk + // 0: don't use chunk uint32_t chunk_size; uint32_t total_length = 0; std::queue queue_; public: - Buffer(uint32_t chunk_size); + explicit Buffer(uint32_t _chunk_size); ~Buffer(); BufferChunk *alloc(BufferChunk::Type type, uint32_t size); - BufferChunk *front() { + BufferChunk *front() const { return queue_.front(); } void pop(); - void append(const void *data, uint32_t size); + void append(const char *data, uint32_t size); void append(const struct iovec *iov, size_t iovcnt, off_t offset); - uint32_t length() { + uint32_t length() const { return total_length; } - size_t count() { + size_t count() const { return queue_.size(); } - bool empty() { + bool empty() const { return queue_.empty(); } - static bool empty(Buffer *buffer) { + static bool empty(const Buffer *buffer) { return buffer == nullptr || buffer->queue_.empty(); } }; diff --git a/include/swoole_c_api.h b/include/swoole_c_api.h deleted file mode 100644 index 28e2783906..0000000000 --- a/include/swoole_c_api.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 | - +----------------------------------------------------------------------+ -*/ - -#ifndef SW_C_API_H_ -#define SW_C_API_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#include "swoole_config.h" - -enum swGlobalHookType { - SW_GLOBAL_HOOK_BEFORE_SERVER_START, - SW_GLOBAL_HOOK_BEFORE_CLIENT_START, - SW_GLOBAL_HOOK_BEFORE_WORKER_START, - SW_GLOBAL_HOOK_ON_CORO_START, - SW_GLOBAL_HOOK_ON_CORO_STOP, - SW_GLOBAL_HOOK_ON_REACTOR_CREATE, - SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, - SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN, - SW_GLOBAL_HOOK_BEFORE_WORKER_STOP, - SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, - SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE, - SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, - SW_GLOBAL_HOOK_USER = 24, - SW_GLOBAL_HOOK_END = SW_MAX_HOOK_TYPE - 1, -}; - -typedef void (*swHookFunc)(void *data); - -int swoole_add_function(const char *name, void *func); -void *swoole_get_function(const char *name, uint32_t length); - -int swoole_add_hook(enum swGlobalHookType type, swHookFunc cb, int push_back); -void swoole_call_hook(enum swGlobalHookType type, void *arg); -bool swoole_isset_hook(enum swGlobalHookType type); - -const char *swoole_version(void); -int swoole_version_id(void); -int swoole_api_version_id(void); - -#ifdef __cplusplus -} /* end extern "C" */ -#endif -#endif diff --git a/include/swoole_channel.h b/include/swoole_channel.h index 44d95e6dff..d2ccc26063 100644 --- a/include/swoole_channel.h +++ b/include/swoole_channel.h @@ -19,7 +19,6 @@ #pragma once -#include "swoole.h" #include "swoole_lock.h" namespace swoole { @@ -51,25 +50,25 @@ struct Channel { Lock *lock; Pipe *notify_pipe; - inline bool empty() { + bool empty() const { return num == 0; } - inline bool full() { + bool full() const { return ((head == tail && tail_tag != head_tag) || (bytes + sizeof(int) * num == size)); } int pop(void *out_buf, int buffer_length); int push(const void *in_data, int data_length); int out(void *out_buf, int buffer_length); int in(const void *in_data, int data_length); - int peek(void *out, int buffer_length); - int wait(); - int notify(); + int peek(void *out, int buffer_length) const; + int wait() const; + int notify() const; void destroy(); - void print(); - inline int count() { + void print() const; + int count() const { return num; } - inline int get_bytes() { + size_t get_bytes() const { return bytes; } static Channel *make(size_t size, size_t maxlen, int flags); diff --git a/include/swoole_client.h b/include/swoole_client.h index c888a1ff38..3be52ce4ba 100644 --- a/include/swoole_client.h +++ b/include/swoole_client.h @@ -16,40 +16,45 @@ #pragma once -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_protocol.h" #include "swoole_proxy.h" -#define SW_HTTPS_PROXY_HANDSHAKE_RESPONSE "HTTP/1.1 200 Connection established" - namespace swoole { namespace network { class Client { + int (*connect_)(Client *cli, const char *host, int port, double timeout, int sock_flag) = nullptr; + ssize_t (*send_)(Client *cli, const char *data, size_t length, int flags) = nullptr; + int (*sendfile_)(Client *cli, const char *filename, off_t offset, size_t length) = nullptr; + ssize_t (*recv_)(Client *cli, char *data, size_t length, int flags) = nullptr; + public: int id = 0; long timeout_id = 0; // timeout node id - int _sock_type = 0; - int _sock_domain = 0; - int _protocol = 0; + int sock_type_ = 0; + int sock_domain_ = 0; + int sock_flags_ = 0; + int protocol_ = 0; FdType fd_type; bool active = false; bool async = false; bool keep = false; - bool destroyed = false; bool http2 = false; bool sleep_ = false; bool wait_dns = false; - bool shutdow_rw = false; + bool dns_completed = false; + bool host_preseted = false; + bool shutdown_rw = false; bool shutdown_read = false; bool shutdown_write = false; bool remove_delay = false; bool closed = false; bool high_watermark = false; bool async_connect = false; + bool onerror_called = false; /** * one package: length check @@ -58,33 +63,25 @@ class Client { bool open_eof_check = false; Protocol protocol = {}; - Socks5Proxy *socks5_proxy = nullptr; - HttpProxy *http_proxy = nullptr; + std::unique_ptr socks5_proxy = nullptr; + std::unique_ptr http_proxy = nullptr; uint32_t reuse_count = 0; - const char *server_str = nullptr; - const char *server_host = nullptr; + std::string server_id; + std::string server_host; int server_port = 0; void *ptr = nullptr; void *params = nullptr; - uint8_t server_strlen = 0; - double timeout = 0; TimerNode *timer = nullptr; /** - * signal interruption - */ - double interrupt_time = 0; - - /** - * sendto, read only. + * for connect()/sendto() */ Address server_addr = {}; - /** - * recvfrom + * for recvfrom() */ Address remote_addr = {}; @@ -99,52 +96,149 @@ class Client { uint32_t buffer_high_watermark = 0; uint32_t buffer_low_watermark = 0; -#ifdef SW_USE_OPENSSL bool open_ssl = false; bool ssl_wait_handshake = false; std::shared_ptr ssl_context = nullptr; -#endif - - std::function onConnect = nullptr; - std::function onError = nullptr; - std::function onReceive = nullptr; - std::function onClose = nullptr; - std::function onBufferFull = nullptr; - std::function onBufferEmpty = nullptr; - int (*connect)(Client *cli, const char *host, int port, double _timeout, int sock_flag) = nullptr; - ssize_t (*send)(Client *cli, const char *data, size_t length, int flags) = nullptr; - int (*sendfile)(Client *cli, const char *filename, off_t offset, size_t length) = nullptr; - ssize_t (*recv)(Client *cli, char *data, size_t length, int flags) = nullptr; + std::function onConnect = nullptr; + std::function onError = nullptr; + std::function onReceive = nullptr; + std::function onClose = nullptr; + std::function onBufferFull = nullptr; + std::function onBufferEmpty = nullptr; static void init_reactor(Reactor *reactor); - Client(SocketType type, bool async); + Client(SocketType _type, bool async); ~Client(); - void set_http_proxy(const std::string &host, int port) { - http_proxy = new swoole::HttpProxy; - http_proxy->proxy_host = host; - http_proxy->proxy_port = port; + Socket *get_socket() const { + return socket; } - Socket *get_socket() { - return socket; + bool ready() const { + return socket != nullptr; + } + + SocketType get_socket_type() const { + return socket->socket_type; + } + + const std::string &get_http_proxy_host_name() const { + if (ssl_context && !ssl_context->tls_host_name.empty()) { + return ssl_context->tls_host_name; + } + return http_proxy->target_host; + } + + int connect(const char *_host, int _port, double _timeout = -1, int _sock_flag = 0) { + return connect_(this, _host, _port, _timeout, _sock_flag); + } + + ssize_t send(const char *_data, size_t _length, int _flags = 0) { + return send_(this, _data, _length, _flags); } + int sendfile(const char *_filename, off_t _offset = 0, size_t _length = 0) { + return sendfile_(this, _filename, _offset, _length); + } + + ssize_t recv(char *_data, size_t _length, int _flags = 0) { + return recv_(this, _data, _length, _flags); + } + + int bind(const std::string &addr, int port) const; int sleep(); int wakeup(); - int shutdown(int __how); + int sendto(const std::string &host, int port, const char *data, size_t len) const; + int get_peer_name(Address *addr) const; + int shutdown(int _how = SHUT_RDWR); int close(); - void destroy(); - int socks5_handshake(const char *recv_data, size_t length); -#ifdef SW_USE_OPENSSL + bool socks5_handshake(const char *recv_data, size_t length); + void set_timeout(double timeout, TimeoutType type = SW_TIMEOUT_ALL) const; + bool has_timedout() const; + void set_socks5_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); + void set_http_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); + int enable_ssl_encrypt(); #ifdef SW_SUPPORT_DTLS void enable_dtls(); #endif - int ssl_handshake(); - int ssl_verify(int allow_self_signed); + int ssl_handshake() const; + int ssl_verify(int allow_self_signed) const; + + bool set_ssl_key_file(const std::string &file) const { + return ssl_context->set_key_file(file); + } + + bool set_ssl_cert_file(const std::string &file) const { + return ssl_context->set_cert_file(file); + } + + void set_ssl_cafile(const std::string &file) const { + ssl_context->cafile = file; + } + + void set_ssl_capath(const std::string &path) const { + ssl_context->capath = path; + } + + void set_ssl_passphrase(const std::string &str) const { + ssl_context->passphrase = str; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + void set_tls_host_name(const std::string &str) const { + ssl_context->tls_host_name = str; + // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically + ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); + } +#endif + + void set_ssl_dhparam(const std::string &file) const { + ssl_context->dhparam = file; + } + + void set_ssl_ecdh_curve(const std::string &str) const { + ssl_context->ecdh_curve = str; + } + + void set_ssl_protocols(long protocols) const { + ssl_context->protocols = protocols; + } + + void set_ssl_disable_compress(bool value) const { + ssl_context->disable_compress = value; + } + + void set_ssl_verify_peer(bool value) const { + ssl_context->verify_peer = value; + } + + void set_ssl_allow_self_signed(bool value) const { + ssl_context->allow_self_signed = value; + } + + void set_ssl_verify_depth(uint8_t value) const { + ssl_context->verify_depth = value; + } + + void set_ssl_ciphers(const std::string &str) const { + ssl_context->ciphers = str; + } + +#ifdef OPENSSL_IS_BORINGSSL + void set_ssl_grease(uint8_t value) const { + ssl_context->grease = value; + } #endif + + const std::string &get_ssl_cert_file() const { + return ssl_context->cert_file; + } + + const std::string &get_ssl_key_file() const { + return ssl_context->key_file; + } }; //----------------------------------------Stream--------------------------------------- @@ -163,18 +257,10 @@ class Stream { int send(const char *data, size_t length); void set_max_length(uint32_t max_length); - - inline static Stream *create(const char *dst_host, int dst_port, SocketType type) { - Stream *stream = new Stream(dst_host, dst_port, type); - if (!stream->connected) { - delete stream; - return nullptr; - } else { - return stream; - } - } ~Stream(); - static ssize_t recv_blocking(Socket *sock, void *__buf, size_t __len); + + static Stream *create(const char *dst_host, int dst_port, SocketType type); + static ssize_t recv_sync(Socket *sock, void *_buf, size_t _len); static void set_protocol(Protocol *protocol); private: @@ -191,41 +277,55 @@ class SyncClient { SocketType type; public: - SyncClient(SocketType _type, bool _async = false) : client(_type, _async), async(_async), type(_type) { + explicit SyncClient(SocketType _type, bool _async = false) : client(_type, _async), async(_async), type(_type) { created = client.socket != nullptr; } - bool connect(const char *host, int port, double timeout = -1) { + virtual bool connect(const char *host, int port, double timeout = -1) { if (connected || !created) { return false; } - if (client.connect(&client, host, port, timeout, client.socket->is_dgram()) < 0) { + if (client.connect(host, port, timeout, client.socket->is_dgram()) < 0) { return false; } connected = true; return true; } -#ifdef SW_USE_OPENSSL + void set_stream_protocol() { + client.open_length_check = true; + Stream::set_protocol(&client.protocol); + } + + void set_package_max_length(uint32_t max_length) { + client.protocol.package_max_length = max_length; + } + bool enable_ssl_encrypt() { - if (client.enable_ssl_encrypt() < 0 || client.ssl_handshake() < 0) { + if (client.enable_ssl_encrypt() < 0) { return false; + } + if (connected) { + return client.ssl_handshake() == SW_OK; } else { return true; } } -#endif ssize_t send(const std::string &data) { - return client.send(&client, data.c_str(), data.length(), 0); + return client.send(data.c_str(), data.length(), 0); } ssize_t send(const char *buf, size_t len) { - return client.send(&client, buf, len, 0); + return client.send(buf, len, 0); } - ssize_t recv(char *buf, size_t len) { - return client.recv(&client, buf, len, 0); + bool sendfile(const char *filename, off_t offset = 0, size_t length = 0) { + return client.sendfile(filename, offset, length) == SW_OK; + } + + ssize_t recv(char *buf, size_t len, int flags = 0) { + return client.recv(buf, len, flags); } bool close() { @@ -237,6 +337,10 @@ class SyncClient { return true; } + Client *get_client() { + return &client; + } + virtual ~SyncClient() { if (created) { close(); @@ -252,42 +356,42 @@ class AsyncClient : public SyncClient { std::function _onReceive = nullptr; public: - AsyncClient(SocketType _type) : SyncClient(_type, true) {} + explicit AsyncClient(SocketType _type) : SyncClient(_type, true) {} - bool connect(const char *host, int port, double timeout = -1) { + bool connect(const char *host, int port, double timeout = -1) override { client.object = this; client.onConnect = [](Client *cli) { - AsyncClient *ac = (AsyncClient *) cli->object; + auto *ac = (AsyncClient *) cli->object; ac->_onConnect(ac); }; client.onError = [](Client *cli) { - AsyncClient *ac = (AsyncClient *) cli->object; + auto *ac = (AsyncClient *) cli->object; ac->_onError(ac); }; client.onClose = [](Client *cli) { - AsyncClient *ac = (AsyncClient *) cli->object; + auto *ac = (AsyncClient *) cli->object; ac->_onClose(ac); }; client.onReceive = [](Client *cli, const char *data, size_t length) { - AsyncClient *ac = (AsyncClient *) cli->object; + auto *ac = (AsyncClient *) cli->object; ac->_onReceive(ac, data, length); }; return SyncClient::connect(host, port, timeout); } - void on_connect(std::function fn) { + void on_connect(const std::function &fn) { _onConnect = fn; } - void on_error(std::function fn) { + void on_error(const std::function &fn) { _onError = fn; } - void on_close(std::function fn) { + void on_close(const std::function &fn) { _onClose = fn; } - void on_receive(std::function fn) { + void on_receive(const std::function &fn) { _onReceive = fn; } }; diff --git a/include/swoole_config.h b/include/swoole_config.h index 0120dbfd95..8a9b8fa101 100644 --- a/include/swoole_config.h +++ b/include/swoole_config.h @@ -37,23 +37,17 @@ #endif #define SW_MALLOC_TRIM_INTERVAL 60 #define SW_MALLOC_TRIM_PAD 0 -#define SW_USE_MONOTONIC_TIME 1 #define SW_MAX_SOCKETS_DEFAULT 1024 -#define SW_SOCKET_OVERFLOW_WAIT 100 -#define SW_SOCKET_MAX_DEFAULT 65536 -#if defined(__MACH__) || defined(__FreeBSD__) -#define SW_SOCKET_BUFFER_SIZE 262144 -#else #define SW_SOCKET_BUFFER_SIZE 8388608 -#endif #define SW_SOCKET_RETRY_COUNT 10 #define SW_SOCKET_DEFAULT_DNS_TIMEOUT 60 -#define SW_SOCKET_DEFAULT_CONNECT_TIMEOUT 2 +#define SW_SOCKET_DEFAULT_CONNECT_TIMEOUT 10 #define SW_SOCKET_DEFAULT_READ_TIMEOUT 60 #define SW_SOCKET_DEFAULT_WRITE_TIMEOUT 60 +#define SW_SOCKET_CORK_MIN_SIZE 65536 #define SW_SYSTEMD_FDS_START 3 @@ -63,12 +57,10 @@ #define SW_MAX_WORKER_NCPU 1000 // n * cpu_num #define SW_HOST_MAXSIZE \ - sizeof(((struct sockaddr_un *) NULL)->sun_path) // Linux has 108 UNIX_PATH_MAX, but BSD/MacOS limit is only 104 + sizeof(((struct sockaddr_un *) NULL)->sun_path) // Linux has 108 UNIX_PATH_MAX, but BSD/macOS limit is only 104 -#define SW_LOG_NO_SRCINFO 1 // no source info #define SW_CLIENT_BUFFER_SIZE 65536 #define SW_CLIENT_CONNECT_TIMEOUT 0.5 -#define SW_CLIENT_MAX_PORT 65535 // !!!Don't modify.---------------------------------------------------------- #ifdef __MACH__ @@ -78,6 +70,7 @@ #endif #define SW_IPC_BUFFER_MAX_SIZE (64 * 1024) #define SW_IPC_BUFFER_SIZE (SW_IPC_MAX_SIZE - sizeof(swoole::DataHead)) +#define SW_IPC_MSG_MIN (2048 - sizeof(swoole::DataHead)) // !!!End.------------------------------------------------------------------- #define SW_BUFFER_SIZE_STD 8192 @@ -85,10 +78,6 @@ #define SW_BUFFER_SIZE_UDP 65536 #define SW_SENDFILE_CHUNK_SIZE 65536 -#define SW_SENDFILE_MAXLEN 4194304 - -#define SW_HASHMAP_KEY_MAXLEN 256 -#define SW_HASHMAP_INIT_BUCKET_N 32 // hashmap bucket num (default value for init) #define SW_DATA_EOF "\r\n\r\n" #define SW_DATA_EOF_MAXLEN 8 @@ -98,18 +87,8 @@ #define SW_AIO_THREAD_NUM_MULTIPLE 8 #define SW_AIO_THREAD_MAX_IDLE_TIME 1.0 #define SW_AIO_TASK_MAX_WAIT_TIME 0.001 -#define SW_AIO_MAX_FILESIZE (4 * 1024 * 1024) #define SW_AIO_EVENT_NUM 128 -#define SW_AIO_DEFAULT_CHUNK_SIZE 65536 -#define SW_AIO_MAX_CHUNK_SIZE (1 * 1024 * 1024) -#define SW_AIO_MAX_EVENTS 128 -#define SW_AIO_HANDLER_MAX_SIZE 8 -#define SW_THREADPOOL_QUEUE_LEN 10000 -#define SW_IP_MAX_LENGTH 46 -#define SW_WORKER_WAIT_TIMEOUT 1000 - -#define SW_WORKER_USE_SIGNALFD 1 #define SW_WORKER_MAX_WAIT_TIME 3 #define SW_WORKER_MIN_REQUEST 10 #define SW_WORKER_MAX_RECV_CHUNK_COUNT 32 @@ -119,8 +98,7 @@ #define SW_SESSION_LIST_SIZE (1 * 1024 * 1024) #define SW_MSGMAX 65536 - -#define SW_DGRAM_HEADER_SIZE 32 +#define SW_MESSAGE_BOX_SIZE 65536 /** * The maximum number of Reactor threads @@ -129,24 +107,6 @@ */ #define SW_REACTOR_MAX_THREAD 8 -/** - * Loops read data from the pipeline, - * helping to alleviate pipeline cache congestion - * reduce the pressure of interprocess communication - */ -#define SW_REACTOR_RECV_AGAIN 1 - -/** - * RINGBUFFER - */ -#define SW_RINGQUEUE_LEN 1024 -#define SW_RINGBUFFER_FREE_N_MAX 4 // when free_n > MAX, execute collect -#define SW_RINGBUFFER_WARNING 100 - -/** - * ringbuffer memory pool size - */ -#define SW_OUTPUT_BUFFER_SIZE (2 * 1024 * 1024) #define SW_INPUT_BUFFER_SIZE (2 * 1024 * 1024) #define SW_BUFFER_MIN_SIZE 65536 #define SW_SEND_BUFFER_SIZE 65536 @@ -163,9 +123,6 @@ #define SW_TCP_KEEPIDLE 3600 // 1 hour #define SW_TCP_KEEPINTERVAL 60 -#define SW_USE_EVENTFD \ - 1 // Whether to use eventfd for message notification, Linux 2.6.22 or later is required to support - #define SW_TASK_TMP_PATH_SIZE 256 #define SW_TASK_TMP_DIR "/tmp" #define SW_TASK_TMP_FILE "swoole.task.XXXXXX" @@ -180,11 +137,8 @@ #define SW_SSL_ECDH_CURVE "auto" #define SW_SPINLOCK_LOOP_N 1024 - -#define SW_STRING_BUFFER_MAXLEN (1024 * 1024 * 128) -#define SW_STRING_BUFFER_DEFAULT 128 -#define SW_STRING_BUFFER_GARBAGE_MIN (1024 * 64) -#define SW_STRING_BUFFER_GARBAGE_RATIO 4 +#define SW_FILE_LOCK_DEFAULT_SECOND 0.001 +#define SW_FILE_LOCK_MAX_SECOND 0.1 #define SW_SIGNO_MAX 128 #define SW_UNREGISTERED_SIGNAL_FMT "Unable to find callback function for signal %s" @@ -200,12 +154,18 @@ #define IOV_MAX 16 #endif -#define IOV_MAX_ERROR_MSG "The maximum of iov count is %d" +#define SW_IOV_MAX_ERROR_MSG "The maximum of iov count is %d" + +#define SW_IOURING_QUEUE_SIZE 8192 +#define SW_IOURING_SQE_BATCH_SIZE 16 /** * HTTP Protocol */ #define SW_HTTP_SERVER_SOFTWARE "swoole-http-server" +#define SW_HTTP_SERVER_BOUNDARY_PREKEY "SwooleBoundary" +#define SW_HTTP_SERVER_BOUNDARY_TOTAL_SIZE 39 +#define SW_HTTP_SERVER_PART_HEADER 256 #define SW_HTTP_PARAM_MAX_NUM 128 #define SW_HTTP_FORM_KEYLEN 512 #define SW_HTTP_RESPONSE_INIT_SIZE 65536 @@ -220,14 +180,15 @@ #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_MAX_APPEND_DATA 16384 -// #define SW_HTTP_100_CONTINUE #define SW_HTTP_100_CONTINUE_PACKET "HTTP/1.1 100 Continue\r\n\r\n" #define SW_HTTP_BAD_REQUEST_PACKET "HTTP/1.1 400 Bad Request\r\n\r\n" #define SW_HTTP_REQUEST_ENTITY_TOO_LARGE_PACKET "HTTP/1.1 413 Request Entity Too Large\r\n\r\n" #define SW_HTTP_SERVICE_UNAVAILABLE_PACKET "HTTP/1.1 503 Service Unavailable\r\n\r\n" -#define SW_HTTP_PAGE_CSS "" -#define SW_HTTP_POWER_BY "
Powered by Swoole
" +#define SW_HTTP_POWER_BY "
Powered by Swoole
" -#define SW_HTTP_PAGE_400 "" SW_HTTP_PAGE_CSS "

HTTP 400 Bad Request

" SW_HTTP_POWER_BY "" +#define SW_HTTP_PAGE_400 \ + "" SW_HTTP_PAGE_CSS "

HTTP 400 Bad Request

" SW_HTTP_POWER_BY "" #define SW_HTTP_PAGE_404 "" SW_HTTP_PAGE_CSS "

HTTP 404 Not Found

" SW_HTTP_POWER_BY "" -#define SW_HTTP_PAGE_500 "" SW_HTTP_PAGE_CSS "

HTTP 500 Internal Server Error

" SW_HTTP_POWER_BY "" +#define SW_HTTP_PAGE_500 \ + "" SW_HTTP_PAGE_CSS "

HTTP 500 Internal Server Error

" SW_HTTP_POWER_BY "" /** * HTTP2 Protocol */ #define SW_HTTP2_DATA_BUFFER_SIZE 8192 #define SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE (1 << 12) -#define SW_HTTP2_MAX_MAX_CONCURRENT_STREAMS 128 -#define SW_HTTP2_MAX_MAX_FRAME_SIZE ((1u << 14)) -#define SW_HTTP2_MAX_WINDOW_SIZE ((1u << 31) - 1) -#define SW_HTTP2_DEFAULT_WINDOW_SIZE 65535 -#define SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE (1 << 12) -#define SW_HTTP2_MAX_MAX_HEADER_LIST_SIZE UINT32_MAX +#define SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS UINT_MAX +#define SW_HTTP2_DEFAULT_ENABLE_PUSH 0 +#define SW_HTTP2_DEFAULT_MAX_FRAME_SIZE (1u << 14) +#define SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE ((1 << 16) - 1) +#define SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE UINT_MAX #define SW_HTTP_CLIENT_USERAGENT "swoole-http-client" #define SW_HTTP_CLIENT_BOUNDARY_PREKEY "----SwooleBoundary" @@ -283,11 +245,11 @@ a { color: #0969da; } \ * Coroutine */ #define SW_DEFAULT_C_STACK_SIZE (2 * 1024 * 1024) -#define SW_CORO_SUPPORT_BAILOUT 1 -#define SW_CORO_SWAP_BAILOUT 1 #define SW_CORO_BAILOUT_EXIT_CODE 1 -//#define SW_CONTEXT_PROTECT_STACK_PAGE 1 -//#define SW_CONTEXT_DETECT_STACK_USAGE 1 +#if 0 +#define SW_CONTEXT_PROTECT_STACK_PAGE 1 +#define SW_CONTEXT_DETECT_STACK_USAGE 1 +#endif #ifdef SW_DEBUG #ifndef SW_LOG_TRACE_OPEN diff --git a/include/swoole_coroutine.h b/include/swoole_coroutine.h index fb3a011b4f..9e490bce28 100644 --- a/include/swoole_coroutine.h +++ b/include/swoole_coroutine.h @@ -17,7 +17,6 @@ #pragma once -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_reactor.h" @@ -27,11 +26,14 @@ #include "swoole_coroutine_context.h" -#include +#include #include #include #include +#ifdef SW_USE_THREAD_CONTEXT +#include +#endif typedef std::chrono::microseconds seconds_type; @@ -70,7 +72,7 @@ class Coroutine { typedef void (*SwapCallback)(void *); typedef std::function BailoutCallback; - typedef std::function CancelFunc; + typedef std::function CancelFunc; void resume(); void yield(); @@ -79,31 +81,31 @@ class Coroutine { bool yield_ex(double timeout = -1); - inline enum State get_state() const { + enum State get_state() const { return state; } - inline long get_init_msec() const { + long get_init_msec() const { return init_msec; } - inline long get_cid() const { + long get_cid() const { return cid; } - inline Coroutine *get_origin() { + Coroutine *get_origin() const { return origin; } - inline long get_origin_cid() { + long get_origin_cid() const { return sw_likely(origin) ? origin->get_cid() : -1; } - inline void *get_task() { + void *get_task() const { return task; } - inline bool is_end() { + bool is_end() const { return ctx.is_end(); } @@ -119,7 +121,7 @@ class Coroutine { return state == STATE_WAITING; } - inline void set_task(void *_task) { + void set_task(void *_task) { task = _task; } @@ -127,29 +129,26 @@ class Coroutine { cancel_fn_ = cancel_fn; } - inline long get_execute_usec() const { + long get_execute_usec() const { return time(true) - switch_usec + execute_usec; } - static std::unordered_map coroutines; + coroutine::Context &get_ctx() { + return ctx; + } + + static SW_THREAD_LOCAL std::unordered_map coroutines; static void set_on_yield(SwapCallback func); static void set_on_resume(SwapCallback func); static void set_on_close(SwapCallback func); - static void bailout(BailoutCallback func); + static void bailout(const BailoutCallback &func); - static inline bool run(const CoroutineFunc &fn, void *args = nullptr) { - swoole_event_init(SW_EVENTLOOP_WAIT_EXIT); - long cid = create(fn, args); - swoole_event_wait(); - return cid > 0; - } - - static inline long create(const CoroutineFunc &fn, void *args = nullptr) { + static long create(const CoroutineFunc &fn, void *args = nullptr) { #ifdef SW_USE_THREAD_CONTEXT try { return (new Coroutine(fn, args))->run(); - } catch (const std::system_error& e) { + } catch (const std::system_error &e) { swoole_set_last_error(e.code().value()); swoole_warning("failed to create coroutine, Error: %s[%d]", e.what(), swoole_get_last_error()); return -1; @@ -162,88 +161,90 @@ class Coroutine { static void activate(); static void deactivate(); - static inline Coroutine *get_current() { + static Coroutine *get_current() { return current; } - static inline Coroutine *get_current_safe() { + static Coroutine *get_current_safe() { if (sw_unlikely(!current)) { swoole_fatal_error(SW_ERROR_CO_OUT_OF_COROUTINE, "API must be called in the coroutine"); } return current; } - static inline void *get_current_task() { + static void *get_current_task() { return sw_likely(current) ? current->get_task() : nullptr; } - static inline long get_current_cid() { + static long get_current_cid() { return sw_likely(current) ? current->get_cid() : -1; } - static inline Coroutine *get_by_cid(long cid) { + static Coroutine *get_by_cid(long cid) { auto i = coroutines.find(cid); return sw_likely(i != coroutines.end()) ? i->second : nullptr; } - static inline void *get_task_by_cid(long cid) { + static void *get_task_by_cid(long cid) { Coroutine *co = get_by_cid(cid); return sw_likely(co) ? co->get_task() : nullptr; } - static inline size_t get_stack_size() { + static size_t get_stack_size() { return stack_size; } - static inline void set_stack_size(size_t size) { + static void set_stack_size(size_t size) { stack_size = SW_MEM_ALIGNED_SIZE_EX(SW_MAX(MIN_STACK_SIZE, SW_MIN(size, MAX_STACK_SIZE)), STACK_ALIGNED_SIZE); } - static inline long get_last_cid() { + static void set_socket_bound_cid(long cid) { + socket_bound_cid = cid; + } + + static long get_last_cid() { return last_cid; } - static inline size_t count() { + static long get_socket_bound_cid() { + return socket_bound_cid; + } + + static size_t count() { return coroutines.size(); } - static inline uint64_t get_peak_num() { + static uint64_t get_peak_num() { return peak_num; } - static inline long get_elapsed(long cid) { + static long get_elapsed(long cid) { Coroutine *co = cid == 0 ? get_current() : get_by_cid(cid); return sw_likely(co) ? Timer::get_absolute_msec() - co->get_init_msec() : -1; } - static inline long get_execute_time(long cid) { + static long get_execute_time(long cid) { Coroutine *co = cid == 0 ? get_current() : get_by_cid(cid); return sw_likely(co) ? co->get_execute_usec() : -1; } - static inline void calc_execute_usec(Coroutine *yield_coroutine, Coroutine *resume_coroutine) { - long current_usec = time(true); - if (yield_coroutine) { - yield_coroutine->execute_usec += current_usec - yield_coroutine->switch_usec; - } - - if (resume_coroutine) { - resume_coroutine->switch_usec = current_usec; - } - } - +#ifdef SW_CORO_TIME + static void calc_execute_usec(Coroutine *yield_coroutine, Coroutine *resume_coroutine); +#endif static void print_list(); + static void print_socket_bound_error(int sock_fd, const char *event_str, long bound_cid); protected: - static Coroutine *current; - static long last_cid; - static uint64_t peak_num; - static size_t stack_size; - static SwapCallback on_yield; /* before yield */ - static SwapCallback on_resume; /* before resume */ - static SwapCallback on_close; /* before close */ - static BailoutCallback on_bailout; /* when bailout */ - static bool activated; + static SW_THREAD_LOCAL Coroutine *current; + static SW_THREAD_LOCAL long last_cid; + static SW_THREAD_LOCAL long socket_bound_cid; + static SW_THREAD_LOCAL uint64_t peak_num; + static SW_THREAD_LOCAL size_t stack_size; + static SW_THREAD_LOCAL SwapCallback on_yield; /* before yield */ + static SW_THREAD_LOCAL SwapCallback on_resume; /* before resume */ + static SW_THREAD_LOCAL SwapCallback on_close; /* before close */ + static SW_THREAD_LOCAL BailoutCallback on_bailout; /* when bailout */ + static SW_THREAD_LOCAL bool activated; enum State state = STATE_INIT; enum ResumeCode resume_code_ = RC_OK; @@ -256,41 +257,32 @@ class Coroutine { Coroutine *origin = nullptr; CancelFunc *cancel_fn_ = nullptr; - Coroutine(const CoroutineFunc &fn, void *private_data) : ctx(stack_size, fn, private_data) { - cid = ++last_cid; - coroutines[cid] = this; - if (sw_unlikely(count() > peak_num)) { - peak_num = count(); - } - } - - inline long run() { - long cid = this->cid; - origin = current; - current = this; - CALC_EXECUTE_USEC(origin, nullptr); - ctx.swap_in(); - check_end(); - return cid; - } - - inline void check_end() { - if (ctx.is_end()) { - close(); - } else if (sw_unlikely(on_bailout)) { - SW_ASSERT(current == nullptr); - on_bailout(); - exit(SW_CORO_BAILOUT_EXIT_CODE); - } - } - + Coroutine(const CoroutineFunc &fn, void *private_data); + long run(); + void check_end(); void close(); }; //------------------------------------------------------------------------------- namespace coroutine { +/** + * Support for timeouts and cancellations requires the caller to store the memory pointers of + * the input and output parameter objects in the `data` pointer of the `AsyncEvent` object. + * This field is a `shared_ptr`, which increments the reference count when dispatched to the AIO thread, + * collectively managing the `data` pointer. + * When the async task is completed, the caller receives the results or cancels or timeouts, + * the reference count will reach zero, and the memory will be released. + */ bool async(async::Handler handler, AsyncEvent &event, double timeout = -1); -bool async(const std::function &fn, double timeout = -1); +/** + * This function should be used for asynchronous operations that do not support cancellation and timeouts. + * For example, in write/read operations, + * asynchronous tasks cannot transfer the memory ownership of wbuf/rbuf to the AIO thread. + * In the event of a timeout or cancellation, the memory of wbuf/rbuf will be released by the caller, + * which may lead the AIO thread to read from an erroneous memory pointer and consequently crash. + */ +bool async(const std::function &fn); bool run(const CoroutineFunc &fn, void *arg = nullptr); +bool wait_for(const std::function &fn); } // namespace coroutine //------------------------------------------------------------------------------- } // namespace swoole diff --git a/include/swoole_coroutine_api.h b/include/swoole_coroutine_api.h new file mode 100644 index 0000000000..3d7cf540fb --- /dev/null +++ b/include/swoole_coroutine_api.h @@ -0,0 +1,124 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#ifndef SW_COROUTINE_API_H_ +#define SW_COROUTINE_API_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * basic API + */ +long swoole_coroutine_create(void (*routine)(void *), void *arg); +uint8_t swoole_coroutine_is_in(void); +long swoole_coroutine_get_id(void); +void swoole_coroutine_sleep(int sec); +void swoole_coroutine_usleep(int usec); + +/** + * file + */ +int swoole_coroutine_access(const char *pathname, int mode); +int swoole_coroutine_open(const char *pathname, int flags, mode_t mode); +ssize_t swoole_coroutine_read(int fd, void *buf, size_t count); +ssize_t swoole_coroutine_write(int fd, const void *buf, size_t count); +off_t swoole_coroutine_lseek(int fd, off_t offset, int whence); +int swoole_coroutine_fstat(int fd, struct stat *statbuf); +int swoole_coroutine_stat(const char *path, struct stat *statbuf); +int swoole_coroutine_lstat(const char *path, struct stat *statbuf); +ssize_t swoole_coroutine_readlink(const char *pathname, char *buf, size_t len); +int swoole_coroutine_unlink(const char *pathname); +int swoole_coroutine_mkdir(const char *pathname, mode_t mode); +int swoole_coroutine_rmdir(const char *pathname); +int swoole_coroutine_rename(const char *oldpath, const char *newpath); +int swoole_coroutine_flock(int fd, int operation); +int swoole_coroutine_statvfs(const char *path, struct statvfs *buf); +int swoole_coroutine_close(int fd); +int swoole_coroutine_fsync(int fd); +int swoole_coroutine_fdatasync(int fd); +int swoole_coroutine_ftruncate(int fd, off_t length); + +/** + * stdio + */ +FILE *swoole_coroutine_fopen(const char *pathname, const char *mode); +FILE *swoole_coroutine_fdopen(int fd, const char *mode); +FILE *swoole_coroutine_freopen(const char *pathname, const char *mode, FILE *stream); +size_t swoole_coroutine_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +size_t swoole_coroutine_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +char *swoole_coroutine_fgets(char *s, int size, FILE *stream); +int swoole_coroutine_fputs(const char *s, FILE *stream); +int swoole_coroutine_fflush(FILE *stream); +int swoole_coroutine_feof(FILE *stream); +int swoole_coroutine_fclose(FILE *stream); + +/** + * dir + */ +DIR *swoole_coroutine_opendir(const char *name); +struct dirent *swoole_coroutine_readdir(DIR *dirp); +int swoole_coroutine_closedir(DIR *dirp); + +/** + * socket API + * The `bind()` and `listen()` functions do not block IO waiting, + * so the original functions can be used directly without the need for hooks. + */ +int swoole_coroutine_socket(int domain, int type, int protocol); +int swoole_coroutine_socket_create(int fd); +int swoole_coroutine_socket_unwrap(int fd); +uint8_t swoole_coroutine_socket_exists(int fd); +ssize_t swoole_coroutine_send(int sockfd, const void *buf, size_t len, int flags); +ssize_t swoole_coroutine_sendmsg(int sockfd, const struct msghdr *msg, int flags); +ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t swoole_coroutine_recvmsg(int sockfd, struct msghdr *msg, int flags); +int swoole_coroutine_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int swoole_coroutine_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout); +int swoole_coroutine_poll_fake(struct pollfd *fds, nfds_t nfds, int timeout); +int swoole_coroutine_socket_set_timeout(int fd, int which, double timeout); +int swoole_coroutine_socket_set_connect_timeout(int fd, double timeout); +int swoole_coroutine_socket_wait_event(int fd, int event, double timeout); +int swoole_coroutine_getaddrinfo(const char *name, + const char *service, + const struct addrinfo *req, + struct addrinfo **pai); +struct hostent *swoole_coroutine_gethostbyname(const char *name); + +/** + * wait + */ +size_t swoole_coroutine_wait_count(void); +pid_t swoole_coroutine_waitpid(pid_t _pid, int *_stat_loc, int _options); +pid_t swoole_coroutine_wait(int *_stat_loc); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif +#endif diff --git a/include/swoole_coroutine_c_api.h b/include/swoole_coroutine_c_api.h deleted file mode 100644 index 84e1ef1458..0000000000 --- a/include/swoole_coroutine_c_api.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 | - +----------------------------------------------------------------------+ -*/ - -#ifndef SW_COROUTINE_API_H_ -#define SW_COROUTINE_API_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * base - */ -uint8_t swoole_coroutine_is_in(void); -long swoole_coroutine_get_current_id(void); -void swoole_coroutine_sleep(int sec); -void swoole_coroutine_usleep(int usec); -/** - * file - */ -int swoole_coroutine_access(const char *pathname, int mode); -int swoole_coroutine_open(const char *pathname, int flags, mode_t mode); -ssize_t swoole_coroutine_read(int fd, void *buf, size_t count); -ssize_t swoole_coroutine_write(int fd, const void *buf, size_t count); -off_t swoole_coroutine_lseek(int fd, off_t offset, int whence); -int swoole_coroutine_fstat(int fd, struct stat *statbuf); -int swoole_coroutine_readlink(const char *pathname, char *buf, size_t len); -int swoole_coroutine_unlink(const char *pathname); -int swoole_coroutine_mkdir(const char *pathname, mode_t mode); -int swoole_coroutine_rmdir(const char *pathname); -int swoole_coroutine_rename(const char *oldpath, const char *newpath); -int swoole_coroutine_flock(int fd, int operation); -int swoole_coroutine_flock_ex(const char *filename, int fd, int operation); -int swoole_coroutine_statvfs(const char *path, struct statvfs *buf); -/** - * stdio - */ -FILE *swoole_coroutine_fopen(const char *pathname, const char *mode); -FILE *swoole_coroutine_fdopen(int fd, const char *mode); -FILE *swoole_coroutine_freopen(const char *pathname, const char *mode, FILE *stream); -size_t swoole_coroutine_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); -size_t swoole_coroutine_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); -char *swoole_coroutine_fgets(char *s, int size, FILE *stream); -int swoole_coroutine_fputs(const char *s, FILE *stream); -int swoole_coroutine_feof(FILE *stream); -int swoole_coroutine_fclose(FILE *stream); -/** - * dir - */ -DIR *swoole_coroutine_opendir(const char *name); -struct dirent *swoole_coroutine_readdir(DIR *dirp); -int swoole_coroutine_closedir(DIR *dirp); -/** - * socket - */ -int swoole_coroutine_socket(int domain, int type, int protocol); -int swoole_coroutine_socket_create(int fd); -uint8_t swoole_coroutine_socket_exists(int fd); -ssize_t swoole_coroutine_send(int sockfd, const void *buf, size_t len, int flags); -ssize_t swoole_coroutine_sendmsg(int sockfd, const struct msghdr *msg, int flags); -ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags); -ssize_t swoole_coroutine_recvmsg(int sockfd, struct msghdr *msg, int flags); -int swoole_coroutine_close(int fd); -int swoole_coroutine_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); -int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout); -int swoole_coroutine_socket_set_timeout(int fd, int which, double timeout); -int swoole_coroutine_socket_set_connect_timeout(int fd, double timeout); -int swoole_coroutine_socket_wait_event(int fd, int event, double timeout); -int swoole_coroutine_getaddrinfo(const char *name, - const char *service, - const struct addrinfo *req, - struct addrinfo **pai); -struct hostent *swoole_coroutine_gethostbyname(const char *name); -/** - * wait - */ -size_t swoole_coroutine_wait_count(void); -pid_t swoole_coroutine_waitpid(pid_t __pid, int *__stat_loc, int __options); -pid_t swoole_coroutine_wait(int *__stat_loc); - -#ifdef __cplusplus -} /* end extern "C" */ -#endif -#endif diff --git a/include/swoole_coroutine_channel.h b/include/swoole_coroutine_channel.h index 1dec97f656..fbcc17dd66 100644 --- a/include/swoole_coroutine_channel.h +++ b/include/swoole_coroutine_channel.h @@ -17,11 +17,8 @@ #pragma once -#include "swoole.h" #include "swoole_coroutine.h" -#include - #include #include #include @@ -56,7 +53,7 @@ class Channel { bool push(void *data, double timeout = -1); bool close(); - Channel(size_t _capacity = 1) : capacity(_capacity) {} + explicit Channel(size_t _capacity = 1) : capacity(_capacity) {} ~Channel() { if (!producer_queue.empty()) { @@ -73,32 +70,32 @@ class Channel { } } - inline bool is_closed() { + bool is_closed() const { return closed; } - inline bool is_empty() { - return data_queue.size() == 0; + bool is_empty() const { + return data_queue.empty(); } - inline bool is_full() { + bool is_full() const { return data_queue.size() == capacity; } - inline size_t length() { + size_t length() const { return data_queue.size(); } - inline size_t consumer_num() { + size_t consumer_num() const { return consumer_queue.size(); } - inline size_t producer_num() { + size_t producer_num() const { return producer_queue.size(); } - inline void *pop_data() { - if (data_queue.size() == 0) { + void *pop_data() { + if (data_queue.empty()) { return nullptr; } void *data = data_queue.front(); @@ -106,7 +103,7 @@ class Channel { return data; } - int get_error() { + int get_error() const { return error_; } @@ -120,17 +117,17 @@ class Channel { static void timer_callback(Timer *timer, TimerNode *tnode); - void yield(enum Opcode type); + void yield(Opcode type); - inline void consumer_remove(Coroutine *co) { + void consumer_remove(Coroutine *co) { consumer_queue.remove(co); } - inline void producer_remove(Coroutine *co) { + void producer_remove(Coroutine *co) { producer_queue.remove(co); } - inline Coroutine *pop_coroutine(enum Opcode type) { + Coroutine *pop_coroutine(Opcode type) { Coroutine *co; if (type == PRODUCER) { co = producer_queue.front(); diff --git a/include/swoole_coroutine_context.h b/include/swoole_coroutine_context.h index fcdeeef348..25df2c1def 100644 --- a/include/swoole_coroutine_context.h +++ b/include/swoole_coroutine_context.h @@ -42,6 +42,11 @@ typedef ucontext_t coroutine_context_t; #elif defined(USE_ASM_CONTEXT) typedef fcontext_t coroutine_context_t; +typedef transfer_t coroutine_transfer_t; +#endif + +#if defined(USE_UCONTEXT) || defined(SW_USE_THREAD_CONTEXT) +typedef void *coroutine_transfer_t; #endif typedef std::function CoroutineFunc; @@ -51,16 +56,26 @@ namespace coroutine { class Context { public: - Context(size_t stack_size, const CoroutineFunc &fn, void *private_data); + Context(size_t stack_size, CoroutineFunc fn, void *private_data); ~Context(); bool swap_in(); bool swap_out(); #if !defined(SW_USE_THREAD_CONTEXT) && defined(SW_CONTEXT_DETECT_STACK_USAGE) ssize_t get_stack_usage(); #endif - inline bool is_end() { +#ifndef SW_USE_THREAD_CONTEXT + char *get_stack() const { + return stack_; + } + + size_t get_stack_size() const { + return stack_size_; + } +#endif + bool is_end() const { return end_; } + protected: CoroutineFunc fn_; #ifdef SW_USE_THREAD_CONTEXT @@ -79,7 +94,7 @@ class Context { void *private_data_; bool end_; - static void context_func(void *arg); + static void context_func(coroutine_transfer_t arg); }; } // namespace coroutine diff --git a/include/swoole_coroutine_socket.h b/include/swoole_coroutine_socket.h index d248d95924..4f0058a198 100644 --- a/include/swoole_coroutine_socket.h +++ b/include/swoole_coroutine_socket.h @@ -17,8 +17,6 @@ #pragma once -#include "swoole.h" -#include "swoole_api.h" #include "swoole_socket.h" #include "swoole_coroutine.h" #include "swoole_protocol.h" @@ -46,63 +44,67 @@ class Socket { bool http2 = false; Protocol protocol = {}; - Socks5Proxy *socks5_proxy = nullptr; - HttpProxy *http_proxy = nullptr; - - enum TimeoutType { - TIMEOUT_DNS = 1 << 0, - TIMEOUT_CONNECT = 1 << 1, - TIMEOUT_READ = 1 << 2, - TIMEOUT_WRITE = 1 << 3, - TIMEOUT_RDWR = TIMEOUT_READ | TIMEOUT_WRITE, - TIMEOUT_ALL = TIMEOUT_DNS | TIMEOUT_CONNECT | TIMEOUT_RDWR, - }; + std::unique_ptr socks5_proxy = nullptr; + std::unique_ptr http_proxy = nullptr; - static enum TimeoutType timeout_type_list[4]; + static TimeoutType timeout_type_list[4]; Socket(int domain, int type, int protocol); Socket(int _fd, int _domain, int _type, int _protocol); - Socket(SocketType type = SW_SOCK_TCP); + explicit Socket(SocketType type = SW_SOCK_TCP); Socket(int _fd, SocketType _type); - ~Socket(); - bool connect(std::string host, int port, int flags = 0); - bool connect(const struct sockaddr *addr, socklen_t addrlen); + virtual ~Socket(); + /** + * If SSL is enabled, an SSL handshake will automatically take place during the connect() method. + * When connect() returns true, it indicates that the TCP connection has been successfully + * established and the SSL handshake has also succeeded. + */ + bool connect(const std::string &host, int port = 0, int flags = 0); + virtual bool connect(const sockaddr *addr, socklen_t addrlen); bool shutdown(int how = SHUT_RDWR); - bool cancel(const EventType event); + bool cancel(EventType event); bool close(); - inline bool is_connected() { - return connected && !closed; + bool is_connected() const { + return connected && !is_closed(); } - bool is_closed() { - return closed; + bool is_closed() const { + return sock_fd == SW_BAD_SOCKET; + } + + bool is_port_required() const { + return type <= SW_SOCK_UDP6; } bool check_liveness(); - ssize_t peek(void *__buf, size_t __n); - ssize_t recv(void *__buf, size_t __n); - ssize_t send(const void *__buf, size_t __n); + ssize_t peek(void *_buf, size_t _n); + virtual ssize_t recv(void *_buf, size_t _n); + virtual ssize_t send(const void *_buf, size_t _n); - inline ssize_t send(const std::string &buf) { + ssize_t send(const std::string &buf) { return send(buf.c_str(), buf.length()); } - ssize_t read(void *__buf, size_t __n); - ssize_t write(const void *__buf, size_t __n); - ssize_t readv(network::IOVector *io_vector); - ssize_t readv_all(network::IOVector *io_vector); - ssize_t writev(network::IOVector *io_vector); - ssize_t writev_all(network::IOVector *io_vector); - ssize_t recvmsg(struct msghdr *msg, int flags); - ssize_t sendmsg(const struct msghdr *msg, int flags); - ssize_t recv_all(void *__buf, size_t __n); - ssize_t send_all(const void *__buf, size_t __n); + /** + * The read()/write()/recvmsg()/sendmsg() functions currently does not support SSL + */ + virtual ssize_t read(void *_buf, size_t _n); + virtual ssize_t write(const void *_buf, size_t _n); + virtual ssize_t recvmsg(msghdr *msg, int flags); + virtual ssize_t sendmsg(const msghdr *msg, int flags); + + virtual ssize_t readv(network::IOVector *io_vector); + virtual ssize_t readv_all(network::IOVector *io_vector); + virtual ssize_t writev(network::IOVector *io_vector); + virtual ssize_t writev_all(network::IOVector *io_vector); + virtual ssize_t recv_all(void *_buf, size_t _n); + virtual ssize_t send_all(const void *_buf, size_t _n); ssize_t recv_packet(double timeout = 0); - ssize_t recv_line(void *__buf, size_t maxlen); - ssize_t recv_with_buffer(void *__buf, size_t __n); + ssize_t recv_line(void *_buf, size_t maxlen); + ssize_t recv_with_buffer(void *_buf, size_t _n); - inline char *pop_packet() { + char *pop_packet() const { if (read_buffer->offset == 0) { return nullptr; } else { @@ -110,100 +112,176 @@ class Socket { } } - bool poll(EventType type); + virtual bool poll(EventType _type, double timeout = 0); + /** + * If the server has SSL enabled, you must explicitly call `ssl_handshake()`, + * as it will not be automatically executed within the `accept()` function. + * This behavior is inconsistent with `connect()`, which internally executes `ssl_handshake()` automatically, + * thus not requiring an explicit call at the application level. + * The reason for this design is that `ssl_handshake()` can typically be performed concurrently within a separate + * client coroutine. If `ssl_handshake()` were to be automatically executed inside the `accept()` function, + * it would block the server's listening coroutine, + * causing the `ssl_handshake()` processes to execute sequentially rather than in parallel. + */ Socket *accept(double timeout = 0); - bool bind(std::string address, int port = 0); - bool bind(const struct sockaddr *sa, socklen_t len); + bool bind(const std::string &address, int port = 0); + bool bind(const sockaddr *sa, socklen_t len); bool listen(int backlog = 0); - bool sendfile(const char *filename, off_t offset, size_t length); - ssize_t sendto(const std::string &host, int port, const void *__buf, size_t __n); - ssize_t recvfrom(void *__buf, size_t __n); - ssize_t recvfrom(void *__buf, size_t __n, struct sockaddr *_addr, socklen_t *_socklen); + virtual bool sendfile(const char *filename, off_t offset, size_t length); + virtual ssize_t sendto(const std::string &host, int port, const void *_buf, size_t _n); + ssize_t recvfrom(void *_buf, size_t _n); + virtual ssize_t recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen); -#ifdef SW_USE_OPENSSL /** * Operation sequence: * 1. enable_ssl_encrypt() * 2. Set SSL parameters, such as certificate file, key file - * 3. ssl_check_context() - * 4. ssl_accept()/ssl_connect()/ssl_handshake() + * 3. ssl_handshake(), to be executed after connect or accept */ bool enable_ssl_encrypt() { if (ssl_context.get()) { return false; } - ssl_context.reset(new SSLContext()); + ssl_context = std::make_shared(); return true; } - bool ssl_is_enable() { + bool ssl_is_enable() const { return get_ssl_context() != nullptr; } - SSLContext *get_ssl_context() { + SSLContext *get_ssl_context() const { return ssl_context.get(); } - bool ssl_check_context(); - bool ssl_handshake(); + virtual bool ssl_handshake(); bool ssl_verify(bool allow_self_signed); std::string ssl_get_peer_cert(); + + bool set_ssl_key_file(const std::string &file) const { + return ssl_context->set_key_file(file); + } + + bool set_ssl_cert_file(const std::string &file) const { + return ssl_context->set_cert_file(file); + } + + void set_ssl_cafile(const std::string &file) const { + ssl_context->cafile = file; + } + + void set_ssl_capath(const std::string &path) const { + ssl_context->capath = path; + } + + void set_ssl_passphrase(const std::string &str) const { + ssl_context->passphrase = str; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + void set_tls_host_name(const std::string &str) const { + ssl_context->tls_host_name = str; + // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically + ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); + } #endif + void set_ssl_dhparam(const std::string &file) const { + ssl_context->dhparam = file; + } + + void set_ssl_ecdh_curve(const std::string &str) const { + ssl_context->ecdh_curve = str; + } + + void set_ssl_protocols(long protocols) const { + ssl_context->protocols = protocols; + } + + void set_ssl_disable_compress(bool value) const { + ssl_context->disable_compress = value; + } + + void set_ssl_verify_peer(bool value) const { + ssl_context->verify_peer = value; + } + + void set_ssl_allow_self_signed(bool value) const { + ssl_context->allow_self_signed = value; + } + + void set_ssl_verify_depth(uint8_t value) const { + ssl_context->verify_depth = value; + } + + void set_ssl_ciphers(const std::string &str) const { + ssl_context->ciphers = str; + } + +#ifdef OPENSSL_IS_BORINGSSL + void set_ssl_grease(uint8_t value) { + ssl_context->grease = value; + } +#endif + + const std::string &get_ssl_cert_file() const { + return ssl_context->cert_file; + } + + const std::string &get_ssl_key_file() const { + return ssl_context->key_file; + } + static inline void init_reactor(Reactor *reactor) { - reactor->set_handler(SW_FD_CO_SOCKET | SW_EVENT_READ, readable_event_callback); - reactor->set_handler(SW_FD_CO_SOCKET | SW_EVENT_WRITE, writable_event_callback); - reactor->set_handler(SW_FD_CO_SOCKET | SW_EVENT_ERROR, error_event_callback); + reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_READ, readable_event_callback); + reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_WRITE, writable_event_callback); + reactor->set_handler(SW_FD_CO_SOCKET, SW_EVENT_ERROR, error_event_callback); } - inline SocketType get_type() { + SocketType get_type() const { return type; } - inline FdType get_fd_type() { + FdType get_fd_type() const { return socket->fd_type; } - inline int get_sock_domain() { + int get_sock_domain() const { return sock_domain; } - inline int get_sock_type() { + int get_sock_type() const { return sock_type; } - inline int get_sock_protocol() { + int get_sock_protocol() const { return sock_protocol; } - inline int get_fd() { + int get_fd() const { return sock_fd; } - inline int get_bind_port() { - return bind_port; - } - - inline network::Socket *get_socket() { + network::Socket *get_socket() const { return socket; } - bool getsockname(network::Address *sa); + bool getsockname() const; bool getpeername(network::Address *sa); - inline const char *get_ip() { - return socket->info.get_ip(); + const char *get_addr() const { + return socket->get_addr(); } - inline int get_port() { - return socket->info.get_port(); + int get_port() const { + return socket->get_port(); } - inline bool has_bound(const EventType event = SW_EVENT_RDWR) { + bool has_bound(const EventType event = SW_EVENT_RDWR) const { return get_bound_co(event) != nullptr; } - inline Coroutine *get_bound_co(const EventType event) { + Coroutine *get_bound_co(const EventType event) const { if (event & SW_EVENT_READ) { if (read_co) { return read_co; @@ -217,151 +295,84 @@ class Socket { return nullptr; } - inline long get_bound_cid(const EventType event = SW_EVENT_RDWR) { + long get_bound_cid(const EventType event = SW_EVENT_RDWR) const { Coroutine *co = get_bound_co(event); return co ? co->get_cid() : 0; } - const char *get_event_str(const EventType event) { - if (event == SW_EVENT_READ) { - return "reading"; - } else if (event == SW_EVENT_WRITE) { - return "writing"; - } else { - return read_co && write_co ? "reading or writing" : (read_co ? "reading" : "writing"); - } - } + const char *get_event_str(EventType event) const; - inline void check_bound_co(const EventType event) { - long cid = get_bound_cid(event); - if (sw_unlikely(cid)) { - swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, - "Socket#%d has already been bound to another coroutine#%ld, " - "%s of the same socket in coroutine#%ld at the same time is not allowed", - sock_fd, - cid, - get_event_str(event), - Coroutine::get_current_cid()); + void check_bound_co(const EventType event) const { + auto bound_cid = get_bound_cid(event); + if (sw_unlikely(bound_cid)) { + Coroutine::print_socket_bound_error(sock_fd, get_event_str(event), bound_cid); } } - inline void set_err(int e) { + void set_err(const int e) { errCode = errno = e; swoole_set_last_error(errCode); errMsg = e ? swoole_strerror(e) : ""; } - inline void set_err(int e, const char *s) { + void set_err() { + errCode = swoole_get_last_error() ? swoole_get_last_error() : errno; + errMsg = swoole_strerror(errCode); + } + + void set_err(const int e, const char *s) { errCode = errno = e; swoole_set_last_error(errCode); errMsg = s; } - inline void set_err(int e, std::string s) { + void set_err(const int e, const std::string &s) { errCode = errno = e; swoole_set_last_error(errCode); errString = s; errMsg = errString.c_str(); } - /* set connect read write timeout */ - inline void set_timeout(double timeout, int type = TIMEOUT_ALL) { - if (timeout == 0) { - return; - } - if (type & TIMEOUT_DNS) { - dns_timeout = timeout; - } - if (type & TIMEOUT_CONNECT) { - connect_timeout = timeout; - } - if (type & TIMEOUT_READ) { - read_timeout = timeout; - } - if (type & TIMEOUT_WRITE) { - write_timeout = timeout; - } - } - - inline void set_timeout(struct timeval *timeout, int type = TIMEOUT_ALL) { - set_timeout((double) timeout->tv_sec + ((double) timeout->tv_usec / 1000 / 1000), type); - } - - inline double get_timeout(enum TimeoutType type = TIMEOUT_ALL) { - SW_ASSERT_1BYTE(type); - if (type == TIMEOUT_DNS) { - return dns_timeout; - } else if (type == TIMEOUT_CONNECT) { - return connect_timeout; - } else if (type == TIMEOUT_READ) { - return read_timeout; - } else if (type == TIMEOUT_WRITE) { - return write_timeout; - } else { - assert(0); - return -1; - } + const char *get_err() { + return swoole_strerror(errCode); } - inline bool set_option(int level, int optname, int optval) { - if (socket->set_option(level, optname, optval) < 0) { - swoole_sys_warning("setsockopt(%d, %d, %d, %d) failed", sock_fd, level, optname, optval); - return false; - } - return true; - } + /* set connect read write timeout */ + void set_timeout(double timeout, int _type = SW_TIMEOUT_ALL) const; - inline String *get_read_buffer() { - if (sw_unlikely(!read_buffer)) { - read_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); - if (!read_buffer) { - throw std::bad_alloc(); - } - } - return read_buffer; + void set_timeout(const timeval *timeout, int _type = SW_TIMEOUT_ALL) const { + set_timeout((double) timeout->tv_sec + ((double) timeout->tv_usec / 1000 / 1000), _type); } - inline String *get_write_buffer() { - if (sw_unlikely(!write_buffer)) { - write_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); - if (!write_buffer) { - throw std::bad_alloc(); - } - } - return write_buffer; - } + double get_timeout(TimeoutType _type) const; + bool get_option(int level, int optname, void *optval, socklen_t *optlen) const; + bool get_option(int level, int optname, int *optval) const; + bool set_option(int level, int optname, const void *optval, socklen_t optlen) const; + bool set_option(int level, int optname, int optval) const; + void set_socks5_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); + void set_http_proxy(const std::string &host, int port, const std::string &user = "", const std::string &pwd = ""); + String *get_read_buffer(); + String *get_write_buffer(); + String *pop_read_buffer(); + String *pop_write_buffer(); void set_resolve_context(NameResolver::Context *ctx) { resolve_context_ = ctx; } - inline String *pop_read_buffer() { - if (sw_unlikely(!read_buffer)) { - return nullptr; - } - auto tmp = read_buffer; - read_buffer = nullptr; - return tmp; - } - - inline String *pop_write_buffer() { - if (sw_unlikely(!write_buffer)) { - return nullptr; - } - auto tmp = write_buffer; - write_buffer = nullptr; - return tmp; + void set_dtor(const std::function &dtor) { + dtor_ = dtor; } - inline void set_zero_copy(bool enable) { + void set_zero_copy(bool enable) { zero_copy = enable; } - inline void set_buffer_allocator(const Allocator *allocator) { + void set_buffer_allocator(const Allocator *allocator) { buffer_allocator = allocator; } - inline void set_buffer_init_size(size_t size) { + void set_buffer_init_size(size_t size) { if (size == 0) { return; } @@ -369,9 +380,8 @@ class Socket { } int move_fd() { - int sockfd = socket->fd; - socket->fd = -1; - return sockfd; + sock_fd = SW_BAD_SOCKET; + return socket->move_fd(); } network::Socket *move_socket() { @@ -380,19 +390,17 @@ class Socket { return _socket; } -#ifdef SW_USE_OPENSSL - inline bool ssl_is_available() { + bool ssl_is_available() const { return socket && ssl_handshaked; } - SSL *get_ssl() { + SSL *get_ssl() const { return socket->ssl; } - bool ssl_shutdown(); -#endif + void ssl_close() const; - private: + protected: SocketType type; network::Socket *socket = nullptr; int sock_domain = 0; @@ -402,21 +410,13 @@ class Socket { Coroutine *read_co = nullptr; Coroutine *write_co = nullptr; -#ifdef SW_USE_OPENSSL + EventType want_event = SW_EVENT_NULL; -#endif std::string connect_host; int connect_port = 0; - - std::string bind_address; - int bind_port = 0; int backlog = 0; - double dns_timeout = network::Socket::default_dns_timeout; - double connect_timeout = network::Socket::default_connect_timeout; - double read_timeout = network::Socket::default_read_timeout; - double write_timeout = network::Socket::default_write_timeout; TimerNode *read_timer = nullptr; TimerNode *write_timer = nullptr; @@ -424,35 +424,36 @@ class Socket { size_t buffer_init_size = SW_BUFFER_SIZE_BIG; String *read_buffer = nullptr; String *write_buffer = nullptr; - network::Address bind_address_info = {}; EventBarrier *recv_barrier = nullptr; EventBarrier *send_barrier = nullptr; -#ifdef SW_USE_OPENSSL bool ssl_is_server = false; bool ssl_handshaked = false; std::shared_ptr ssl_context = nullptr; std::string ssl_host_name; + bool ssl_context_create(); bool ssl_create(SSLContext *ssl_context); -#endif bool connected = false; bool shutdown_read = false; bool shutdown_write = false; - bool closed = false; bool zero_copy = false; - Socket(network::Socket *sock, Socket *socket); + NameResolver::Context *resolve_context_ = nullptr; + std::function dtor_; + + Socket(network::Socket *sock, const Socket *server_sock); static void timer_callback(Timer *timer, TimerNode *tnode); static int readable_event_callback(Reactor *reactor, Event *event); static int writable_event_callback(Reactor *reactor, Event *event); static int error_event_callback(Reactor *reactor, Event *event); - inline void init_sock_type(SocketType _type); - inline bool init_sock(); + void init_sock_type(SocketType _type); + bool init_sock(); + bool reinit_sock(SocketType _type); bool init_reactor_socket(int fd); void check_return_value(ssize_t retval) { @@ -463,10 +464,13 @@ class Socket { } } - inline void init_options() { - if (type == SW_SOCK_TCP || type == SW_SOCK_TCP6) { + void init_options() { + if (socket->is_tcp()) { set_option(IPPROTO_TCP, TCP_NODELAY, 1); } + if (socket->is_udp()) { + socket->set_buffer_size(network::Socket::default_buffer_size); + } protocol.package_length_type = 'N'; protocol.package_length_size = 4; protocol.package_length_offset = 0; @@ -474,52 +478,44 @@ class Socket { protocol.package_max_length = SW_INPUT_BUFFER_SIZE; } - bool add_event(const EventType event); - bool wait_event(const EventType event, const void **__buf = nullptr, size_t __n = 0); - bool try_connect(); + bool add_event(EventType event); + bool wait_event(EventType event, const void **_buf = nullptr, size_t _n = 0); ssize_t recv_packet_with_length_protocol(); ssize_t recv_packet_with_eof_protocol(); - NameResolver::Context *resolve_context_ = nullptr; - - inline bool is_available(const EventType event) { + bool is_available(const EventType event) { if (event != SW_EVENT_NULL) { check_bound_co(event); } - if (sw_unlikely(closed)) { - set_err(ECONNRESET); + if (sw_unlikely(is_closed())) { + set_err(EBADF); + return false; + } + if (sw_unlikely(socket->close_wait)) { + set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); return false; } return true; } bool socks5_handshake(); + + const std::string &get_http_proxy_host_name() const { + if (ssl_context && !ssl_context->tls_host_name.empty()) { + return ssl_context->tls_host_name; + } + return http_proxy->target_host; + } + bool http_proxy_handshake(); class TimerController { public: - TimerController(TimerNode **timer_pp, double timeout, Socket *sock, TimerCallback callback) - : timer_pp(timer_pp), timeout(timeout), socket_(sock), callback(callback) {} - bool start() { - if (timeout != 0 && !*timer_pp) { - enabled = true; - if (timeout > 0) { - *timer_pp = swoole_timer_add((long) (timeout * 1000), false, callback, socket_); - return *timer_pp != nullptr; - } - *timer_pp = (TimerNode *) -1; - } - return true; - } - ~TimerController() { - if (enabled && *timer_pp) { - if (*timer_pp != (TimerNode *) -1) { - swoole_timer_del(*timer_pp); - } - *timer_pp = nullptr; - } - } + TimerController(TimerNode **_timer_pp, double _timeout, Socket *_socket, TimerCallback _callback) + : timer_pp(_timer_pp), timeout(_timeout), socket_(_socket), callback(std::move(_callback)) {} + bool start(); + ~TimerController(); private: bool enabled = false; @@ -532,60 +528,21 @@ class Socket { public: class TimeoutSetter { public: - TimeoutSetter(Socket *socket, double timeout, const enum TimeoutType type) - : socket_(socket), timeout(timeout), type(type) { - if (timeout == 0) { - return; - } - for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { - if (type & timeout_type_list[i]) { - original_timeout[i] = socket->get_timeout(timeout_type_list[i]); - if (timeout != original_timeout[i]) { - socket->set_timeout(timeout, timeout_type_list[i]); - } - } - } - } - ~TimeoutSetter() { - if (timeout == 0) { - return; - } - for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { - if (type & timeout_type_list[i]) { - if (timeout != original_timeout[i]) { - socket_->set_timeout(original_timeout[i], timeout_type_list[i]); - } - } - } - } + TimeoutSetter(Socket *socket, double _timeout, TimeoutType _type); + ~TimeoutSetter(); protected: Socket *socket_; double timeout; - enum TimeoutType type; + TimeoutType type; double original_timeout[sizeof(timeout_type_list)] = {}; }; - class timeout_controller : public TimeoutSetter { + class TimeoutController : public TimeoutSetter { public: - timeout_controller(Socket *socket, double timeout, const enum TimeoutType type) - : TimeoutSetter(socket, timeout, type) {} - inline bool has_timedout(const enum TimeoutType type) { - SW_ASSERT_1BYTE(type); - if (timeout > 0) { - if (sw_unlikely(startup_time == 0)) { - startup_time = microtime(); - } else { - double used_time = microtime() - startup_time; - if (sw_unlikely(timeout - used_time < SW_TIMER_MIN_SEC)) { - socket_->set_err(ETIMEDOUT); - return true; - } - socket_->set_timeout(timeout - used_time, type); - } - } - return false; - } + TimeoutController(Socket *_socket, double _timeout, const TimeoutType _type) + : TimeoutSetter(_socket, _timeout, _type) {} + bool has_timedout(TimeoutType _type); protected: double startup_time = 0; @@ -593,14 +550,13 @@ class Socket { }; class ProtocolSwitch { - private: bool ori_open_eof_check; bool ori_open_length_check; Protocol ori_protocol; Socket *socket_; public: - ProtocolSwitch(Socket *socket) { + explicit ProtocolSwitch(Socket *socket) { ori_open_eof_check = socket->open_eof_check; ori_open_length_check = socket->open_length_check; ori_protocol = socket->protocol; @@ -624,5 +580,3 @@ std::string get_ip_by_hosts(const std::string &domain); //------------------------------------------------------------------------------- } // namespace coroutine } // namespace swoole - -swoole::coroutine::Socket *swoole_coroutine_get_socket_object(int sockfd); diff --git a/include/swoole_coroutine_system.h b/include/swoole_coroutine_system.h index 2a05536077..f1dc95cc17 100644 --- a/include/swoole_coroutine_system.h +++ b/include/swoole_coroutine_system.h @@ -18,7 +18,6 @@ #pragma once #include "swoole_coroutine.h" -#include "swoole_file.h" #include @@ -26,12 +25,12 @@ namespace swoole { namespace coroutine { //------------------------------------------------------------------------------- struct PollSocket { - int16_t events; + int events; int16_t revents; void *ptr; network::Socket *socket; - PollSocket(int16_t _event, void *_ptr) { + PollSocket(int _event, void *_ptr) { events = _event; ptr = _ptr; revents = 0; @@ -46,7 +45,7 @@ class System { static int sleep(double sec); /* file */ static std::shared_ptr read_file(const char *file, bool lock = false); - static ssize_t write_file(const char *file, char *buf, size_t length, bool lock = 0, int flags = 0); + static ssize_t write_file(const char *file, const char *buf, size_t length, bool lock = false, int flags = 0); /* dns */ static std::string gethostbyname(const std::string &hostname, int domain, double timeout = -1); static std::vector getaddrinfo(const std::string &hostname, @@ -58,15 +57,28 @@ class System { static void set_dns_cache_expire(time_t expire); static void set_dns_cache_capacity(size_t capacity); static void clear_dns_cache(); + static float get_dns_cache_hit_ratio(); /* multiplexing */ static bool socket_poll(std::unordered_map &fds, double timeout); /* wait */ - static pid_t wait(int *__stat_loc, double timeout = -1); - static pid_t waitpid(pid_t __pid, int *__stat_loc, int __options, double timeout = -1); + static pid_t wait(int *_stat_loc, double timeout = -1); + static pid_t waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout = -1); + /** + * waitpid_safe() does not deps on the signal + * and can be safely used in a multithreaded environment. + */ + static pid_t waitpid_safe(pid_t _pid, int *_stat_loc, int _options); /* signal */ - static bool wait_signal(int signo, double timeout = -1); + static int wait_signal(int signal, double timeout = -1); + static int wait_signal(const std::vector &signals, double timeout = -1); /* event */ + + /** + * On failure, it returns -1, and you can use swoole_get_last_error() or errno to determine the cause of the + * failure. If successful, it returns the event set, such as SW_EVENT_READ | SW_EVENT_WRITE. + */ static int wait_event(int fd, int events, double timeout); + static bool exec(const char *command, bool get_error_stream, std::shared_ptr buffer, int *status); }; std::string gethostbyname_impl_with_async(const std::string &hostname, int domain, double timeout = -1); //------------------------------------------------------------------------------- diff --git a/include/swoole_dtls.h b/include/swoole_dtls.h index 221be59d23..0f4a28e8ed 100644 --- a/include/swoole_dtls.h +++ b/include/swoole_dtls.h @@ -16,7 +16,6 @@ #pragma once -#include "swoole_api.h" #include "swoole_ssl.h" #ifdef SW_SUPPORT_DTLS @@ -32,8 +31,8 @@ int BIO_read(BIO *b, char *data, int dlen); long BIO_ctrl(BIO *b, int cmd, long larg, void *pargs); int BIO_create(BIO *b); int BIO_destroy(BIO *b); -BIO_METHOD *BIO_get_methods(void); -void BIO_meth_free(void); +BIO_METHOD *BIO_get_methods(); +void BIO_meth_free(); struct Buffer { uint16_t length; @@ -41,13 +40,13 @@ struct Buffer { }; struct Session { - SSLContext *ctx; + std::shared_ptr ctx; bool listened = false; Socket *socket; std::deque rxqueue; bool peek_mode = false; - Session(Socket *_sock, SSLContext *_ctx) { + Session(Socket *_sock, const std::shared_ptr &_ctx) { socket = _sock; ctx = _ctx; } @@ -65,13 +64,13 @@ struct Session { void append(const char *data, ssize_t len); - inline void append(Buffer *buffer) { + void append(Buffer *buffer) { rxqueue.push_back(buffer); } - inline size_t get_buffer_length() { + size_t get_buffer_length() const { size_t total_length = 0; - for (auto i : rxqueue) { + for (const auto i : rxqueue) { total_length += i->length; } return total_length; diff --git a/include/swoole_error.h b/include/swoole_error.h index 02a993b549..6fa6165878 100644 --- a/include/swoole_error.h +++ b/include/swoole_error.h @@ -34,10 +34,18 @@ enum swErrorCode { SW_ERROR_OPERATION_NOT_SUPPORT, SW_ERROR_PROTOCOL_ERROR, SW_ERROR_WRONG_OPERATION, + SW_ERROR_PHP_RUNTIME_NOTICE, // Non-fatal errors, just runtime warnings + SW_ERROR_FOR_TEST, + + SW_ERROR_NO_PAYLOAD = 550, + + SW_ERROR_UNDEFINED_BEHAVIOR = 600, + SW_ERROR_NOT_THREAD_SAFETY, SW_ERROR_FILE_NOT_EXIST = 700, SW_ERROR_FILE_TOO_LARGE, SW_ERROR_FILE_EMPTY, + SW_ERROR_DIR_NOT_EXIST, SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST = 710, SW_ERROR_DNSLOOKUP_RESOLVE_FAILED, @@ -47,9 +55,15 @@ enum swErrorCode { SW_ERROR_BAD_IPV6_ADDRESS = 720, SW_ERROR_UNREGISTERED_SIGNAL, + SW_ERROR_BAD_HOST_ADDR, + SW_ERROR_BAD_PORT, + SW_ERROR_BAD_SOCKET_TYPE, // EventLoop - SW_ERROR_EVENT_SOCKET_REMOVED = 800, + SW_ERROR_EVENT_REMOVE_FAILED = 800, + SW_ERROR_EVENT_ADD_FAILED, + SW_ERROR_EVENT_UPDATE_FAILED, + SW_ERROR_EVENT_UNKNOWN_DATA, /** * connection error @@ -72,6 +86,8 @@ enum swErrorCode { SW_ERROR_SSL_BAD_PROTOCOL, SW_ERROR_SSL_RESET, SW_ERROR_SSL_HANDSHAKE_FAILED, + SW_ERROR_SSL_CREATE_CONTEXT_FAILED, + SW_ERROR_SSL_CREATE_SESSION_FAILED, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE = 1201, SW_ERROR_PACKAGE_LENGTH_NOT_FOUND, @@ -93,6 +109,7 @@ enum swErrorCode { SW_ERROR_HTTP2_STREAM_NOT_FOUND, SW_ERROR_HTTP2_STREAM_IGNORE, SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED, + SW_ERROR_HTTP2_INTERNAL_ERROR, /** * AIO @@ -111,6 +128,7 @@ enum swErrorCode { */ SW_ERROR_SOCKET_CLOSED = 6001, SW_ERROR_SOCKET_POLL_TIMEOUT, + SW_ERROR_SOCKET_NOT_EXISTS, /** * Proxy @@ -120,11 +138,15 @@ enum swErrorCode { SW_ERROR_SOCKS5_AUTH_FAILED, SW_ERROR_SOCKS5_SERVER_ERROR, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, + SW_ERROR_SOCKS5_CONNECT_FAILED, SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR = 7101, SW_ERROR_HTTP_INVALID_PROTOCOL, SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED, SW_ERROR_HTTP_PROXY_BAD_RESPONSE, + SW_ERROR_HTTP_CONFLICT_HEADER, + SW_ERROR_HTTP_CONTEXT_UNAVAILABLE, + SW_ERROR_HTTP_COOKIE_UNAVAILABLE, SW_ERROR_WEBSOCKET_BAD_CLIENT = 8501, SW_ERROR_WEBSOCKET_BAD_OPCODE, @@ -151,6 +173,8 @@ enum swErrorCode { SW_ERROR_SERVER_INVALID_COMMAND, SW_ERROR_SERVER_IS_NOT_REGULAR_FILE, SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT, + SW_ERROR_SERVER_INVALID_CALLBACK, + SW_ERROR_SERVER_UNRELATED_THREAD, /** * Process exit timeout, forced to end. @@ -184,15 +208,19 @@ enum swErrorCode { SW_ERROR_CO_CANCELED, SW_ERROR_CO_TIMEDOUT, + // close failed, there are currently other coroutines holding this socket, + // need to wait for the bound coroutine to return from the socket wait_event operation + SW_ERROR_CO_SOCKET_CLOSE_WAIT, + SW_ERROR_END }; namespace swoole { -class Exception { +class Exception final : std::exception { public: int code; const char *msg; - Exception(int code) throw(); + explicit Exception(int code) noexcept; }; } // namespace swoole diff --git a/include/swoole_file.h b/include/swoole_file.h index 4ffb6155ef..864c43721e 100644 --- a/include/swoole_file.h +++ b/include/swoole_file.h @@ -16,7 +16,6 @@ #pragma once -#include "swoole.h" #include "swoole_string.h" #include @@ -35,7 +34,6 @@ bool file_exists(const std::string &filename); typedef struct stat FileStatus; class File { - private: int fd_; int flags_; std::string path_; @@ -50,7 +48,7 @@ class File { APPEND = O_APPEND, }; - explicit File(int fd) { + explicit File(const int fd) { fd_ = fd; flags_ = 0; } @@ -61,112 +59,131 @@ class File { flags_ = 0; } - File(const std::string &path, int oflags) { - fd_ = ::open(path.c_str(), oflags); - path_ = path; - flags_ = oflags; - } + File(const std::string &path, int oflags); + File(const std::string &path, int oflags, int mode); + ~File(); - File(const std::string &path, int oflags, int mode) { - fd_ = ::open(path.c_str(), oflags, mode); - path_ = path; - flags_ = oflags; - } - - ~File() { - if (fd_ >= 0) { - ::close(fd_); - } - } + bool open(const std::string &path, int oflags, int mode = 0); + bool close(); + bool stat(FileStatus *_stat) const; - bool ready() { + bool ready() const { return fd_ != -1; } - ssize_t write(const void *__buf, size_t __n) const { - return ::write(fd_, __buf, __n); + ssize_t write(const void *_buf, size_t _n) const { + return ::write(fd_, _buf, _n); } - ssize_t read(void *__buf, size_t __n) const { - return ::read(fd_, __buf, __n); + ssize_t write(const std::string &str) const { + return ::write(fd_, str.c_str(), str.length()); } - ssize_t pwrite(const void *__buf, size_t __n, off_t __offset) const { - return ::pwrite(fd_, __buf, __n, __offset); + ssize_t read(void *_buf, const size_t _n) const { + return ::read(fd_, _buf, _n); } - ssize_t pread(void *__buf, size_t __n, off_t __offset) const { - return ::pread(fd_, __buf, __n, __offset); + ssize_t pwrite(const void *_buf, size_t _n, off_t _offset) const { + return ::pwrite(fd_, _buf, _n, _offset); } - size_t write_all(const void *__buf, size_t __n); - size_t read_all(void *__buf, size_t __n); + ssize_t pread(void *_buf, const size_t _n, off_t _offset) const { + return ::pread(fd_, _buf, _n, _offset); + } - std::shared_ptr read_content(); + size_t write_all(const void *data, size_t len) const; + size_t read_all(void *buf, size_t len) const; + /** + * Read one line of file, reading ends when __n - 1 bytes have been read, + * or a newline (which is included in the return value), + * or an EOF (read bytes less than __n) + * Returns length of line on success, -1 otherwise. + * NOTE: `buf` must be ended with zero. + */ + ssize_t read_line(void *_buf, size_t _n) const; - bool stat(FileStatus *_stat) const { - if (::fstat(fd_, _stat) < 0) { - swoole_sys_warning("fstat() failed"); - return false; - } else { - return true; - } - } + std::shared_ptr read_content() const; - bool sync() { + bool sync() const { return ::fsync(fd_) == 0; } - bool truncate(size_t size) { - return ::ftruncate(fd_, size) == 0; + bool truncate(off_t length) const { + return ::ftruncate(fd_, length) == 0; } - off_t set_offest(off_t offset) { + off_t set_offset(off_t offset) const { return lseek(fd_, offset, SEEK_SET); } - off_t get_offset() { + off_t get_offset() const { return lseek(fd_, 0, SEEK_CUR); } - bool lock(int operation) { + bool lock(int operation) const { return ::flock(fd_, operation) == 0; } - bool unlock() { + bool unlock() const { return ::flock(fd_, LOCK_UN) == 0; } - ssize_t get_size() { + ssize_t get_size() const { return file_get_size(fd_); } - bool close() { - if (fd_ == -1) { - return false; - } - int tmp_fd = fd_; - fd_ = -1; - return ::close(tmp_fd) == 0; - } - void release() { fd_ = -1; } - int get_fd() { + int get_fd() const { return fd_; } - const std::string &get_path() { + const std::string &get_path() const { return path_; } static bool exists(const std::string &file) { - return access(file.c_str(), R_OK) == 0; + return ::access(file.c_str(), R_OK) == 0; + } + + static bool remove(const std::string &file) { + return ::remove(file.c_str()) == 0; } }; File make_tmpfile(); +class AsyncFile { + private: + int fd = -1; + int flags_ = 0; + mode_t mode_ = 0; + std::string path_; + + public: + AsyncFile(const std::string &path, int flags, int mode); + ~AsyncFile(); + + bool open(const std::string &path, int flags, mode_t mode); + bool close() const; + + ssize_t read(void *buf, size_t count) const; + ssize_t write(const void *buf, size_t count) const; + ssize_t write(const String *buf) const { + return write(SW_STRINGL(buf)); + } + + bool sync() const; + bool truncate(off_t length) const; + bool stat(FileStatus *statbuf) const; + + off_t get_offset() const; + off_t set_offset(off_t offset) const; + + bool ready() const { + return fd != -1; + } +}; } // namespace swoole diff --git a/include/swoole_file_hook.h b/include/swoole_file_hook.h index 0a78d01311..378dcfc4bc 100644 --- a/include/swoole_file_hook.h +++ b/include/swoole_file_hook.h @@ -17,29 +17,33 @@ #ifndef SW_FILE_HOOK_H_ #define SW_FILE_HOOK_H_ -#include "swoole_coroutine_c_api.h" +#include "swoole_coroutine_api.h" -#define access(pathname, mode) swoole_coroutine_access(pathname, mode) #define open(pathname, flags, mode) swoole_coroutine_open(pathname, flags, mode) +#define close(fd) swoole_coroutine_close(fd) #define read(fd, buf, count) swoole_coroutine_read(fd, buf, count) #define write(fd, buf, count) swoole_coroutine_write(fd, buf, count) #define lseek(fd, offset, whence) swoole_coroutine_lseek(fd, offset, whence) -#define fstat(fd, statbuf) swoole_coroutine_fstat(fd, statbuf) #define readlink(fd, buf, size) swoole_coroutine_readlink(fd, buf, size) #define unlink(pathname) swoole_coroutine_unlink(pathname) #define mkdir(pathname, mode) swoole_coroutine_mkdir(pathname, mode) #define rmdir(pathname) swoole_coroutine_rmdir(pathname) #define rename(oldpath, newpath) swoole_coroutine_rename(oldpath, newpath) +#define fsync(fd) swoole_coroutine_fsync(fd) +#define fdatasync(fd) swoole_coroutine_fdatasync(fd) +#define ftruncate(fd, length) swoole_coroutine_ftruncate(fd, length) -#define fopen(pathname, mode) swoole_coroutine_fopen(pathname, mode) -#define fdopen(fd, mode) swoole_coroutine_fdopen(fd, mode) -#define freopen(pathname, mode, stream) swoole_coroutine_freopen(pathname, mode, stream) -#define fread(ptr, size, nmemb, stream) swoole_coroutine_fread(ptr, size, nmemb, stream) -#define fwrite(ptr, size, nmemb, stream) swoole_coroutine_fwrite(ptr, size, nmemb, stream) -#define fgets(s, size, stream) swoole_coroutine_fgets(s, size, stream) -#define fputs(s, stream) swoole_coroutine_fputs(s, stream) -#define feof(stream) swoole_coroutine_feof(stream) -#define fclose(stream) swoole_coroutine_fclose(stream) +#define access(pathname, mode) swoole_coroutine_access(pathname, mode) +#define fopen(pathname, mode) swoole_coroutine_fopen(pathname, mode) +#define fdopen(fd, mode) swoole_coroutine_fdopen(fd, mode) +#define freopen(pathname, mode, stream) swoole_coroutine_freopen(pathname, mode, stream) +#define fread(ptr, size, nmemb, stream) swoole_coroutine_fread(ptr, size, nmemb, stream) +#define fwrite(ptr, size, nmemb, stream) swoole_coroutine_fwrite(ptr, size, nmemb, stream) +#define fgets(s, size, stream) swoole_coroutine_fgets(s, size, stream) +#define fputs(s, stream) swoole_coroutine_fputs(s, stream) +#define feof(stream) swoole_coroutine_feof(stream) +#define fflush(stream) swoole_coroutine_fflush(stream) +#define fclose(stream) swoole_coroutine_fclose(stream) #define opendir(name) swoole_coroutine_opendir(name) #define readdir(dir) swoole_coroutine_readdir(dir) diff --git a/include/swoole_hash.h b/include/swoole_hash.h index 321317e4fc..fd028eeabf 100644 --- a/include/swoole_hash.h +++ b/include/swoole_hash.h @@ -14,225 +14,12 @@ +----------------------------------------------------------------------+ */ -#ifndef SW_HASH_H_ -#define SW_HASH_H_ +#pragma once -SW_EXTERN_C_BEGIN +#include +#include -#include - -#define HASH_JEN_MIX(a, b, c) \ - do { \ - a -= b; \ - a -= c; \ - a ^= (c >> 13); \ - b -= c; \ - b -= a; \ - b ^= (a << 8); \ - c -= a; \ - c -= b; \ - c ^= (b >> 13); \ - a -= b; \ - a -= c; \ - a ^= (c >> 12); \ - b -= c; \ - b -= a; \ - b ^= (a << 16); \ - c -= a; \ - c -= b; \ - c ^= (b >> 5); \ - a -= b; \ - a -= c; \ - a ^= (c >> 3); \ - b -= c; \ - b -= a; \ - b ^= (a << 10); \ - c -= a; \ - c -= b; \ - c ^= (b >> 15); \ - } while (0) - -/** - * jenkins - */ -static inline uint64_t swoole_hash_jenkins(const char *key, size_t keylen) { - uint64_t hashv; - - unsigned i, j, k; - hashv = 0xfeedbeef; - i = j = 0x9e3779b9; - k = (unsigned) (keylen); - - while (k >= 12) { - i += (key[0] + ((unsigned) key[1] << 8) + ((unsigned) key[2] << 16) + ((unsigned) key[3] << 24)); - j += (key[4] + ((unsigned) key[5] << 8) + ((unsigned) key[6] << 16) + ((unsigned) key[7] << 24)); - hashv += (key[8] + ((unsigned) key[9] << 8) + ((unsigned) key[10] << 16) + ((unsigned) key[11] << 24)); - - HASH_JEN_MIX(i, j, hashv); - - key += 12; - k -= 12; - } - hashv += keylen; - switch (k) { - case 11: - hashv += ((unsigned) key[10] << 24); - /* no break */ - case 10: - hashv += ((unsigned) key[9] << 16); - /* no break */ - case 9: - hashv += ((unsigned) key[8] << 8); - /* no break */ - case 8: - j += ((unsigned) key[7] << 24); - /* no break */ - case 7: - j += ((unsigned) key[6] << 16); - /* no break */ - case 6: - j += ((unsigned) key[5] << 8); - /* no break */ - case 5: - j += key[4]; - /* no break */ - case 4: - i += ((unsigned) key[3] << 24); - /* no break */ - case 3: - i += ((unsigned) key[2] << 16); - /* no break */ - case 2: - i += ((unsigned) key[1] << 8); - /* no break */ - case 1: - i += key[0]; - } - HASH_JEN_MIX(i, j, hashv); - return hashv; -} - -/** - * MurmurHash2(Austin Appleby) - */ -static inline uint32_t swoole_hash_austin(const char *key, unsigned int keylen) { - unsigned int h, k; - h = 0 ^ keylen; - - while (keylen >= 4) { - k = key[0]; - k |= key[1] << 8; - k |= key[2] << 16; - k |= key[3] << 24; - - k *= 0x5bd1e995; - k ^= k >> 24; - k *= 0x5bd1e995; - - h *= 0x5bd1e995; - h ^= k; - - key += 4; - keylen -= 4; - } - - switch (keylen) { - case 3: - h ^= key[2] << 16; - /* no break */ - case 2: - h ^= key[1] << 8; - /* no break */ - case 1: - h ^= key[0]; - h *= 0x5bd1e995; - } - - h ^= h >> 13; - h *= 0x5bd1e995; - h ^= h >> 15; - - return h; -} - -/* {{{ DJBX33A (Daniel J. Bernstein, Times 33 with Addition) - * - * This is Daniel J. Bernstein's popular `times 33' hash function as - * posted by him years ago on comp->lang.c. It basically uses a function - * like ``hash(i) = hash(i-1) * 33 + str[i]''. This is one of the best - * known hash functions for strings. Because it is both computed very - * fast and distributes very well. - * - * The magic of number 33, i.e. why it works better than many other - * constants, prime or not, has never been adequately explained by - * anyone. So I try an explanation: if one experimentally tests all - * multipliers between 1 and 256 (as RSE did now) one detects that even - * numbers are not useable at all. The remaining 128 odd numbers - * (except for the number 1) work more or less all equally well. They - * all distribute in an acceptable way and this way fill a hash table - * with an average percent of approx. 86%. - * - * If one compares the Chi^2 values of the variants, the number 33 not - * even has the best value. But the number 33 and a few other equally - * good numbers like 17, 31, 63, 127 and 129 have nevertheless a great - * advantage to the remaining numbers in the large set of possible - * multipliers: their multiply operation can be replaced by a faster - * operation based on just one shift plus either a single addition - * or subtraction operation. And because a hash function has to both - * distribute good _and_ has to be very fast to compute, those few - * numbers should be preferred and seems to be the reason why Daniel J. - * Bernstein also preferred it. - * - * -- Ralf S. Engelschall - */ -static inline uint64_t swoole_hash_php(const char *key, size_t len) { - ulong_t hash = 5381; - /* variant with the hash unrolled eight times */ - for (; len >= 8; len -= 8) { - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - hash = ((hash << 5) + hash) + *key++; - } - - switch (len) { - case 7: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 6: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 5: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 4: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 3: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 2: - hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ - /* no break */ - case 1: - hash = ((hash << 5) + hash) + *key++; - break; - case 0: - break; - default: - break; - } - return hash; -} - -#define CRC_STRING_MAXLEN 256 - -uint32_t swoole_crc32(const char *data, uint32_t size); - -SW_EXTERN_C_END - -#endif /* SW_HASH_H_ */ +uint64_t swoole_hash_jenkins(const char *key, size_t keylen); +uint64_t swoole_hash_php(const char *key, size_t len); +uint64_t swoole_hash_austin(const char *key, size_t keylen); +uint32_t swoole_crc32(const char *data, size_t size); diff --git a/include/swoole_heap.h b/include/swoole_heap.h index b5c08fd083..c43632570b 100644 --- a/include/swoole_heap.h +++ b/include/swoole_heap.h @@ -16,6 +16,9 @@ #pragma once +#include +#include + namespace swoole { struct HeapNode { @@ -25,7 +28,7 @@ struct HeapNode { }; class Heap { - public: + public: enum Type { MIN_HEAP, MAX_HEAP, @@ -34,34 +37,33 @@ class Heap { Heap(size_t _n, Type _type); ~Heap(); - size_t count() { + size_t count() const { return num - 1; } HeapNode *push(uint64_t priority, void *data); void *pop(); - void change_priority(uint64_t new_priority, HeapNode *ptr); + void change_priority(uint64_t new_priority, HeapNode *ptr) const; void remove(HeapNode *node); - void *peek(); - void print(); - int compare(uint64_t a, uint64_t b); + void *peek() const; + void print() const; + int compare(uint64_t a, uint64_t b) const; - HeapNode *top() { + HeapNode *top() const { if (num == 1) { return nullptr; } return nodes[1]; } - private: + private: uint32_t num; uint32_t size; enum Type type; HeapNode **nodes; - void bubble_up(uint32_t i); - uint32_t maxchild(uint32_t i); - void percolate_down(uint32_t i); + void bubble_up(uint32_t i) const; + uint32_t maxchild(uint32_t i) const; + void percolate_down(uint32_t i) const; }; -} - +} // namespace swoole diff --git a/include/swoole_http.h b/include/swoole_http.h index db87a6ebd1..ae93ec4739 100644 --- a/include/swoole_http.h +++ b/include/swoole_http.h @@ -15,7 +15,6 @@ */ #pragma once -#include "swoole.h" #include "swoole_protocol.h" #include @@ -96,6 +95,7 @@ enum swHttpStatusCode { SW_HTTP_RANGE_NOT_SATISFIABLE = 416, SW_HTTP_MISDIRECTED_REQUEST = 421, SW_HTTP_TOO_MANY_REQUESTS = 429, + SW_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, SW_HTTP_INTERNAL_SERVER_ERROR = 500, SW_HTTP_NOT_IMPLEMENTED = 501, @@ -126,11 +126,11 @@ struct FormData { }; struct Request { - public: uint8_t method; uint8_t version; uchar excepted : 1; uchar too_large : 1; + uchar unavailable : 1; uchar header_parsed : 1; uchar tried_to_dispatch : 1; @@ -144,32 +144,32 @@ struct Request { uint32_t url_offset_; uint32_t url_length_; + uint32_t max_length_; uint32_t request_line_length_; /* without \r\n */ uint32_t header_length_; /* include request_line_length + \r\n */ uint64_t content_length_; FormData *form_data_; - String *buffer_; - public: Request() { clean(); + form_data_ = nullptr; buffer_ = nullptr; } ~Request(); - inline void clean() { - memset(this, 0, offsetof(Request, buffer_)); + void clean() { + memset(&method, 0, offsetof(Request, form_data_)); } int get_protocol(); int get_header_length(); int get_chunked_body_length(); void parse_header_info(); bool parse_multipart_data(String *buffer); - bool init_multipart_parser(Server *server); + bool init_multipart_parser(const Server *server); void destroy_multipart_parser(); - std::string get_date_if_modified_since(); - bool has_expect_header(); + std::string get_header(const char *name) const; + bool has_expect_header() const; }; typedef std::function ParseCookieCallback; @@ -177,7 +177,14 @@ typedef std::function listen(const std::string addr, std::function cb, int mode = 1); +std::shared_ptr listen(const std::string &addr, const std::function &cb, int mode = 1); //----------------------------------------------------------------- } // namespace http_server } // namespace swoole diff --git a/include/swoole_http2.h b/include/swoole_http2.h index 72e288cdd8..9eb0c1d25b 100644 --- a/include/swoole_http2.h +++ b/include/swoole_http2.h @@ -22,19 +22,20 @@ #define SW_HTTP2_PRI_STRING "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" enum swHttp2ErrorCode { - SW_HTTP2_ERROR_NO_ERROR = 0, - SW_HTTP2_ERROR_PROTOCOL_ERROR = 1, - SW_HTTP2_ERROR_INTERNAL_ERROR = 2, - SW_HTTP2_ERROR_FLOW_CONTROL_ERROR = 3, - SW_HTTP2_ERROR_SETTINGS_TIMEOUT = 4, - SW_HTTP2_ERROR_STREAM_CLOSED = 5, - SW_HTTP2_ERROR_FRAME_SIZE_ERROR = 6, - SW_HTTP2_ERROR_REFUSED_STREAM = 7, - SW_HTTP2_ERROR_CANCEL = 8, - SW_HTTP2_ERROR_COMPRESSION_ERROR = 9, - SW_HTTP2_ERROR_CONNECT_ERROR = 10, - SW_HTTP2_ERROR_ENHANCE_YOUR_CALM = 11, - SW_HTTP2_ERROR_INADEQUATE_SECURITY = 12, + SW_HTTP2_ERROR_NO_ERROR = 0x0, + SW_HTTP2_ERROR_PROTOCOL_ERROR = 0x1, + SW_HTTP2_ERROR_INTERNAL_ERROR = 0x2, + SW_HTTP2_ERROR_FLOW_CONTROL_ERROR = 0x3, + SW_HTTP2_ERROR_SETTINGS_TIMEOUT = 0x4, + SW_HTTP2_ERROR_STREAM_CLOSED = 0x5, + SW_HTTP2_ERROR_FRAME_SIZE_ERROR = 0x6, + SW_HTTP2_ERROR_REFUSED_STREAM = 0x7, + SW_HTTP2_ERROR_CANCEL = 0x8, + SW_HTTP2_ERROR_COMPRESSION_ERROR = 0x9, + SW_HTTP2_ERROR_CONNECT_ERROR = 0xa, + SW_HTTP2_ERROR_ENHANCE_YOUR_CALM = 0xb, + SW_HTTP2_ERROR_INADEQUATE_SECURITY = 0xc, + SW_HTTP2_ERROR_HTTP_1_1_REQUIRED = 0xd, }; enum swHttp2FrameType { @@ -78,6 +79,7 @@ enum swHttp2StreamFlag { #define SW_HTTP2_FRAME_HEADER_SIZE 9 #define SW_HTTP2_SETTING_OPTION_SIZE 6 +#define SW_HTTP2_SETTING_FRAME_SIZE (SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_SETTING_OPTION_SIZE * 6) #define SW_HTTP2_FRAME_PING_PAYLOAD_SIZE 8 #define SW_HTTP2_RST_STREAM_SIZE 4 @@ -89,24 +91,31 @@ enum swHttp2StreamFlag { #define SW_HTTP2_STREAM_ID_SIZE 4 #define SW_HTTP2_SETTINGS_PARAM_SIZE 6 -#define swoole_http2_frame_trace_log(_trace_fn, _trace_str, ...) \ +#define swoole_http2_frame_trace_log(_trace_str, ...) \ swoole_trace_log(SW_TRACE_HTTP2, \ - "%s [" SW_ECHO_GREEN "] frame" \ - " " _trace_str, \ - #_trace_fn, \ + SW_ECHO_RED_BG " [" SW_ECHO_GREEN "] " \ + " " _trace_str, \ + " RECV ", \ swoole::http2::get_type(type), \ length, \ swoole::http2::get_flag_string(flags).c_str(), \ stream_id, \ ##__VA_ARGS__) +#define swoole_http2_send_trace_log(_trace_str, ...) \ + swoole_trace_log(SW_TRACE_HTTP2, SW_ECHO_GREEN_BG " " _trace_str, " SEND ", ##__VA_ARGS__) + +#define swoole_http2_recv_trace_log(_trace_str, ...) \ + swoole_trace_log(SW_TRACE_HTTP2, SW_ECHO_RED_BG " " _trace_str, " RECV ", ##__VA_ARGS__) + namespace swoole { namespace http2 { struct Settings { uint32_t header_table_size; - uint32_t window_size; + uint32_t enable_push; uint32_t max_concurrent_streams; + uint32_t init_window_size; uint32_t max_frame_size; uint32_t max_header_list_size; }; @@ -135,40 +144,47 @@ static sw_inline ssize_t get_length(const char *buf) { return (((uint8_t) buf[0]) << 16) + (((uint8_t) buf[1]) << 8) + (uint8_t) buf[2]; } +void put_default_setting(enum swHttp2SettingId id, uint32_t value); +uint32_t get_default_setting(enum swHttp2SettingId id); +size_t pack_setting_frame(char *buf, const Settings &settings, bool server_side); +ReturnCode unpack_setting_data(const char *buf, + ssize_t length, + const std::function &cb); ssize_t get_frame_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); int send_setting_frame(Protocol *protocol, network::Socket *conn); const char *get_type(int type); int get_type_color(int type); static sw_inline void init_settings(Settings *settings) { - settings->header_table_size = SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE; - settings->window_size = SW_HTTP2_DEFAULT_WINDOW_SIZE; - settings->max_concurrent_streams = SW_HTTP2_MAX_MAX_CONCURRENT_STREAMS; - settings->max_frame_size = SW_HTTP2_MAX_MAX_FRAME_SIZE; - settings->max_header_list_size = SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE; + settings->header_table_size = get_default_setting(SW_HTTP2_SETTING_HEADER_TABLE_SIZE); + settings->enable_push = get_default_setting(SW_HTTP2_SETTINGS_ENABLE_PUSH); + settings->max_concurrent_streams = get_default_setting(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); + settings->init_window_size = get_default_setting(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE); + settings->max_frame_size = get_default_setting(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE); + settings->max_header_list_size = get_default_setting(SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE); } -static inline const std::string get_flag_string(int __flags) { +static inline std::string get_flag_string(int _flags) { std::string str; - if (__flags & SW_HTTP2_FLAG_ACK) { + if (_flags & SW_HTTP2_FLAG_ACK) { str.append("ACK|"); } - if (__flags & SW_HTTP2_FLAG_END_STREAM) { + if (_flags & SW_HTTP2_FLAG_END_STREAM) { str.append("END_STREAM|"); } - if (__flags & SW_HTTP2_FLAG_END_HEADERS) { + if (_flags & SW_HTTP2_FLAG_END_HEADERS) { str.append("END_HEADERS|"); } - if (__flags & SW_HTTP2_FLAG_PADDED) { + if (_flags & SW_HTTP2_FLAG_PADDED) { str.append("PADDED|"); } - if (__flags & SW_HTTP2_FLAG_PRIORITY) { + if (_flags & SW_HTTP2_FLAG_PRIORITY) { str.append("PRIORITY|"); } if (str.back() == '|') { return str.substr(0, str.length() - 1); } else { - return "none"; + return {"none"}; } } diff --git a/include/swoole_iouring.h b/include/swoole_iouring.h new file mode 100644 index 0000000000..060a2611de --- /dev/null +++ b/include/swoole_iouring.h @@ -0,0 +1,149 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "swoole_coroutine.h" + +#ifdef SW_USE_IOURING +#include + +using swoole::Coroutine; + +enum swIouringFlag { + SW_IOURING_DEFAULT = 0, + SW_IOURING_SQPOLL = IORING_SETUP_SQPOLL, +}; + +namespace swoole { + +struct IouringEvent; + +struct IouringTimeout { + int64_t tv_sec; + int64_t tv_nsec; +}; + +class Iouring { + uint32_t task_num = 0; + uint32_t entries = SW_IOURING_QUEUE_SIZE; + io_uring ring; + std::queue waiting_tasks; + network::Socket *ring_socket = nullptr; + Reactor *reactor = nullptr; + + IouringEvent *ready_events[SW_IOURING_QUEUE_SIZE]; + io_uring_cqe *cqes[SW_IOURING_QUEUE_SIZE]; + + explicit Iouring(Reactor *reactor_); + bool ready() const; + void yield(IouringEvent *event); + void resume(IouringEvent *event); + bool cancel(IouringEvent *prev_event); + void dispatch(IouringEvent *event); + void submit(bool immediately); + bool wakeup(); + + io_uring_sqe *alloc_sqe() { + return io_uring_get_sqe(&ring); + } + + static Iouring *get_instance(); + static ssize_t execute(IouringEvent *event); + static const char *get_opcode_name(enum io_uring_op opcode); + + public: + ~Iouring(); + + bool is_empty_waiting_tasks() const { + return waiting_tasks.empty(); + } + + uint64_t get_task_num() const { + return task_num; + } + + uint32_t get_sq_space_left() const { + return io_uring_sq_space_left(&ring); + } + + uint32_t get_sq_capacity() const { + return ring.sq.ring_entries; + } + + uint32_t get_sq_used() const { + return get_sq_capacity() - get_sq_space_left(); + } + + size_t get_waiting_task_num() const { + return waiting_tasks.size(); + } + + float get_sq_usage_percent() const { + return (float) get_sq_used() / get_sq_capacity() * 100.0f; + } + + static int socket(int domain, int type, int protocol = 0, int flags = 0); + static int open(const char *pathname, int flags, mode_t mode); + static int connect(int fd, const struct sockaddr *addr, socklen_t len, double timeout = -1); + static int accept(int fd, struct sockaddr *addr, socklen_t *len, int flags = 0, double timeout = -1); + static int bind(int fd, const struct sockaddr *addr, socklen_t len); + static int listen(int fd, int backlog); + static int sleep(int tv_sec, int tv_nsec, int flags = 0); + static int sleep(double seconds); + static ssize_t recv(int fd, void *buf, size_t len, int flags, double timeout = -1); + static ssize_t send(int fd, const void *buf, size_t len, int flags, double timeout = -1); + static ssize_t recvmsg(int fd, struct msghdr *message, int flags, double timeout = -1); + static ssize_t sendmsg(int fd, const struct msghdr *message, int flags, double timeout = -1); + static ssize_t sendto( + int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t len, double timeout = -1); + static ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t size, double timeout = -1); + static ssize_t recvfrom(int fd, void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen, double timeout = -1); + static ssize_t readv(int fd, const struct iovec *iovec, int count, double timeout = -1); + static ssize_t writev(int fd, const struct iovec *iovec, int count, double timeout = -1); + static int shutdown(int fd, int how); + static int close(int fd); + static ssize_t read(int fd, void *buf, size_t size, double timeout = -1); + static ssize_t write(int fd, const void *buf, size_t size, double timeout = -1); + static int rename(const char *oldpath, const char *newpath); + static int mkdir(const char *pathname, mode_t mode); + static int unlink(const char *pathname); +#ifdef HAVE_IOURING_STATX + static int fstat(int fd, struct stat *statbuf); + static int stat(const char *path, struct stat *statbuf); +#endif + static int rmdir(const char *pathname); + static int fsync(int fd); + static int fdatasync(int fd); + static pid_t wait(int *stat_loc, double timeout = -1); + static pid_t waitpid(pid_t pid, int *stat_loc, int options, double timeout = -1); + /** + * Only supports listening to the readable and writable events of a single fd; nfds must be 1. + */ + static int poll(struct pollfd *fds, nfds_t nfds, int timeout); +#ifdef HAVE_IOURING_FUTEX + static int futex_wait(uint32_t *futex); + static int futex_wakeup(uint32_t *futex); +#endif +#ifdef HAVE_IOURING_FTRUNCATE + static int ftruncate(int fd, off_t length); +#endif + + static std::unordered_map list_all_opcode(); + static int callback(Reactor *reactor, Event *event); +}; +}; // namespace swoole +#endif diff --git a/include/swoole_llhttp.h b/include/swoole_llhttp.h new file mode 100644 index 0000000000..49768c75ca --- /dev/null +++ b/include/swoole_llhttp.h @@ -0,0 +1,54 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ +*/ + +#ifndef SWOOLE_LLHTTP_H +#define SWOOLE_LLHTTP_H + +#include "swoole.h" +#include "thirdparty/llhttp/llhttp.h" + +static sw_inline void swoole_llhttp_parser_init(llhttp_t *parser, llhttp_type_t type, void *ctx) { + llhttp_init(parser, type, nullptr); + parser->data = ctx; +} + +static sw_inline size_t swoole_llhttp_parser_execute(llhttp_t *parser, + const llhttp_settings_t *settings, + const char *data, + size_t length) { + parser->settings = (void *) settings; + const llhttp_errno_t result = llhttp_execute(parser, data, length); + + if (result == HPE_OK) { + return length; + } + + const size_t parsed_length = llhttp_get_error_pos(parser) - data; + switch (result) { + case HPE_PAUSED: + llhttp_resume(parser); + break; + case HPE_PAUSED_UPGRADE: + llhttp_resume_after_upgrade(parser); + break; + default: + break; + } + + return parsed_length; +} + +#endif // SWOOLE_LLHTTP_H diff --git a/include/swoole_lock.h b/include/swoole_lock.h index cdbb27dbc5..f16bcf923a 100644 --- a/include/swoole_lock.h +++ b/include/swoole_lock.h @@ -17,9 +17,9 @@ #pragma once -#include "swoole.h" #include "swoole_memory.h" +#include #include namespace swoole { @@ -27,83 +27,101 @@ namespace swoole { class Lock { public: enum Type { - NONE, RW_LOCK = 1, - FILE_LOCK = 2, MUTEX = 3, - SEM = 4, SPIN_LOCK = 5, - ATOMIC_LOCK = 6, + COROUTINE_LOCK = 6, }; - Type get_type() { + Type get_type() const { return type_; } - virtual ~Lock(){}; - virtual int lock_rd() = 0; - virtual int lock() = 0; + virtual ~Lock() = default; + virtual int lock(int operation = LOCK_EX, int timeout_msec = -1) = 0; virtual int unlock() = 0; - virtual int trylock_rd() = 0; - virtual int trylock() = 0; protected: - Lock() { - type_ = NONE; - shared_ = false; + Lock(Type type, bool shared) { + type_ = type; + shared_ = shared; } - enum Type type_; + Type type_; bool shared_; }; struct MutexImpl; -class Mutex : public Lock { +class Mutex final : public Lock { MutexImpl *impl; public: - enum Flag { - PROCESS_SHARED = 1, - ROBUST = 2, - }; - - Mutex(int flags); - ~Mutex(); - int lock_rd() override; - int lock() override; + explicit Mutex(bool shared); + ~Mutex() override; + int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; - int trylock_rd() override; - int trylock() override; - int lock_wait(int timeout_msec); }; #ifdef HAVE_RWLOCK struct RWLockImpl; -class RWLock : public Lock { +class RWLock final : public Lock { RWLockImpl *impl; public: - RWLock(int use_in_process); - ~RWLock(); - int lock_rd() override; - int lock() override; + explicit RWLock(bool shared); + ~RWLock() override; + int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; - int trylock_rd() override; - int trylock() override; + int lock_rd() { + return lock(LOCK_SH); + } + int lock_wr() { + return lock(LOCK_EX); + } }; #endif #ifdef HAVE_SPINLOCK -class SpinLock : public Lock { +class SpinLock final : public Lock { pthread_spinlock_t *impl; public: - SpinLock(int use_in_process); - ~SpinLock(); - int lock_rd() override; - int lock() override; + explicit SpinLock(bool shared); + ~SpinLock() override; + int lock(int operation = LOCK_EX, int timeout_msec = -1) override; int unlock() override; - int trylock_rd() override; - int trylock() override; }; #endif + +class CoroutineLock final : public Lock { + long cid = 0; + sw_atomic_t *value = nullptr; + void *coroutine = nullptr; + + int lock_impl(bool blocking = true); + + public: + explicit CoroutineLock(bool shared); + ~CoroutineLock() override; + int lock(int operation = LOCK_EX, int timeout_msec = -1) override; + int unlock() override; +}; + +#if defined(HAVE_PTHREAD_BARRIER) && !(defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)) +#define SW_USE_PTHREAD_BARRIER +#endif + +struct Barrier { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_t barrier_; + pthread_barrierattr_t barrier_attr_; + bool shared_; +#else + sw_atomic_t count_; + sw_atomic_t barrier_; +#endif + void init(bool shared, int count); + void wait(); + void destroy(); +}; + } // namespace swoole diff --git a/include/swoole_log.h b/include/swoole_log.h index baa5d9431f..ab1d2f5126 100644 --- a/include/swoole_log.h +++ b/include/swoole_log.h @@ -16,8 +16,8 @@ #pragma once -#include -#include +#include +#include #include #include @@ -52,30 +52,38 @@ class Logger { bool display_backtrace_ = false; int stdout_fd = -1; int stderr_fd = -1; - int log_fd = STDOUT_FILENO; + FILE *log_fp = stdout; int log_level = SW_LOG_INFO; bool date_with_microseconds = false; std::string date_format = SW_LOG_DEFAULT_DATE_FORMAT; - std::string log_file = ""; + std::string log_file; std::string log_real_file; + std::mutex lock; int log_rotation = SW_LOG_ROTATION_SINGLE; + void reopen_without_lock(); + public: bool open(const char *logfile); - void put(int level, const char *content, size_t length); void reopen(); - void close(void); + void set_stream(FILE *stream); + /** + * Only the `put` and `reopen` functions are thread-safe, + * other functions must be used in a single-threaded environment. + */ + void put(int level, const char *content, size_t length); + void close(); void reset(); void set_level(int lv); - int get_level(); + int get_level() const; bool set_date_format(const char *format); void set_rotation(int rotation); const char *get_real_file(); - const char *get_file(); - bool is_opened(); - bool redirect_stdout_and_stderr(int enable); + const char *get_file() const; + bool is_opened() const; + bool redirect_stdout_and_stderr(bool enable); void set_date_with_microseconds(bool enable); - std::string gen_real_file(const std::string &file); + std::string gen_real_file(const std::string &file) const; static std::string get_pretty_name(const std::string &prettyFunction, bool strip = true); void display_backtrace() { @@ -88,13 +96,13 @@ swoole::Logger *sw_logger(); #define __SW_FUNC__ (swoole::Logger::get_pretty_name(__PRETTY_FUNCTION__).c_str()) #define swoole_info(str, ...) \ - if (SW_LOG_INFO >= sw_logger()->get_level()) { \ + if (SW_LOG_INFO >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, str, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_INFO, sw_error, _sw_error_len); \ } #define swoole_notice(str, ...) \ - if (SW_LOG_NOTICE >= sw_logger()->get_level()) { \ + if (SW_LOG_NOTICE >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, str, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_NOTICE, sw_error, _sw_error_len); \ } @@ -102,7 +110,7 @@ swoole::Logger *sw_logger(); #define swoole_sys_notice(str, ...) \ do { \ swoole_set_last_error(errno); \ - if (SW_LOG_ERROR >= sw_logger()->get_level()) { \ + if (SW_LOG_ERROR >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, \ SW_ERROR_MSG_SIZE, \ "%s(:%d): " str ", Error: %s[%d]", \ @@ -117,7 +125,7 @@ swoole::Logger *sw_logger(); #define swoole_warning(str, ...) \ do { \ - if (SW_LOG_WARNING >= sw_logger()->get_level()) { \ + if (SW_LOG_WARNING >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(): " str, __SW_FUNC__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_WARNING, sw_error, _sw_error_len); \ } \ @@ -126,7 +134,7 @@ swoole::Logger *sw_logger(); #define swoole_sys_warning(str, ...) \ do { \ swoole_set_last_error(errno); \ - if (SW_LOG_ERROR >= sw_logger()->get_level()) { \ + if (SW_LOG_ERROR >= swoole_get_log_level()) { \ size_t _sw_error_len = sw_snprintf(sw_error, \ SW_ERROR_MSG_SIZE, \ "%s(): " str ", Error: %s[%d]", \ @@ -140,9 +148,9 @@ swoole::Logger *sw_logger(); #define swoole_error(str, ...) \ do { \ - size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, str, ##__VA_ARGS__); \ + size_t _sw_error_len = sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(): " str, __SW_FUNC__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_ERROR, sw_error, _sw_error_len); \ - exit(1); \ + swoole_exit(1); \ } while (0) #define swoole_sys_error(str, ...) \ @@ -155,19 +163,15 @@ swoole::Logger *sw_logger(); swoole_strerror(errno), \ errno); \ sw_logger()->put(SW_LOG_ERROR, sw_error, _sw_error_len); \ - exit(1); \ + swoole_exit(1); \ } while (0) -#define swoole_fatal_error(code, str, ...) \ - do { \ - SwooleG.fatal_error(code, str, ##__VA_ARGS__); \ - exit(255); \ - } while (0) +#define swoole_fatal_error(code, str, ...) SwooleG.fatal_error(code, str, ##__VA_ARGS__) #define swoole_error_log(level, error, str, ...) \ do { \ swoole_set_last_error(error); \ - if (level >= sw_logger()->get_level() && !swoole_is_ignored_error(error)) { \ + if (level >= swoole_get_log_level() && !swoole_is_ignored_error(error)) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s() (ERRNO %d): " str, __SW_FUNC__, error, ##__VA_ARGS__); \ sw_logger()->put(level, sw_error, _sw_error_len); \ @@ -176,7 +180,7 @@ swoole::Logger *sw_logger(); #ifdef SW_DEBUG #define swoole_debug(str, ...) \ - if (SW_LOG_DEBUG >= sw_logger()->get_level()) { \ + if (SW_LOG_DEBUG >= swoole_get_log_level()) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(:%d): " str, __SW_FUNC__, __LINE__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_DEBUG, sw_error, _sw_error_len); \ @@ -204,7 +208,7 @@ swoole::Logger *sw_logger(); #define swoole_hex_dump(data, length) #endif -enum swTrace_type { +enum swTraceWhat : long { /** * Server */ @@ -245,12 +249,24 @@ enum swTrace_type { SW_TRACE_CO_CURL = 1u << 29, SW_TRACE_CARES = 1u << 30, + SW_TRACE_ZLIB = 1u << 31, + SW_TRACE_CO_PGSQL = 1ul << 32, + SW_TRACE_CO_ODBC = 1ul << 33, + SW_TRACE_CO_ORACLE = 1ul << 34, + SW_TRACE_CO_SQLITE = 1ul << 35, + SW_TRACE_CO_FIREBIRD = 1ul << 36, + SW_TRACE_CO_SSH2 = 1ul << 37, + /** + * Thread + */ + SW_TRACE_THREAD = 1ul << 40, + SW_TRACE_ALL = 0x7fffffffffffffff }; #ifdef SW_LOG_TRACE_OPEN #define swoole_trace_log(what, str, ...) \ - if (SW_LOG_TRACE >= sw_logger()->get_level() && (what & SwooleG.trace_flags)) { \ + if (SW_LOG_TRACE >= swoole_get_log_level() && (what & SwooleG.trace_flags)) { \ size_t _sw_error_len = \ sw_snprintf(sw_error, SW_ERROR_MSG_SIZE, "%s(:%d): " str, __SW_FUNC__, __LINE__, ##__VA_ARGS__); \ sw_logger()->put(SW_LOG_TRACE, sw_error, _sw_error_len); \ diff --git a/include/swoole_lru_cache.h b/include/swoole_lru_cache.h index 89a30e1d72..9c8d0250c5 100644 --- a/include/swoole_lru_cache.h +++ b/include/swoole_lru_cache.h @@ -20,18 +20,18 @@ #include #include #include -#include +#include namespace swoole { /** * This cache isn't thread safe */ +template class LRUCache { - private: - typedef std::pair> cache_node_t; + typedef std::pair> cache_node_t; typedef std::list> cache_list_t; - std::unordered_map cache_map; + std::unordered_map cache_map; cache_list_t cache_list; size_t cache_capacity; @@ -40,7 +40,7 @@ class LRUCache { cache_capacity = capacity; } - inline std::shared_ptr get(const std::string &key) { + std::shared_ptr get(const std::string &key) { auto iter = cache_map.find(key); if (iter == cache_map.end()) { return nullptr; @@ -54,7 +54,7 @@ class LRUCache { return iter->second->second.second; // iter -> list::iter -> cache_node_t -> value } - inline void set(const std::string &key, const std::shared_ptr &val, time_t expire = 0) { + void set(const std::string &key, const std::shared_ptr &val, time_t expire = 0) { time_t expire_time; if (expire <= 0) { @@ -82,7 +82,7 @@ class LRUCache { cache_map[key] = cache_list.begin(); } - inline void del(const std::string &key) { + void del(const std::string &key) { auto iter = cache_map.find(key); if (iter == cache_map.end()) { return; @@ -92,7 +92,7 @@ class LRUCache { cache_map.erase(iter); } - inline void clear() { + void clear() { cache_list.clear(); cache_map.clear(); } diff --git a/include/swoole_memory.h b/include/swoole_memory.h index 022fda3625..a713c6ef20 100644 --- a/include/swoole_memory.h +++ b/include/swoole_memory.h @@ -24,7 +24,7 @@ namespace swoole { class MemoryPool { public: - virtual ~MemoryPool(){}; + virtual ~MemoryPool() = default; virtual void *alloc(uint32_t size) = 0; virtual void free(void *ptr) = 0; @@ -34,20 +34,19 @@ class MemoryPool { struct FixedPoolImpl; -class FixedPool : public MemoryPool { - private: +class FixedPool final : public MemoryPool { FixedPoolImpl *impl; public: FixedPool(uint32_t slice_num, uint32_t slice_size, bool shared); FixedPool(uint32_t slice_size, void *memory, size_t size, bool shared); - ~FixedPool(); - void *alloc(uint32_t size); - void free(void *ptr); - void debug(int max_lines = 100); - uint32_t get_number_of_spare_slice(); - uint32_t get_number_of_total_slice(); - uint32_t get_slice_size(); + ~FixedPool() override; + void *alloc(uint32_t size) override; + void free(void *ptr) override; + void debug(int max_lines = 100) const; + uint32_t get_number_of_spare_slice() const; + uint32_t get_number_of_total_slice() const; + uint32_t get_slice_size() const; static size_t sizeof_struct_slice(); static size_t sizeof_struct_impl(); }; @@ -56,14 +55,13 @@ struct RingBufferImpl; // RingBuffer, In order for malloc / free class RingBuffer : public MemoryPool { - private: RingBufferImpl *impl; public: RingBuffer(uint32_t size, bool shared); - ~RingBuffer(); - void *alloc(uint32_t size); - void free(void *ptr); + ~RingBuffer() override; + void *alloc(uint32_t size) override; + void free(void *ptr) override; }; struct GlobalMemoryImpl; @@ -75,12 +73,11 @@ class GlobalMemory : public MemoryPool { public: GlobalMemory(uint32_t page_size, bool shared); - ~GlobalMemory(); - void *alloc(uint32_t size); - void free(void *ptr); - void destroy(); - size_t capacity(); - size_t get_memory_size(); + ~GlobalMemory() override; + void *alloc(uint32_t size) override; + void free(void *ptr) override; + size_t capacity() const; + size_t get_memory_size() const; }; } // namespace swoole diff --git a/include/swoole_message_bus.h b/include/swoole_message_bus.h new file mode 100644 index 0000000000..88c9a53a8c --- /dev/null +++ b/include/swoole_message_bus.h @@ -0,0 +1,173 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "swoole_string.h" +#include "swoole_socket.h" +#include "swoole_protocol.h" + +#include + +namespace swoole { +struct PipeBuffer { + DataHead info; + char data[0]; + + bool is_begin() const { + return info.flags & SW_EVENT_DATA_BEGIN; + } + + bool is_chunked() const { + return info.flags & SW_EVENT_DATA_CHUNK; + } + + bool is_end() const { + return info.flags & SW_EVENT_DATA_END; + } +}; + +struct PacketPtr { + size_t length; + char *data; +}; + +struct DgramPacket { + SocketType socket_type; + network::Address socket_addr; + uint32_t length; + char data[0]; +}; + +struct PacketTask { + size_t length; + char tmpfile[SW_TASK_TMP_PATH_SIZE]; +}; + +class MessageBus { + private: + const Allocator *allocator_; + std::unordered_map> packet_pool_; + std::vector pipe_sockets_; + std::function id_generator_; + size_t buffer_size_; + PipeBuffer *buffer_ = nullptr; + bool always_chunked_transfer_ = false; + + String *get_packet_buffer(); + ReturnCode prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer); + + public: + MessageBus() { + allocator_ = sw_std_allocator(); + buffer_size_ = SW_BUFFER_SIZE_STD; + } + + ~MessageBus(); + + bool empty() const { + return packet_pool_.empty(); + } + + size_t count() const { + return packet_pool_.size(); + } + + void clear() { + packet_pool_.clear(); + } + + void set_allocator(const Allocator *allocator) { + allocator_ = allocator; + } + + void set_id_generator(const std::function &id_generator) { + id_generator_ = id_generator; + } + + void set_buffer_size(size_t buffer_size) { + buffer_size_ = buffer_size; + } + + void set_always_chunked_transfer() { + always_chunked_transfer_ = true; + } + + size_t get_buffer_size() const { + return buffer_size_; + } + + size_t get_memory_size() const; + bool alloc_buffer(); + + /** + * If use the zend_string_allocator, must manually call this function to release the memory, + * otherwise coredump will occur when php shutdown, because zend_string has been released + */ + void free_buffer() { + allocator_->free(buffer_); + buffer_ = nullptr; + } + + void pass(const SendData *task) const; + + /** + * Send data to socket. If the data sent is larger than Server::ipc_max_size, then it is sent in chunks. + * Otherwise, send it directly. + * When sending data in multi-thread environment, must use get_pipe_socket() to separate socket memory. + * @return: send success returns true, send failure returns false. + */ + bool write(network::Socket *sock, SendData *packet) const; + /** + * Receive data from socket, if only one chunk is received, packet will be saved in packet_pool. + * Then continue to listen to readable events, waiting for more chunks. + * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred + */ + ssize_t read(network::Socket *sock); + /** + * Receive data from pipeline, and store data to buffer + * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred + */ + ssize_t read_with_buffer(network::Socket *sock); + /** + * The last chunk of data has been received, return address and length, start processing this packet. + */ + PacketPtr get_packet() const; + PipeBuffer *get_buffer() const { + return buffer_; + } + /** + * Pop the data memory address to the outer layer, no longer managed by MessageBus + */ + char *move_packet(); + /** + * The processing of this data packet has been completed, and the relevant memory has been released + */ + void pop() { + if (buffer_->is_end()) { + packet_pool_.erase(buffer_->info.msg_id); + } + } + /** + * It is possible to operate the same pipe in multiple threads. + * Each thread must have a unique buffer and the socket memory must be separated. + */ + network::Socket *get_pipe_socket(const network::Socket *sock) const { + return pipe_sockets_[sock->get_fd()]; + } + void init_pipe_socket(const network::Socket *sock); +}; +} // namespace swoole diff --git a/include/swoole_mime_type.h b/include/swoole_mime_type.h index 27a7122c62..7293365435 100644 --- a/include/swoole_mime_type.h +++ b/include/swoole_mime_type.h @@ -16,7 +16,6 @@ #pragma once -#include #include #include diff --git a/include/swoole_mqtt.h b/include/swoole_mqtt.h index 1c0ee7f10c..e0b7dde7df 100644 --- a/include/swoole_mqtt.h +++ b/include/swoole_mqtt.h @@ -17,7 +17,6 @@ */ #pragma once -#include "swoole.h" #include "swoole_protocol.h" #define SW_MQTT_MIN_LENGTH_SIZE 1 diff --git a/include/swoole_msg_queue.h b/include/swoole_msg_queue.h index ad6e1cba02..f3d85c1fac 100644 --- a/include/swoole_msg_queue.h +++ b/include/swoole_msg_queue.h @@ -23,9 +23,14 @@ namespace swoole { +enum { + SW_MSGQUEUE_ORIENT = 1, + SW_MSGQUEUE_BALANCE = 2, +}; + struct QueueNode { - long mtype; /* type of received/sent message */ - char mdata[sizeof(EventData)]; /* text of the message */ + long mtype; /* type of received/sent message */ + char mdata[sizeof(EventData)]; /* text of the message */ }; class MsgQueue { @@ -35,23 +40,24 @@ class MsgQueue { key_t msg_key_; int flags_; int perms_; + public: explicit MsgQueue(key_t msg_key, bool blocking = true, int perms = 0); ~MsgQueue(); - bool ready() { + bool ready() const { return msg_id_ >= 0; } - int get_id() { + int get_id() const { return msg_id_; } void set_blocking(bool blocking); - bool set_capacity(size_t queue_bytes); - bool push(QueueNode *in, size_t mdata_length); - ssize_t pop(QueueNode *out, size_t mdata_size); - bool stat(size_t *queue_num, size_t *queue_bytes); + bool set_capacity(size_t queue_bytes) const; + bool push(const QueueNode *in, size_t mdata_length) const; + ssize_t pop(QueueNode *out, size_t mdata_size) const; + bool stat(size_t *queue_num, size_t *queue_bytes) const; bool destroy(); }; -} +} // namespace swoole diff --git a/include/swoole_pipe.h b/include/swoole_pipe.h index df7ced681c..4a38339253 100644 --- a/include/swoole_pipe.h +++ b/include/swoole_pipe.h @@ -17,7 +17,6 @@ #pragma once -#include "swoole.h" #include "swoole_socket.h" enum swPipe_close_which { @@ -35,60 +34,56 @@ class SocketPair { double timeout; /** - * master : socks[1] - * worker : socks[0] + * master : socks[1], for write operation + * worker : socks[0], for read operation */ - int socks[2]; + int socks[2]{}; network::Socket *master_socket = nullptr; network::Socket *worker_socket = nullptr; - bool init_socket(int master_fd, int worker_fd); + void init_socket(int master_fd, int worker_fd); public: - SocketPair(bool _blocking) { + explicit SocketPair(bool _blocking) { blocking = _blocking; timeout = network::Socket::default_read_timeout; } ~SocketPair(); - ssize_t read(void *_buf, size_t length); - ssize_t write(const void *_buf, size_t length); + ssize_t read(void *_buf, size_t length) const; + ssize_t write(const void *_buf, size_t length) const; + void clean() const; bool close(int which = 0); - network::Socket *get_socket(bool _master) { + network::Socket *get_socket(bool _master) const { return _master ? master_socket : worker_socket; } - bool ready() { + bool ready() const { return master_socket != nullptr && worker_socket != nullptr; } void set_timeout(double _timeout) { timeout = _timeout; + master_socket->set_timeout(timeout); + worker_socket->set_timeout(timeout); } - void set_blocking(bool blocking) { - if (blocking) { - worker_socket->set_block(); - master_socket->set_block(); - } else { - worker_socket->set_nonblock(); - master_socket->set_nonblock(); - } - } + void set_blocking(bool _blocking); }; class Pipe : public SocketPair { - public: - Pipe(bool blocking); + public: + explicit Pipe(bool blocking); }; class UnixSocket : public SocketPair { int protocol_; + public: UnixSocket(bool blocking, int _protocol); - bool set_buffer_size(size_t _size); + bool set_buffer_size(size_t _size) const; }; } // namespace swoole diff --git a/include/swoole_process_pool.h b/include/swoole_process_pool.h index 42a9131626..87e9b4ff3c 100644 --- a/include/swoole_process_pool.h +++ b/include/swoole_process_pool.h @@ -17,15 +17,19 @@ #pragma once -#include "swoole.h" - -#include -#include - +#include "swoole_signal.h" #include "swoole_lock.h" #include "swoole_pipe.h" #include "swoole_channel.h" #include "swoole_msg_queue.h" +#include "swoole_message_bus.h" + +#include + +#include +#include +#include +#include enum swWorkerStatus { SW_WORKER_BUSY = 1, @@ -33,6 +37,15 @@ enum swWorkerStatus { SW_WORKER_EXIT = 3, }; +enum swWorkerType { + SW_MASTER = 1, + SW_WORKER = 2, + SW_MANAGER = 3, + SW_EVENT_WORKER = 2, + SW_TASK_WORKER = 4, + SW_USER_WORKER = 5, +}; + enum swIPCMode { SW_IPC_NONE = 0, SW_IPC_UNIXSOCK = 1, @@ -40,19 +53,31 @@ enum swIPCMode { SW_IPC_SOCKET = 3, }; -namespace swoole { +SW_API swoole::WorkerId swoole_get_worker_id(); +SW_API pid_t swoole_get_worker_pid(); +SW_API int swoole_get_worker_type(); +SW_API void swoole_set_worker_pid(pid_t pid); +SW_API void swoole_set_worker_id(swoole::WorkerId worker_id); +SW_API void swoole_set_worker_type(int type); +SW_API char swoole_get_worker_symbol(); +namespace swoole { enum WorkerMessageType { SW_WORKER_MESSAGE_STOP = 1, }; +enum ProtocolType { + SW_PROTOCOL_TASK = 1, + SW_PROTOCOL_STREAM, + SW_PROTOCOL_MESSAGE, +}; + struct WorkerStopMessage { pid_t pid; uint16_t worker_id; }; class ExitStatus { - private: pid_t pid_; int status_; @@ -75,7 +100,7 @@ class ExitStatus { return WTERMSIG(status_); } - bool is_normal_exit() { + bool is_normal_exit() const { return WIFEXITED(status_); } }; @@ -83,23 +108,37 @@ class ExitStatus { static inline ExitStatus wait_process() { int status = 0; pid_t pid = ::wait(&status); - return ExitStatus(pid, status); + return {pid, status}; } static inline ExitStatus wait_process(pid_t _pid, int options) { int status = 0; pid_t pid = ::waitpid(_pid, &status, options); - return ExitStatus(pid, status); + return {pid, status}; } struct ProcessPool; struct Worker; struct WorkerGlobal { - bool run_always; + WorkerId id; + uint8_t type; + pid_t pid; bool shutdown; + bool running; uint32_t max_request; + /** + * worker is shared memory, visible in other work processes. + * When a worker process restarts, it may be held by both the old and new processes simultaneously, + * necessitating careful handling of the state. + */ Worker *worker; + /** + * worker_copy is a copy of worker, + * but it must be local memory and only used within the current process or thread. + * It is not visible to other worker processes. + */ + Worker *worker_copy; time_t exit_time; }; @@ -108,6 +147,7 @@ struct Worker { WorkerId id; ProcessPool *pool; MsgQueue *queue; + bool shared; bool redirect_stdout; bool redirect_stdin; @@ -118,10 +158,9 @@ struct Worker { */ uint8_t status; uint8_t type; - uint8_t ipc_mode; + uint8_t msgqueue_mode; uint8_t child_process; - sw_atomic_t tasking_num; uint32_t concurrency; time_t start_time; @@ -138,19 +177,41 @@ struct Worker { network::Socket *pipe_current; void *ptr; - void *ptr2; - ssize_t send_pipe_message(const void *buf, size_t n, int flags); + ssize_t send_pipe_message(const void *buf, size_t n, int flags) const; + bool has_exceeded_max_request() const; + void set_max_request(uint32_t max_request, uint32_t max_request_grace); + void report_error(const ExitStatus &exit_status) const; + /** + * Init global state for worker process. + * Must be called after the process is spawned and before the main loop is executed. + */ + void init(); + void shutdown(); + bool is_shutdown(); + static bool is_running(); - void set_status(enum swWorkerStatus _status) { + void set_status(swWorkerStatus _status) { status = _status; } - bool is_busy() { + void set_status_to_idle() { + set_status(SW_WORKER_IDLE); + } + + void set_status_to_busy() { + set_status(SW_WORKER_BUSY); + } + + void add_request_count() { + request_count++; + } + + bool is_busy() const { return status == SW_WORKER_BUSY; } - bool is_idle() { + bool is_idle() const { return status == SW_WORKER_IDLE; } }; @@ -163,23 +224,47 @@ struct StreamInfo { String *response_buffer; }; +struct ReloadTask { + std::unordered_map workers; + std::queue kill_queue; + TimerNode *timer = nullptr; + + size_t count() const { + return workers.size(); + } + + bool is_completed() const { + return workers.empty(); + } + + bool exists(pid_t pid) { + return workers.find(pid) != workers.end(); + } + + ReloadTask() = default; + ~ReloadTask(); + void kill_one(int signal_number = SIGTERM); + void kill_all(int signal_number = SIGKILL); + void add_workers(Worker *list, size_t n); + void add_timeout_killer(int timeout); + bool remove(pid_t pid); + void clear_queue(); +}; + struct ProcessPool { - /** - * reloading - */ - bool reloading; bool running; bool reload_init; bool read_message; bool started; bool schedule_by_sysvmsg; + bool async; + uint8_t ipc_mode; + ProtocolType protocol_type_; pid_t master_pid; - uint32_t reload_worker_i; uint32_t max_wait_time; uint64_t reload_count; time_t reload_last_time; - Worker *reload_workers; /** * process type @@ -219,10 +304,16 @@ struct ProcessPool { uint8_t scheduler_warning; time_t warning_time; - int (*onTask)(ProcessPool *pool, EventData *task); - void (*onWorkerStart)(ProcessPool *pool, int worker_id); - void (*onMessage)(ProcessPool *pool, const char *data, uint32_t length); - void (*onWorkerStop)(ProcessPool *pool, int worker_id); + void (*onStart)(ProcessPool *pool); + void (*onShutdown)(ProcessPool *pool); + void (*onBeforeReload)(ProcessPool *pool); + void (*onAfterReload)(ProcessPool *pool); + int (*onTask)(ProcessPool *pool, Worker *worker, EventData *task); + void (*onWorkerStart)(ProcessPool *pool, Worker *worker); + void (*onMessage)(ProcessPool *pool, RecvData *msg); + void (*onWorkerExit)(ProcessPool *pool, Worker *worker); + void (*onWorkerStop)(ProcessPool *pool, Worker *worker); + void (*onWorkerError)(ProcessPool *pool, Worker *worker, const ExitStatus &exit_status); void (*onWorkerMessage)(ProcessPool *pool, EventData *msg); int (*onWorkerNotFound)(ProcessPool *pool, const ExitStatus &exit_status); int (*main_loop)(ProcessPool *pool, Worker *worker); @@ -232,79 +323,134 @@ struct ProcessPool { Worker *workers; std::vector> *pipes; std::unordered_map *map_; - Reactor *reactor; MsgQueue *queue; StreamInfo *stream_info_; Channel *message_box = nullptr; + MessageBus *message_bus = nullptr; + ReloadTask *reload_task = nullptr; void *ptr; - inline void set_type(int _type) { - uint32_t i; - type = _type; - for (i = 0; i < worker_num; i++) { - workers[i].type = type; - } + Worker *get_worker(WorkerId worker_id) const { + return &(workers[worker_id - start_id]); } - inline void set_start_id(int _start_id) { - uint32_t i; - start_id = _start_id; - for (i = 0; i < worker_num; i++) { - workers[i].id = start_id + i; - } + static TaskId get_task_id(const EventData *task) { + return task->info.fd; } - inline Worker *get_worker(int worker_id) { - return &(workers[worker_id - start_id]); + static WorkerId get_task_src_worker_id(const EventData *task) { + return task->info.reactor_id; } - Worker *get_worker_by_pid(pid_t pid) { - auto iter = map_->find(pid); - if (iter == map_->end()) { - return nullptr; - } - return iter->second; + void set_max_packet_size(uint32_t _max_packet_size) { + max_packet_size_ = _max_packet_size; } + bool is_master() const { + return swoole_get_worker_type() == SW_MASTER; + } + + bool is_worker() const { + return swoole_get_worker_type() == SW_WORKER; + } + + /** + * SW_PROTOCOL_TASK + * ================================================================== + * The `EventData` structure must be sent as a single message and cannot be split into multiple transmissions. + * If the length of the message content exceeds the size limit of the data field in EventData, + * it should be written to a temporary file. + * In this case, set the SW_TASK_TMPFILE flag in info.ext_flags. + * Only the path to the temporary file will be transmitted, + * and the receiving end should retrieve the actual message content from this temporary file. + * Reference: Server::task_pack() + * + * SW_PROTOCOL_MESSAGE + * ================================================================== + * When sending the `EventData` structure, the message can be split into multiple transmissions. + * When sending data in multiple parts, you must set a unique `info.msg_id`. + * For the first slice, set the `info.flags` with the SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN flag, + * and for the last slice, set the `info.flags` with the SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_END flag. + * The receiving end will place the data into a memory cache table, merge the data, + * and only execute the onMessage callback once the complete message has been received. + * + * Reference: MessageBus::write() and MessageBus::read() + * + * SW_PROTOCOL_STREAM + * ================================================================== + * +-------------------------------+-------------------------------+ + * | Payload Length ( 4 byte, network byte order) | + * | Payload Data ... ( Payload Length byte ) | + * +-------------------------------- - - - - - - - - - - - - - - - + + * + * The packet consists of a 4 byte length header followed by the data payload. + * The receiving end should first use `socket.recv(&payload_len, 4)` to obtain the length of the data payload. + * Then, execute `socket.recv(payload, payload_len)` to receive the complete data. + * Please note that sufficient memory space must be allocated for the payload, + * for example, `payload = malloc(payload_len)`. + */ + void set_protocol(ProtocolType _protocol_type); + void set_type(int _type); + void set_start_id(int _start_id); void set_max_request(uint32_t _max_request, uint32_t _max_request_grace); - int get_max_request(); - void set_protocol(int task_protocol, uint32_t max_packet_size); bool detach(); int wait(); + int start_check(); int start(); - void shutdown(); + bool shutdown(); bool reload(); + void reopen_logger(); + + void trigger_read_message_event() { + read_message = true; + } + pid_t spawn(Worker *worker); - int dispatch(EventData *data, int *worker_id); - int response(const char *data, int length); - int dispatch_blocking(EventData *data, int *dst_worker_id); - int dispatch_blocking(const char *data, uint32_t len); - int add_worker(Worker *worker); - int del_worker(Worker *worker); + void stop(Worker *worker); + void kill_all_workers(int signo = SIGKILL); + swResultCode dispatch(EventData *data, int *worker_id); + int response(const char *data, uint32_t length) const; + swResultCode dispatch_sync(EventData *data, int *dst_worker_id); + swResultCode dispatch_sync(const char *data, uint32_t len) const; + void add_worker(Worker *worker) const; + bool del_worker(const Worker *worker) const; + Worker *get_worker_by_pid(pid_t pid) const; void destroy(); int create(uint32_t worker_num, key_t msgqueue_key = 0, swIPCMode ipc_mode = SW_IPC_NONE); int create_message_box(size_t memory_size); - int push_message(uint8_t type, const void *data, size_t length); - int push_message(EventData *msg); - int pop_message(void *data, size_t size); - int listen(const char *socket_file, int blacklog); - int listen(const char *host, int port, int blacklog); + int create_message_bus(); + int push_message(uint8_t _type, const void *data, size_t length) const; + int push_message(const EventData *msg) const; + bool send_message(WorkerId worker_id, const char *message, size_t l_message) const; + int pop_message(void *data, size_t size) const; + int listen(const char *socket_file, int backlog) const; + int listen(const char *host, int port, int backlog) const; int schedule(); - static void kill_timeout_worker(Timer *timer, TimerNode *tnode); + bool is_worker_running(Worker *worker) const; + + private: + static int recv_packet(Reactor *reactor, Event *event); + static int recv_message(Reactor *reactor, Event *event); + static int run_with_task_protocol(ProcessPool *pool, Worker *worker); + static int run_with_stream_protocol(ProcessPool *pool, Worker *worker); + static int run_with_message_protocol(ProcessPool *pool, Worker *worker); + static int run_async(ProcessPool *pool, Worker *worker); + void at_worker_enter(Worker *worker) const; + void at_worker_exit(Worker *worker); + + bool wait_detached_worker(std::unordered_set &detached_workers, pid_t pid); }; }; // namespace swoole -static sw_inline int swoole_waitpid(pid_t __pid, int *__stat_loc, int __options) { - int ret; - do { - ret = waitpid(__pid, __stat_loc, __options); - } while (ret < 0 && errno == EINTR); - return ret; +static sw_inline int swoole_kill(pid_t _pid, int _sig) { + return kill(_pid, _sig); } -static sw_inline int swoole_kill(pid_t __pid, int __sig) { - return kill(__pid, __sig); -} +typedef swoole::ProtocolType swProtocolType; + +extern SW_THREAD_LOCAL swoole::WorkerGlobal SwooleWG; -extern swoole::WorkerGlobal SwooleWG; // Worker Global Variable +static inline swoole::Worker *sw_worker() { + return SwooleWG.worker; +} diff --git a/include/swoole_protocol.h b/include/swoole_protocol.h index d141487f27..c6bc9329cb 100644 --- a/include/swoole_protocol.h +++ b/include/swoole_protocol.h @@ -17,14 +17,28 @@ #pragma once -#include "swoole.h" -#include "swoole_c_api.h" #include "swoole_socket.h" -#include #include namespace swoole { +struct SendData { + DataHead info; + const char *data; +}; + +struct RecvData { + DataHead info; + const char *data; + + SessionId session_id() const { + return info.fd; + } + + uint32_t length() const { + return info.len; + } +}; struct PacketLength { const char *buf; @@ -32,6 +46,21 @@ struct PacketLength { uint32_t header_len; }; +struct WebSocketSettings { + std::string protocol; // with `Sec-WebSocket-Protocol` HTTP Header + bool in_server; // server or client + bool mask = true; // enable websocket mask + bool open_ping_frame = false; // handle websocket ping frame by user + bool open_pong_frame = false; // handle websocket pong frame by user + bool open_close_frame = false; // handle websocket close frame by user + /** + * The default value is false, which means that websocket frame data compression is not enabled. + * If supported by `zlib` or other compression libraries, the client can accept compressed data + * (depending on whether the `Sec-Websocket-Extensions` header contains `permessage-deflate`) + */ + bool compression = false; +}; + struct Protocol { typedef ssize_t (*LengthFunc)(const Protocol *, network::Socket *, PacketLength *pl); /* one package: eof check */ @@ -46,7 +75,7 @@ struct Protocol { uint16_t package_body_offset; uint32_t package_max_length; - void *private_data; + void *private_data_1; void *private_data_2; /** @@ -62,18 +91,34 @@ struct Protocol { LengthFunc get_package_length; uint8_t (*get_package_length_size)(network::Socket *); - int recv_with_eof_protocol(network::Socket *socket, String *buffer); - int recv_with_length_protocol(network::Socket *socket, String *buffer); - int recv_split_by_eof(network::Socket *socket, String *buffer); + int recv_with_eof_protocol(network::Socket *socket, String *buffer) const; + int recv_with_length_protocol(network::Socket *socket, String *buffer) const; + int recv_split_by_eof(network::Socket *socket, String *buffer) const; static ssize_t default_length_func(const Protocol *protocol, network::Socket *socket, PacketLength *pl); - - inline static LengthFunc get_function(const std::string &name) { - return (LengthFunc) swoole_get_function(name.c_str(), name.length()); - } }; } // namespace swoole +#ifdef __BYTE_ORDER +#define SW_BYTE_ORDER __BYTE_ORDER +#elif defined(_BYTE_ORDER) +#define SW_BYTE_ORDER _BYTE_ORDER +#elif defined(BYTE_ORDER) +#define SW_BYTE_ORDER BYTE_ORDER +#else +#error "Unable to determine machine byte order" +#endif + +#ifdef __LITTLE_ENDIAN +#define SW_LITTLE_ENDIAN __LITTLE_ENDIAN +#elif defined(_LITTLE_ENDIAN) +#define SW_LITTLE_ENDIAN _LITTLE_ENDIAN +#elif defined(LITTLE_ENDIAN) +#define SW_LITTLE_ENDIAN LITTLE_ENDIAN +#else +#error "No LITTLE_ENDIAN macro" +#endif + static sw_inline uint16_t swoole_swap_endian16(uint16_t x) { return (((x & 0xff) << 8) | ((x & 0xff00) >> 8)); } @@ -82,95 +127,19 @@ static sw_inline uint32_t swoole_swap_endian32(uint32_t x) { return (((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24)); } -static sw_inline int32_t swoole_unpack(char type, const void *data) { - switch (type) { - /*-------------------------16bit-----------------------------*/ - case 'c': - return *((int8_t *) data); - case 'C': - return *((uint8_t *) data); - /*-------------------------16bit-----------------------------*/ - /** - * signed short (always 16 bit, machine byte order) - */ - case 's': - return *((int16_t *) data); - /** - * unsigned short (always 16 bit, machine byte order) - */ - case 'S': - return *((uint16_t *) data); - /** - * unsigned short (always 16 bit, big endian byte order) - */ - case 'n': - return ntohs(*((uint16_t *) data)); - /** - * unsigned short (always 32 bit, little endian byte order) - */ - case 'v': - return swoole_swap_endian16(ntohs(*((uint16_t *) data))); - - /*-------------------------32bit-----------------------------*/ - /** - * unsigned long (always 32 bit, machine byte order) - */ - case 'L': - return *((uint32_t *) data); - /** - * signed long (always 32 bit, machine byte order) - */ - case 'l': - return *((int *) data); - /** - * unsigned long (always 32 bit, big endian byte order) - */ - case 'N': - return ntohl(*((uint32_t *) data)); - /** - * unsigned short (always 32 bit, little endian byte order) - */ - case 'V': - return swoole_swap_endian32(ntohl(*((uint32_t *) data))); - - default: - return *((uint32_t *) data); - } -} - -static sw_inline uint64_t swoole_hton64(uint64_t host) { - uint64_t ret = 0; - uint32_t high, low; - - low = host & 0xFFFFFFFF; - high = (host >> 32) & 0xFFFFFFFF; - low = htonl(low); - high = htonl(high); - - ret = low; - ret <<= 32; - ret |= high; - return ret; +static sw_inline uint64_t swoole_swap_endian64(uint64_t x) { + return (((x & 0xff) << 56) | ((x & 0xff00) << 40) | ((x & 0xff0000) << 24) | ((x & 0xff000000) << 8) | + ((x & 0xff00000000) >> 8) | ((x & 0xff0000000000) >> 24) | ((x & 0xff000000000000) >> 40) | + ((x & 0xff00000000000000) >> 56)); } -static sw_inline uint64_t swoole_ntoh64(uint64_t net) { - uint64_t ret = 0; - uint32_t high, low; - - low = net & 0xFFFFFFFF; - high = net >> 32; - low = ntohl(low); - high = ntohl(high); - - ret = low; - ret <<= 32; - ret |= high; - return ret; -} +int64_t swoole_unpack(char type, const void *data); +uint64_t swoole_hton64(uint64_t value); +uint64_t swoole_ntoh64(uint64_t value); void swoole_dump_ascii(const char *data, size_t size); -void swoole_dump_bin(const char *data, char type, size_t size); -void swoole_dump_hex(const char *data, size_t outlen); +void swoole_dump_bin(const uchar *data, char type, size_t size); +void swoole_dump_hex(const uchar *data, size_t outlen); char *swoole_dec2hex(ulong_t value, int base); ulong_t swoole_hex2dec(const char *hex, size_t *parsed_bytes); diff --git a/include/swoole_proxy.h b/include/swoole_proxy.h index 7a0a955a7a..9d34ff7887 100644 --- a/include/swoole_proxy.h +++ b/include/swoole_proxy.h @@ -17,8 +17,18 @@ #pragma once #include +#include +#include #define SW_SOCKS5_VERSION_CODE 0x05 +#define SW_HTTP_PROXY_CHECK_MESSAGE 0 +#define SW_HTTP_PROXY_HANDSHAKE_RESPONSE "HTTP/1.1 200 Connection established\r\n" + +#define SW_HTTP_PROXY_FMT \ + "CONNECT %.*s:%d HTTP/1.1\r\n" \ + "Host: %.*s:%d\r\n" \ + "User-Agent: Swoole/" SWOOLE_VERSION "\r\n" \ + "Proxy-Connection: Keep-Alive\r\n" enum swHttpProxyState { SW_HTTP_PROXY_STATE_WAIT = 0, @@ -35,22 +45,28 @@ enum swSocks5State { }; enum swSocks5Method { + SW_SOCKS5_METHOD_NO_AUTH = 0x00, SW_SOCKS5_METHOD_AUTH = 0x02, }; namespace swoole { +class String; + struct HttpProxy { uint8_t state; uint8_t dont_handshake; - int proxy_port; - std::string proxy_host; + int port; + std::string host; std::string username; std::string password; std::string target_host; int target_port; - char buf[512]; - std::string get_auth_str(); + std::string get_auth_str() const; + size_t pack(const String *send_buffer, const std::string &host_name) const; + static bool handshake(const String *recv_buffer); + + static HttpProxy *create(const std::string &host, int port, const std::string &user, const std::string &pwd); }; struct Socks5Proxy { @@ -64,14 +80,16 @@ struct Socks5Proxy { std::string password; std::string target_host; int target_port; - char buf[600]; + int socket_type; + char buf[512]; - static const char *strerror(int code); + ssize_t pack_negotiate_request(); + ssize_t pack_auth_request(); + ssize_t pack_connect_request(); + bool handshake(const char *rbuf, size_t rlen, const std::function &send_fn); - static void pack(char *buf, int method) { - buf[0] = SW_SOCKS5_VERSION_CODE; - buf[1] = 0x01; - buf[2] = method; - } + static const char *strerror(int code); + static Socks5Proxy *create( + int socket_type, const std::string &host, int port, const std::string &user, const std::string &pwd); }; } // namespace swoole diff --git a/include/swoole_reactor.h b/include/swoole_reactor.h index c1291091c9..d89b3bfd1f 100644 --- a/include/swoole_reactor.h +++ b/include/swoole_reactor.h @@ -17,7 +17,6 @@ #pragma once -#include "swoole.h" #include "swoole_socket.h" #include @@ -38,29 +37,29 @@ class ReactorImpl { Reactor *reactor_; public: - ReactorImpl(Reactor *_reactor) { + explicit ReactorImpl(Reactor *_reactor) { reactor_ = _reactor; } - void after_removal_failure(network::Socket *_socket); - virtual ~ReactorImpl(){}; + void after_removal_failure(const network::Socket *_socket) const; + virtual ~ReactorImpl() = default; virtual bool ready() = 0; virtual int add(network::Socket *socket, int events) = 0; virtual int set(network::Socket *socket, int events) = 0; virtual int del(network::Socket *socket) = 0; - virtual int wait(struct timeval *) = 0; + virtual int wait() = 0; }; class CallbackManager { public: typedef std::list> TaskList; - void append(Callback fn, void *private_data) { + void append(const Callback &fn, void *private_data) { list_.emplace_back(fn, private_data); } - void prepend(Callback fn, void *private_data) { + void prepend(const Callback &fn, void *private_data) { list_.emplace_front(fn, private_data); auto t = list_.back(); } - void remove(TaskList::iterator iter) { + void remove(const TaskList::iterator iter) { list_.erase(iter); } void execute() { @@ -82,7 +81,6 @@ class Reactor { TYPE_EPOLL, TYPE_KQUEUE, TYPE_POLL, - TYPE_SELECT, }; enum EndCallback { @@ -93,6 +91,12 @@ class Reactor { PRIORITY_TRY_EXIT, PRIORITY_MALLOC_TRIM, PRIORITY_WORKER_CALLBACK, + /** + * PRIORITY_IOURING_SUBMIT must be the last one, as other callback functions might allocate new SQEs. + * It is essential to ensure that the SQE is submitted before the next event loop iteration and before the + * epoll_wait() call. + */ + PRIORITY_IOURING_SUBMIT, }; enum ExitCondition { @@ -105,6 +109,7 @@ class Reactor { EXIT_CONDITION_SIGNALFD, EXIT_CONDITION_USER_BEFORE_DEFAULT, EXIT_CONDITION_FORCED_TERMINATION, + EXIT_CONDITION_IOURING, EXIT_CONDITION_DEFAULT = 999, EXIT_CONDITION_USER_AFTER_DEFAULT, }; @@ -112,20 +117,16 @@ class Reactor { Type type_; void *ptr = nullptr; int native_handle = -1; - - /** - * last signal number - */ - int singal_no = 0; - uint32_t max_event_num = 0; + bool ready_ = false; bool running = false; bool start = false; bool once = false; bool wait_exit = false; bool destroyed = false; bool bailout = false; + bool timed_out = false; /** * reactor->wait timeout (millisecond) or -1 @@ -147,20 +148,20 @@ class Reactor { ReactorHandler default_write_handler = nullptr; ReactorHandler default_error_handler = nullptr; - int add(network::Socket *socket, int events) { + int add(network::Socket *socket, int events) const { return impl->add(socket, events); } - int set(network::Socket *socket, int events) { + int set(network::Socket *socket, int events) const { return impl->set(socket, events); } - int del(network::Socket *socket) { + int del(network::Socket *socket) const { return impl->del(socket); } - int wait(struct timeval *timeout) { - return impl->wait(timeout); + int wait() const { + return impl->wait(); } CallbackManager *defer_tasks = nullptr; @@ -169,7 +170,9 @@ class Reactor { DeferCallback idle_task; DeferCallback future_task; - std::function onBegin; +#ifdef SW_USE_IOURING + std::function iouring_interrupt_handler; +#endif ssize_t (*write)(Reactor *reactor, network::Socket *socket, const void *buf, size_t n) = nullptr; ssize_t (*writev)(Reactor *reactor, network::Socket *socket, const iovec *iov, size_t iovcnt) = nullptr; @@ -182,48 +185,51 @@ class Reactor { std::unordered_map sockets_; public: - Reactor(int max_event = SW_REACTOR_MAXEVENTS, Type _type = TYPE_AUTO); + explicit Reactor(int max_event = SW_REACTOR_MAXEVENTS, Type _type = TYPE_AUTO); ~Reactor(); bool if_exit(); - void defer(Callback cb, void *data = nullptr); - void set_end_callback(enum EndCallback id, const std::function &fn); - void set_exit_condition(enum ExitCondition id, const std::function &fn); - bool set_handler(int _fdtype, ReactorHandler handler); - void add_destroy_callback(Callback cb, void *data = nullptr); - void execute_end_callbacks(bool timedout = false); + void defer(const Callback &cb, void *data = nullptr); + void set_end_callback(EndCallback _id, const std::function &fn); + void erase_end_callback(EndCallback _id); + void set_exit_condition(ExitCondition _id, const std::function &fn); + void set_handler(int fd_type, int event, ReactorHandler handler); + bool isset_handler(int fd_type, int event) const; + void add_destroy_callback(const Callback &cb, void *data = nullptr); + void execute_begin_callback() const; + void execute_end_callbacks(bool _timed_out = false); void drain_write_buffer(network::Socket *socket); - bool ready() { - return running; + bool ready() const { + return ready_; } - inline size_t remove_exit_condition(enum ExitCondition id) { - return exit_conditions.erase(id); + bool is_running() const { + return running; } - inline bool isset_exit_condition(enum ExitCondition id) { - return exit_conditions.find(id) != exit_conditions.end(); + size_t remove_exit_condition(const ExitCondition _id) { + return exit_conditions.erase(_id); } - inline bool isset_handler(int fdtype) { - return read_handler[fdtype] != nullptr; + bool isset_exit_condition(const ExitCondition _id) { + return exit_conditions.find(_id) != exit_conditions.end(); } - inline int add_event(network::Socket *_socket, EventType event_type) { + int add_event(network::Socket *_socket, EventType event_type) const { if (!(_socket->events & event_type)) { return set(_socket, _socket->events | event_type); } return SW_OK; } - inline int del_event(network::Socket *_socket, EventType event_type) { + int del_event(network::Socket *_socket, EventType event_type) const { if (_socket->events & event_type) { return set(_socket, _socket->events & (~event_type)); } return SW_OK; } - inline int remove_read_event(network::Socket *_socket) { + int remove_read_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_WRITE) { _socket->events &= (~SW_EVENT_READ); return set(_socket, _socket->events); @@ -232,7 +238,7 @@ class Reactor { } } - inline int remove_write_event(network::Socket *_socket) { + int remove_write_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_READ) { _socket->events &= (~SW_EVENT_WRITE); return set(_socket, _socket->events); @@ -241,7 +247,7 @@ class Reactor { } } - inline int add_read_event(network::Socket *_socket) { + int add_read_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_WRITE) { _socket->events |= SW_EVENT_READ; return set(_socket, _socket->events); @@ -250,7 +256,7 @@ class Reactor { } } - inline int add_write_event(network::Socket *_socket) { + int add_write_event(network::Socket *_socket) const { if (_socket->events & SW_EVENT_READ) { _socket->events |= SW_EVENT_WRITE; return set(_socket, _socket->events); @@ -259,15 +265,23 @@ class Reactor { } } - inline bool exists(network::Socket *_socket) { + bool exists(const network::Socket *_socket) const { return !_socket->removed && _socket->events; } - inline int get_timeout_msec() { + bool exists(const int fd) const { + return sockets_.find(fd) != sockets_.end(); + } + + int get_timeout_msec() const { return defer_tasks == nullptr ? timeout_msec : 0; } - size_t get_event_num() { + void set_timeout_msec(int mesc) { + timeout_msec = mesc; + } + + size_t get_event_num() const { return sockets_.size(); } @@ -275,18 +289,18 @@ class Reactor { return sockets_; } - network::Socket *get_socket(int fd) { + network::Socket *get_socket(const int fd) { return sockets_[fd]; } - void foreach_socket(const std::function &callback) { - for (auto kv : sockets_) { + void foreach_socket(const std::function &callback) const { + for (auto &kv : sockets_) { callback(kv.first, kv.second); } } - inline ReactorHandler get_handler(EventType event_type, FdType fd_type) { - switch (event_type) { + ReactorHandler get_handler(const FdType fd_type, const EventType event) const { + switch (event) { case SW_EVENT_READ: return read_handler[fd_type]; case SW_EVENT_WRITE: @@ -295,55 +309,62 @@ class Reactor { return error_handler[fd_type] ? error_handler[fd_type] : default_error_handler; default: abort(); - break; } - return nullptr; } - inline ReactorHandler get_error_handler(FdType fd_type) { - ReactorHandler handler = get_handler(SW_EVENT_ERROR, fd_type); + ReactorHandler get_error_handler(const FdType fd_type) const { + ReactorHandler handler = get_handler(fd_type, SW_EVENT_ERROR); // error callback is not set, try to use readable or writable callback if (handler == nullptr) { - handler = get_handler(SW_EVENT_READ, fd_type); + handler = get_handler(fd_type, SW_EVENT_READ); if (handler == nullptr) { - handler = get_handler(SW_EVENT_WRITE, fd_type); + handler = get_handler(fd_type, SW_EVENT_WRITE); } } return handler; } - inline void before_wait() { + void before_wait() { start = running = true; + if (timeout_msec == 0) { + timeout_msec = -1; + } } - inline int trigger_close_event(Event *event) { + int trigger_close_event(Event *event) { return default_error_handler(this, event); } - inline void set_wait_exit(bool enable) { + void set_wait_exit(const bool enable) { wait_exit = enable; } - inline void _add(network::Socket *_socket, int events) { + void _add(network::Socket *_socket, const int events) { _socket->events = events; _socket->removed = 0; sockets_[_socket->fd] = _socket; } - inline void _set(network::Socket *_socket, int events) { + void _set(network::Socket *_socket, const int events) { _socket->events = events; } - inline void _del(network::Socket *_socket) { + bool _exists(const network::Socket *_socket) { + return sockets_.find(_socket->fd) != sockets_.end(); + } + + void _del(network::Socket *_socket) { _socket->events = 0; _socket->removed = 1; sockets_.erase(_socket->fd); } - bool catch_error() { + bool catch_error() const { switch (errno) { case EINTR: return true; + default: + break; } return false; } @@ -352,25 +373,27 @@ class Reactor { static ssize_t _writev(Reactor *reactor, network::Socket *socket, const iovec *iov, size_t iovcnt); static int _close(Reactor *reactor, network::Socket *socket); static int _writable_callback(Reactor *reactor, Event *ev); + static ssize_t write_func(const Reactor *reactor, + network::Socket *socket, + size_t _len, + const std::function &send_fn, + const std::function &append_fn); - void activate_future_task(); - - static FdType get_fd_type(int flags) { - return (FdType)(flags & (~SW_EVENT_READ) & (~SW_EVENT_WRITE) & (~SW_EVENT_ERROR) & (~SW_EVENT_ONCE)); - } - - static bool isset_read_event(int events) { + static bool isset_read_event(const int events) { return (events < SW_EVENT_DEAULT) || (events & SW_EVENT_READ); } - static bool isset_write_event(int events) { + static bool isset_write_event(const int events) { return events & SW_EVENT_WRITE; } - static bool isset_error_event(int events) { + static bool isset_error_event(const int events) { return events & SW_EVENT_ERROR; } }; + +int16_t translate_events_to_poll(int events); +int translate_events_from_poll(int16_t events); } // namespace swoole #define SW_REACTOR_CONTINUE \ diff --git a/include/swoole_redis.h b/include/swoole_redis.h index f7718adb6e..ec0abf9950 100644 --- a/include/swoole_redis.h +++ b/include/swoole_redis.h @@ -46,24 +46,12 @@ enum ReplyType { REPLY_MAP, }; -static sw_inline const char *get_number(const char *p, int *_ret) { - char *endptr; - p++; - int ret = strtol(p, &endptr, 10); - if (strncmp(SW_CRLF, endptr, SW_CRLF_LEN) == 0) { - p += (endptr - p) + SW_CRLF_LEN; - *_ret = ret; - return p; - } else { - return nullptr; - } -} - +const char *get_number(const char *p, int *_ret); int recv_packet(Protocol *protocol, Connection *conn, String *buffer); std::vector parse(const char *data, size_t len); -bool format(String *buf); -bool format(String *buf, enum ReplyType type, const std::string &value); -bool format(String *buf, enum ReplyType type, long value); +void format_nil(String *buf); +void format(String *buf, ReplyType type, const std::string &value); +void format(String *buf, ReplyType type, long value); } // namespace redis } // namespace swoole diff --git a/include/swoole_server.h b/include/swoole_server.h index 19da3598d1..91dfa1f82e 100644 --- a/include/swoole_server.h +++ b/include/swoole_server.h @@ -16,7 +16,6 @@ #pragma once -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_timer.h" @@ -26,8 +25,9 @@ #include "swoole_process_pool.h" #include "swoole_pipe.h" #include "swoole_channel.h" +#include "swoole_message_bus.h" -#ifdef SW_USE_OPENSSL +#ifdef SW_SUPPORT_DTLS #include "swoole_dtls.h" #endif @@ -42,16 +42,22 @@ #include #include #include +#include //------------------------------------Server------------------------------------------- namespace swoole { namespace http_server { struct Request; -} +struct RewriteRule; +class StaticHandler; +} // namespace http_server class Server; struct Manager; +class Thread; + +typedef std::function WorkerFn; struct Session { SessionId id; @@ -72,20 +78,15 @@ struct Connection { int worker_id; SessionId session_id; //-------------------------------------------------------------- -#ifdef SW_USE_OPENSSL uint8_t ssl; uint8_t ssl_ready; -#endif uint8_t overflow; uint8_t high_watermark; uint8_t http_upgrade; uint8_t http2_stream; -#ifdef SW_HAVE_ZLIB uint8_t websocket_compression; -#endif // If it is equal to 1, it means server actively closed the connection uint8_t close_actively; - uint8_t closed; uint8_t close_queued; uint8_t closing; uint8_t close_reset; @@ -97,16 +98,19 @@ struct Connection { ReactorId reactor_id; uint16_t close_errno; int server_fd; + sw_atomic_t closed; sw_atomic_t recv_queued_bytes; uint32_t send_queued_bytes; uint16_t waiting_time; + uint16_t local_port; + uint16_t local_addr_index; TimerNode *timer; /** * socket address */ network::Address info; /** - * link any thing, for kernel, do not use with application. + * link anything, for kernel, do not use with application. */ void *object; /** @@ -125,7 +129,7 @@ struct Connection { */ uint32_t uid; /** - * upgarde websocket + * upgrade websocket */ uint8_t websocket_status; /** @@ -133,186 +137,30 @@ struct Connection { */ String *websocket_buffer; -#ifdef SW_USE_OPENSSL String *ssl_client_cert; pid_t ssl_client_cert_pid; -#endif sw_atomic_t lock; }; -struct PipeBuffer { - DataHead info; - char data[0]; - - bool is_begin() { - return info.flags & SW_EVENT_DATA_BEGIN; - } - - bool is_chunked() { - return info.flags & SW_EVENT_DATA_CHUNK; - } - - bool is_end() { - return info.flags & SW_EVENT_DATA_END; - } -}; - -//------------------------------------Packet------------------------------------------- -struct PacketPtr { - size_t length; - char *data; -}; - -struct DgramPacket { - SocketType socket_type; - network::Address socket_addr; - uint32_t length; - char data[0]; -}; - -struct PacketTask { - size_t length; - char tmpfile[SW_TASK_TMP_PATH_SIZE]; -}; - -class MessageBus { - private: - const Allocator *allocator_; - std::unordered_map> packet_pool_; - std::function id_generator_; - size_t buffer_size_; - PipeBuffer *buffer_ = nullptr; - bool always_chunked_transfer_ = false; - - String *get_packet_buffer(); - ReturnCode prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer); - - public: - MessageBus() { - allocator_ = sw_std_allocator(); - buffer_size_ = SW_BUFFER_SIZE_STD; - } - - ~MessageBus() { - allocator_->free(buffer_); - } - - bool empty() { - return packet_pool_.empty(); - } - - void clear() { - packet_pool_.clear(); - } - - void set_allocator(const Allocator *allocator) { - allocator_ = allocator; - } - - void set_id_generator(const std::function &id_generator) { - id_generator_ = id_generator; - } - - void set_buffer_size(size_t buffer_size) { - buffer_size_ = buffer_size; - } - - void set_always_chunked_transfer() { - always_chunked_transfer_ = true; - } - - size_t get_buffer_size() { - return buffer_size_; - } - - size_t get_memory_size(); - - bool alloc_buffer() { - void *_ptr = allocator_->malloc(sizeof(*buffer_) + buffer_size_); - if (_ptr) { - buffer_ = (PipeBuffer *) _ptr; - sw_memset_zero(&buffer_->info, sizeof(buffer_->info)); - return true; - } else { - return false; - } - } - - void pass(SendData *task) { - memcpy(&buffer_->info, &task->info, sizeof(buffer_->info)); - if (task->info.len > 0) { - buffer_->info.flags = SW_EVENT_DATA_PTR; - PacketPtr pkt{task->info.len, (char *) task->data}; - buffer_->info.len = sizeof(pkt); - memcpy(buffer_->data, &pkt, sizeof(pkt)); - } - } - - /** - * Send data to socket. If the data sent is larger than Server::ipc_max_size, then it is sent in chunks. - * Otherwise send it directly. - * @return: send success returns true, send failure returns false. - */ - bool write(network::Socket *sock, SendData *packet); - /** - * Receive data from socket, if only one chunk is received, packet will be saved in packet_pool. - * Then continue to listen to readable events, waiting for more chunks. - * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred - */ - ssize_t read(network::Socket *sock); - /** - * Receive data from pipeline, and store data to buffer - * @return: >0: receive a complete packet, 0: continue to wait for data, -1: an error occurred - */ - ssize_t read_with_buffer(network::Socket *sock); - /** - * The last chunk of data has been received, return address and length, start processing this packet. - */ - PacketPtr get_packet() const; - PipeBuffer *get_buffer() { - return buffer_; - } - /** - * Pop the data memory address to the outer layer, no longer managed by MessageBus - */ - char *move_packet() { - uint64_t msg_id = buffer_->info.msg_id; - auto iter = packet_pool_.find(msg_id); - if (iter != packet_pool_.end()) { - auto str = iter->second.get(); - char *val = str->str; - str->str = nullptr; - return val; - } else { - return nullptr; - } - } - /** - * The processing of this data packet has been completed, and the relevant memory has been released - */ - void pop() { - if (buffer_->is_end()) { - packet_pool_.erase(buffer_->info.msg_id); - } - } -}; - //------------------------------------ReactorThread------------------------------------------- struct ReactorThread { int id; std::thread thread; network::Socket *notify_pipe = nullptr; - uint32_t pipe_num = 0; uint64_t dispatch_count = 0; - network::Socket *pipe_sockets = nullptr; network::Socket *pipe_command = nullptr; + TimerNode *heartbeat_timer = nullptr; MessageBus message_bus; int init(Server *serv, Reactor *reactor, uint16_t reactor_id); + void shutdown(Reactor *reactor); + int close_connection(Reactor *reactor, SessionId session_id); + void clean(); }; struct ServerPortGS { sw_atomic_t connection_num; + sw_atomic_t *connection_nums = nullptr; sw_atomic_long_t abort_count; sw_atomic_long_t accept_count; sw_atomic_long_t close_count; @@ -324,6 +172,7 @@ struct ServerPortGS { }; struct ListenPort { + uint16_t object_id; /** * tcp socket listen backlog */ @@ -346,7 +195,7 @@ struct ListenPort { int tcp_user_timeout = 0; - uint16_t max_idle_time = 0; + double max_idle_time = 0; int socket_buffer_size = network::Socket::default_buffer_size; uint32_t buffer_high_watermark = 0; @@ -378,17 +227,9 @@ struct ListenPort { */ bool open_websocket_protocol = false; /** - * open websocket close frame - */ - bool open_websocket_close_frame = false; - /** - * open websocket ping frame + * Relevant settings of websocket server */ - bool open_websocket_ping_frame = false; - /** - * open websocket pong frame - */ - bool open_websocket_pong_frame = false; + WebSocketSettings websocket_settings = {}; /** * one package: length check */ @@ -413,27 +254,34 @@ struct ListenPort { * open tcp keepalive */ bool open_tcp_keepalive = false; - /** - * Sec-WebSocket-Protocol - */ - std::string websocket_subprotocol; /** * set socket option */ int kernel_socket_recv_buffer_size = 0; int kernel_socket_send_buffer_size = 0; -#ifdef SW_USE_OPENSSL - SSLContext *ssl_context = nullptr; + std::shared_ptr ssl_context = nullptr; std::unordered_map> sni_contexts; + #ifdef SW_SUPPORT_DTLS std::unordered_map *dtls_sessions = nullptr; - bool is_dtls() { - return ssl_context && (ssl_context->protocols & SW_SSL_DTLS); - } -#endif + dtls::Session *create_dtls_session(network::Socket *sock) const; #endif + bool ssl_is_enable() const { + return get_ssl_context() != nullptr; + } + + SSLContext *get_ssl_context() const { + return ssl_context.get(); + } + + std::shared_ptr dup_ssl_context() const { + auto new_ctx = std::make_shared(); + *new_ctx = *ssl_context; + return new_ctx; + } + ServerPortGS *gs = nullptr; Protocol protocol = {}; @@ -441,69 +289,197 @@ struct ListenPort { int (*onRead)(Reactor *reactor, ListenPort *port, Event *event) = nullptr; - inline bool is_dgram() { + bool is_dgram() const { return network::Socket::is_dgram(type); } - inline bool is_stream() { - return network::Socket::is_stream(type); + bool is_dtls() const { +#ifdef SW_SUPPORT_DTLS + return ssl_context && (ssl_context->protocols & SW_SSL_DTLS); +#else + return false; +#endif } - inline void set_eof_protocol(const std::string &eof, bool find_from_right = false) { - open_eof_check = true; - protocol.split_by_eof = !find_from_right; - protocol.package_eof_len = std::min(eof.length(), sizeof(protocol.package_eof)); - memcpy(protocol.package_eof, eof.c_str(), protocol.package_eof_len); + bool is_stream() const { + return network::Socket::is_stream(type); } - inline void set_length_protocol(uint32_t length_offset, char length_type, uint32_t body_offset) { - open_length_check = true; - protocol.package_length_type = length_type; - protocol.package_length_size = swoole_type_size(length_type); - protocol.package_body_offset = length_offset; - protocol.package_body_offset = body_offset; - } + void set_eof_protocol(const std::string &eof, bool find_from_right = false); + void set_length_protocol(uint32_t length_offset, char length_type, uint32_t body_offset); + void set_stream_protocol(); void set_package_max_length(uint32_t max_length) { protocol.package_max_length = max_length; } - ListenPort(); + explicit ListenPort(Server *server); ~ListenPort() = default; int listen(); void close(); bool import(int sock); - const char *get_protocols(); + void init_protocol(); + const char *get_protocols() const; + int create_socket(); + void close_socket(); + void destroy_http_request(Connection *conn); + + static int readable_callback_raw(Reactor *reactor, ListenPort *lp, Event *event); + static int readable_callback_length(Reactor *reactor, ListenPort *lp, Event *event); + static int readable_callback_eof(Reactor *reactor, ListenPort *lp, Event *event); + static int readable_callback_http(Reactor *reactor, ListenPort *lp, Event *event); + static int readable_callback_redis(Reactor *reactor, ListenPort *lp, Event *event); + + bool ssl_context_init(); + bool ssl_context_create(SSLContext *context) const; + bool ssl_create(network::Socket *sock); + bool ssl_add_sni_cert(const std::string &name, const std::shared_ptr &ctx); + static bool ssl_matches_wildcard_name(const char *subject_name, const char *cert_name); + bool ssl_init() const; + + bool set_ssl_key_file(const std::string &file) const { + return ssl_context->set_key_file(file); + } + + bool set_ssl_cert_file(const std::string &file) const { + return ssl_context->set_cert_file(file); + } + + void set_ssl_cafile(const std::string &file) const { + ssl_context->cafile = file; + } + + bool set_ssl_client_cert_file(const std::string &file) const { + return ssl_context->set_client_cert_file(file); + } + + void set_ssl_capath(const std::string &path) const { + ssl_context->capath = path; + } + + void set_ssl_passphrase(const std::string &str) const { + ssl_context->passphrase = str; + } + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + void set_tls_host_name(const std::string &str) const { + ssl_context->tls_host_name = str; + // if user set empty ssl_host_name, disable it, otherwise the underlying may set it automatically + ssl_context->disable_tls_host_name = ssl_context->tls_host_name.empty(); + } +#endif + + void set_ssl_dhparam(const std::string &file) const { + ssl_context->dhparam = file; + } + + void set_ssl_ecdh_curve(const std::string &str) const { + ssl_context->ecdh_curve = str; + } + + void set_ssl_protocols(long protocols) const { + if (protocols & SW_SSL_DTLS) { +#ifndef SW_SUPPORT_DTLS + protocols ^= SW_SSL_DTLS; +#else + if (is_dgram()) { + protocols ^= SW_SSL_DTLS; + } +#endif + } + ssl_context->protocols = protocols; + } + + void set_ssl_disable_compress(bool value) const { + ssl_context->disable_compress = value; + } + + void set_ssl_verify_peer(bool value) const { + ssl_context->verify_peer = value; + } + + void set_ssl_allow_self_signed(bool value) const { + ssl_context->allow_self_signed = value; + } -#ifdef SW_USE_OPENSSL - bool ssl_create_context(SSLContext *context); - bool ssl_create(Connection *conn, network::Socket *sock); - bool ssl_add_sni_cert(const std::string &name, SSLContext *context); - bool ssl_init(); + void set_ssl_verify_depth(uint8_t value) const { + ssl_context->verify_depth = value; + } - void ssl_set_key_file(const std::string &file) { - ssl_context->key_file = file; + void set_ssl_ciphers(const std::string &str) const { + ssl_context->ciphers = str; } - void ssl_set_cert_file(const std::string &file) { - ssl_context->cert_file = file; + + void set_ssl_prefer_server_ciphers(bool value) const { + ssl_context->prefer_server_ciphers = value; + } + +#ifdef OPENSSL_IS_BORINGSSL + void set_ssl_grease(uint8_t value) { + ssl_context->grease = value; } #endif + + const std::string &get_ssl_cert_file() const { + return ssl_context->cert_file; + } + + const std::string &get_ssl_key_file() const { + return ssl_context->key_file; + } + + const std::string &get_ssl_client_cert_file() const { + return ssl_context->client_cert_file; + } + + const std::string &get_ssl_capath() const { + return ssl_context->capath; + } + + const std::string &get_ssl_cafile() const { + return ssl_context->cafile; + } + + bool get_ssl_verify_peer() const { + return ssl_context->verify_peer; + } + + bool get_ssl_allow_self_signed() const { + return ssl_context->allow_self_signed; + } + + uint32_t get_ssl_protocols() const { + return ssl_context->protocols; + } + + bool has_sni_contexts() const { + return !sni_contexts.empty(); + } + + static int ssl_server_sni_callback(SSL *ssl, int *al, void *arg); void clear_protocol(); - inline network::Socket *get_socket() { + + network::Socket *get_socket() const { return socket; } - int get_port() { + + int get_port() const { return port; } - const char *get_host() { + + const char *get_host() const { return host.c_str(); } - SocketType get_type() { + + SocketType get_type() const { return type; } - int get_fd() { + + int get_fd() const { return socket ? socket->fd : -1; } + + size_t get_connection_num() const; }; struct ServerGS { @@ -517,8 +493,10 @@ struct ServerGS { int max_fd; int min_fd; + bool onstart_called; time_t start_time; sw_atomic_t connection_num; + sw_atomic_t *connection_nums = nullptr; sw_atomic_t tasking_num; uint32_t max_concurrency; sw_atomic_t concurrency; @@ -531,13 +509,11 @@ struct ServerGS { sw_atomic_long_t total_recv_bytes; sw_atomic_long_t total_send_bytes; sw_atomic_long_t pipe_packet_msg_id; + sw_atomic_long_t task_count; sw_atomic_t spinlock; -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_t manager_barrier; - pthread_barrierattr_t manager_barrier_attr; -#endif + Barrier manager_barrier; ProcessPool task_workers; ProcessPool event_workers; @@ -548,43 +524,92 @@ class Factory { Server *server_; public: - Factory(Server *_server) { + explicit Factory(Server *_server) { server_ = _server; } - virtual ~Factory() {} + pid_t spawn_event_worker(Worker *worker) const; + pid_t spawn_user_worker(Worker *worker) const; + pid_t spawn_task_worker(Worker *worker) const; + void kill_user_workers() const; + void kill_event_workers() const; + void kill_task_workers() const; + void check_worker_exit_status(Worker *worker, const ExitStatus &exit_status) const; + virtual ~Factory() = default; virtual bool start() = 0; virtual bool shutdown() = 0; virtual bool dispatch(SendData *) = 0; virtual bool finish(SendData *) = 0; virtual bool notify(DataHead *) = 0; - virtual bool end(SessionId sesion_id, int flags) = 0; + virtual bool end(SessionId session_id, int flags) = 0; }; class BaseFactory : public Factory { public: - BaseFactory(Server *server) : Factory(server) {} - ~BaseFactory(); + explicit BaseFactory(Server *server); + ~BaseFactory() override; bool start() override; bool shutdown() override; bool dispatch(SendData *) override; bool finish(SendData *) override; bool notify(DataHead *) override; - bool end(SessionId sesion_id, int flags) override; + bool end(SessionId session_id, int flags) override; + bool forward_message(const Session *session, SendData *data) const; }; class ProcessFactory : public Factory { - private: - std::vector> pipes; - public: - ProcessFactory(Server *server); - ~ProcessFactory(); + explicit ProcessFactory(Server *server); + ~ProcessFactory() override; bool start() override; bool shutdown() override; bool dispatch(SendData *) override; bool finish(SendData *) override; bool notify(DataHead *) override; - bool end(SessionId sesion_id, int flags) override; + bool end(SessionId session_id, int flags) override; +}; + +struct ThreadReloadTask { + Server *server_; + uint16_t worker_num; + uint16_t reloaded_num; + + bool is_completed() const { + return reloaded_num == worker_num; + } + + ThreadReloadTask(Server *_server, bool _reload_all_workers); + ~ThreadReloadTask() = default; +}; + +class ThreadFactory : public BaseFactory { + std::vector> threads_; + std::mutex lock_; + std::condition_variable cv_; + std::queue queue_; + bool reload_all_workers = false; + sw_atomic_t reloading = 0; + std::shared_ptr reload_task; + void at_thread_enter(WorkerId id, int process_type); + void at_thread_exit(Worker *worker); + void create_message_bus() const; + void destroy_message_bus(); + void do_reload(); + void push_to_wait_queue(Worker *worker); + + public: + explicit ThreadFactory(Server *server); + ~ThreadFactory() override; + WorkerId get_manager_thread_id() const; + WorkerId get_master_thread_id() const; + void spawn_event_worker(WorkerId i); + void spawn_task_worker(WorkerId i); + void spawn_user_worker(WorkerId i); + void spawn_manager_thread(WorkerId i); + void terminate_manager_thread(); + void wait(); + bool reload(bool reload_all_workers); + bool start() override; + bool shutdown() override; }; enum ServerEventType { @@ -598,6 +623,7 @@ enum ServerEventType { SW_SERVER_EVENT_CLOSE, SW_SERVER_EVENT_CONNECT, SW_SERVER_EVENT_CLOSE_FORCE, + SW_SERVER_EVENT_CLOSE_FORWARD, // task SW_SERVER_EVENT_TASK, SW_SERVER_EVENT_FINISH, @@ -614,6 +640,7 @@ enum ServerEventType { SW_SERVER_EVENT_SHUTDOWN, SW_SERVER_EVENT_COMMAND_REQUEST, SW_SERVER_EVENT_COMMAND_RESPONSE, + SW_SERVER_EVENT_SHUTDOWN_SIGNAL, }; class Server { @@ -636,9 +663,25 @@ class Server { std::string name; }; + struct MultiTask { + uint16_t count; + std::unordered_map map; + + std::function pack; + std::function unpack; + std::function fail; + + explicit MultiTask(uint16_t n) { + count = n; + } + + int find(TaskId task_id); + }; + enum Mode { MODE_BASE = 1, MODE_PROCESS = 2, + MODE_THREAD = 3, }; enum TaskIpcMode { @@ -649,9 +692,11 @@ class Server { }; enum ThreadType { + THREAD_NORMAL = 0, THREAD_MASTER = 1, THREAD_REACTOR = 2, THREAD_HEARTBEAT = 3, + THREAD_WORKER = 4, }; enum DispatchMode { @@ -661,7 +706,6 @@ class Server { DISPATCH_IPMOD = 4, DISPATCH_UIDMOD = 5, DISPATCH_USERFUNC = 6, - DISPATCH_STREAM = 7, DISPATCH_CO_CONN_LB = 8, DISPATCH_CO_REQ_LB = 9, DISPATCH_CONCURRENT_LB = 10, @@ -673,6 +717,7 @@ class Server { DISPATCH_RESULT_USERFUNC_FALLBACK = -3, }; + // deprecated, will be removed in the next minor version enum HookType { HOOK_MASTER_START, HOOK_MASTER_TIMER, @@ -730,7 +775,6 @@ class Server { network::Socket *udp_socket_ipv4 = nullptr; network::Socket *udp_socket_ipv6 = nullptr; network::Socket *dgram_socket = nullptr; - int null_fd = -1; uint32_t max_wait_time = SW_WORKER_MAX_WAIT_TIME; uint32_t worker_max_concurrency = UINT_MAX; @@ -842,7 +886,7 @@ class Server { */ bool running = true; - int *cpu_affinity_available = 0; + int *cpu_affinity_available = nullptr; int cpu_affinity_available_num = 0; UnixSocket *pipe_command = nullptr; @@ -873,17 +917,26 @@ class Server { void *private_data_1 = nullptr; void *private_data_2 = nullptr; void *private_data_3 = nullptr; + void *private_data_4 = nullptr; - Factory *factory = nullptr; - Manager *manager = nullptr; + Factory *factory_ = nullptr; + Manager *manager_ = nullptr; std::vector ports; + std::vector> worker_pipes; - inline ListenPort *get_primary_port() { + ListenPort *get_primary_port() const { return ports.front(); } - inline ListenPort *get_port(int _port) { + Mode get_mode() const { + return mode_; + }; + + /** + * This method can only be used for INET ports and cannot obtain Unix socket ports. + */ + ListenPort *get_port(int _port) const { for (auto port : ports) { if (port->port == _port || _port == 0) { return port; @@ -892,42 +945,80 @@ class Server { return nullptr; } - inline ListenPort *get_port_by_server_fd(int server_fd) { - return (ListenPort *) connection_list[server_fd].object; + ListenPort *get_port(SocketType type, const char *host, int _port) const { + for (auto port : ports) { + if (port->port == _port && port->type == type && strcmp(host, port->host.c_str()) == 0) { + return port; + } + } + return nullptr; + } + + ListenPort *get_port_by_server_fd(const int server_fd) const { + return static_cast(connection_list[server_fd].object); } - inline ListenPort *get_port_by_fd(int fd) { + ListenPort *get_port_by_fd(int fd) const { return get_port_by_server_fd(connection_list[fd].server_fd); } - inline ListenPort *get_port_by_session_id(SessionId session_id) { - Connection *conn = get_connection_by_session_id(session_id); + ListenPort *get_port_by_session_id(SessionId session_id) const { + const Connection *conn = get_connection_by_session_id(session_id); if (!conn) { return nullptr; } return get_port_by_fd(conn->fd); } - inline network::Socket *get_server_socket(int fd) { - return connection_list[fd].socket; + uint32_t get_package_max_length(Connection *conn) { + return get_port_by_fd(conn->fd)->protocol.package_max_length; } - /** - * [ReactorThread] - */ - network::Socket *get_worker_pipe_socket(Worker *worker) { - return &get_thread(SwooleTG.id)->pipe_sockets[worker->pipe_master->fd]; + network::Socket *get_server_socket(int fd) const { + return connection_list[fd].socket; } - network::Socket *get_command_reply_socket() { + network::Socket *get_command_reply_socket() const { return is_base_mode() ? get_worker(0)->pipe_master : pipe_command->get_socket(false); } + network::Socket *get_worker_pipe_master(WorkerId id) const { + return get_worker(id)->pipe_master; + } + + network::Socket *get_worker_pipe_worker(WorkerId id) const { + return get_worker(id)->pipe_worker; + } + + network::Socket *get_pipe_socket_in_message_bus(network::Socket *_socket) { + if (is_thread_mode()) { + return get_worker_message_bus()->get_pipe_socket(_socket); + } else { + return _socket; + } + } + + network::Socket *get_worker_pipe_master_in_message_bus(const Worker *worker) { + return get_pipe_socket_in_message_bus(worker->pipe_master); + } + + network::Socket *get_worker_pipe_worker_in_message_bus(const Worker *worker) { + return get_pipe_socket_in_message_bus(worker->pipe_worker); + } + + network::Socket *get_worker_pipe_master_in_message_bus(WorkerId id) { + return get_worker_pipe_master_in_message_bus(get_worker(id)); + } + + network::Socket *get_worker_pipe_worker_in_message_bus(WorkerId id) { + return get_worker_pipe_worker_in_message_bus(get_worker(id)); + } + /** * [Worker|Master] */ - inline network::Socket *get_reactor_pipe_socket(SessionId session_id, int reactor_id) { - int pipe_index = session_id % reactor_pipe_num; + network::Socket *get_reactor_pipe_socket(const SessionId session_id, int reactor_id) const { + const int pipe_index = session_id % reactor_pipe_num; /** * pipe_worker_id: The pipe in which worker. */ @@ -944,13 +1035,15 @@ class Server { uint32_t task_max_request = 0; uint32_t task_max_request_grace = 0; std::vector> task_notify_pipes; - EventData *task_result = nullptr; + EventData *task_results = nullptr; /** - * user process + * Used for process management, saving the mapping relationship between PID and worker pointers */ - std::vector user_worker_list; std::unordered_map user_worker_map; + /** + * Shared memory, sharing state between processes + */ Worker *user_workers = nullptr; std::unordered_map commands; @@ -966,9 +1059,7 @@ class Server { std::shared_ptr> http_index_files = nullptr; std::shared_ptr> http_compression_types = nullptr; -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_t reactor_thread_barrier = {}; -#endif + Barrier reactor_thread_barrier = {}; /** * temporary directory for HTTP uploaded file. @@ -981,22 +1072,13 @@ class Server { /** * http compression level for gzip/br */ -#ifdef SW_HAVE_COMPRESSION uint8_t http_compression_level = 0; uint32_t compression_min_length; -#endif /** * master process pid */ std::string pid_file; - /** - * stream - */ - char *stream_socket_file = nullptr; - network::Socket *stream_socket = nullptr; - Protocol stream_protocol = {}; - network::Socket *last_stream_socket = nullptr; - EventData *last_task = nullptr; + std::queue *buffer_pool = nullptr; const Allocator *recv_buffer_allocator = &SwooleG.std_allocator; @@ -1023,16 +1105,16 @@ class Server { */ std::function onManagerStart; std::function onManagerStop; - std::function onWorkerError; + std::function onWorkerError; std::function onBeforeReload; std::function onAfterReload; /** * Worker Process */ std::function onPipeMessage; - std::function onWorkerStart; - std::function onWorkerStop; - std::function onWorkerExit; + std::function onWorkerStart; + std::function onWorkerStop; + std::function onWorkerExit; std::function onUserWorkerStart; /** * Connection @@ -1048,120 +1130,150 @@ class Server { */ std::function onTask; std::function onFinish; - + /** + * for MessageBus + */ + std::function msg_id_generator; /** * Hook */ int (*dispatch_func)(Server *, Connection *, SendData *) = nullptr; - public: - Server(enum Mode _mode = MODE_BASE); + explicit Server(Mode _mode = MODE_BASE); ~Server(); - bool set_document_root(const std::string &path) { - if (path.length() > PATH_MAX) { - swoole_warning("The length of document_root must be less than %d", PATH_MAX); - return false; - } - - char _realpath[PATH_MAX]; - if (!realpath(path.c_str(), _realpath)) { - swoole_warning("document_root[%s] does not exist", path.c_str()); - return false; - } - - document_root = std::string(_realpath); - return true; - } - + bool set_document_root(const std::string &path); void add_static_handler_location(const std::string &); void add_static_handler_index_files(const std::string &); - bool select_static_handler(http_server::Request *request, Connection *conn); + bool select_static_handler(const http_server::Request *request, const Connection *conn); + bool apply_rewrite_rules(http_server::StaticHandler *handler); void add_http_compression_type(const std::string &type); + void add_rewrite_rule(const std::string &pattern, const std::string &replacement); int create(); + bool create_worker_pipes(); + int start(); - void shutdown(); + void destroy(); + bool reload(bool reload_all_workers) const; + bool shutdown(); int add_worker(Worker *worker); ListenPort *add_port(SocketType type, const char *host, int port); int add_systemd_socket(); - int add_hook(enum HookType type, const Callback &func, int push_back); + void add_hook(enum HookType type, const Callback &func, int push_back); bool add_command(const std::string &command, int accepted_process_types, const Command::Handler &func); - Connection *add_connection(ListenPort *ls, network::Socket *_socket, int server_fd); - void abort_connection(Reactor *reactor, ListenPort *ls, network::Socket *_socket); - int connection_incoming(Reactor *reactor, Connection *conn); + Connection *add_connection(const ListenPort *ls, network::Socket *_socket, int server_fd); + const char *get_local_addr(Connection *conn); + const char *get_remote_addr(Connection *conn); + void abort_connection(Reactor *reactor, const ListenPort *ls, network::Socket *_socket) const; + void abort_worker(Worker *worker) const; + void reset_worker_counter(Worker *worker) const; + int connection_incoming(Reactor *reactor, Connection *conn) const; - int get_idle_worker_num(); - int get_idle_task_worker_num(); - int get_task_count(); + uint32_t get_idle_worker_num() const; + int get_idle_task_worker_num() const; + int get_tasking_num() const; - inline int get_minfd() { + TaskId get_task_id(const EventData *task) const { + return get_task_worker_pool()->get_task_id(task); + } + + static uint16_t get_command_id(const EventData *cmd) { + return cmd->info.server_fd; + } + + EventData *get_task_result() const { + return &(task_results[swoole_get_worker_id()]); + } + + WorkerId get_task_src_worker_id(const EventData *task) const { + return get_task_worker_pool()->get_task_src_worker_id(task); + } + + int get_minfd() const { return gs->min_fd; } - inline int get_maxfd() { + int get_maxfd() const { return gs->max_fd; } - inline void set_maxfd(int maxfd) { + void set_maxfd(int maxfd) const { gs->max_fd = maxfd; } - inline void set_minfd(int minfd) { + void set_minfd(int minfd) const { gs->min_fd = minfd; } - pid_t get_master_pid() { + pid_t get_master_pid() const { return gs->master_pid; } - pid_t get_manager_pid() { + pid_t get_manager_pid() const { return gs->manager_pid; } - void store_listen_socket(); - void store_pipe_fd(UnixSocket *p); + pid_t get_worker_pid(WorkerId worker_id) const { + return get_worker(worker_id)->pid; + } - inline const std::string &get_document_root() { + const std::string &get_document_root() { return document_root; } - inline String *get_recv_buffer(network::Socket *_socket) { + String *get_recv_buffer(network::Socket *_socket) const { String *buffer = _socket->recv_buffer; if (buffer == nullptr) { - buffer = swoole::make_string(SW_BUFFER_SIZE_BIG, recv_buffer_allocator); - if (!buffer) { - return nullptr; - } + buffer = new String(SW_BUFFER_SIZE_BIG, recv_buffer_allocator); _socket->recv_buffer = buffer; } return buffer; } - inline uint32_t get_worker_buffer_num() { + MessageBus *get_worker_message_bus() { +#ifdef SW_THREAD + return sw_likely(is_thread_mode()) ? SwooleTG.message_bus : &message_bus; +#else + return &message_bus; +#endif + } + + uint32_t get_worker_buffer_num() const { return is_base_mode() ? 1 : reactor_num + dgram_port_num; } - inline bool is_support_unsafe_events() { - if (dispatch_mode != DISPATCH_ROUND && dispatch_mode != DISPATCH_IDLE_WORKER && - dispatch_mode != DISPATCH_STREAM) { + ProcessPool *get_task_worker_pool() const { + return &gs->task_workers; + } + + ProcessPool *get_event_worker_pool() const { + return &gs->event_workers; + } + + bool is_support_unsafe_events() const { + if (is_hash_dispatch_mode()) { return true; } else { return enable_unsafe_event; } } - inline bool is_process_mode() { + bool is_process_mode() const { return mode_ == MODE_PROCESS; } - inline bool is_base_mode() { + bool is_base_mode() const { return mode_ == MODE_BASE; } - inline bool is_enable_coroutine() { + bool is_thread_mode() const { + return mode_ == MODE_THREAD; + } + + bool is_enable_coroutine() const { if (is_task_worker()) { return task_enable_coroutine; } else if (is_manager()) { @@ -1171,108 +1283,117 @@ class Server { } } - inline bool is_hash_dispatch_mode() { + bool is_master_thread() const { + return swoole_get_thread_type() == THREAD_MASTER; + } + + bool is_hash_dispatch_mode() const { return dispatch_mode == DISPATCH_FDMOD || dispatch_mode == DISPATCH_IPMOD || dispatch_mode == DISPATCH_CO_CONN_LB; } - inline bool is_support_send_yield() { + bool is_support_send_yield() const { return is_hash_dispatch_mode(); } - inline bool if_require_packet_callback(ListenPort *port, bool isset) { -#ifdef SW_USE_OPENSSL + bool if_require_packet_callback(const ListenPort *port, bool isset) const { return (port->is_dgram() && !port->ssl && !isset); -#else - return (port->is_dgram() && !isset); -#endif } - inline bool if_require_receive_callback(ListenPort *port, bool isset) { -#ifdef SW_USE_OPENSSL + bool if_require_receive_callback(const ListenPort *port, bool isset) const { return (((port->is_dgram() && port->ssl) || port->is_stream()) && !isset); -#else - return (port->is_stream() && !isset); -#endif } - inline Worker *get_worker(uint16_t worker_id) { - // Event Worker - if (worker_id < worker_num) { - return &(gs->event_workers.workers[worker_id]); - } - - // Task Worker - uint32_t task_worker_max = task_worker_num + worker_num; - if (worker_id < task_worker_max) { - return &(gs->task_workers.workers[worker_id - worker_num]); - } - - // User Worker - uint32_t user_worker_max = task_worker_max + user_worker_list.size(); - if (worker_id < user_worker_max) { - return &(user_workers[worker_id - task_worker_max]); - } + bool if_forward_message(const Session *session) const { + return session->reactor_id != swoole_get_worker_id(); + } - return nullptr; + bool if_do_close_callback(Connection *conn) const { + return onClose != nullptr && sw_atomic_cmp_set(&conn->closed, 0, 1); } + Worker *get_worker(uint16_t worker_id) const; + bool kill_worker(int worker_id); void stop_async_worker(Worker *worker); - inline Pipe *get_pipe_object(int pipe_fd) { - return (Pipe *) connection_list[pipe_fd].object; + Pipe *get_pipe_object(int pipe_fd) const { + return static_cast(connection_list[pipe_fd].object); } - size_t get_all_worker_num() { - return worker_num + task_worker_num + get_user_worker_num(); + size_t get_all_worker_num() const { + return get_core_worker_num() + get_user_worker_num(); } - size_t get_user_worker_num() { + size_t get_user_worker_num() const { return user_worker_list.size(); } - inline ReactorThread *get_thread(int reactor_id) { + size_t get_core_worker_num() const { + return worker_num + task_worker_num; + } + + ReactorThread *get_thread(int reactor_id) const { return &reactor_threads[reactor_id]; } - inline bool is_started() { + bool is_started() const { return gs->start; } - bool is_created() { - return factory != nullptr; + bool is_created() const { + return factory_ != nullptr; + } + + bool is_running() const { + return running; + } + + bool is_master() const { + return swoole_get_worker_type() == SW_MASTER; + } + + bool is_worker() const { + return swoole_get_worker_type() == SW_EVENT_WORKER; } - bool is_master() { - return SwooleG.process_type == SW_PROCESS_MASTER; + bool is_event_worker() const { + return is_worker(); } - bool is_worker() { - return SwooleG.process_type == SW_PROCESS_WORKER; + bool is_task_worker() const { + return swoole_get_worker_type() == SW_TASK_WORKER; } - bool is_task_worker() { - return SwooleG.process_type == SW_PROCESS_TASKWORKER; + bool is_manager() const { + return swoole_get_worker_type() == SW_MANAGER; } - bool is_manager() { - return SwooleG.process_type == SW_PROCESS_MANAGER; + bool is_user_worker() const { + return swoole_get_worker_type() == SW_USER_WORKER; } - bool is_user_worker() { - return SwooleG.process_type == SW_PROCESS_USERWORKER; + bool is_worker_thread() const { + return is_thread_mode() && swoole_get_thread_type() == THREAD_WORKER; + } + + bool is_worker_process() const { + return !is_thread_mode() && (is_worker() || is_task_worker()); } bool is_reactor_thread() { - return SwooleG.process_type == SW_PROCESS_MASTER && SwooleTG.type == Server::THREAD_REACTOR; + return swoole_get_thread_type() == THREAD_REACTOR; + } + + bool is_single_worker() const { + return (worker_num == 1 && task_worker_num == 0 && max_request == 0 && get_user_worker_num() == 0); } - bool isset_hook(enum HookType type) { + bool isset_hook(HookType type) const { assert(type <= HOOK_END); return hooks[type]; } - bool is_sync_process() { + bool is_sync_process() const { if (is_manager()) { return true; } @@ -1281,27 +1402,23 @@ class Server { } return false; } - inline bool is_shutdown() { + + bool is_shutdown() const { return gs->shutdown; } // can only be used in the main process - inline bool is_valid_connection(Connection *conn) { + static bool is_valid_connection(const Connection *conn) { return (conn && conn->socket && conn->active && conn->socket->fd_type == SW_FD_SESSION); } - bool is_healthy_connection(double now, Connection *conn); + bool is_healthy_connection(double now, const Connection *conn) const; - static int is_dgram_event(uint8_t type) { - switch (type) { - case SW_SERVER_EVENT_RECV_DGRAM: - return true; - default: - return false; - } + static bool is_dgram_event(uint8_t type) { + return type == SW_SERVER_EVENT_RECV_DGRAM; } - static int is_stream_event(uint8_t type) { + static bool is_stream_event(uint8_t type) { switch (type) { case SW_SERVER_EVENT_RECV_DATA: case SW_SERVER_EVENT_SEND_DATA: @@ -1318,11 +1435,11 @@ class Server { } } - inline int get_connection_fd(SessionId session_id) { + int get_connection_fd(SessionId session_id) const { return session_list[session_id % SW_SESSION_LIST_SIZE].fd; } - inline Connection *get_connection_verify_no_ssl(SessionId session_id) { + Connection *get_connection_verify_no_ssl(SessionId session_id) const { Session *session = get_session(session_id); int fd = session->fd; Connection *conn = get_connection(fd); @@ -1335,107 +1452,114 @@ class Server { return conn; } - inline Connection *get_connection_verify(SessionId session_id) { + Connection *get_connection_verify(SessionId session_id) const { Connection *conn = get_connection_verify_no_ssl(session_id); -#ifdef SW_USE_OPENSSL if (conn && conn->ssl && !conn->ssl_ready) { return nullptr; } -#endif return conn; } - inline Connection *get_connection(int fd) { - if ((uint32_t) fd > max_connection) { + Connection *get_connection(const int fd) const { + if (static_cast(fd) > max_connection) { return nullptr; } return &connection_list[fd]; } - inline Connection *get_connection_for_iterator(int fd) { + Connection *get_connection_for_iterator(int fd) const { Connection *conn = get_connection(fd); if (conn && conn->active && !conn->closed) { -#ifdef SW_USE_OPENSSL if (conn->ssl && !conn->ssl_ready) { return nullptr; } -#endif return conn; } return nullptr; } - inline Connection *get_connection_by_session_id(SessionId session_id) { + Connection *get_connection_by_session_id(SessionId session_id) const { return get_connection(get_connection_fd(session_id)); } - inline Session *get_session(SessionId session_id) { + Session *get_session(SessionId session_id) const { return &session_list[session_id % SW_SESSION_LIST_SIZE]; } - inline void lock() { - lock_.lock(); - } - - inline void unlock() { - lock_.unlock(); - } - - void close_port(bool only_stream_port); void clear_timer(); static void timer_callback(Timer *timer, TimerNode *tnode); - int create_task_workers(); - int create_user_workers(); + bool create_event_workers(); + bool create_task_workers(); + bool create_user_workers(); int start_manager_process(); void call_hook(enum HookType type, void *arg); void call_worker_start_callback(Worker *worker); - ResultCode call_command_handler(MessageBus &mb, uint16_t worker_id, network::Socket *sock); + void call_worker_stop_callback(Worker *worker); + void call_worker_error_callback(Worker *worker, const ExitStatus &status); + void call_command_handler(MessageBus &mb, uint16_t worker_id, network::Socket *sock); std::string call_command_handler_in_master(int command_id, const std::string &msg); void call_command_callback(int64_t request_id, const std::string &result); - void foreach_connection(const std::function &callback); + void foreach_connection(const std::function &callback) const; static int accept_connection(Reactor *reactor, Event *event); #ifdef SW_SUPPORT_DTLS - dtls::Session *accept_dtls_connection(ListenPort *ls, network::Address *sa); + dtls::Session *accept_dtls_connection(const ListenPort *ls, const network::Address *sa); #endif static int accept_command_result(Reactor *reactor, Event *event); static int close_connection(Reactor *reactor, network::Socket *_socket); static int dispatch_task(const Protocol *proto, network::Socket *_socket, const RecvData *rdata); - int send_to_connection(SendData *); - ssize_t send_to_worker_from_worker(Worker *dst_worker, const void *buf, size_t len, int flags); + int send_to_connection(const SendData *) const; + ssize_t send_to_worker_from_worker(const Worker *dst_worker, const void *buf, size_t len, int flags); + bool has_kernel_nobufs_error(SessionId session_id) const; - ssize_t send_to_worker_from_worker(WorkerId id, EventData *data, int flags) { - return send_to_worker_from_worker(get_worker(id), data, sizeof(data->info) + data->info.len, flags); + ssize_t send_to_worker_from_worker(WorkerId id, const EventData *data, int flags) { + return send_to_worker_from_worker(get_worker(id), data, data->size(), flags); } - ssize_t send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id); - int reply_task_result(const char *data, size_t data_len, int flags, EventData *current_task); + ssize_t send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id) const; - bool send(SessionId session_id, const void *data, uint32_t length); - bool sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length); - bool sendwait(SessionId session_id, const void *data, uint32_t length); - bool close(SessionId session_id, bool reset); + /** + * Send data to session. + * This function is used for sending data to the client in the server. + * @return true on success, false on failure. + */ + bool send(SessionId session_id, const void *data, uint32_t length) const; + /** + * Send file to session. + * This function is used for sending files in the HTTP server. + * It will read the file from disk and send it to the client. + */ + bool sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length) const; + bool sendwait(SessionId session_id, const void *data, uint32_t length) const; + bool close(SessionId session_id, bool reset = false) const; - bool notify(Connection *conn, enum ServerEventType event); - bool feedback(Connection *conn, enum ServerEventType event); + bool notify(Connection *conn, ServerEventType event) const; + bool feedback(Connection *conn, ServerEventType event); bool command(WorkerId process_id, Command::ProcessType process_type, const std::string &name, const std::string &msg, const Command::Callback &fn); + bool task(EventData *_task, int *dst_worker_id, bool blocking = false); + bool finish(const char *data, size_t data_len, int flags = 0, const EventData *current_task = nullptr); + bool task_sync(EventData *task, int *dst_worker_id, double timeout = -1); + bool task_sync(MultiTask &mtask, double timeout = -1); + bool send_pipe_message(WorkerId worker_id, EventData *msg); + bool send_pipe_message(WorkerId worker_id, const char *data, size_t len); + void init_reactor(Reactor *reactor); - void init_worker(Worker *worker); - void init_task_workers(); - void init_port_protocol(ListenPort *port); - void init_signal_handler(); + void init_event_worker(Worker *worker) const; + bool init_task_workers(); + void init_signal_handler() const; void init_ipc_max_size(); + void init_pipe_sockets(MessageBus *mb) const; void set_max_connection(uint32_t _max_connection); - void set_max_concurrency(uint32_t _max_concurrency) { + void set_max_concurrency(uint32_t _max_concurrency) const { if (_max_concurrency == 0) { _max_concurrency = UINT_MAX; } @@ -1449,70 +1573,85 @@ class Server { worker_max_concurrency = _max_concurrency; } - uint32_t get_max_connection() { + uint32_t get_max_connection() const { return max_connection; } - uint32_t get_max_concurrency() { + uint32_t get_max_concurrency() const { return gs->max_concurrency; } - uint32_t get_concurrency() { + uint32_t get_concurrency() const { return gs->concurrency; } - bool is_unavailable() { + bool is_unavailable() const { return get_concurrency() >= get_max_concurrency(); } - uint32_t get_worker_max_concurrency() { + uint32_t get_worker_max_concurrency() const { return worker_max_concurrency; } - void set_start_session_id(SessionId value) { + void set_start_session_id(SessionId value) const { if (value > UINT_MAX) { value = UINT_MAX; } gs->session_round = value; } - int create_pipe_buffers(); - void release_pipe_buffers(); - void create_worker(Worker *worker); - void destroy_worker(Worker *worker); void disable_accept(); - void destroy_http_request(Connection *conn); - int schedule_worker(int fd, SendData *data); - /** - * [Manager] - */ - pid_t spawn_event_worker(Worker *worker); - pid_t spawn_user_worker(Worker *worker); - pid_t spawn_task_worker(Worker *worker); - - void kill_user_workers(); - void kill_event_workers(); - void kill_task_workers(); + size_t get_connection_num() const { + if (gs->connection_nums) { + size_t num = 0; + for (uint32_t i = 0; i < worker_num; i++) { + num += gs->connection_nums[i]; + } + return num; + } else { + return gs->connection_num; + } + } static int wait_other_worker(ProcessPool *pool, const ExitStatus &exit_status); static void read_worker_message(ProcessPool *pool, EventData *msg); - void drain_worker_pipe(); - - void check_worker_exit_status(int worker_id, const ExitStatus &exit_status); + void drain_worker_pipe() const; + void clean_worker_connections(Worker *worker); /** * [Worker] */ - void worker_start_callback(); - void worker_stop_callback(); + void worker_start_callback(Worker *worker); + void worker_stop_callback(Worker *worker); void worker_accept_event(DataHead *info); + void worker_signal_init() const; + + std::function, const WorkerFn &fn)> worker_thread_start; + + /** + * [Master] + */ + bool signal_handler_shutdown(); + bool signal_handler_child_exit() const; + bool signal_handler_reload(bool reload_all_workers) const; + bool signal_handler_read_message() const; + bool signal_handler_reopen_logger() const; + static void worker_signal_handler(int signo); - static void worker_signal_init(void); + static int reactor_process_main_loop(ProcessPool *pool, Worker *worker); + static void reactor_thread_main_loop(Server *serv, int reactor_id); static bool task_pack(EventData *task, const void *data, size_t data_len); + static void task_dump(EventData *task); static bool task_unpack(EventData *task, String *buffer, PacketPtr *packet); + static void master_signal_handler(int signo); + static void heartbeat_check(Timer *timer, TimerNode *tnode); + + int start_event_worker(Worker *worker); + + const char *get_startup_error_message() const; private: enum Mode mode_; @@ -1523,6 +1662,8 @@ class Server { * http static file directory */ std::string document_root; + + std::shared_ptr> rewrite_rules; std::mutex lock_; uint32_t max_connection = 0; TimerNode *enable_accept_timer = nullptr; @@ -1532,43 +1673,61 @@ class Server { */ uint16_t reactor_pipe_num = 0; ReactorThread *reactor_threads = nullptr; + /** + * Only used for temporarily saving pointers in add_worker() + */ + std::vector user_worker_list; + std::unordered_map local_addr_v4_map; + std::unordered_map local_addr_v6_map; + int create_pipe_buffers(); + void release_pipe_buffers(); + void create_worker(Worker *worker); + Factory *create_base_factory(); + Factory *create_thread_factory(); + Factory *create_process_factory(); int start_check(); - void check_port_type(ListenPort *ls); - void destroy(); - void destroy_reactor_threads(); - void destroy_reactor_processes(); - int create_reactor_processes(); - int create_reactor_threads(); + void check_port_type(const ListenPort *ls); + void store_listen_socket(); + void store_pipe_fd(UnixSocket *p); + void destroy_base_factory() const; + void destroy_thread_factory() const; + void destroy_process_factory(); + void destroy_worker(Worker *worker); + void destroy_task_workers() const; int start_reactor_threads(); int start_reactor_processes(); - int start_master_thread(); - int start_event_worker(Worker *worker); + int start_worker_threads(); + int start_master_thread(Reactor *reactor); void start_heartbeat_thread(); + void stop_worker_threads(); + bool reload_worker_threads(bool reload_all_workers) const; void join_reactor_thread(); - TimerCallback get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn); + void stop_master_thread(); + void join_heartbeat_thread(); + TimerCallback get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn) const; + bool init_network_interface_addr_map(); + uint16_t get_local_addr_index(network::Address *addr); - int get_lowest_load_worker_id() { + int get_lowest_load_worker_id() const { uint32_t lowest_load_worker_id = 0; size_t min_coroutine = workers[0].coroutine_num; for (uint32_t i = 1; i < worker_num; i++) { if (workers[i].coroutine_num < min_coroutine) { min_coroutine = workers[i].coroutine_num; lowest_load_worker_id = i; - continue; } } return lowest_load_worker_id; } - int get_lowest_concurrent_worker_id() { + int get_lowest_concurrent_worker_id() const { uint32_t lowest_concurrent_worker_id = 0; size_t min_concurrency = workers[0].concurrency; for (uint32_t i = 1; i < worker_num; i++) { if (workers[i].concurrency < min_concurrency) { min_concurrency = workers[i].concurrency; lowest_concurrent_worker_id = i; - continue; } } return lowest_concurrent_worker_id; @@ -1579,7 +1738,7 @@ class Server { uint32_t key = 0; SW_LOOP_N(worker_num + 1) { key = sw_atomic_fetch_add(&worker_round_id, 1) % worker_num; - if (workers[key].status == SW_WORKER_IDLE) { + if (workers[key].is_idle()) { found = true; break; } @@ -1590,16 +1749,20 @@ class Server { swoole_trace_log(SW_TRACE_SERVER, "schedule=%d, round=%d", key, worker_round_id); return key; } + + void lock() { + lock_.lock(); + } + + void unlock() { + lock_.unlock(); + } }; } // namespace swoole typedef swoole::Server swServer; -typedef swoole::ListenPort swListenPort; -typedef swoole::RecvData swRecvData; - -extern swoole::Server *g_server_instance; static inline swoole::Server *sw_server() { - return g_server_instance; + return SwooleG.server; } diff --git a/include/swoole_signal.h b/include/swoole_signal.h index f20a056e8b..e2e1db5c09 100644 --- a/include/swoole_signal.h +++ b/include/swoole_signal.h @@ -18,7 +18,7 @@ #include "swoole.h" -#include +#include namespace swoole { typedef void (*SignalHandler)(int); @@ -36,11 +36,35 @@ typedef swoole::SignalHandler swSignalHandler; void swoole_signalfd_init(); #endif -SW_API swSignalHandler swoole_signal_set(int signo, swSignalHandler func); +/** + * The synchronous blocking IO mode is unsafe for executing PHP code within signal callback functions, + * such as in the Server's Task worker process or the Manager process. + * If a new signal is triggered during the execution of a signal function, + * the recursive execution of the signal function can lead to a crash of the ZendVM. + * When using `Swoole\Process::signal()` to register a PHP function as a signal handler, + * it is crucial to set the third parameter to true; + * this way, the underlying layer will not execute directly but will call + * `swoole_signal_dispatch()` in a safe manner to execute the PHP signal callback function. + */ +SW_API swSignalHandler swoole_signal_set(int signo, swSignalHandler func, bool safety = false); +SW_API bool swoole_signal_isset(int signo); SW_API swSignalHandler swoole_signal_set(int signo, swSignalHandler func, int restart, int mask); SW_API swSignalHandler swoole_signal_get_handler(int signo); +SW_API uint32_t swoole_signal_get_listener_num(); -SW_API void swoole_signal_clear(void); -SW_API void swoole_signal_block_all(void); +SW_API void swoole_signal_clear(); +SW_API void swoole_signal_block_all(); +SW_API void swoole_signal_unblock_all(); SW_API char *swoole_signal_to_str(int sig); SW_API void swoole_signal_callback(int signo); + +/** + * Only for synchronously blocked processes. + * Due to the unreliability of signals, + * executing complex logic directly within the signal handler function may pose security risks. + * Therefore, the lower layer only marks memory in the signal handler + * without directly invoking the application's set signal callback function. + * Executing `swoole_signal_dispatch` in a safe context will actually call the signal callback function, + * allowing for the execution of complex code within the callback. + */ +SW_API void swoole_signal_dispatch(); diff --git a/include/swoole_socket.h b/include/swoole_socket.h index 1ae09c51bf..dbbe694438 100644 --- a/include/swoole_socket.h +++ b/include/swoole_socket.h @@ -17,9 +17,17 @@ #pragma once +#include "swoole.h" +#include "swoole_ssl.h" +#include "swoole_buffer.h" +#include "swoole_file.h" + +#include +#include #include #include #include + #include #include #include @@ -29,11 +37,6 @@ #include #include -#include "swoole.h" -#include "swoole_ssl.h" -#include "swoole_buffer.h" -#include "swoole_file.h" - #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK O_NONBLOCK #endif @@ -44,30 +47,17 @@ #define s6_addr32 _S6_un._S6_u32 #endif -// OS Feature -#if defined(HAVE_KQUEUE) || !defined(HAVE_SENDFILE) -int swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size); -#else -#include -#define swoole_sendfile(out_fd, in_fd, offset, limit) sendfile(out_fd, in_fd, offset, limit) -#endif - -namespace swoole { -namespace network { +ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size); -struct GetaddrinfoRequest { - const char *hostname; - const char *service; - int family; - int socktype; - int protocol; - int error; - void *result; - int count; - - void parse_result(std::vector &retval); +enum { + SW_BAD_SOCKET = -1, }; +namespace swoole { +struct GethostbynameRequest; +struct GetaddrinfoRequest; + +namespace network { struct SendfileTask { off_t offset; size_t length; @@ -76,54 +66,109 @@ struct SendfileTask { struct SendfileRequest { File file; - size_t length; - off_t offset; + int8_t corked; + off_t begin; + off_t end; public: - SendfileRequest(const char *filename, off_t _offset, size_t _length) : file(filename, O_RDONLY) { - offset = _offset; - length = _length; + SendfileRequest(const char *filename, off_t _offset) : file(filename, O_RDONLY) { + begin = _offset; + end = 0; + corked = 0; + } + + const char *get_filename() const { + return file.get_path().c_str(); } }; struct Address { union { - struct sockaddr ss; - struct sockaddr_in inet_v4; - struct sockaddr_in6 inet_v6; - struct sockaddr_un un; + sockaddr ss; + sockaddr_in inet_v4; + sockaddr_in6 inet_v6; + sockaddr_un un; } addr; socklen_t len; SocketType type; - bool assign(SocketType _type, const std::string &_host, int _port); - const char *get_ip() { - return get_addr(); + /** + * Assign an address based on the socket type and host/port. + * For IPv4, the host can be an IP address like "192.168.1.100" + * or a domain name like "www.example.com". + * For IPv6, the host can be an IP address like "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + * or a domain name like "ipv6.example.com". + * For UNIX socket, the host is the path to the socket file. + * If _port is 0, it will not set the port. + * If _resolve_name is false, it will not resolve the domain name. + * + * Returns true on success, false on failure. + */ + bool assign(SocketType _type, const std::string &_host, int _port = 0, bool _resolve_name = true); + /** + * Assign an address based on a URL string. + * The format of the URL can be: + * - tcp://hostname:port + * - udp://hostname:port + * - tcp://[IPv6_address]:port + * - udp://[IPv6_address]:port + * - unix:///path/to/socket + * - udg:///path/to/socket + * + * Returns true on success, false on failure. + */ + bool assign(const std::string &url); + + int get_port() const; + void set_port(int _port); + const char *get_addr() const; + bool is_loopback_addr() const; + bool empty() const; + + in_addr *addr_v4() { + return &addr.inet_v4.sin_addr; } - int get_port(); - const char *get_addr(); - static bool verify_ip(int __af, const std::string &str) { - char tmp_address[INET6_ADDRSTRLEN]; - return inet_pton(__af, str.c_str(), tmp_address) != -1; + in6_addr *addr_v6() { + return &addr.inet_v6.sin6_addr; } + + /** + * Get the string representation of the address + */ + static const char *type_str(SocketType type); + /** + * Convert the address to a string representation. + * For IPv4, it will be in the format "192.168.1.100" + * For IPv6, it will be in the format "2001:0db8:85a3:0000:0000:8a2e:0370:7334" + * For UNIX socket, it will be the path of the socket file. + * The returned pointer is a static buffer, so it should not be freed. + */ + static const char *addr_str(int family, const void *addr); + /** + * Verify if the input string is an IP address, + * where AF_INET indicates an IPv4 address, such as 192.168.1.100, + * and AF_INET6 indicates an IPv6 address, for example, 2001:0000:130F:0000:0000:09C0:876A:130B. + */ + static bool verify_ip(int family, const std::string &str); + static bool verify_port(int port, bool for_connect = false); }; struct IOVector { // we should modify iov_iterator instead of iov, iov is readonly - struct iovec *iov = nullptr; - struct iovec *iov_iterator = nullptr; + iovec *iov = nullptr; + iovec *iov_iterator = nullptr; int count = 0; int remain_count = 0; int index = 0; size_t offset_bytes = 0; - IOVector(struct iovec *_iov, int _iovcnt); + IOVector(const iovec *_iov, int _iovcnt); ~IOVector(); - void update_iterator(ssize_t __n); + void update_iterator(ssize_t _n); - inline struct iovec *get_iterator() { + iovec *get_iterator() const { return iov_iterator; } @@ -135,15 +180,15 @@ struct IOVector { return len; } - inline int get_remain_count() { + int get_remain_count() const { return remain_count; } - inline int get_index() { + int get_index() const { return index; } - inline size_t get_offset_bytes() { + size_t get_offset_bytes() const { return offset_bytes; } }; @@ -160,13 +205,15 @@ struct Socket { SocketType socket_type; int events; bool enable_tcp_nodelay; + bool kernel_nobufs; uchar removed : 1; uchar silent_remove : 1; uchar nonblock : 1; uchar cloexec : 1; uchar direct_send : 1; -#ifdef SW_USE_OPENSSL + uchar bound : 1; + uchar listened : 1; uchar ssl_send_ : 1; uchar ssl_want_read : 1; uchar ssl_want_write : 1; @@ -177,8 +224,6 @@ struct Socket { #ifdef SW_SUPPORT_DTLS uchar dtls : 1; #endif -#endif - uchar dontwait : 1; uchar close_wait : 1; uchar send_wait : 1; uchar tcp_nopush : 1; @@ -186,6 +231,12 @@ struct Socket { uchar skip_recv : 1; uchar recv_wait : 1; uchar event_hup : 1; + /** + * The default setting is false, meaning that system calls interrupted by signals will be automatically retried. If + * set to true, the call will not be retried but will immediately return -1, setting errno to EINTR. In this case, + * the caller must explicitly handle this error. + */ + uchar dont_restart : 1; // memory buffer size [user space] uint32_t buffer_size; @@ -193,14 +244,17 @@ struct Socket { void *object; -#ifdef SW_USE_OPENSSL SSL *ssl; uint32_t ssl_state; -#endif + /** + * Only used for getsockname, written by the OS, not user. This is the exact actual address. + */ Address info; - double recv_timeout_ = default_read_timeout; - double send_timeout_ = default_write_timeout; + double dns_timeout = default_dns_timeout; + double connect_timeout = default_connect_timeout; + double read_timeout = default_read_timeout; + double write_timeout = default_write_timeout; double last_received_time; double last_sent_time; @@ -222,320 +276,317 @@ struct Socket { void set_memory_buffer_size(uint32_t _buffer_size) { buffer_size = _buffer_size; } - // socket option [kernel buffer] - bool set_buffer_size(uint32_t _buffer_size); - bool set_recv_buffer_size(uint32_t _buffer_size); - bool set_send_buffer_size(uint32_t _buffer_size); - bool set_timeout(double timeout); - bool set_recv_timeout(double timeout); - bool set_send_timeout(double timeout); + // socket option [kernel space] + bool set_buffer_size(uint32_t _buffer_size) const; + bool set_recv_buffer_size(uint32_t _buffer_size) const; + bool set_send_buffer_size(uint32_t _buffer_size) const; + bool set_kernel_read_timeout(double timeout); + bool set_kernel_write_timeout(double timeout); - inline bool set_nonblock() { + bool set_kernel_timeout(double timeout) { + return set_kernel_read_timeout(timeout) && set_kernel_write_timeout(timeout); + } + + // socket option [user space] + void set_timeout(double timeout, int type = SW_TIMEOUT_ALL); + double get_timeout(TimeoutType type) const; + bool has_timedout() const; + bool has_kernel_nobufs(); + + bool set_nonblock() { return set_fd_option(1, -1); } - inline bool set_block() { + bool set_block() { return set_fd_option(0, -1); } bool set_fd_option(int _nonblock, int _cloexec); - inline int set_option(int level, int optname, int optval) { + int set_option(int level, int optname, int optval) const { return setsockopt(fd, level, optname, &optval, sizeof(optval)); } - inline int set_option(int level, int optname, const void *optval, socklen_t optlen) { + int set_option(int level, int optname, const void *optval, socklen_t optlen) const { return setsockopt(fd, level, optname, optval, optlen); } - inline int get_option(int level, int optname, void *optval, socklen_t *optlen) { + int get_option(int level, int optname, void *optval, socklen_t *optlen) const { return getsockopt(fd, level, optname, optval, optlen); } - inline int get_option(int level, int optname, int *optval) { + int get_option(int level, int optname, int *optval) const { socklen_t optlen = sizeof(*optval); return get_option(level, optname, optval, &optlen); } - inline int get_fd() { + int get_fd() const { return fd; } - inline int get_name(Address *sa) { - sa->len = sizeof(sa->addr); - return getsockname(fd, &sa->addr.ss, &sa->len); + const char *get_addr() const { + return info.get_addr(); } - inline int set_tcp_nopush(int nopush) { -#ifdef TCP_CORK - if (set_option(IPPROTO_TCP, TCP_CORK, nopush) == SW_ERR) { - return -1; - } else { - tcp_nopush = nopush; - return 0; - } -#else - return -1; -#endif + int get_port() const { + return info.get_port(); } - int set_reuse_addr(int enable = 1) { + uint32_t get_out_buffer_length() const { + return out_buffer ? out_buffer->length() : 0; + } + + int move_fd() { + int sock_fd = fd; + fd = SW_BAD_SOCKET; + return sock_fd; + } + + int get_name(); + int get_peer_name(Address *sa) const; + int set_tcp_nopush(int nopush); + + int set_reuse_addr(int enable = 1) const { return set_option(SOL_SOCKET, SO_REUSEADDR, enable); } - int set_reuse_port(int enable = 1) { + int set_reuse_port(int enable = 1) const { #ifdef SO_REUSEPORT return set_option(SOL_SOCKET, SO_REUSEPORT, enable); #endif return -1; } - int set_tcp_nodelay(int nodelay = 1) { - if (set_option(IPPROTO_TCP, TCP_NODELAY, nodelay) == SW_ERR) { - return -1; - } else { - tcp_nodelay = nodelay; - return 0; - } - } + bool set_tcp_nodelay(int nodelay = 1); + bool check_liveness(); - bool check_liveness() { - char buf; - errno = 0; - ssize_t retval = peek(&buf, sizeof(buf), MSG_DONTWAIT); - return !(retval == 0 || (retval < 0 && catch_read_error(errno) == SW_CLOSE)); - } + int sendfile_async(const char *filename, off_t offset, size_t length); + int sendfile_sync(const char *filename, off_t offset, size_t length); + ssize_t sendfile(const File &fp, off_t *offset, size_t length); - /** - * socket io operation - */ - int sendfile(const char *filename, off_t offset, size_t length); - ssize_t recv(void *__buf, size_t __n, int __flags); - ssize_t send(const void *__buf, size_t __n, int __flags); - ssize_t peek(void *__buf, size_t __n, int __flags); + ssize_t recv(void *_buf, size_t _n, int _flags); + ssize_t send(const void *_buf, size_t _n, int _flags); + ssize_t peek(void *_buf, size_t _n, int _flags) const; Socket *accept(); - int bind(const std::string &_host, int *port); + Socket *dup() const; ssize_t readv(IOVector *io_vector); ssize_t writev(IOVector *io_vector); - ssize_t writev(const struct iovec *iov, size_t iovcnt) { + ssize_t writev(const iovec *iov, size_t iovcnt) const { return ::writev(fd, iov, iovcnt); } - int bind(const Address &sa) { - return ::bind(fd, &sa.addr.ss, sizeof(sa.addr.ss)); - } + /** + * If the port is 0, the system will automatically allocate an available port. + */ + int bind(const std::string &_host, int port = 0); - int listen(int backlog = 0) { - return ::listen(fd, backlog <= 0 ? SW_BACKLOG : backlog); + int bind(const Address &addr) { + return bind(&addr.addr.ss, addr.len); } - void clean(); - ssize_t send_blocking(const void *__data, size_t __len); - ssize_t send_async(const void *__data, size_t __len); - ssize_t recv_blocking(void *__data, size_t __len, int flags); - int sendfile_blocking(const char *filename, off_t offset, size_t length, double timeout); - ssize_t writev_blocking(const struct iovec *iov, size_t iovcnt); + int bind(const struct sockaddr *sa, socklen_t len); + int listen(int backlog = 0); - inline int connect(const Address &sa) { + void clean() const; + ssize_t send_sync(const void *_data, size_t _len, int flags = 0); + ssize_t send_async(const void *_data, size_t _len); + ssize_t recv_sync(void *_data, size_t _len, int flags = 0); + ssize_t writev_sync(const iovec *iov, size_t iovcnt); + + int connect(const Address &sa) const { return ::connect(fd, &sa.addr.ss, sa.len); } - inline int connect(const Address *sa) { + int connect(const Address *sa) const { return ::connect(fd, &sa->addr.ss, sa->len); } - inline int connect(const std::string &host, int port) { + int connect(const std::string &host, int port) const { Address addr; addr.assign(socket_type, host, port); return connect(addr); } -#ifdef SW_USE_OPENSSL + int connect_sync(const Address &sa); + ReturnCode connect_async(const Address &sa); + void ssl_clear_error() { ERR_clear_error(); ssl_want_read = 0; ssl_want_write = 0; } + /** + * This function does not set the last error; to obtain internal SSL error information, you should call + * ERR_get_error(). + */ int ssl_create(SSLContext *_ssl_context, int _flags); int ssl_connect(); ReturnCode ssl_accept(); - ssize_t ssl_recv(void *__buf, size_t __n); - ssize_t ssl_send(const void *__buf, size_t __n); + ssize_t ssl_recv(void *_buf, size_t _n); + ssize_t ssl_send(const void *_buf, size_t _n); ssize_t ssl_readv(IOVector *io_vector); ssize_t ssl_writev(IOVector *io_vector); - int ssl_sendfile(const File &fp, off_t *offset, size_t size); - STACK_OF(X509) *ssl_get_peer_cert_chain(); - std::vector ssl_get_peer_cert_chain(int limit); - X509 *ssl_get_peer_certificate(); - int ssl_get_peer_certificate(char *buf, size_t n); - bool ssl_get_peer_certificate(String *buf); - bool ssl_verify(bool allow_self_signed); - bool ssl_check_host(const char *tls_host_name); - void ssl_catch_error(); + ssize_t ssl_sendfile(const File &fp, off_t *offset, size_t size); + STACK_OF(X509) * ssl_get_peer_cert_chain() const; + std::vector ssl_get_peer_cert_chain(int limit) const; + X509 *ssl_get_peer_certificate() const; + int ssl_get_peer_certificate(char *buf, size_t n) const; + bool ssl_get_peer_certificate(String *buf) const; + bool ssl_verify(bool allow_self_signed) const; + bool ssl_check_host(const char *tls_host_name) const; + void ssl_catch_error() const; bool ssl_shutdown(); void ssl_close(); - const char *ssl_get_error_reason(int *reason); -#endif + static const char *ssl_get_error_reason(int *reason); - inline ssize_t recvfrom(char *__buf, size_t __len, int flags, Address *sa) { + ssize_t recvfrom(char *_buf, size_t _len, int flags, Address *sa) const { sa->len = sizeof(sa->addr); - return ::recvfrom(fd, __buf, __len, flags, &sa->addr.ss, &sa->len); + return recvfrom(_buf, _len, flags, &sa->addr.ss, &sa->len); } - inline bool cork() { - if (tcp_nopush) { - return false; - } -#ifdef TCP_CORK - if (set_tcp_nopush(1) < 0) { - swoole_sys_warning("set_tcp_nopush(fd=%d, ON) failed", fd); - return false; - } -#endif - // Need to turn off tcp nodelay when using nopush - if (tcp_nodelay && set_tcp_nodelay(0) != 0) { - swoole_sys_warning("set_tcp_nodelay(fd=%d, OFF) failed", fd); - } - return true; - } + ssize_t recvfrom(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) const; + ssize_t recvfrom_sync(char *_buf, size_t _len, int flags, Address *sa); + ssize_t recvfrom_sync(char *_buf, size_t _len, int flags, sockaddr *addr, socklen_t *addr_len); - inline bool uncork() { - if (!tcp_nopush) { - return false; - } -#ifdef TCP_CORK - if (set_tcp_nopush(0) < 0) { - swoole_sys_warning("set_tcp_nopush(fd=%d, OFF) failed", fd); - return false; - } -#endif - // Restore tcp_nodelay setting - if (enable_tcp_nodelay && tcp_nodelay == 0 && set_tcp_nodelay(1) != 0) { - swoole_sys_warning("set_tcp_nodelay(fd=%d, ON) failed", fd); - return false; - } - return true; - } + bool cork(); + bool uncork(); - bool isset_readable_event() { + bool isset_readable_event() const { return events & SW_EVENT_READ; } - bool isset_writable_event() { + bool isset_writable_event() const { return events & SW_EVENT_WRITE; } - int wait_event(int timeout_ms, int events); + int wait_event(int timeout_ms, int _events) const; + bool wait_for(const std::function &fn, int event, int timeout_msec = -1); + int what_event_want(int default_event) const; void free(); - static inline int is_dgram(SocketType type) { - return (type == SW_SOCK_UDP || type == SW_SOCK_UDP6 || type == SW_SOCK_UNIX_DGRAM); + static inline bool is_dgram(SocketType type) { + return type == SW_SOCK_UDP || type == SW_SOCK_UDP6 || type == SW_SOCK_UNIX_DGRAM; + } + + static inline bool is_stream(SocketType type) { + return type == SW_SOCK_TCP || type == SW_SOCK_TCP6 || type == SW_SOCK_UNIX_STREAM; + } + + static inline bool is_inet4(SocketType type) { + return type == SW_SOCK_TCP || type == SW_SOCK_UDP || type == SW_SOCK_RAW; + } + + static inline bool is_inet6(SocketType type) { + return type == SW_SOCK_TCP6 || type == SW_SOCK_UDP6 || type == SW_SOCK_RAW6; } - static inline int is_stream(SocketType type) { - return (type == SW_SOCK_TCP || type == SW_SOCK_TCP6 || type == SW_SOCK_UNIX_STREAM); + static inline bool is_tcp(SocketType type) { + return type == SW_SOCK_TCP || type == SW_SOCK_TCP6; } - bool is_stream() { - return socket_type == SW_SOCK_TCP || socket_type == SW_SOCK_TCP6 || socket_type == SW_SOCK_UNIX_STREAM; + static inline bool is_udp(SocketType type) { + return type == SW_SOCK_UDP || type == SW_SOCK_UDP6; } - bool is_dgram() { - return socket_type == SW_SOCK_UDP || socket_type == SW_SOCK_UDP6 || socket_type == SW_SOCK_UNIX_DGRAM; + static inline bool is_local(SocketType type) { + return type == SW_SOCK_UNIX_STREAM || type == SW_SOCK_UNIX_DGRAM; } - bool is_inet4() { - return socket_type == SW_SOCK_TCP || socket_type == SW_SOCK_UDP; + static inline bool is_raw(SocketType type) { + return type == SW_SOCK_RAW || type == SW_SOCK_RAW6; } - bool is_inet6() { - return socket_type == SW_SOCK_TCP6 || socket_type == SW_SOCK_UDP6; + bool is_stream() const { + return is_stream(socket_type); } - bool is_inet() { + bool is_tcp() const { + return is_tcp(socket_type); + } + + bool is_udp() const { + return is_udp(socket_type); + } + + bool is_dgram() const { + return is_dgram(socket_type); + } + + bool is_inet4() const { + return is_inet4(socket_type); + } + + bool is_inet6() const { + return is_inet6(socket_type); + } + + bool is_inet() const { return is_inet4() || is_inet6(); } - bool is_local() { - return socket_type == SW_SOCK_UNIX_STREAM || socket_type == SW_SOCK_UNIX_DGRAM; + bool is_local() const { + return is_local(socket_type); } - ssize_t write(const void *__buf, size_t __len) { - return ::write(fd, __buf, __len); + bool is_raw() const { + return is_raw(socket_type); } - ssize_t read(void *__buf, size_t __len) { - return ::read(fd, __buf, __len); + ssize_t write(const void *_buf, size_t _len) const { + return ::write(fd, _buf, _len); } - int shutdown(int __how) { - return ::shutdown(fd, __how); + ssize_t read(void *_buf, size_t _len) const { + return ::read(fd, _buf, _len); } - ssize_t sendto_blocking(const Address &dst_addr, const void *__buf, size_t __n, int flags = 0); - ssize_t recvfrom_blocking(char *__buf, size_t __len, int flags, Address *sa); + /** + * Read data from the socket synchronously without setting non-blocking or blocking IO, + * and allow interruptions by signals. + */ + ssize_t read_sync(void *_buf, size_t _len); + + /** + * Write data to the socket synchronously without setting non-blocking or blocking IO, + * and allow interruptions by signals. + */ + ssize_t write_sync(const void *_buf, size_t _len); + + int shutdown(int _how) const { + return ::shutdown(fd, _how); + } - inline ssize_t sendto(const char *dst_host, int dst_port, const void *data, size_t len, int flags = 0) const { - Address addr = {}; + ssize_t sendto_sync(const Address &dst_addr, const void *_buf, size_t _n, int flags = 0); + + ssize_t sendto(const char *dst_host, int dst_port, const void *data, size_t len, int flags = 0) const { + Address addr; if (!addr.assign(socket_type, dst_host, dst_port)) { return SW_ERR; } return sendto(addr, data, len, flags); } - inline ssize_t sendto(const Address &dst_addr, const void *data, size_t len, int flags) const { + ssize_t sendto(const Address &dst_addr, const void *data, size_t len, int flags = 0) const { return ::sendto(fd, data, len, flags, &dst_addr.addr.ss, dst_addr.len); } - inline int catch_error(int err) const { - switch (err) { - case EFAULT: - abort(); - return SW_ERROR; - case EBADF: - case ENOENT: - return SW_INVALID; - case ECONNRESET: - case ECONNABORTED: - case EPIPE: - case ENOTCONN: - case ETIMEDOUT: - case ECONNREFUSED: - case ENETDOWN: - case ENETUNREACH: - case EHOSTDOWN: - case EHOSTUNREACH: - case SW_ERROR_SSL_BAD_CLIENT: - case SW_ERROR_SSL_RESET: - return SW_CLOSE; - case EAGAIN: -#if EAGAIN != EWOULDBLOCK - case EWOULDBLOCK: -#endif -#ifdef HAVE_KQUEUE - case ENOBUFS: -#endif - case 0: - return SW_WAIT; - default: - return SW_ERROR; - } - } + int catch_error(int err); - inline int catch_write_error(int err) const { - switch (err) { - case ENOBUFS: - return SW_WAIT; - default: - return catch_error(err); - } + int catch_write_error(const int err) { + return catch_error(err); } - inline int catch_write_pipe_error(int err) { + int catch_write_pipe_error(const int err) { switch (err) { case ENOBUFS: +#ifdef __linux__ + kernel_nobufs = true; + return SW_REDUCE_SIZE; +#else + return catch_error(err); +#endif case EMSGSIZE: return SW_REDUCE_SIZE; default: @@ -543,79 +594,40 @@ struct Socket { } } - inline int catch_read_error(int err) const { + int catch_read_error(const int err) { return catch_error(err); } - static inline SocketType convert_to_type(int domain, int type, int protocol = 0) { - switch (domain) { - case AF_INET: - return type == SOCK_STREAM ? SW_SOCK_TCP : SW_SOCK_UDP; - case AF_INET6: - return type == SOCK_STREAM ? SW_SOCK_TCP6 : SW_SOCK_UDP6; - case AF_UNIX: - return type == SOCK_STREAM ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM; - default: - return SW_SOCK_TCP; - } - } - - static inline SocketType convert_to_type(std::string &host) { - if (host.compare(0, 6, "unix:/", 0, 6) == 0) { - host = host.substr(sizeof("unix:") - 1); - host.erase(0, host.find_first_not_of('/') - 1); - return SW_SOCK_UNIX_STREAM; - } else if (host.find(':') != std::string::npos) { - return SW_SOCK_TCP6; - } else { - return SW_SOCK_TCP; - } - } - - static inline int get_domain_and_type(SocketType type, int *sock_domain, int *sock_type) { - switch (type) { - case SW_SOCK_TCP6: - *sock_domain = AF_INET6; - *sock_type = SOCK_STREAM; - break; - case SW_SOCK_UNIX_STREAM: - *sock_domain = AF_UNIX; - *sock_type = SOCK_STREAM; - break; - case SW_SOCK_UDP: - *sock_domain = AF_INET; - *sock_type = SOCK_DGRAM; - break; - case SW_SOCK_UDP6: - *sock_domain = AF_INET6; - *sock_type = SOCK_DGRAM; - break; - case SW_SOCK_UNIX_DGRAM: - *sock_domain = AF_UNIX; - *sock_type = SOCK_DGRAM; - break; - case SW_SOCK_TCP: - *sock_domain = AF_INET; - *sock_type = SOCK_STREAM; - break; - default: - return SW_ERR; - } - - return SW_OK; - } + static SocketType convert_to_type(int domain, int type); + static SocketType convert_to_type(std::string &host); + static int get_domain_and_type(SocketType type, int *sock_domain, int *sock_type); }; +std::string gethostbyname(int type, const std::string &name); int gethostbyname(int type, const char *name, char *addr); +int gethostbyname(GethostbynameRequest *req); int getaddrinfo(GetaddrinfoRequest *req); } // namespace network + +/** + * This function will never return NULL; if memory allocation fails, a C++ exception will be thrown. + * Must use the `socket->free()` function to release the object pointer instead of the `delete` operator. + * When the socket is released, it will close the file descriptor (fd). + * If you do not want the fd to be closed, use `socket->move_fd()` to relinquish ownership of the fd. + */ network::Socket *make_socket(int fd, FdType fd_type); +/** + * The following three functions will return a null pointer if the socket creation fails. + * It is essential to check the return value; + * if it is nullptr, you should inspect errno to determine the cause of the error. + */ network::Socket *make_socket(SocketType socket_type, FdType fd_type, int flags); +network::Socket *make_socket( + SocketType type, FdType fd_type, int sock_domain, int sock_type, int socket_protocol, int flags); +int socket(int sock_domain, int sock_type, int socket_protocol, int flags); network::Socket *make_server_socket(SocketType socket_type, const char *address, int port = 0, int backlog = SW_BACKLOG); -bool verify_ip(int __af, const std::string &str); } // namespace swoole - diff --git a/include/swoole_socket_hook.h b/include/swoole_socket_hook.h index a9e918768d..6dbe05c082 100644 --- a/include/swoole_socket_hook.h +++ b/include/swoole_socket_hook.h @@ -26,7 +26,7 @@ extern "C" { #endif -#include "swoole_coroutine_c_api.h" +#include "swoole_coroutine_api.h" #define socket(domain, type, protocol) swoole_coroutine_socket(domain, type, protocol) #define send(sockfd, buf, len, flags) swoole_coroutine_send(sockfd, buf, len, flags) @@ -35,7 +35,12 @@ extern "C" { #define recv(sockfd, buf, len, flags) swoole_coroutine_recv(sockfd, buf, len, flags) #define close(fd) swoole_coroutine_close(fd) #define connect(sockfd, addr, addrlen) swoole_coroutine_connect(sockfd, addr, addrlen) +#define accept(sockfd, addr, addrlen) swoole_coroutine_accept(sockfd, addr, addrlen) +#ifdef SW_HOOK_POLL_FAKE +#define poll(fds, nfds, timeout) swoole_coroutine_poll_fake(fds, nfds, timeout) +#else #define poll(fds, nfds, timeout) swoole_coroutine_poll(fds, nfds, timeout) +#endif #define sendmsg(sockfd, msg, flags) swoole_coroutine_sendmsg(sockfd, msg, flags) #define recvmsg(sockfd, msg, flags) swoole_coroutine_recvmsg(sockfd, msg, flags) #define getaddrinfo(name, service, req, pai) swoole_coroutine_getaddrinfo(name, service, req, pai) diff --git a/include/swoole_socket_impl.h b/include/swoole_socket_impl.h new file mode 100644 index 0000000000..ea5f90f054 --- /dev/null +++ b/include/swoole_socket_impl.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "swoole_coroutine_socket.h" + +#include + +using CoSocket = swoole::coroutine::Socket; +using NetSocket = swoole::network::Socket; + +#ifdef SW_USE_URING_SOCKET +#include "swoole_uring_socket.h" +using swoole::coroutine::UringSocket; +using SocketImpl = UringSocket; +#else +using SocketImpl = CoSocket; +#endif + +std::shared_ptr swoole_coroutine_get_socket_object(int sockfd); +std::shared_ptr swoole_coroutine_get_socket_object_ex(int sockfd); diff --git a/include/swoole_ssl.h b/include/swoole_ssl.h index 469c85d327..0a509160c1 100644 --- a/include/swoole_ssl.h +++ b/include/swoole_ssl.h @@ -18,21 +18,17 @@ #include "swoole.h" -#ifdef SW_USE_OPENSSL - #include #include -#include #include #include #include -#include -#include #include #include #include #include +#include #include #if OPENSSL_VERSION_NUMBER >= 0x10100000L @@ -47,9 +43,8 @@ #define BIO_CTRL_DGRAM_SET_CONNECTED 32 #define BIO_CTRL_DGRAM_SET_PEER 44 #define BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT 45 -#define BIO_dgram_get_peer(b,peer) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_PEER, 0, (char *)(peer)) -#define OPENSSL_assert(x) assert(x) +#define BIO_dgram_get_peer(b, peer) (int) BIO_ctrl(b, BIO_CTRL_DGRAM_GET_PEER, 0, (char *) (peer)) +#define OPENSSL_assert(x) assert(x) #endif enum swSSLCreateFlag { @@ -137,11 +132,11 @@ struct SSLContext { uint8_t create_flag; SSL_CTX *context; - SSL_CTX *get_context() { + SSL_CTX *get_context() const { return context; } - bool ready() { + bool ready() const { return context != nullptr; } @@ -167,23 +162,30 @@ struct SSLContext { return true; } + bool set_client_cert_file(const std::string &file) { + if (access(file.c_str(), R_OK) < 0) { + swoole_warning("ssl client cert file[%s] not found", file.c_str()); + return false; + } + client_cert_file = file; + return true; + } + bool create(); - bool set_capath(); - bool set_ciphers(); - bool set_client_certificate(); - bool set_ecdh_curve(); - bool set_dhparam(); + bool set_capath() const; + bool set_ciphers() const; + bool set_client_certificate() const; + bool set_ecdh_curve() const; + bool set_dhparam() const; ~SSLContext(); }; -} +} // namespace swoole -void swoole_ssl_init(void); -void swoole_ssl_init_thread_safety(); -bool swoole_ssl_is_thread_safety(); +void swoole_ssl_init(); +void swoole_ssl_destroy(); +void swoole_ssl_lock_callback(int mode, int type, const char *file, int line); void swoole_ssl_server_http_advise(swoole::SSLContext &); const char *swoole_ssl_get_error(); int swoole_ssl_get_ex_connection_index(); int swoole_ssl_get_ex_port_index(); std::string swoole_ssl_get_version_message(); - -#endif diff --git a/include/swoole_static_handler.h b/include/swoole_static_handler.h index d8b7418898..71a18bb423 100644 --- a/include/swoole_static_handler.h +++ b/include/swoole_static_handler.h @@ -26,58 +26,76 @@ namespace swoole { namespace http_server { +struct RewriteRule { + std::string pattern; + std::string replacement; + bool is_regex; +}; + class StaticHandler { private: Server *serv; std::string request_url; + std::string original_url; std::string dir_path; std::set dir_files; std::string index_file; - struct { + typedef struct { off_t offset; size_t length; - char filename[PATH_MAX]; - } task; + char part_header[SW_HTTP_SERVER_PART_HEADER]; + } task_t; + std::vector tasks; - size_t l_filename; + size_t l_filename = 0; + char filename[PATH_MAX]; struct stat file_stat; - bool last; + bool last = false; + std::string content_type; + std::string boundary; + std::string end_part; + size_t content_length = 0; public: - int status_code; - StaticHandler(Server *_server, const char *url, size_t url_length) : request_url(url, url_length) { + int status_code = SW_HTTP_OK; + StaticHandler(Server *_server, const char *url, size_t url_length) + : request_url(url, url_length), original_url(url, url_length) { serv = _server; - task.length = 0; - task.offset = 0; - last = false; - status_code = 200; - l_filename = 0; - dir_path = ""; } /** * @return true: continue to execute backwards * @return false: break static handler */ - bool hit(); - bool hit_index_file(); + bool try_serve(); + bool try_serve_index_file(); - bool is_modified(const std::string &date_if_modified_since); + bool is_modified(const std::string &date_if_modified_since) const; + bool is_modified_range(const std::string &date_range) const; size_t make_index_page(String *buffer); bool get_dir_files(); - bool set_filename(std::string &filename); + bool set_filename(const std::string &filename); + + bool catch_error() { + if (last) { + status_code = SW_HTTP_NOT_FOUND; + return true; + } else { + return false; + } + } - bool has_index_file() { + bool has_index_file() const { return !index_file.empty(); } - bool is_enabled_auto_index() { + bool is_enabled_auto_index() const { return serv->http_autoindex; } - std::string get_date(); + static std::string get_date(); - inline time_t get_file_mtime() { + time_t get_file_mtime() const { #ifdef __MACH__ return file_stat.st_mtimespec.tv_sec; #else @@ -85,31 +103,92 @@ class StaticHandler { #endif } - std::string get_date_last_modified(); + std::string get_date_last_modified() const; + + const char *get_filename() const { + return filename; + } + + const std::string &get_request_url() const { + return request_url; + } + + void set_request_url(const std::string &rewritten_url) { + request_url = rewritten_url; + } + + const std::string &get_boundary() { + if (boundary.empty()) { + boundary = std::string(SW_HTTP_SERVER_BOUNDARY_PREKEY); + swoole_random_string(boundary, SW_HTTP_SERVER_BOUNDARY_TOTAL_SIZE - sizeof(SW_HTTP_SERVER_BOUNDARY_PREKEY)); + } + return boundary; + } - inline const char *get_filename() { - return task.filename; + const std::string &get_content_type() { + if (tasks.size() > 1) { + content_type = std::string("multipart/byteranges; boundary=") + get_boundary(); + return content_type; + } else { + return get_mimetype(); + } } - inline const char *get_mimetype() { - return swoole::mime_type::get(get_filename()).c_str(); + const std::string &get_mimetype() const { + return mime_type::get(get_filename()); } - inline std::string get_filename_std_string() { - return std::string(task.filename, l_filename); + std::string get_filename_std_string() { + return {filename, l_filename}; + } + + bool get_absolute_path(); + + const std::string &get_original_url() const { + return original_url; } - inline size_t get_filesize() { + size_t get_filesize() const { return file_stat.st_size; } - inline const network::SendfileTask *get_task() { - return (const network::SendfileTask *) &task; + const std::vector &get_tasks() { + return tasks; } - inline bool is_dir() { + bool is_dir() const { return S_ISDIR(file_stat.st_mode); } + + bool is_link() const { + return S_ISLNK(file_stat.st_mode); + } + + bool is_file() const { + return S_ISREG(file_stat.st_mode); + } + + bool is_absolute_path() { + return swoole_strnpos(filename, l_filename, SW_STRL("..")) == -1; + } + + bool is_located_in_document_root() { + const std::string &document_root = serv->get_document_root(); + const size_t l_document_root = document_root.length(); + + return l_filename > l_document_root && filename[l_document_root] == '/' && + swoole_str_starts_with(filename, l_filename, document_root.c_str(), l_document_root); + } + + size_t get_content_length() const { + return content_length; + } + + const std::string &get_end_part() { + return end_part; + } + + void parse_range(const char *range, const char *if_range); }; }; // namespace http_server diff --git a/include/swoole_string.h b/include/swoole_string.h index 04ce535e38..ea22f034d1 100644 --- a/include/swoole_string.h +++ b/include/swoole_string.h @@ -23,43 +23,19 @@ #define SW_STRINGL(s) s->str, s->length #define SW_STRINGS(s) s->str, s->size +// copy value #define SW_STRINGCVL(s) s->str + s->offset, s->length - s->offset +// append value +#define SW_STRINGAVL(s) s->str + s->length, s->size - s->length namespace swoole { - typedef std::function StringExplodeHandler; class String { private: - void alloc(size_t _size, const Allocator *_allocator) { - if (_allocator == nullptr) { - _allocator = sw_std_allocator(); - } - - _size = SW_MEM_ALIGNED_SIZE(_size); - length = 0; - size = _size; - offset = 0; - str = (char *) _allocator->malloc(_size); - allocator = _allocator; - - if (str == nullptr) { - throw std::bad_alloc(); - } - } - - void move(String &&src) { - str = src.str; - length = src.length; - offset = src.offset; - size = src.size; - allocator = src.allocator; - - src.str = nullptr; - src.length = 0; - src.size = 0; - src.offset = 0; - } + void alloc(size_t _size, const Allocator *_allocator); + void move(String &&src); + void copy(const String &src); public: size_t length; @@ -85,43 +61,18 @@ class String { length = _length; } - String(const std::string &_str) : String(_str.c_str(), _str.length()) {} + explicit String(const std::string &_str) : String(_str.c_str(), _str.length()) {} - String(String &_str) { - alloc(_str.size, _str.allocator); - memcpy(_str.str, str, _str.length); - length = _str.length; - offset = _str.offset; + String(const String &src) { + copy(src); } - String(String &&src) { + String(String &&src) noexcept { move(std::move(src)); } - String &operator=(String &src) { - if (&src == this) { - return *this; - } - if (allocator && str) { - allocator->free(str); - } - alloc(src.size, src.allocator); - memcpy(src.str, str, src.length); - length = src.length; - offset = src.offset; - return *this; - } - - String &operator=(String &&src) { - if (&src == this) { - return *this; - } - if (allocator && str) { - allocator->free(str); - } - move(std::move(src)); - return *this; - } + String &operator=(const String &src) noexcept; + String &operator=(String &&src) noexcept; ~String() { if (allocator && str) { @@ -129,128 +80,128 @@ class String { } } - inline char *value() { + char *value() const { return str; } - inline size_t get_length() { + size_t get_length() const { return length; } - inline size_t capacity() { + size_t capacity() const { return size; } - inline std::string to_std_string() { - return std::string(str, length); + std::string to_std_string() const { + return {str, length}; } - inline bool contains(const char *needle, size_t l_needle) { + bool contains(const char *needle, size_t l_needle) const { return swoole_strnstr(str, length, needle, l_needle) != nullptr; } - inline bool contains(const std::string &needle) { + bool contains(const std::string &needle) const { return contains(needle.c_str(), needle.size()); } - inline bool grow(size_t incr_value) { - length += incr_value; - if (length == size && !reserve(size * 2)) { + bool starts_with(const char *needle, size_t l_needle) const { + if (length < l_needle) { + return false; + } + return memcmp(str, needle, l_needle) == 0; + } + + bool starts_with(const std::string &needle) const { + return starts_with(needle.c_str(), needle.length()); + } + + bool ends_with(const char *needle, size_t l_needle) const { + if (length < l_needle) { return false; - } else { - return true; } + return memcmp(str + length - l_needle, needle, l_needle) == 0; } - String *substr(size_t offset, size_t len) { - if (offset + len > length) { - return nullptr; + bool ends_with(const std::string &needle) const { + return ends_with(needle.c_str(), needle.length()); + } + + bool equals(const char *data, size_t len) const { + if (length != len) { + return false; } - auto _substr = new String(len); - _substr->append(str + offset, len); - return _substr; + return memcmp(str, data, len) == 0; } - bool empty() { + bool equals(const std::string &data) const { + if (length != data.size()) { + return false; + } + return memcmp(str, data.c_str(), length) == 0; + } + + void grow(size_t incr_value); + String substr(size_t offset, size_t len) const; + + bool empty() const { return str == nullptr || length == 0; } - inline void clear() { + void clear() { length = 0; offset = 0; } - inline bool extend() { - return extend(size * 2); + void extend() { + extend(size * 2); } - inline bool extend(size_t new_size) { + void extend(size_t new_size) { assert(new_size > size); - return reserve(new_size); + reserve(new_size); } - inline bool extend_align(size_t _new_size) { + void extend_align(size_t _new_size) { size_t align_size = SW_MEM_ALIGNED_SIZE(size * 2); while (align_size < _new_size) { align_size *= 2; } - return reserve(align_size); - } - - bool reserve(size_t new_size); - bool repeat(const char *data, size_t len, size_t n); - int append(const char *append_str, size_t length); - - inline int append(const std::string &append_str) { - return append(append_str.c_str(), append_str.length()); + reserve(align_size); } - inline int append(char c) { - return append(&c, sizeof(c)); + void reserve(size_t new_size); + /** + * Transfer ownership of the string content pointer to the caller, who will capture this memory. + * The caller must manage and free this memory; it will not free when the string is destructed. + */ + char *release(); + void repeat(const char *data, size_t len, size_t n); + void append(const char *append_str, size_t length); + + void append(const std::string &append_str) { + append(append_str.c_str(), append_str.length()); } - inline int append(const String &append_str) { - size_t new_size = length + append_str.length; - if (new_size > size) { - if (!reserve(new_size)) { - return SW_ERR; - } - } - - memcpy(str + length, append_str.str, append_str.length); - length += append_str.length; - return SW_OK; + void append(const char c) { + append(&c, sizeof(c)); } - inline void write(off_t _offset, String *write_str) { - size_t new_length = _offset + write_str->length; - if (new_length > size) { - reserve(swoole_size_align(new_length * 2, swoole_pagesize())); - } + void append(int value); + void append(const String &append_str); + bool append_random_bytes(size_t length, bool base64 = false); - memcpy(str + _offset, write_str->str, write_str->length); - if (new_length > length) { - length = new_length; - } - } + void write(off_t _offset, const String &write_str); + void write(off_t _offset, const char *write_str, size_t _length); - inline void write(off_t _offset, const char *write_str, size_t _length) { - size_t new_length = _offset + _length; - if (new_length > size) { - reserve(swoole_size_align(new_length * 2, swoole_pagesize())); - } - - memcpy(str + _offset, write_str, _length); - if (new_length > length) { - length = new_length; + void set_null_terminated() { + if (length == size) { + extend(length + 1); } + str[length] = '\0'; } - int append(int value); - ssize_t split(const char *delimiter, size_t delimiter_length, const StringExplodeHandler &handler); - int append_random_bytes(size_t length, bool base64 = false); - void print(bool print_value = true); + void print(bool print_value = true) const; enum FormatFlag { FORMAT_APPEND = 1 << 0, @@ -258,13 +209,13 @@ class String { }; template - inline size_t format_impl(int flags, const char *format, Args... args) { + size_t format_impl(int flags, const char *format, Args... args) { size_t _size = sw_snprintf(nullptr, 0, format, args...); if (_size == 0) { return 0; } // store \0 terminator - _size++; + ++_size; size_t new_size = (flags & FORMAT_APPEND) ? length + _size : _size; if (flags & FORMAT_GROW) { @@ -277,14 +228,14 @@ class String { size_t n; if (flags & FORMAT_APPEND) { - if (_size > size - length && !reserve(new_size)) { - return 0; + if (_size > size - length) { + reserve(new_size); } n = sw_snprintf(str + length, size - length, format, args...); length += n; } else { - if (_size > size && !reserve(new_size)) { - return 0; + if (_size > size) { + reserve(new_size); } n = sw_snprintf(str, size, format, args...); length = n; @@ -293,11 +244,17 @@ class String { return n; } + // This function replaces the entire string instead of appending content. template - inline size_t format(const char *format, Args... args) { + size_t format(const char *format, Args... args) { return format_impl(0, format, args...); } + template + size_t append_format(const char *format, Args... args) { + return format_impl(FORMAT_APPEND, format, args...); + } + char *pop(size_t init_size); void reduce(off_t offset); }; diff --git a/include/swoole_table.h b/include/swoole_table.h index 06d30daea0..b8a0eadd3b 100644 --- a/include/swoole_table.h +++ b/include/swoole_table.h @@ -16,13 +16,10 @@ #pragma once -#include "swoole.h" #include "swoole_memory.h" -#include "swoole_util.h" #include "swoole_lock.h" -#include "swoole_hash.h" -#include +#include #include #include @@ -30,9 +27,9 @@ //#define SW_TABLE_DEBUG 0 #define SW_TABLE_FORCE_UNLOCK_TIME 2000 // milliseconds #define SW_TABLE_USE_PHP_HASH +#define SW_TABLE_MAX_ROW_SIZE 0x80000000 namespace swoole { - typedef uint32_t TableStringLength; typedef uint64_t (*HashFunc)(const char *key, size_t len); @@ -66,49 +63,31 @@ struct TableRow { sw_memset_zero((char *) &lock_pid, sizeof(TableRow) - offsetof(TableRow, lock_pid)); } - void set_value(TableColumn *col, void *value, size_t vlen); - void get_value(TableColumn *col, double *dval); - void get_value(TableColumn *col, long *lval); - void get_value(TableColumn *col, char **strval, TableStringLength *strlen); + void set_value(const TableColumn *col, const void *value, size_t vlen); + void get_value(const TableColumn *col, double *dval) const; + void get_value(const TableColumn *col, long *lval) const; + void get_value(const TableColumn *col, char **strval, TableStringLength *strlen); }; struct TableIterator { size_t row_memory_size_; - uint32_t absolute_index; - uint32_t collision_index; + uint32_t absolute_index = 0; + uint32_t collision_index = 0; TableRow *current_; Mutex *mutex_; - TableIterator(size_t row_size) { - current_ = (TableRow *) sw_malloc(row_size); - if (!current_) { - throw std::bad_alloc(); - } - mutex_ = new Mutex(Mutex::PROCESS_SHARED); - row_memory_size_ = row_size; - reset(); - } + explicit TableIterator(size_t row_size); + ~TableIterator(); + + void reset(); - void lock() { + void lock() const { mutex_->lock(); } - void unlock() { + void unlock() const { mutex_->unlock(); } - - void reset() { - absolute_index = 0; - collision_index = 0; - sw_memset_zero(current_, row_memory_size_); - } - - ~TableIterator() { - if (current_) { - sw_free(current_); - } - delete mutex_; - } }; enum TableFlag { @@ -123,39 +102,17 @@ struct TableColumn { TYPE_STRING, }; - enum Type type; + Type type; uint32_t size; std::string name; size_t index; - TableColumn(const std::string &_name, enum Type _type, size_t _size) { - index = 0; - name = _name; - type = _type; - switch (_type) { - case TYPE_INT: - size = sizeof(long); - break; - case TYPE_FLOAT: - size = sizeof(double); - break; - case TYPE_STRING: - size = _size + sizeof(TableStringLength); - break; - default: - abort(); - break; - } - } + TableColumn(const std::string &_name, Type _type, size_t _size); - void clear(TableRow *row); + void clear(TableRow *row) const; }; class Table { - private: - Table() = delete; - ~Table() = delete; - std::unordered_map *column_map; Mutex *mutex; size_t size; @@ -187,26 +144,30 @@ class Table { sw_atomic_long_t update_count; uint32_t conflict_max_level; + Table() = delete; + ~Table() = delete; + static Table *make(uint32_t rows_size, float conflict_proportion); - size_t get_memory_size(); - uint32_t get_available_slice_num(); - uint32_t get_total_slice_num(); + size_t calc_memory_size() const; + size_t get_memory_size() const; + uint32_t get_available_slice_num() const; + uint32_t get_total_slice_num() const; bool create(); bool add_column(const std::string &name, enum TableColumn::Type type, size_t size); + TableColumn *get_column(const std::string &key) const; TableRow *set(const char *key, uint16_t keylen, TableRow **rowlock, int *out_flags); - TableRow *get(const char *key, uint16_t keylen, TableRow **rowlock); + TableRow *get(const char *key, uint16_t keylen, TableRow **rowlock) const; + bool exists(const char *key, uint16_t keylen) const; bool del(const char *key, uint16_t keylen); - void forward(); - // only release local memory of the current process - void free(); + void forward() const; // release shared memory void destroy(); - bool is_created() { + bool is_created() const { return created; } - bool ready() { + bool ready() const { return memory != nullptr; } @@ -214,86 +175,78 @@ class Table { hash_func = _fn; } - size_t get_size() { + size_t get_size() const { return size; } - TableRow *get_by_index(uint32_t index) { - TableRow *row = rows[index]; - return row->active ? row : nullptr; + float get_conflict_proportion() const { + return conflict_proportion; } - TableColumn *get_column(const std::string &key) { - auto i = column_map->find(key); - if (i == column_map->end()) { - return nullptr; - } else { - return i->second; - } + size_t get_column_size() const { + return column_map->size(); } - size_t count() { - return row_num; + TableRow *get_by_index(uint32_t index) const { + TableRow *row = rows[index]; + return row->active ? row : nullptr; } - bool exists(const char *key, uint16_t keylen) { - TableRow *_rowlock = nullptr; - const TableRow *row = get(key, keylen, &_rowlock); - _rowlock->unlock(); - return row != nullptr; + size_t count() const { + return row_num; } - bool exists(const std::string &key) { + bool exists(const std::string &key) const { return exists(key.c_str(), key.length()); } - TableRow *current() { + TableRow *current() const { return iterator->current_; } - void rewind() { + void rewind() const { iterator->lock(); iterator->reset(); iterator->unlock(); } - void clear_row(TableRow *row) { - for (auto i = column_list->begin(); i != column_list->end(); i++) { - (*i)->clear(row); + void clear_row(TableRow *row) const { + for (auto &i : *column_list) { + i->clear(row); } } private: - - TableRow *hash(const char *key, int keylen) { + TableRow *hash(const char *key, int keylen) const { uint64_t hashv = hash_func(key, keylen); uint64_t index = hashv & mask; assert(index < size); return rows[index]; } - TableRow *alloc_row() { + TableRow *alloc_row() const { lock(); - TableRow *new_row = (TableRow *) pool->alloc(0); + const auto new_row = static_cast(pool->alloc(0)); unlock(); return new_row; } - void free_row(TableRow *tmp) { + void free_row(TableRow *tmp) const { lock(); tmp->clear(); pool->free(tmp); unlock(); } - void check_key_length(uint16_t *keylen) { + static void check_key_length(uint16_t *keylen) { if (*keylen >= SW_TABLE_KEY_SIZE) { *keylen = SW_TABLE_KEY_SIZE - 1; } } void init_row(TableRow *new_row, const char *key, int keylen) { - sw_memset_zero((char *) new_row + offsetof(TableRow, active), sizeof(TableRow) - offsetof(TableRow, active)); + sw_memset_zero(reinterpret_cast(new_row) + offsetof(TableRow, active), + sizeof(TableRow) - offsetof(TableRow, active)); memcpy(new_row->key, key, keylen); new_row->key[keylen] = '\0'; new_row->key_len = keylen; @@ -301,11 +254,11 @@ class Table { sw_atomic_fetch_add(&(row_num), 1); } - int lock() { + int lock() const { return mutex->lock(); } - int unlock() { + int unlock() const { return mutex->unlock(); } }; diff --git a/include/swoole_thread.h b/include/swoole_thread.h new file mode 100644 index 0000000000..324f30e178 --- /dev/null +++ b/include/swoole_thread.h @@ -0,0 +1,75 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "swoole_lock.h" + +#include +#include + +long swoole_thread_get_native_id(); +bool swoole_thread_set_name(const char *name); +bool swoole_thread_get_name(char *buf, size_t len); +std::string swoole_thread_id_to_str(std::thread::id id); + +namespace swoole { +class Thread { + int exit_status = 0; + bool living = false; + std::thread thread; + + public: + bool is_alive() const { + return living; + } + + bool joinable() const { + return thread.joinable(); + } + + void join() { + thread.join(); + } + + void detach() { + thread.detach(); + } + + int get_exit_status() const { + return exit_status; + } + + pthread_t get_id() { + return thread.native_handle(); + } + + template + void start(Callable fn) { + thread = std::thread(fn); + } + + void enter() { + exit_status = 0; + living = true; + } + + void exit(const int status) { + exit_status = status; + living = false; + } +}; +} // namespace swoole diff --git a/include/swoole_timer.h b/include/swoole_timer.h index 2fe78ee039..f4e3724fe6 100644 --- a/include/swoole_timer.h +++ b/include/swoole_timer.h @@ -17,9 +17,9 @@ #pragma once -#include "swoole.h" #include "swoole_heap.h" #include "swoole_reactor.h" +#include "swoole_util.h" #include @@ -29,7 +29,6 @@ #define SW_TIMER_MAX_SEC ((double) LONG_MAX / 1000) namespace swoole { - typedef std::function TimerDestructor; struct TimerNode { @@ -38,7 +37,7 @@ struct TimerNode { TYPE_PHP, }; long id; - enum Type type; + Type type; int64_t exec_msec; int64_t interval; uint64_t exec_count; @@ -51,7 +50,6 @@ struct TimerNode { }; class Timer { - private: /*--------------signal timer--------------*/ Reactor *reactor_ = nullptr; Heap heap; @@ -60,87 +58,98 @@ class Timer { long _next_id; long _current_id; /*---------------event timer--------------*/ - struct timeval base_time; + int64_t base_time; + /** + * The time when the next timer will trigger, in milliseconds. + * This event will serve as the timeout for the event loop's epoll/poll/kqueue, + * or be set as the trigger time for the system clock. + */ + int64_t next_msec_; /*----------------------------------------*/ - int (*set)(Timer *timer, long exec_msec) = nullptr; - void (*close)(Timer *timer) = nullptr; + std::function set; + std::function close; - bool init_reactor(Reactor *reactor); - bool init_system_timer(); + void init(bool manually_trigger); + void init_with_reactor(Reactor *reactor); + void init_with_system_timer(); + void release_node(TimerNode *tnode); public: - long next_msec_; - - Timer(); + explicit Timer(bool manually_trigger); ~Timer(); - static int now(struct timeval *time); - inline int64_t get_relative_msec() { - struct timeval _now; - if (now(&_now) < 0) { - return SW_ERR; - } - int64_t msec1 = (_now.tv_sec - base_time.tv_sec) * 1000; - int64_t msec2 = (_now.tv_usec - base_time.tv_usec) / 1000; - return msec1 + msec2; + int64_t get_relative_msec() const { + return get_absolute_msec() - base_time; } - inline static int64_t get_absolute_msec() { - struct timeval now; - if (Timer::now(&now) < 0) { - return SW_ERR; - } - int64_t msec1 = (now.tv_sec) * 1000; - int64_t msec2 = (now.tv_usec) / 1000; - return msec1 + msec2; + int64_t get_next_msec() const { + return next_msec_; + } + + static int64_t get_absolute_msec() { + return time(true); } - inline Reactor *get_reactor() { + Reactor *get_reactor() const { return reactor_; } - bool init(); TimerNode *add(long _msec, bool persistent, void *data, const TimerCallback &callback); bool remove(TimerNode *tnode); - void update(TimerNode *tnode) { + void update(const TimerNode *tnode) const { heap.change_priority(tnode->exec_msec, tnode->heap_node); } - void delay(TimerNode *tnode, long delay_ms) { + void delay(TimerNode *tnode, long delay_ms) const { long now_ms = get_relative_msec(); tnode->exec_msec = (now_ms < 0 ? tnode->exec_msec : now_ms) + delay_ms; update(tnode); } - void reinit(Reactor *reactor); - int select(); + void reinit(bool manually_trigger = false); + void select(); - inline TimerNode *get(long id) { - auto it = map.find(id); + TimerNode *get(long id) { + const auto it = map.find(id); if (it == map.end()) { return nullptr; - } else { - return it->second; } + return it->second; } - inline TimerNode *get(long id, const enum TimerNode::Type type) { + TimerNode *get(long id, const TimerNode::Type type) { TimerNode *tnode = get(id); return (tnode && tnode->type == type) ? tnode : nullptr; } - inline size_t count() { + size_t count() const { return map.size(); } - inline uint64_t get_round() { + uint64_t get_round() const { return round; } - inline bool remove(long id) { + bool remove(long id) { return remove(get(id)); } - inline const std::unordered_map &get_map() { + const std::unordered_map &get_map() { return map; } }; + +static inline long sec2msec(const long sec) { + return sec * 1000; +} + +static inline long sec2msec(const int sec) { + return sec * 1000; +} + +static inline long sec2msec(const double sec) { + return static_cast(sec * 1000); +} + +static inline double msec2sec(const int msec) { + return static_cast(msec) / 1000.0; +} } // namespace swoole diff --git a/include/swoole_uring_socket.h b/include/swoole_uring_socket.h new file mode 100644 index 0000000000..e73551729a --- /dev/null +++ b/include/swoole_uring_socket.h @@ -0,0 +1,86 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + | Twosee | + +----------------------------------------------------------------------+ +*/ + +#pragma once + +#include "swoole_coroutine_socket.h" + +namespace swoole { +namespace coroutine { +class UringSocket : public Socket { + BIO *rbio = nullptr; + BIO *wbio = nullptr; + + bool ssl_bio_write(); + bool ssl_bio_read(); + bool ssl_bio_prepare(); + bool ssl_bio_perform(int rc, const char *fn); + ssize_t ssl_recv(void *_buf, size_t _n); + ssize_t ssl_send(const void *_buf, size_t _n); + ssize_t ssl_readv(network::IOVector *io_vector); + ssize_t ssl_writev(network::IOVector *io_vector); + ssize_t ssl_sendfile(const File &file, off_t *offset, size_t size); + + ssize_t uring_send(const void *_buf, size_t _n); + ssize_t uring_recv(void *_buf, size_t _n); + ssize_t uring_readv(const struct iovec *iovec, int count); + ssize_t uring_writev(const struct iovec *iovec, int count); + ssize_t uring_sendfile(const File &file, off_t *offset, size_t size); + network::Socket *uring_accept(double timeout); + + bool is_ssl() { + return !!socket->ssl; + } + + public: + UringSocket(SocketType sock_type) : Socket(sock_type) {} + UringSocket(int domain, int type, int protocol) : Socket(domain, type, protocol) {} + UringSocket(int _fd, int _domain, int _type, int _protocol) : Socket(_fd, _domain, _type, _protocol) {} + UringSocket(int _fd, SocketType _type) : Socket(_fd, _type) {} + UringSocket(network::Socket *sock, const UringSocket *server_sock) : Socket(sock, server_sock) {} + + bool connect(const std::string &_host, int _port = 0, int flags = 0) { + return Socket::connect(_host, _port, flags); + } + + ssize_t recvfrom(void *_buf, size_t _n) { + return Socket::recvfrom(_buf, _n); + } + + bool connect(const sockaddr *addr, socklen_t addrlen) override; + UringSocket *accept(double timeout = 0); + + ssize_t read(void *_buf, size_t _n) override; + ssize_t write(const void *_buf, size_t _n) override; + ssize_t recvmsg(msghdr *msg, int flags) override; + ssize_t sendmsg(const msghdr *msg, int flags) override; + ssize_t recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) override; + ssize_t sendto(const std::string &host, int port, const void *_buf, size_t _n) override; + ssize_t recv(void *_buf, size_t _n) override; + ssize_t send(const void *_buf, size_t _n) override; + ssize_t recv_all(void *_buf, size_t _n) override; + ssize_t send_all(const void *_buf, size_t _n) override; + bool sendfile(const char *filename, off_t offset, size_t length) override; + ssize_t readv(network::IOVector *io_vector) override; + ssize_t readv_all(network::IOVector *io_vector) override; + ssize_t writev(network::IOVector *io_vector) override; + ssize_t writev_all(network::IOVector *io_vector) override; + bool poll(EventType _type, double timeout = 0) override; + bool ssl_handshake() override; +}; +} // namespace coroutine +} // namespace swoole diff --git a/include/swoole_util.h b/include/swoole_util.h index 6abe7511ee..37067fab44 100644 --- a/include/swoole_util.h +++ b/include/swoole_util.h @@ -17,8 +17,10 @@ #pragma once -#include -#include +#include +#include +#include +#include #include #include @@ -26,12 +28,17 @@ #include #include #include +#include #include +#include -#define __SCOPEGUARD_CONCATENATE_IMPL(s1, s2) s1##s2 -#define __SCOPEGUARD_CONCATENATE(s1, s2) __SCOPEGUARD_CONCATENATE_IMPL(s1, s2) +#define SW_STRUCT_MEMBER_SIZE(_s, _m) sizeof(std::declval()._m) namespace swoole { +template +bool in_range(T value, std::initializer_list allowed_values) { + return std::find(allowed_values.begin(), allowed_values.end(), value) != allowed_values.end(); +} namespace std_string { template @@ -39,7 +46,7 @@ inline std::string format(const char *format, Args... args) { size_t size = snprintf(nullptr, 0, format, args...) + 1; // Extra space for '\0' std::unique_ptr buf(new char[size]); snprintf(buf.get(), size, format, args...); - return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside + return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside } inline std::string vformat(const char *format, va_list args) { @@ -49,13 +56,14 @@ inline std::string vformat(const char *format, va_list args) { va_end(_args); std::unique_ptr buf(new char[size]); vsnprintf(buf.get(), size, format, args); - return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside + return {buf.get(), buf.get() + size - 1}; // We don't want the '\0' inside } } // namespace std_string +// Keep parameter 'steady' as false for backward compatibility. template static inline long time(bool steady = false) { - if (steady) { + if (sw_likely(steady)) { auto now = std::chrono::steady_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } else { @@ -64,12 +72,19 @@ static inline long time(bool steady = false) { } } +static inline long get_timezone() { + using namespace std::chrono; + auto now_time_t = system_clock::to_time_t(system_clock::now()); + std::tm local_tm{}; + localtime_r(&now_time_t, &local_tm); + return local_tm.tm_gmtoff; +} + class DeferTask { - private: std::stack list_; public: - void add(Callback fn) { + void add(const Callback &fn) { list_.push(fn); } @@ -85,7 +100,7 @@ class DeferTask { template class ScopeGuard { public: - ScopeGuard(Fun &&f) : _fun(std::forward(f)), _active(true) {} + explicit ScopeGuard(Fun &&f) : _fun(std::forward(f)), _active(true) {} ~ScopeGuard() { if (_active) { @@ -101,7 +116,7 @@ class ScopeGuard { ScopeGuard(const ScopeGuard &) = delete; ScopeGuard &operator=(const ScopeGuard &) = delete; - ScopeGuard(ScopeGuard &&rhs) : _fun(std::move(rhs._fun)), _active(rhs._active) { + ScopeGuard(ScopeGuard &&rhs) noexcept : _fun(std::move(rhs._fun)), _active(rhs._active) { rhs.dismiss(); } @@ -110,6 +125,55 @@ class ScopeGuard { bool _active; }; +class BitMap { + uint64_t *array_; + size_t n_bits_; + + static size_t get_array_size(size_t n_bits) { + return (((n_bits) + 63) / 64 * 8); + } + + size_t get_offset(size_t i) const { + assert(i < n_bits_); + /* (i / 64) */ + return i >> 6; + } + + static uint64_t to_int(const size_t i, const size_t offset) { + return static_cast(1) << (i - (offset << 6)); + } + + public: + explicit BitMap(const size_t n_bits) { + assert(n_bits > 0); + array_ = new uint64_t[get_array_size(n_bits)]; + n_bits_ = n_bits; + } + + ~BitMap() { + delete[] array_; + } + + void clear() const { + memset(array_, 0, sizeof(uint64_t) * get_array_size(n_bits_)); + } + + void set(const size_t i) const { + const size_t off = get_offset(i); + array_[off] |= to_int(i, off); + } + + void unset(const size_t i) const { + const size_t off = get_offset(i); + array_[off] &= ~to_int(i, off); + } + + bool get(const size_t i) const { + const size_t off = get_offset(i); + return array_[off] & to_int(i, off); + } +}; + namespace detail { enum class ScopeGuardOnExit {}; @@ -119,11 +183,30 @@ inline ScopeGuard operator+(ScopeGuardOnExit, Fun &&fn) { } } // namespace detail -// Helper macro +#define __SCOPE_GUARD_CONCATENATE_IMPL(s1, s2) s1##s2 +#define __SCOPE_GUARD_CONCATENATE(s1, s2) __SCOPE_GUARD_CONCATENATE_IMPL(s1, s2) + +/** + * Call the specified function when exiting the scope, similar to Golang's defer function. + * After using this helper macro, + * it is not necessary to manually release resources before the return statement of the failed branch. + */ #define ON_SCOPE_EXIT \ - auto __SCOPEGUARD_CONCATENATE(ext_exitBlock_, __LINE__) = swoole::detail::ScopeGuardOnExit() + [&]() + auto __SCOPE_GUARD_CONCATENATE(ext_exitBlock_, __LINE__) = swoole::detail::ScopeGuardOnExit() + [&]() -std::string intersection(std::vector &vec1, std::set &vec2); +std::string intersection(const std::vector &vec1, std::set &vec2); + +static inline size_t ltrim(char **str, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + if ('\0' != **str && isspace(**str)) { + ++*str; + } else { + break; + } + } + return len - i; +} static inline size_t rtrim(char *str, size_t len) { for (size_t i = len; i > 0;) { @@ -149,7 +232,7 @@ static inline size_t rtrim(const char *str, size_t len) { } static inline ssize_t substr_len(const char *str, size_t len, char separator, bool before = false) { - const char *substr = (const char *) memchr(str, separator, len); + const auto substr = static_cast(memchr(str, separator, len)); if (substr == nullptr) { return -1; } @@ -163,11 +246,17 @@ static inline bool starts_with(const char *haystack, size_t l_haystack, const ch return memcmp(haystack, needle, l_needle) == 0; } +static inline bool starts_with(const std::string &str, const std::string &prefix) { + if (prefix.size() > str.size()) { + return false; + } + return std::equal(prefix.begin(), prefix.end(), str.begin()); +} + static inline bool ends_with(const char *haystack, size_t l_haystack, const char *needle, size_t l_needle) { if (l_needle > l_haystack) { return false; } return memcmp(haystack + l_haystack - l_needle, needle, l_needle) == 0; } - } // namespace swoole diff --git a/include/swoole_version.h b/include/swoole_version.h index c9bab9644b..ddc5960d92 100644 --- a/include/swoole_version.h +++ b/include/swoole_version.h @@ -18,21 +18,16 @@ #ifndef SWOOLE_VERSION_H_ #define SWOOLE_VERSION_H_ -#define SWOOLE_MAJOR_VERSION 5 -#define SWOOLE_MINOR_VERSION 0 +#define SWOOLE_MAJOR_VERSION 6 +#define SWOOLE_MINOR_VERSION 2 #define SWOOLE_RELEASE_VERSION 0 #define SWOOLE_EXTRA_VERSION "" -#define SWOOLE_VERSION "5.0.0" -#define SWOOLE_VERSION_ID 50000 +#define SWOOLE_VERSION "6.2.0" +#define SWOOLE_VERSION_ID 60200 #define SWOOLE_API_VERSION_ID 0x202208a #define SWOOLE_BUG_REPORT \ - "A bug occurred in Swoole-v" SWOOLE_VERSION ", please report it.\n" \ - "The Swoole developers probably don't know about it,\n" \ - "and unless you report it, chances are it won't be fixed.\n" \ - "You can read How to report a bug doc before submitting any bug reports:\n" \ - ">> https://github.com/swoole/swoole-src/blob/master/.github/ISSUE.md \n" \ - "Please do not send bug reports in the mailing list or personal letters.\n" \ - "The issue page is also suitable to submit feature requests.\n" - + "A process crash occurred in Swoole-v" SWOOLE_VERSION ". Please report this issue.\n" \ + "You can refer to the documentation below, submit an issue to us on GitHub.\n" \ + ">> https://github.com/swoole/swoole-src/blob/master/docs/ISSUE.md\n" #endif diff --git a/include/swoole_websocket.h b/include/swoole_websocket.h index f2bf7d338f..03c047cde0 100644 --- a/include/swoole_websocket.h +++ b/include/swoole_websocket.h @@ -29,6 +29,8 @@ #define SW_WEBSOCKET_CLOSE_CODE_LEN 2 #define SW_WEBSOCKET_CLOSE_REASON_MAX_LEN 125 #define SW_WEBSOCKET_OPCODE_MAX swoole::websocket::OPCODE_PONG +#define SW_WEBSOCKET_FRAME_HEADER_SIZE (SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_MASK_LEN + sizeof(uint64_t)) +#define SW_WEBSOCKET_DEFAULT_PAYLOAD_SIZE 1024 namespace swoole { namespace websocket { @@ -39,6 +41,7 @@ enum Status { STATUS_HANDSHAKE = 2, STATUS_ACTIVE = 3, STATUS_CLOSING = 4, + STATUS_HANDSHAKE_FAILED = 5, }; enum Flag { @@ -74,6 +77,30 @@ struct Frame { uint16_t header_length; size_t payload_length; char *payload; + + uchar get_flags() const { + uchar flags = 0; + if (header.FIN) { + flags |= FLAG_FIN; + } + if (header.RSV1) { + flags |= FLAG_RSV1; + } + if (header.RSV2) { + flags |= FLAG_RSV2; + } + if (header.RSV3) { + flags |= FLAG_RSV3; + } + if (header.MASK) { + flags |= FLAG_MASK; + } + return flags; + } + + bool compressed() const { + return header.RSV1; + } }; #define WEBSOCKET_VERSION 13 @@ -99,27 +126,17 @@ enum CloseReason { CLOSE_MESSAGE_TOO_BIG = 1009, CLOSE_EXTENSION_MISSING = 1010, CLOSE_SERVER_ERROR = 1011, + CLOSE_SERVICE_RESTART = 1012, + CLOSE_TRY_AGAIN_LATER = 1013, + CLOSE_BAD_GATEWAY = 1014, CLOSE_TLS = 1015, }; -static inline uchar get_flags(Frame *frame) { - uchar flags = 0; - if (frame->header.FIN) { - flags |= FLAG_FIN; - } - if (frame->header.RSV1) { - flags |= FLAG_RSV1; - } - if (frame->header.RSV2) { - flags |= FLAG_RSV2; - } - if (frame->header.RSV3) { - flags |= FLAG_RSV3; - } - if (frame->header.MASK) { - flags |= FLAG_MASK; - } - return flags; +static inline uint16_t get_ext_flags(uchar opcode, uchar flags) { + uint16_t ext_flags = opcode; + ext_flags = ext_flags << 8; + ext_flags += flags; + return ext_flags; } static inline uchar set_flags(uchar fin, uchar mask, uchar rsv1, uchar rsv2, uchar rsv3) { @@ -142,15 +159,21 @@ static inline uchar set_flags(uchar fin, uchar mask, uchar rsv1, uchar rsv2, uch return flags; } -bool encode(String *buffer, const char *data, size_t length, char opcode, uint8_t flags); +bool encode(String *buffer, const char *data, size_t length, uint8_t opcode, uint8_t flags); bool decode(Frame *frame, char *data, size_t length); -int pack_close_frame(String *buffer, int code, char *reason, size_t length, uint8_t flags); -void print_frame(Frame *frame); +void mask(char *data, size_t len, const char *mask_key); +bool pack_close_frame(String *buffer, int code, const char *reason, size_t length, uint8_t flags); +void print_frame(const Frame *frame); -static inline bool decode(Frame *frame, String *str) { +static inline bool decode(Frame *frame, const String *str) { return decode(frame, str->str, str->length); } +static inline void parse_ext_flags(uint16_t ext_flags, uchar *opcode, uchar *flags) { + *opcode = (uchar) (ext_flags >> 8); + *flags = (uchar) (ext_flags & 0xFF); +} + ssize_t get_package_length(const Protocol *protocol, network::Socket *conn, PacketLength *pl); int dispatch_frame(const Protocol *protocol, network::Socket *conn, const RecvData *rdata); diff --git a/include/swoole_wheel_timer.h b/include/swoole_wheel_timer.h deleted file mode 100644 index 0b852c1856..0000000000 --- a/include/swoole_wheel_timer.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 | - +----------------------------------------------------------------------+ -*/ - -#pragma once - -#include "swoole.h" - -#include -#include - -namespace swoole { - -struct WheelTimerNode; - -using WheelTimerCallback = std::function; - -struct WheelTimerNode { - std::list::iterator position_; - uint16_t index_; - WheelTimerCallback callback_; -}; - -class WheelTimer { - private: - uint64_t round_ = 0; - uint16_t size_; - std::vector> buckets_; - - void push(WheelTimerNode *node) { - node->index_ = (round_ + size_ - 1) % size_; - buckets_[node->index_].push_front(node); - node->position_ = buckets_[node->index_].begin(); - } - - public: - WheelTimer(uint16_t size) { - size_ = size; - buckets_.resize(size); - } - - uint64_t get_round() { - return round_; - } - - WheelTimerNode *add(const WheelTimerCallback &cb) { - WheelTimerNode *node = new WheelTimerNode; - push(node); - node->callback_ = cb; - return node; - } - - void update(WheelTimerNode *node) { - buckets_[node->index_].erase(node->position_); - push(node); - } - - void remove(WheelTimerNode *node) { - buckets_[node->index_].erase(node->position_); - delete node; - } - - void next() { - uint16_t current_index = round_ % size_; - round_++; - std::list &_list = buckets_[current_index]; - for (auto node : _list) { - node->callback_(node); - delete node; - } - _list.clear(); - } -}; -} // namespace swoole diff --git a/make.sh b/make.sh deleted file mode 100755 index b44458ca26..0000000000 --- a/make.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/sh -e -__DIR__=$(cd "$(dirname "$0")";pwd) -COMPILE_PARAMS="--enable-openssl --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares" - -if [ "$(uname | grep -i darwin)"x != ""x ]; then - CPU_COUNT="$(sysctl -n machdep.cpu.core_count)" -else - CPU_COUNT="$(/usr/bin/nproc)" -fi -if [ -z ${CPU_COUNT} ]; then - CPU_COUNT=4 -fi - -cd "${__DIR__}" - -if [ "$1" = "cmake" ] ;then - phpize - ./configure ${COMPILE_PARAMS} - cmake . - make -j ${CPU_COUNT} - make install - exit 0 -fi - -if [ "$1" = "clean" ] ;then - make clean - phpize --clean - exit 0 -fi - -if [ "$1" = "install-module" ] ;then - make ext-swoole - __EXT_DIR__=$(php-config --extension-dir) - cp lib/swoole.so "${__EXT_DIR__}" - echo "cp lib/swoole.so ${__EXT_DIR__}" - exit 0 -fi - -if [ "$1" = "library" ] ;then - set -e - cd ${__DIR__} - set +e - echo "rm ext-src/php_swoole.lo" - rm -f ext-src/php_swoole.lo - echo "rm ext-src/php_swoole_library.h" - rm -f ext-src/php_swoole_library.h - set -e - - if [ "$2" = "dev" ] ;then - /usr/bin/env php tools/build-library.php dev - else - /usr/bin/env php tools/build-library.php - fi - - echo "remake..." - make - echo "done" - exit 0 -fi - -if [ "$1" = "help" ] ;then - echo "./make.sh cmake" - echo "./make.sh install-module" - echo "./make.sh clean" - echo "./make.sh debug" - echo "./make.sh trace" - echo "./make.sh library [dev]" - echo "./make.sh" - exit 0 -fi - -phpize -if [ "$1" = "debug" ] ;then - ./configure ${COMPILE_PARAMS} --enable-debug-log -elif [ "$1" = "trace" ] ;then - ./configure ${COMPILE_PARAMS} --enable-trace-log -else - ./configure ${COMPILE_PARAMS} -fi -make clean -make -j ${CPU_COUNT} - -if [ "$(whoami)" = "root" ]; then - make install -else - sudo make install -fi diff --git a/mascot.png b/mascot.png deleted file mode 100644 index 2cb36e88f8..0000000000 Binary files a/mascot.png and /dev/null differ diff --git a/package.xml b/package.xml index 89042590c5..a02eeedfe4 100644 --- a/package.xml +++ b/package.xml @@ -14,8 +14,6 @@ - millisecond timer - built-in tcp/http/websocket/http2 server - coroutine tcp/http/websocket client - - coroutine mysql client - - coroutine redis client - coroutine read/write file system - coroutine dns lookup - support IPv4/IPv6/UnixSocket/TCP/UDP @@ -51,11 +49,11 @@ doubaokun@php.net yes - 2022-07-07 - + 2026-03-10 + - 5.0.0 - 5.0 + 6.2.0 + 6.0 stable @@ -63,56 +61,52 @@ Apache2.0 - Added - --- - * Added max_concurrency option for Server - * Added max_retries option for Coroutine\Http\Client - * Added name_resolver global option - * Added upload_max_filesize option for Server - * Added Coroutine::getExecuteTime() - * Added SWOOLE_DISPATCH_CONCURRENT_LB dispatch_mode for Server - - Changed - --- - * Enhanced type system, added types for parameters and return values of all functions - * Optimized error handling, all constructors will throw exceptions when fail - * Adjusted the default mode of Server, the default is SWOOLE_BASE mode - * Migrate pgsql coroutine client to core - * Contains all bugfixes from the 4.8.x branch - - Removed - --- - - Removed PSR-0 style class names - - Removed the automatic addition of Event::wait() in shutdown function - - Removed Server::tick/after/clearTimer/defer aliases - - Removed --enable-http/--enable-swoole-json, adjusted to be enable by default - - Deprecated - --- - - Deprecated Coroutine\Redis and Coroutine\MySQL + - Added coroutine-based `FTP` client. By including the `--enable-swoole-ftp` option during compilation, coroutine support for `FTP` operations can be enabled to avoid network blocking. + - Added coroutine-based `SSH` client. By including the `--enable-swoole-ssh` option during compilation, coroutine support for `SSH` operations can be enabled to improve concurrency efficiency. + - Added support for `io_uring` in the `HTTP` coroutine server. The `HTTP` coroutine server can now utilize the high-performance `io_uring` event mechanism. Enable it by adding the `--enable-uring_socket` option during compilation for better I/O performance. + - Added the `Swoole\RemoteObject\Server` module, providing transparent coroutine operation support for `MongoDB`. + - Added the `Swoole\Coroutine::setTimeLimit()` function to control coroutine execution timeout, preventing coroutines from occupying resources for extended periods. + - Added `URL` rewriting support for the `HTTP` static file server. + - Added coroutine support for `pdo_firebird`. + - Added support for `PHP 8.5`. + - Added coroutine support for the `gethostbyname` function. + - Swoole\Coroutine::cancel now supports canceling iouring operations. + - Optimized the implementation of `Server::shutdown()`, replacing signal communication with pipe communication in `Process` mode. + - In the callback functions of Swoole's HTTP servers (including Swoole\Http\Server, Swoole\Http2\Server, and Swoole\Coroutine\Http\Server), the `server` property of the `Swoole\Http\Request` object now includes a new `server_addr` field, which identifies the server's IP address. + - `SSH` and `FTP` coroutine clients cannot coexist with PHP's `ext-ssh` and `ext-ftp`. To enable these features, the PHP `ssh` and `ftp` extensions must be disabled. + - Fixed an issue where the HTTP2 server session was released multiple times in a multi-threaded environment. + - Fixed an issue in version `8.5` where `refresh_memory_manager()` must be executed after forking a child process or creating a child thread; otherwise, the program would crash. + - Fixed an issue where the `swoole_get_local_mac` function did not work correctly on `macOS` systems. + - Fixed a potential crash issue that could occur when users manually suspended coroutines in a multi-threaded environment. + - Fixed compilation errors on Alpine systems. + - Fixed a memory leak issue when function hooking failed. + - Fixed thread safety issues during the coroutinization process of `pdo_sqlite` and `pdo_oci`. + - Fixed the `sw_php_print_backtrace` function to ensure its output format matches the backtrace information format of PHP's built-in functions. + - Fixed compilation failures caused by enabling --enable-uring-socket, --enable-trace-log, and --enable-error-log. + - Fixed the issue where `pdo_firebird` could not be compiled due to undefined macros. + - Executing `php --ri swoole` now outputs version information for `libpq`. + - Optimized logic related to `io_uring` in file coroutine operations. + - Added PHP call stack information output when event addition fails. + - Optimized the implementation code for the `swoole_get_local_ip` and `swoole_get_local_mac` functions. + - Optimized the coroutine waiting time for file locks to prevent exponentially increasing sleep times from making locks increasingly difficult to acquire. + - Improved compatibility with the `Android` platform. + - Removed the `--enable-openssl` compilation option; support for `OpenSSL` is now included by default. + - `--enable-uring-socket` requires explicitly specifying either `--enable-iouring` or `--with-liburing-dir`. + - `PHP 8.1` is no longer supported. - - + - - - - - + - - - - - - - + + + @@ -124,13 +118,18 @@ - + + + + + + @@ -138,15 +137,19 @@ - + + + + + @@ -155,12 +158,15 @@ + + + @@ -168,165 +174,43 @@ + + - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - + + + + - @@ -337,6 +221,7 @@ + @@ -370,6 +255,7 @@ + @@ -446,7 +332,6 @@ - @@ -457,21 +342,24 @@ - - + + + + + @@ -482,18 +370,15 @@ - - + + + - - - - - + @@ -521,53 +406,57 @@ + - - - - - + + + + - - - - - + + + + + + + + + @@ -575,8 +464,6 @@ - - @@ -609,6 +496,12 @@ + + + + + + @@ -625,18 +518,43 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -650,55 +568,162 @@ + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - + + + + + + + + + + - @@ -707,12 +732,11 @@ - - + @@ -725,10 +749,13 @@ + + + @@ -742,38 +769,72 @@ + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -796,10 +857,10 @@ - + @@ -810,99 +871,31 @@ - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - + @@ -923,6 +916,8 @@ + + @@ -932,14 +927,17 @@ + + + @@ -955,19 +953,35 @@ + + + + + + + + + + + + + + + + @@ -985,7 +999,6 @@ - @@ -1038,10 +1051,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -1051,6 +1088,7 @@ + @@ -1063,11 +1101,14 @@ + + + @@ -1086,8 +1127,12 @@ + + + + @@ -1105,6 +1150,7 @@ + @@ -1116,6 +1162,11 @@ + + + + + @@ -1126,6 +1177,7 @@ + @@ -1136,9 +1188,11 @@ + + @@ -1164,16 +1218,25 @@ + + + + + + + + + @@ -1204,6 +1267,7 @@ + @@ -1217,9 +1281,13 @@ + + + + @@ -1237,6 +1305,7 @@ + @@ -1247,9 +1316,8 @@ - - + @@ -1259,6 +1327,7 @@ + @@ -1266,13 +1335,15 @@ - + + + @@ -1300,6 +1371,8 @@ + + @@ -1309,6 +1382,14 @@ + + + + + + + + @@ -1316,6 +1397,7 @@ + @@ -1329,17 +1411,13 @@ + + - - - - - - + + + - - - @@ -1365,6 +1443,7 @@ + @@ -1372,21 +1451,25 @@ + + + + + - - - - - - + + + + + @@ -1403,6 +1486,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1414,7 +1555,9 @@ + + @@ -1422,6 +1565,7 @@ + @@ -1431,6 +1575,7 @@ + @@ -1442,9 +1587,11 @@ + + @@ -1455,7 +1602,10 @@ + + + @@ -1469,6 +1619,7 @@ + @@ -1478,6 +1629,7 @@ + @@ -1487,6 +1639,7 @@ + @@ -1507,6 +1660,7 @@ + @@ -1518,13 +1672,36 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -1534,6 +1711,12 @@ + + + + + + @@ -1543,11 +1726,15 @@ + + + + @@ -1555,12 +1742,15 @@ + + + @@ -1571,18 +1761,33 @@ + + + + + + + + + + + + + + + @@ -1592,19 +1797,27 @@ + + + + + + + + @@ -1620,8 +1833,11 @@ + + + @@ -1630,25 +1846,41 @@ + + + + + + + + + + + + + + + + @@ -1658,7 +1890,10 @@ + + + @@ -1672,106 +1907,165 @@ - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + @@ -1782,80 +2076,52 @@ + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - + + + + - + + @@ -1864,9 +2130,12 @@ - + + + + @@ -1874,6 +2143,7 @@ + @@ -1885,6 +2155,7 @@ + @@ -1946,10 +2217,12 @@ + + + + - - @@ -1964,6 +2237,7 @@ + @@ -1985,6 +2259,10 @@ + + + + @@ -2041,6 +2319,8 @@ + + @@ -2063,7 +2343,6 @@ - @@ -2071,6 +2350,10 @@ + + + + @@ -2085,15 +2368,17 @@ + - - - + + + + @@ -2107,13 +2392,14 @@ - - + + + @@ -2168,6 +2454,7 @@ + @@ -2178,6 +2465,7 @@ + @@ -2196,6 +2484,7 @@ + @@ -2203,13 +2492,17 @@ + + + + @@ -2244,12 +2537,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2257,8 +2600,10 @@ + + @@ -2267,14 +2612,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2287,14 +2700,17 @@ + + + @@ -2302,18 +2718,21 @@ + + + @@ -2325,48 +2744,26 @@ - - - - - - - + - - - - - - - - - - - - + - - - - - @@ -2384,6 +2781,12 @@ + + + + + + @@ -2401,6 +2804,7 @@ + @@ -2436,13 +2840,24 @@ + + + + + + + + + + + - + @@ -2450,19 +2865,78 @@ + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -2477,24 +2951,13 @@ - - - - - - - - - - - - - 8.0.0 + 8.2.0 + 8.5.99 1.4.0 @@ -2503,10 +2966,24 @@ swoole - - + + + + + + + + + + + + + + + + diff --git a/php-cs-fix b/php-cs-fix new file mode 100755 index 0000000000..230e2ce8f7 --- /dev/null +++ b/php-cs-fix @@ -0,0 +1,2 @@ +#/bin/bash +./tests/include/lib/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose $1 diff --git a/php_swoole.h b/php_swoole.h index a8dd765825..a0442f6669 100644 --- a/php_swoole.h +++ b/php_swoole.h @@ -17,6 +17,10 @@ #ifndef PHP_SWOOLE_H #define PHP_SWOOLE_H +#ifdef __cplusplus +extern "C" { +#endif + #include "php.h" #include "php_ini.h" #include "php_globals.h" @@ -29,11 +33,16 @@ #include "zend_interfaces.h" #include "zend_closures.h" #include "zend_exceptions.h" +#include "zend_attributes.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif +#ifdef __cplusplus +} +#endif + extern zend_module_entry swoole_module_entry; #define phpext_swoole_ptr &swoole_module_entry @@ -48,11 +57,16 @@ ZEND_BEGIN_MODULE_GLOBALS(swoole) zend_bool display_errors; zend_bool cli; zend_bool use_shortname; - zend_bool enable_coroutine; zend_bool enable_preemptive_scheduler; zend_bool enable_library; - long socket_buffer_size; + zend_bool enable_fiber_mock; + zend_bool blocking_detection; + zend_long blocking_threshold; + zend_bool profile; + zend_bool leak_detection; + zend_long socket_buffer_size; int req_status; + HashTable *in_autoload; ZEND_END_MODULE_GLOBALS(swoole) // clang-format on diff --git a/php_swoole_api.h b/php_swoole_api.h new file mode 100644 index 0000000000..c811f245f3 --- /dev/null +++ b/php_swoole_api.h @@ -0,0 +1,64 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_SWOOLE_API_H +#define PHP_SWOOLE_API_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "php.h" +#include "php_network.h" + +int php_async_socket_connect_to_host(const char *host, + unsigned short port, + int socktype, + int asynchronous, + struct timeval *timeout, + zend_string **error_string, + int *error_code, + const char *bindto, + unsigned short bindport, + long sockopts); + +int php_async_socket_poll(php_socket_t fd, int events, int timeout); + +#if PHP_VERSION_ID < 80300 +static inline const char *zend_zval_value_name(const zval *arg) { + ZVAL_DEREF(arg); + + if (Z_ISUNDEF_P(arg)) { + return "null"; + } + + if (Z_TYPE_P(arg) == IS_OBJECT) { + return ZSTR_VAL(Z_OBJCE_P(arg)->name); + } else if (Z_TYPE_P(arg) == IS_FALSE) { + return "false"; + } else if (Z_TYPE_P(arg) == IS_TRUE) { + return "true"; + } + + return zend_get_type_by_const(Z_TYPE_P(arg)); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PHP_SWOOLE_API_H */ diff --git a/run-core-tests.sh b/run-core-tests.sh new file mode 100755 index 0000000000..b4c51e70d9 --- /dev/null +++ b/run-core-tests.sh @@ -0,0 +1,26 @@ +#!/bin/bash +__DIR__=$(cd "$(dirname "$0")" || exit;pwd) + +export ASAN_OPTIONS=detect_leaks=0 +sudo sysctl -w kernel.randomize_va_space=0 + +ipcs -q + +cd "${__DIR__}"/core-tests/js || exit 1 +npm install +cd "${__DIR__}" || exit 1 + +tasks=$(./bin/core-tests --gtest_list_tests | awk '/\./') || exit 255 +for task in $tasks; do + execute_command="./bin/core-tests" + + if [ "$task" = "log." ]; then + $execute_command --gtest_filter="$task"* + else + sudo -E "$execute_command" --gtest_filter="$task"* + fi + + if [ $? -ne 0 ]; then + exit 255 + fi +done diff --git a/travis/.gitignore b/scripts/.gitignore similarity index 100% rename from travis/.gitignore rename to scripts/.gitignore diff --git a/clear.sh b/scripts/clear.sh similarity index 76% rename from clear.sh rename to scripts/clear.sh index 2a1cfc2f42..690905b793 100755 --- a/clear.sh +++ b/scripts/clear.sh @@ -1,12 +1,14 @@ #!/bin/sh __DIR__=$(cd "$(dirname "$0")";pwd) +__SRC_DIR__=$(cd "$(dirname "${__DIR__}")";pwd) set -e -cd "${__DIR__}" +cd "${__SRC_DIR__}" set +e find . \( -name \*.gcno -o -name \*.gcda \) -print0 | xargs -0 rm -f find . \( -name \*.lo -o -name \*.o \) -print0 | xargs -0 rm -f find . \( -name \*.la -o -name \*.a \) -print0 | xargs -0 rm -f +find . \( -name \*.dep \) -print0 | xargs -0 rm -f find . -name \*.so -print0 | xargs -0 rm -f find . -name .libs -a -type d -print0 | xargs -0 rm -rf rm -f libphp.la modules/* libs/* diff --git a/scripts/code-format.sh b/scripts/code-format.sh new file mode 100755 index 0000000000..7e5feb03c9 --- /dev/null +++ b/scripts/code-format.sh @@ -0,0 +1,42 @@ +#!/bin/sh +__DIR__=$(cd "$(dirname "$0")";pwd) +__SRC_DIR__=$(cd "$(dirname "${__DIR__}")";pwd) + +cd $__SRC_DIR__ + +## core source file +clang-format -i src/core/*.cc +clang-format -i src/coroutine/*.cc +clang-format -i src/lock/*.cc +clang-format -i src/memory/*.cc +clang-format -i src/network/*.cc +clang-format -i src/os/*.cc +clang-format -i src/pipe/*.cc +clang-format -i src/protocol/*.cc +clang-format -i src/reactor/*.cc +clang-format -i src/server/*.cc +clang-format -i src/wrapper/*.cc +## core header file +clang-format -i include/*.h + +## ext source file +clang-format -i *.cc +clang-format -i *.h + +## examples +clang-format -i examples/cpp/*.cc + +## core-tests source file +clang-format -i core-tests/src/_lib/*.cpp +clang-format -i core-tests/src/client/*.cpp +clang-format -i core-tests/src/core/*.cpp +clang-format -i core-tests/src/coroutine/*.cpp +clang-format -i core-tests/src/lock/*.cpp +clang-format -i core-tests/src/memory/*.cpp +clang-format -i core-tests/src/network/*.cpp +clang-format -i core-tests/src/os/*.cpp +clang-format -i core-tests/src/process/*.cpp +clang-format -i core-tests/src/protocol/*.cpp +clang-format -i core-tests/src/reactor/*.cpp +clang-format -i core-tests/src/server/*.cpp +clang-format -i core-tests/src/main.cpp diff --git a/scripts/code-stats.sh b/scripts/code-stats.sh new file mode 100755 index 0000000000..9fe1eb79eb --- /dev/null +++ b/scripts/code-stats.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +# enter the dir +cd "${__DIR__}/.." +cloc . --exclude-dir=thirdparty,Debug,CMakeFiles,build,.git \ + --fullpath \ + --not-match-d='tools/vendor' \ + --not-match-d='tests/include/lib/vendor' \ + --not-match-f='ext-src/php_swoole_library\.h$' diff --git a/travis/debug/swoole_info.php b/scripts/debug/swoole_info.php similarity index 100% rename from travis/debug/swoole_info.php rename to scripts/debug/swoole_info.php diff --git a/travis/debug/swoole_table_implements.php b/scripts/debug/swoole_table_implements.php similarity index 100% rename from travis/debug/swoole_table_implements.php rename to scripts/debug/swoole_table_implements.php diff --git a/scripts/docker-compile-with-iouring.sh b/scripts/docker-compile-with-iouring.sh new file mode 100755 index 0000000000..65de84556b --- /dev/null +++ b/scripts/docker-compile-with-iouring.sh @@ -0,0 +1,30 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +if [ ! -f "/.dockerenv" ]; then + echo "" && echo "❌ This script is just for Docker!" + exit +fi + +sh library.sh + +cd "${__DIR__}" && cd .. + +./scripts/clear.sh && phpize +if [ -n "$(php -v | grep "ZTS")" ]; then + echo "" && echo "🚀 php zts + swoole thread mode + iouring!" + ./configure --enable-iouring --enable-swoole-thread +else + echo "" && echo "🚀 php nts + swoole + iouring!" + ./configure --enable-iouring +fi + +make -j$(cat /proc/cpuinfo | grep processor | wc -l) +make install +docker-php-ext-enable swoole +php -v +php -m +php --ri curl +php --ri swoole + diff --git a/scripts/docker-compile-with-thread.sh b/scripts/docker-compile-with-thread.sh new file mode 100755 index 0000000000..4d40c1754e --- /dev/null +++ b/scripts/docker-compile-with-thread.sh @@ -0,0 +1,36 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +sh library.sh + +if [ ! -f "/.dockerenv" ]; then + echo "" && echo "❌ This script is just for Docker!" + exit +fi + +cd "${__DIR__}" && cd .. +./scripts/clear.sh +phpize +./configure \ +--enable-brotli \ +--enable-zstd \ +--enable-sockets \ +--enable-mysqlnd \ +--enable-swoole-curl \ +--enable-cares \ +--enable-swoole-stdext \ +--enable-swoole-pgsql \ +--enable-swoole-thread \ +--with-swoole-odbc=unixODBC,/usr \ +--with-swoole-oracle=instantclient,/usr/local/instantclient \ +--enable-swoole-sqlite + +make -j$(cat /proc/cpuinfo | grep processor | wc -l) +make install +docker-php-ext-enable swoole +php -v +php -m +php --ri curl +php --ri swoole + diff --git a/scripts/docker-compile.sh b/scripts/docker-compile.sh new file mode 100755 index 0000000000..1236235a2a --- /dev/null +++ b/scripts/docker-compile.sh @@ -0,0 +1,50 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +if [ ! -f "/.dockerenv" ]; then + echo "" && echo "❌ This script is just for Docker!" + exit +fi + +sh library.sh +cd "${__DIR__}/.." +./scripts/clear.sh +phpize + +option="--enable-brotli \ + --enable-zstd \ + --enable-sockets \ + --enable-mysqlnd \ + --enable-swoole-curl \ + --enable-swoole-stdext \ + --enable-cares \ + --enable-swoole-pgsql \ + --with-swoole-firebird \ + --with-swoole-odbc=unixODBC,/usr \ + --with-swoole-oracle=instantclient,/usr/local/instantclient \ + --with-swoole-ssh2 \ + --enable-swoole-ftp \ + --enable-swoole-sqlite" + +if [ "$SWOOLE_THREAD" = 1 ]; then + ./configure $option --enable-swoole-thread +elif [ "$SWOOLE_IOURING" = 1 ]; then + if [ -n "$(php -v | grep "ZTS")" ]; then + echo "" && echo "🚀 php zts + swoole thread mode + iouring!" + ./configure --enable-iouring --enable-swoole-thread + else + echo "" && echo "🚀 php nts + swoole + iouring!" + ./configure --enable-iouring + fi +else + ./configure $option +fi + +make -j$(cat /proc/cpuinfo | grep processor | wc -l) +make install +docker-php-ext-enable swoole +php -v +php -m +php --ri swoole + diff --git a/scripts/docker-compose.yml b/scripts/docker-compose.yml new file mode 100755 index 0000000000..7a1b45e38c --- /dev/null +++ b/scripts/docker-compose.yml @@ -0,0 +1,90 @@ +version: '3.4' +services: + swoole: + container_name: "swoole" + image: "phpswoole/php:${PHP_VERSION}" + volumes: + - "${SWOOLE_BUILD_DIR}:/swoole-src:rw" + working_dir: /swoole-src + ulimits: + core: -1 + privileged: true + depends_on: + - mysql + - redis + - pgsql + - oracle + dns: + - 8.8.8.8 + - 1.1.1.1 + environment: + SWOOLE_BRANCH: "${SWOOLE_BRANCH}" + command: tail -f /etc/group + mysql: + container_name: "mysql" + image: "mysql:latest" + volumes: + - ./data/mysql:/var/lib/mysql:rw + - ./data/run/mysqld:/var/run/mysqld:rw + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + MYSQL_USER: swoole + MYSQL_PASSWORD: swoole + pgsql: + image: postgres:14 + container_name: "pgsql" + environment: + POSTGRES_USER: root + POSTGRES_DB: test + POSTGRES_PASSWORD: root + oracle: + image: gvenzl/oracle-free:slim + container_name: "oracle" + environment: + ORACLE_PASSWORD: oracle + ports: + - "1521:1521" + redis: + container_name: "redis" + image: "redis:latest" + volumes: + - ./data/redis:/var/lib/redis:rw + - ./data/run/redis:/var/run/redis:rw + sysctls: + net.core.somaxconn: 65535 + httpbin: + container_name: "httpbin" + image: "arnaudlacour/httpbin" + tinyproxy: + container_name: "tinyproxy" + image: "kalaksi/tinyproxy" + golang-h2demo: + container_name: "golang-h2demo" + image: "phpswoole/golang-h2demo" + socks5: + container_name: "socks5" + image: "serjs/go-socks5-proxy" + ports: + - "1080:1080" + ftp: + container_name: "ftp" + image: "teezily/ftpd" + environment: + FTP_USER: admin + FTP_PASSWORD: admin + + firebirdsql: + container_name: "firebirdsql" + image: "firebirdsql/firebird" + environment: + - FIREBIRD_ROOT_PASSWORD=root + - FIREBIRD_USER=test + - FIREBIRD_PASSWORD=test + - FIREBIRD_DATABASE=test.fdb + - FIREBIRD_DATABASE_DEFAULT_CHARSET=UTF8 + volumes: + - ./data:/var/lib/firebird/data + ports: + - "3050:3050" + diff --git a/scripts/docker-iouring-route.sh b/scripts/docker-iouring-route.sh new file mode 100755 index 0000000000..fe2274f23c --- /dev/null +++ b/scripts/docker-iouring-route.sh @@ -0,0 +1,23 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) +export SWOOLE_USE_IOURING=1 + +# enter the dir +cd "${__DIR__}" + +# show system info +date && echo "" +uname -a && echo "" + +# show php info +php -v && echo "" + +# compile in docker +echo "" && echo "📦 Compile ext-swoole[iouring] in docker..." && echo "" +./docker-compile-with-iouring.sh + +# run unit tests +echo "" && echo "📋 Run php tests[iouring] in docker..." && echo "" +./run-tests.sh + diff --git a/scripts/docker-route.sh b/scripts/docker-route.sh new file mode 100755 index 0000000000..010c82d62b --- /dev/null +++ b/scripts/docker-route.sh @@ -0,0 +1,25 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +# show system info and php info +date && echo "" +uname -a && echo "" +php -v && echo "" + +# enter the dir +cd "${__DIR__}" + +if [ "$1" = "THREAD" ]; then + export SWOOLE_THREAD=1 +elif [ "$1" = "IOURING" ]; then + export SWOOLE_IOURING=1 +fi + +# compile in docker +echo "" && echo "📦 Compile test in docker..." && echo "" +./docker-compile.sh + +# run unit tests +echo "" && echo "📋 PHP unit tests in docker..." && echo "" +./run-tests.sh diff --git a/scripts/docker-thread-route.sh b/scripts/docker-thread-route.sh new file mode 100755 index 0000000000..d6af300e65 --- /dev/null +++ b/scripts/docker-thread-route.sh @@ -0,0 +1,22 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) +export SWOOLE_THREAD=1 + +# enter the dir +cd "${__DIR__}" + +# show system info +date && echo "" +uname -a && echo "" + +# show php info +php -v && echo "" + +# compile in docker +echo "" && echo "📦 Compile ext-swoole[thread] in docker..." && echo "" +./docker-compile-with-thread.sh + +# run unit tests +echo "" && echo "📋 Run phpt tests[thread] in docker..." && echo "" +./run-tests.sh diff --git a/scripts/format-changed-files.sh b/scripts/format-changed-files.sh new file mode 100755 index 0000000000..a1f41c46ca --- /dev/null +++ b/scripts/format-changed-files.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +__CURRENT__=`pwd` +__DIR__=$(cd "$(dirname "$0")" || exit;pwd) + +cpp_files=$(git status --porcelain | grep '^[ M]' | grep '\.\(cc\|cpp\|h\)$' | awk '{print $2}') +php_files=$(git status --porcelain | grep '^[ M]' | grep '\.\(php\|phpt\)$' | awk '{print $2}') +arginfo_files=$(git status --porcelain | grep '^[ M]' | grep '_arginfo\.h$' | awk '{print $2}') + +if [ -z "$cpp_files" ] && [ -z "$php_files" ]; then + echo "No files to format." + exit 0 +fi + +# 格式化 C/C++ 文件 +if [ ! -z "$cpp_files" ]; then + echo "Formatting C/C++ files..." + for file in $cpp_files; do + # 额外检查确保不处理 _arginfo.h 文件 + if [[ "$file" != *_arginfo.h && "$file" != "ext-src/php_swoole_library.h" ]]; then + echo " - $file" + clang-format -i "$file" + fi + done +fi + +# 格式化 PHP 和 PHPT 文件 +if [ -n "$php_files" ]; then + echo "Formatting PHP files..." + + # 过滤掉 .stub.php 文件 + filtered_files=$(echo "$php_files" | grep -v '\.stub\.php$') + + if [ -n "$filtered_files" ]; then + echo "$filtered_files" | xargs -I {} bash -c ' + file="{}" + if [ -f "$file" ]; then + echo " ✓ Formatting: $file" + "'"$__DIR__"'/../tests/include/lib/vendor/bin/php-cs-fixer" fix "$file" + fi + ' + else + echo "All files are stub files, skipping." + fi +fi + +# 显示跳过的 _arginfo.h 文件 +if [ ! -z "$arginfo_files" ]; then + echo "Skipped auto-generated files:" + for file in $arginfo_files; do + echo " - $file (auto-generated)" + done +fi + + +echo "✅ Formatting completed successfully!" diff --git a/scripts/install-deps-on-ubuntu.sh b/scripts/install-deps-on-ubuntu.sh new file mode 100755 index 0000000000..2f9fe2e952 --- /dev/null +++ b/scripts/install-deps-on-ubuntu.sh @@ -0,0 +1,19 @@ +apt install -y cmake make gcc g++ \ + iputils-ping \ + libc-ares-dev \ + libssl-dev \ + libcurl4-openssl-dev \ + libmariadb-dev \ + libaio-dev \ + zlib1g-dev \ + sqlite3 libsqlite3-dev \ + libbrotli-dev \ + libpq-dev \ + unixodbc-dev \ + firebird-dev \ + libzstd-dev \ + libssh2-1-dev \ + iproute2 + +# The built-in liburing version of Ubuntu is 0.7, which is too low. We must install liburing through the source code +# liburing-dev diff --git a/scripts/install-liburing.sh b/scripts/install-liburing.sh new file mode 100755 index 0000000000..a3a04b4357 --- /dev/null +++ b/scripts/install-liburing.sh @@ -0,0 +1,6 @@ +LIBURING_VERSION=2.13 +wget https://github.com/axboe/liburing/archive/refs/tags/liburing-${LIBURING_VERSION}.tar.gz +tar zxf liburing-${LIBURING_VERSION}.tar.gz +cd liburing-liburing-${LIBURING_VERSION} || exit +./configure +make -j$(cat /proc/cpuinfo | grep processor | wc -l) install \ No newline at end of file diff --git a/scripts/library.sh b/scripts/library.sh new file mode 100755 index 0000000000..7dfe30be7a --- /dev/null +++ b/scripts/library.sh @@ -0,0 +1,47 @@ +#!/bin/sh -e +__CURRENT__=$(pwd) +__DIR__=$(cd "$(dirname "$0")";pwd) + +if [ "$(uname -m)" = "aarch64" ]; then + arch="arm64" +else + arch="x64" +fi + +cd "${__DIR__}/" + +apt update +bash ./install-deps-on-ubuntu.sh + +# sshd +apt install -y openssh-server +service ssh start + +# MariaDB ODBC Connector +MARIADB_CONNECTOR_VERSION=3.1.22 +wget https://github.com/mariadb-corporation/mariadb-connector-odbc/archive/refs/tags/${MARIADB_CONNECTOR_VERSION}.tar.gz +tar zxf ${MARIADB_CONNECTOR_VERSION}.tar.gz +mkdir build +cd build +cmake ../mariadb-connector-odbc-${MARIADB_CONNECTOR_VERSION}/ -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCONC_WITH_UNIT_TESTS=Off -DCMAKE_INSTALL_PREFIX=/usr/local -DWITH_SSL=OPENSSL +cmake --build . --config RelWithDebInfo +make install +echo '/usr/local/lib/mariadb/' > /etc/ld.so.conf.d/odbc-mariadb.conf +ldconfig + +wget -nv -O instantclient-basiclite-linux${arch}.zip https://download.oracle.com/otn_software/linux/instantclient/1930000/instantclient-basiclite-linux.${arch}-19.30.0.0.0dbru.zip +unzip instantclient-basiclite-linux${arch}.zip && rm instantclient-basiclite-linux${arch}.zip +wget -nv -O instantclient-sdk-linux${arch}.zip https://download.oracle.com/otn_software/linux/instantclient/1930000/instantclient-sdk-linux.${arch}-19.30.0.0.0dbru.zip +unzip instantclient-sdk-linux${arch}.zip && rm instantclient-sdk-linux${arch}.zip +mv instantclient_*_* ./instantclient +rm ./instantclient/sdk/include/ldap.h +# fix debug build warning: zend_signal: handler was replaced for signal (2) after startup +echo DISABLE_INTERRUPT=on > ./instantclient/network/admin/sqlnet.ora +mv ./instantclient /usr/local/ +echo '/usr/local/instantclient' > /etc/ld.so.conf.d/oracle-instantclient.conf +ldconfig + +cd "${__DIR__}/" +bash ./install-liburing.sh + +cd - diff --git a/scripts/make.sh b/scripts/make.sh new file mode 100755 index 0000000000..e1aca812c3 --- /dev/null +++ b/scripts/make.sh @@ -0,0 +1,157 @@ +#!/bin/sh +__CURRENT_DIR__=$(cd "$(dirname "$0")";pwd) +__DIR__=$(cd "$(dirname "${__CURRENT_DIR__}")";pwd) +__HAVE_ZTS__=$(php -v|grep ZTS) + +COMPILE_PARAMS=" \ +--enable-sockets \ +--enable-mysqlnd \ +--enable-swoole-curl \ +--enable-cares \ +--enable-zstd \ +--enable-swoole-pgsql \ +--enable-swoole-stdext \ +--with-swoole-firebird \ +--enable-uring-socket \ +--with-swoole-ssh2 \ +--enable-swoole-ftp \ +--with-swoole-odbc=unixODBC,/usr \ +--enable-swoole-sqlite" + +TEMP=$(getopt -o ad --long asan,debug:,oci -n "$0" -- "$@") + +if [ $? != 0 ]; then + echo "Parameter parsing failed!" >&2 + exit 1 +fi + +eval set -- "$TEMP" + +while true; do + case "$1" in + -a|--asan) + ASAN=true + shift + ;; + -d|--debug) + DEBUG=true + shift + ;; + --oci) + OCI=true + shift + ;; + --) + shift + break + ;; + *) + echo "Unsupported parameters!" + exit 1 + ;; + esac +done + +if [ "$ASAN" = true ]; then + COMPILE_PARAMS="$COMPILE_PARAMS --enable-asan" +fi + +if [ "$DEBUG" = true ]; then + COMPILE_PARAMS="$COMPILE_PARAMS --enable-debug" +fi + +if [ "$OCI" = true ]; then + COMPILE_PARAMS="$COMPILE_PARAMS --with-swoole-oracle=instantclient,/usr/local/instantclient" +fi + +if [ -n "$__HAVE_ZTS__" ]; then + COMPILE_PARAMS="$COMPILE_PARAMS --enable-swoole-thread" +fi + +if [ "$(uname)" = "Linux" ]; then + COMPILE_PARAMS="$COMPILE_PARAMS --enable-iouring" +fi + +if [ "$(uname | grep -i darwin)"x != ""x ]; then + CPU_COUNT="$(sysctl -n machdep.cpu.core_count)" +else + CPU_COUNT="$(/usr/bin/nproc)" +fi +if [ -z ${CPU_COUNT} ]; then + CPU_COUNT=4 +fi + +cd "${__DIR__}" + +if [ "$1" = "cmake" ] ;then + phpize + ./configure ${COMPILE_PARAMS} + cmake . + make -j ${CPU_COUNT} + make install + exit 0 +fi + +if [ "$1" = "clean" ] ;then + make clean + phpize --clean + exit 0 +fi + +if [ "$1" = "install-module" ] ;then + make ext-swoole + __EXT_DIR__=$(php-config --extension-dir) + cp lib/swoole.so "${__EXT_DIR__}" + echo "cp lib/swoole.so ${__EXT_DIR__}" + exit 0 +fi + +if [ "$1" = "library" ] ;then + set -e + cd ${__DIR__} + set +e + echo "rm ext-src/php_swoole.lo" + rm -f ext-src/php_swoole.lo + echo "rm ext-src/php_swoole_library.h" + rm -f ext-src/php_swoole_library.h + set -e + + if [ "$2" = "dev" ] ;then + /usr/bin/env php tools/build-library.php dev + else + /usr/bin/env php tools/build-library.php + fi + + echo "remake..." + make + echo "done" + exit 0 +fi + +if [ "$1" = "help" ] ;then + echo "./make.sh cmake" + echo "./make.sh install-module" + echo "./make.sh clean" + echo "./make.sh debug" + echo "./make.sh trace" + echo "./make.sh library [dev]" + echo "./make.sh" + exit 0 +fi + +phpize + +if [ "$1" = "debug" ] ;then + ./configure ${COMPILE_PARAMS} --enable-debug-log +elif [ "$1" = "trace" ] ;then + ./configure ${COMPILE_PARAMS} --enable-trace-log +elif [ "$1" = "config" ] ;then + ./configure ${COMPILE_PARAMS} + exit 0 +else + ./configure ${COMPILE_PARAMS} +fi + +make clean +make -j ${CPU_COUNT} +make install diff --git a/scripts/pecl-install.sh b/scripts/pecl-install.sh new file mode 100755 index 0000000000..d13fa0fffd --- /dev/null +++ b/scripts/pecl-install.sh @@ -0,0 +1,12 @@ +#!/bin/sh -e +__CURRENT__=`pwd` +__DIR__=$(cd "$(dirname "$0")";pwd) + +cd ${__DIR__} && cd ../ && \ +pecl config-show && \ +php tools/pecl-package.php && package_file="`ls | grep swoole-*tgz`" && \ +echo "\n" | pecl install -f ${package_file} | tee pecl.log && \ +cat pecl.log | grep "successfully" && \ +php -d extension=swoole --ri swoole && \ +pecl uninstall swoole && \ +rm -f pecl.log diff --git a/scripts/rename.php b/scripts/rename.php new file mode 100644 index 0000000000..dcd9d6838c --- /dev/null +++ b/scripts/rename.php @@ -0,0 +1,17 @@ +&1 | grep "version"`"x = ""x ]; then + echo "\n❌ Docker not found!" + exit 1 + elif [ "`docker ps 2>&1 | grep Cannot`"x != ""x ]; then + echo "\n❌ Docker is not running!" + exit 1 + else + which "docker-compose" > /dev/null + if [ $? -ne 0 ]; then + echo "\n🤔 Can not found docker-compose, try to install it now...\n" + curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose && \ + chmod +x docker-compose && \ + sudo mv docker-compose /usr/local/bin + + which "docker-compose" > /dev/null + if [ $? -ne 0 ]; then + echo "\n❌ Install docker-compose failed!" + exit 1 + fi + + docker -v && docker-compose -v + fi + fi +} + +create_docker_images(){ + arch=`uname -m` + if [ "$arch" = "aarch64" ]; then + echo "\n 📢 create golang-h2demo aarch64 docker image" + git clone https://github.com/swoole/golang-h2demo.git + apt install -y golang + cd ./golang-h2demo && GOOS=linux GOARCH=arm64 go build -o h2demo . && docker build . -t phpswoole/golang-h2demo && cd - + + echo "\n 📢 create ${PHP_VERSION} aarch64 docker image" + git clone https://github.com/swoole/php-docker.git + cd php-docker + cd ${PHP_VERSION} && sed -i '/odbc-mariadb \\/d' Dockerfile && docker build . -t phpswoole/php:${PHP_VERSION} && cd - + cd ../ + fi +} + +prepare_data_files(){ + cd ${__DIR__} && \ + remove_data_files && \ + mkdir -p \ + data \ + data/run \ + data/mysql data/run/mysqld \ + data/redis data/run/redis && \ + chmod -R 777 data + if [ $? -ne 0 ]; then + echo "\n❌ Prepare data files failed!" + exit 1 + fi +} + +remove_data_files(){ + cd ${__DIR__} && \ + rm -rf scripts/data +} + +start_docker_containers(){ + remove_docker_containers + cd ${__DIR__} && \ + docker-compose up -d && \ + docker ps -a + if [ $? -ne 0 ]; then + echo "\n❌ Create containers failed!" + exit 1 + fi +} + +remove_docker_containers(){ + cd ${__DIR__} && \ + docker-compose kill > /dev/null 2>&1 && \ + docker-compose rm -f > /dev/null 2>&1 +} + +run_tests_in_docker(){ + docker exec swoole touch /.cienv && \ + docker exec swoole /swoole-src/scripts/docker-route.sh $SWOOLE_CI_TYPE + code=$? + if [ $code -ne 0 ]; then + echo "\n❌ Run tests failed! ExitCode: $code" + exit 1 + fi +} + +remove_tests_resources(){ + remove_docker_containers + remove_data_files +} + +check_docker_dependency +create_docker_images +echo "\n📖 Prepare for files...\n" +prepare_data_files + +echo "📦 Start docker containers...\n" +start_docker_containers # && trap "remove_tests_resources" + +echo "\n⏳ Run tests in docker...\n" +run_tests_in_docker +echo "\n🚀🚀🚀Completed successfully🚀🚀🚀\n" diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh new file mode 100755 index 0000000000..bfda846dac --- /dev/null +++ b/scripts/run-tests.sh @@ -0,0 +1,109 @@ +#!/bin/sh -e +__CURRENT__=`pwd` +__DIR__=$(cd "$(dirname "$0")";pwd) + +[ -z "${SWOOLE_BRANCH}" ] && export SWOOLE_BRANCH="master" + +#-------------PHPT------------- +cd ${__DIR__} && cd ../tests/ + +# initialization +echo "" && echo "⭐️ Initialization for tests..." && echo "" + +if [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then + echo "run in macOS, skip init database" +else + php ./init +fi + +cd ./include/lib +echo "composer update" +composer update +cd - +echo "" + +# debug +for debug_file in ${__DIR__}/debug/*.php +do + if test -f "${debug_file}";then + debug_file_basename="`basename ${debug_file}`" + echo "" && echo "====== RUN ${debug_file_basename} ======" && echo "" + php "${debug_file}" + echo "" && echo "========================================" && echo "" + fi +done + +# run tests @params($1=list_file, $2=options) +run_tests(){ + ./start.sh \ + "`tr '\n' ' ' < ${1} | xargs`" \ + -w ${1} \ + ${2} +} + +has_failures(){ + cat tests.list +} + +should_exit_with_error(){ + if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then + set +e + find ./ -type f -name "*.mem" + set -e + else + has_failures + fi +} + +touch tests.list +trap "rm -f tests.list; echo ''; echo '⌛ Done on '`date "+%Y-%m-%d %H:%M:%S"`;" EXIT + +cpu_num="$(/usr/bin/env php -r "echo swoole_cpu_num() * 2;")" + +#if [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then +# options="" +#else +# options="-j${cpu_num}" +#fi + +options="-j${cpu_num}" + +echo "" && echo "🌵️️ Current branch is ${SWOOLE_BRANCH}" && echo "" +if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then + dir="base" + options="${options} -m" +elif [ "$SWOOLE_THREAD" = 1 ]; then + dir="swoole_thread" +elif [ "$SWOOLE_IOURING" = 1 ]; then + dir="swoole_runtime/file_hook swoole_iouring swoole_http_client_coro/download_filename_bug.phpt" +elif [ "$SWOOLE_CI_IN_MACOS" = 1 ]; then + dir="swoole_atomic swoole_coroutine swoole_coroutine_wait_group swoole_global swoole_http_server swoole_process_pool swoole_server_port \ + swoole_websocket_server swoole_channel_coro swoole_coroutine_lock swoole_curl swoole_http2_client_coro swoole_http_server_coro \ + swoole_redis_server swoole_socket_coro swoole_client_async swoole_coroutine_scheduler swoole_event swoole_http2_server \ + swoole_runtime swoole_table swoole_client_coro swoole_coroutine_system swoole_feature swoole_http2_server_coro swoole_library swoole_server \ + swoole_client_sync swoole_coroutine_util swoole_function swoole_http_client_coro swoole_lock swoole_process swoole_server_coro swoole_timer" +else + dir="swoole_*" +fi + +echo "${dir}" +echo "${dir}" > tests.list +for i in 1 2 3 4 5 +do + if [ "`has_failures`" ]; then + if [ ${i} -gt "1" ]; then + sleep ${i} + echo "" && echo "😮 Retry failed tests#${i}:" && echo "" + fi + cat tests.list + timeout=`echo | expr ${i} \* 15 + 15` + options="${options} --set-timeout ${timeout}" + run_tests tests.list "${options}" + else + break + fi +done + +if [ "`should_exit_with_error`" ]; then + exit 255 +fi diff --git a/travis/simple-compile-on-github.sh b/scripts/simple-compile-on-github.sh similarity index 84% rename from travis/simple-compile-on-github.sh rename to scripts/simple-compile-on-github.sh index a67e0723ad..20372d04ac 100755 --- a/travis/simple-compile-on-github.sh +++ b/scripts/simple-compile-on-github.sh @@ -17,7 +17,7 @@ cd "${__DIR__}" && cd ../ && \ ./clear.sh > /dev/null && \ phpize --clean > /dev/null && \ phpize > /dev/null && \ -./configure --enable-openssl --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares > /dev/null && \ +./configure --enable-sockets --enable-mysqlnd --enable-swoole-curl --enable-cares > /dev/null && \ make -j8 > /dev/null | tee /tmp/compile.log && \ (test "`cat /tmp/compile.log`"x = ""x || exit 255) && \ make install && \ diff --git a/travis/simple-compile.sh b/scripts/simple-compile.sh similarity index 82% rename from travis/simple-compile.sh rename to scripts/simple-compile.sh index 5376d0f857..6a9549ec80 100755 --- a/travis/simple-compile.sh +++ b/scripts/simple-compile.sh @@ -2,11 +2,6 @@ __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) -if [ "${TRAVIS}"x = ""x ]; then - echo "\n❌ This script is just for Travis!" - exit 255 -fi - cd ${__DIR__} && cd ../ && \ ./clear.sh > /dev/null && \ phpize --clean > /dev/null && \ diff --git a/src/core/base.cc b/src/core/base.cc index ad699ed449..abcea76884 100644 --- a/src/core/base.cc +++ b/src/core/base.cc @@ -16,8 +16,8 @@ #include "swoole.h" -#include -#include +#include +#include #include #include @@ -27,94 +27,37 @@ #include #endif -#include #include #include -#include +#include +#include -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_signal.h" #include "swoole_memory.h" #include "swoole_protocol.h" #include "swoole_util.h" #include "swoole_async.h" -#include "swoole_c_api.h" -#include "swoole_coroutine_c_api.h" #include "swoole_coroutine_system.h" #include "swoole_ssl.h" -#if defined(__APPLE__) && defined(HAVE_CCRANDOMGENERATEBYTES) -#include -#if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) || \ - (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) -#define OPENSSL_APPLE_CRYPTO_RANDOM 1 -#include -#include -#endif -#endif +#include "swoole_api.h" +#include "swoole_coroutine_api.h" +using swoole::Logger; using swoole::NameResolver; using swoole::String; using swoole::coroutine::System; -#ifdef HAVE_GETRANDOM -#include -#else -static ssize_t getrandom(void *buffer, size_t size, unsigned int __flags) { -#if defined(HAVE_CCRANDOMGENERATEBYTES) - /* - * arc4random_buf on macOs uses ccrng_generate internally from which - * the potential error is silented to respect the portable arc4random_buf interface contract - */ - if (CCRandomGenerateBytes(buffer, size) == kCCSuccess) { - return size; - } - return -1; -#elif defined(HAVE_ARC4RANDOM) - arc4random_buf(buffer, size); - return size; -#else - int fd = open("/dev/urandom", O_RDONLY); - if (fd < 0) { - return -1; - } - - size_t read_bytes; - ssize_t n; - for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) { - n = read(fd, (char *) buffer + read_bytes, size - read_bytes); - if (n <= 0) { - break; - } - } - - close(fd); - - return read_bytes; -#endif -} -#endif - swoole::Global SwooleG = {}; -__thread swoole::ThreadGlobal SwooleTG = {}; - -static std::unordered_map functions; -static swoole::Logger *g_logger_instance = nullptr; - -#ifdef __MACH__ -static __thread char _sw_error_buf[SW_ERROR_MSG_SIZE]; -char *sw_error_() { - return _sw_error_buf; -} -#else -__thread char sw_error[SW_ERROR_MSG_SIZE]; -#endif +thread_local swoole::ThreadGlobal SwooleTG = {}; +thread_local char sw_error[SW_ERROR_MSG_SIZE]; +std::mutex sw_thread_lock; static void swoole_fatal_error_impl(int code, const char *format, ...); swoole::Logger *sw_logger() { - return g_logger_instance; + return SwooleG.logger; } void *sw_malloc(size_t size) { @@ -136,7 +79,7 @@ void *sw_realloc(void *ptr, size_t size) { static void bug_report_message_init() { SwooleG.bug_report_message += "\n" + std::string(SWOOLE_BUG_REPORT) + "\n"; - struct utsname u; + utsname u; if (uname(&u) != -1) { SwooleG.bug_report_message += swoole::std_string::format("OS: %s %s %s %s\n", u.sysname, u.release, u.version, u.machine); @@ -146,13 +89,10 @@ static void bug_report_message_init() { SwooleG.bug_report_message += swoole::std_string::format("GCC_VERSION: %s\n", __VERSION__); #endif -#ifdef SW_USE_OPENSSL SwooleG.bug_report_message += swoole_ssl_get_version_message(); - -#endif } -void swoole_init(void) { +void swoole_init() { if (SwooleG.init) { return; } @@ -162,10 +102,13 @@ void swoole_init(void) { SwooleG.running = 1; SwooleG.init = 1; + SwooleG.enable_coroutine = 1; SwooleG.std_allocator = {malloc, calloc, realloc, free}; + SwooleG.stdout_ = stdout; SwooleG.fatal_error = swoole_fatal_error_impl; SwooleG.cpu_num = SW_MAX(1, sysconf(_SC_NPROCESSORS_ONLN)); SwooleG.pagesize = getpagesize(); + SwooleG.max_file_content = SW_MAX_FILE_CONTENT; // DNS options SwooleG.dns_tries = 1; @@ -176,9 +119,11 @@ void swoole_init(void) { // random seed srandom(time(nullptr)); - SwooleG.pid = getpid(); + if (!SwooleG.logger) { + SwooleG.logger = new Logger(); + } - g_logger_instance = new swoole::Logger; + swoole_thread_init(true); #ifdef SW_DEBUG sw_logger()->set_level(0); @@ -190,7 +135,7 @@ void swoole_init(void) { // init global shared memory SwooleG.memory_pool = new swoole::GlobalMemory(SW_GLOBAL_MEMORY_PAGESIZE, true); SwooleG.max_sockets = SW_MAX_SOCKETS_DEFAULT; - struct rlimit rlmt; + rlimit rlmt; if (getrlimit(RLIMIT_NOFILE, &rlmt) < 0) { swoole_sys_warning("getrlimit() failed"); } else { @@ -198,11 +143,7 @@ void swoole_init(void) { SwooleG.max_sockets = SW_MIN((uint32_t) rlmt.rlim_cur, SW_SESSION_LIST_SIZE); } - SwooleTG.buffer_stack = new swoole::String(SW_STACK_BUFFER_SIZE); - - if (!swoole_set_task_tmpdir(SW_TASK_TMP_DIR)) { - exit(4); - } + SwooleG.task_tmpfile = SW_TASK_TMP_DIR "/" SW_TASK_TMP_FILE; // init signalfd #ifdef HAVE_SIGNALFD @@ -216,30 +157,9 @@ void swoole_init(void) { SW_EXTERN_C_BEGIN -SW_API int swoole_add_function(const char *name, void *func) { - std::string _name(name); - auto iter = functions.find(_name); - if (iter != functions.end()) { - swoole_warning("Function '%s' has already been added", name); - return SW_ERR; - } else { - functions.emplace(std::make_pair(_name, func)); - return SW_OK; - } -} - -SW_API void *swoole_get_function(const char *name, uint32_t length) { - auto iter = functions.find(std::string(name, length)); - if (iter != functions.end()) { - return iter->second; - } else { - return nullptr; - } -} - -SW_API int swoole_add_hook(enum swGlobalHookType type, swHookFunc func, int push_back) { +SW_API void swoole_add_hook(enum swGlobalHookType type, swHookFunc func, int push_back) { assert(type <= SW_GLOBAL_HOOK_END); - return swoole::hook_add(SwooleG.hooks, type, func, push_back); + swoole::hook_add(SwooleG.hooks, type, func, push_back); } SW_API void swoole_call_hook(enum swGlobalHookType type, void *arg) { @@ -266,25 +186,22 @@ SW_API int swoole_api_version_id(void) { SW_EXTERN_C_END -void swoole_clean(void) { - if (SwooleTG.timer) { - swoole_timer_free(); - } - if (SwooleTG.reactor) { - swoole_event_free(); - } - if (SwooleG.memory_pool != nullptr) { - delete SwooleG.memory_pool; - } - if (g_logger_instance) { - delete g_logger_instance; - g_logger_instance = nullptr; - } - if (SwooleTG.buffer_stack) { - delete SwooleTG.buffer_stack; - SwooleTG.buffer_stack = nullptr; +void swoole_clean() { + SW_LOOP_N(SW_MAX_HOOK_TYPE) { + if (SwooleG.hooks[i]) { + auto hooks = static_cast *>(SwooleG.hooks[i]); + delete hooks; + } } + swoole_signal_clear(); + swoole_thread_clean(true); + + if (SwooleG.logger) { + SwooleG.logger->close(); + } + delete SwooleG.logger; + delete SwooleG.memory_pool; SwooleG = {}; } @@ -294,46 +211,48 @@ SW_API void swoole_set_log_level(int level) { } } -SW_API void swoole_set_trace_flags(int flags) { - SwooleG.trace_flags = flags; +SW_API void swoole_set_stdout_stream(FILE *fp) { + SwooleG.stdout_ = fp; } -SW_API void swoole_set_dns_server(const std::string &server) { - char *_port; - int dns_server_port = SW_DNS_SERVER_PORT; - char dns_server_host[32]; - strcpy(dns_server_host, server.c_str()); - if ((_port = strchr((char *) server.c_str(), ':'))) { - dns_server_port = atoi(_port + 1); - if (dns_server_port <= 0 || dns_server_port > 65535) { - dns_server_port = SW_DNS_SERVER_PORT; - } - dns_server_host[_port - server.c_str()] = '\0'; - } - SwooleG.dns_server_host = dns_server_host; - SwooleG.dns_server_port = dns_server_port; +SW_API FILE *swoole_get_stdout_stream() { + return SwooleG.stdout_; } -SW_API std::pair swoole_get_dns_server() { - std::pair result; - if (SwooleG.dns_server_host.empty()) { - result.first = ""; - result.second = 0; +SW_API int swoole_get_log_level() { + if (sw_logger()) { + return sw_logger()->get_level(); } else { - result.first = SwooleG.dns_server_host; - result.second = SwooleG.dns_server_port; + return SW_LOG_NONE; } - return result; +} + +SW_API void swoole_set_log_file(const char *file) { + if (sw_logger()) { + sw_logger()->open(file); + } +} + +SW_API void swoole_set_trace_flags(long flags) { + SwooleG.trace_flags = flags; +} + +SW_API void swoole_set_print_backtrace_on_error(bool enable) { + SwooleG.print_backtrace_on_error = enable; } bool swoole_set_task_tmpdir(const std::string &dir) { +#ifdef SW_THREAD + std::unique_lock _lock(sw_thread_lock); +#endif + if (dir.at(0) != '/') { swoole_warning("wrong absolute path '%s'", dir.c_str()); return false; } if (access(dir.c_str(), R_OK) < 0 && !swoole_mkdir_recursive(dir)) { - swoole_warning("create task tmp dir(%s) failed", dir.c_str()); + swoole_warning("create task tmp dir('%s') failed", dir.c_str()); return false; } @@ -348,6 +267,24 @@ bool swoole_set_task_tmpdir(const std::string &dir) { return true; } +const std::string &swoole_get_task_tmpdir() { + return SwooleG.task_tmpfile; +} + +pid_t swoole_fork_exec(const std::function &fn) { + pid_t pid = fork(); + switch (pid) { + case -1: + return false; + case 0: + fn(); + exit(0); + default: + break; + } + return pid; +} + pid_t swoole_fork(int flags) { if (!(flags & SW_FORK_EXEC)) { if (swoole_coroutine_is_in()) { @@ -355,8 +292,7 @@ pid_t swoole_fork(int flags) { } if (SwooleTG.async_threads) { swoole_trace("aio_task_num=%lu, reactor=%p", SwooleTG.async_threads->task_num, sw_reactor()); - swoole_fatal_error(SW_ERROR_OPERATION_NOT_SUPPORT, - "can not create server after using async file operation"); + swoole_fatal_error(SW_ERROR_OPERATION_NOT_SUPPORT, "can not fork after using async-threads"); } } if (flags & SW_FORK_PRECHECK) { @@ -365,7 +301,6 @@ pid_t swoole_fork(int flags) { pid_t pid = fork(); if (pid == 0) { - SwooleG.pid = getpid(); if (flags & SW_FORK_DAEMON) { return pid; } @@ -375,11 +310,11 @@ pid_t swoole_fork(int flags) { if (swoole_timer_is_available()) { swoole_timer_free(); } - if (SwooleG.memory_pool) { - delete SwooleG.memory_pool; - } if (!(flags & SW_FORK_EXEC)) { - // reset SwooleG.memory_pool + /** + * Do not release the allocated memory pages. + * The global memory will be returned to the OS upon process termination. + */ SwooleG.memory_pool = new swoole::GlobalMemory(SW_GLOBAL_MEMORY_PAGESIZE, true); // reopen log file sw_logger()->reopen(); @@ -389,20 +324,46 @@ pid_t swoole_fork(int flags) { swoole_trace_log(SW_TRACE_REACTOR, "reactor has been destroyed"); } } else { - /** - * close log fd - */ sw_logger()->close(); } - /** - * reset signal handler - */ + // reset signal handler swoole_signal_clear(); + + if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_FORK)) { + swoole_call_hook(SW_GLOBAL_HOOK_AFTER_FORK, nullptr); + } } return pid; } +bool swoole_is_main_thread() { + return SwooleTG.main_thread; +} + +void swoole_thread_init(bool main_thread) { + if (!SwooleTG.buffer_stack) { + SwooleTG.buffer_stack = new String(SW_STACK_BUFFER_SIZE); + } + if (!main_thread) { + swoole_signal_block_all(); + } + SwooleTG.main_thread = main_thread; +} + +void swoole_thread_clean(bool main_thread) { + if (SwooleTG.timer) { + swoole_timer_free(); + } + if (SwooleTG.reactor) { + swoole_event_free(); + } + if (SwooleTG.buffer_stack) { + delete SwooleTG.buffer_stack; + SwooleTG.buffer_stack = nullptr; + } +} + void swoole_dump_ascii(const char *data, size_t size) { for (size_t i = 0; i < size; i++) { printf("%u ", (unsigned) data[i]); @@ -410,21 +371,19 @@ void swoole_dump_ascii(const char *data, size_t size) { printf("\n"); } -void swoole_dump_bin(const char *data, char type, size_t size) { - int i; +void swoole_dump_bin(const uchar *data, char type, size_t size) { int type_size = swoole_type_size(type); if (type_size <= 0) { return; } int n = size / type_size; - - for (i = 0; i < n; i++) { - printf("%d,", swoole_unpack(type, data + type_size * i)); + for (int i = 0; i < n; i++) { + printf("%ld,", (long) swoole_unpack(type, data + type_size * i)); } printf("\n"); } -void swoole_dump_hex(const char *data, size_t outlen) { +void swoole_dump_hex(const uchar *data, size_t outlen) { for (size_t i = 0; i < outlen; ++i) { if ((i & 0x0fu) == 0) { printf("%08zX: ", i); @@ -442,11 +401,14 @@ void swoole_dump_hex(const char *data, size_t outlen) { */ bool swoole_mkdir_recursive(const std::string &dir) { char tmp[PATH_MAX]; - size_t i, len = dir.length(); + size_t len = dir.length(); // PATH_MAX limit includes string trailing null character if (len + 1 > PATH_MAX) { - swoole_warning("mkdir(%s) failed. Path exceeds the limit of %d characters", dir.c_str(), PATH_MAX - 1); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_NAME_TOO_LONG, + "mkdir() failed. Path exceeds the limit of %d characters", + PATH_MAX - 1); return false; } swoole_strlcpy(tmp, dir.c_str(), PATH_MAX); @@ -456,13 +418,13 @@ bool swoole_mkdir_recursive(const std::string &dir) { } len = strlen(tmp); - for (i = 1; i < len; i++) { + for (size_t i = 1; i < len; i++) { if (tmp[i] == '/') { tmp[i] = 0; if (access(tmp, R_OK) != 0) { if (mkdir(tmp, 0755) == -1) { - swoole_sys_warning("mkdir(%s) failed", tmp); - return -1; + swoole_sys_warning("mkdir('%s') failed", tmp); + return false; } } tmp[i] = '/'; @@ -487,6 +449,11 @@ int swoole_type_size(char type) { case 'N': case 'V': return 4; + case 'q': + case 'Q': + case 'J': + case 'P': + return 8; default: return 0; } @@ -497,9 +464,9 @@ char *swoole_dec2hex(ulong_t value, int base) { static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char buf[(sizeof(ulong_t) << 3) + 1]; - char *ptr, *end; + char *ptr; - end = ptr = buf + sizeof(buf) - 1; + char *end = ptr = buf + sizeof(buf) - 1; *ptr = '\0'; do { @@ -519,7 +486,7 @@ ulong_t swoole_hex2dec(const char *hex, size_t *parsed_bytes) { p += 2; } - while (1) { + while (true) { char c = *p; if ((c >= '0') && (c <= '9')) { value = value * 16 + (c - '0'); @@ -541,24 +508,8 @@ ulong_t swoole_hex2dec(const char *hex, size_t *parsed_bytes) { #define RAND_MAX 2147483647 #endif -int swoole_rand(int min, int max) { - static int _seed = 0; - assert(max > min); - - if (_seed == 0) { - _seed = time(nullptr); - srand(_seed); - } - - int _rand = rand(); - _rand = min + (int) ((double) ((double) (max) - (min) + 1.0) * ((_rand) / ((RAND_MAX) + 1.0))); - return _rand; -} - int swoole_system_random(int min, int max) { static int dev_random_fd = -1; - char *next_random_byte; - int bytes_to_read; unsigned random_value; assert(max > min); @@ -570,8 +521,8 @@ int swoole_system_random(int min, int max) { } } - next_random_byte = (char *) &random_value; - bytes_to_read = sizeof(random_value); + auto next_random_byte = (char *) &random_value; + constexpr int bytes_to_read = sizeof(random_value); if (read(dev_random_fd, next_random_byte, bytes_to_read) < bytes_to_read) { swoole_sys_warning("read() from /dev/urandom failed"); @@ -589,6 +540,16 @@ void swoole_redirect_stdout(int new_fd) { } } +void swoole_redirect_stdout(const char *file) { + auto fd = open(file, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd >= 0) { + swoole_redirect_stdout(fd); + close(fd); + } else { + swoole_sys_warning("open('%s') failed", file); + } +} + int swoole_version_compare(const char *version1, const char *version2) { int result = 0; @@ -627,10 +588,9 @@ int swoole_version_compare(const char *version1, const char *version2) { uint32_t swoole_common_divisor(uint32_t u, uint32_t v) { assert(u > 0); assert(v > 0); - uint32_t t; while (u > 0) { if (u < v) { - t = u; + uint32_t t = u; u = v; v = t; } @@ -688,13 +648,38 @@ size_t sw_vsnprintf(char *buf, size_t size, const char *format, va_list args) { return retval; } +int sw_printf(const char *format, ...) { + va_list args; + va_start(args, format); + int retval = vfprintf(SwooleG.stdout_, format, args); + va_end(args); + return retval; +} + +bool sw_wait_for(const std::function &fn, int timeout_ms) { + int sleep_msec = 1; + while (timeout_ms >= 0) { + if (fn()) { + return true; + } + usleep(sleep_msec * 1000); + sleep_msec *= 2; + // Align the time so that the timeout is consistent with the user settings + if (timeout_ms > 0 && timeout_ms - sleep_msec < 0) { + sleep_msec = timeout_ms; + timeout_ms = 0; + } else { + timeout_ms -= sleep_msec; + } + } + return false; +} + int swoole_itoa(char *buf, long value) { long i = 0, j; - long sign_mask; - unsigned long nn; - sign_mask = value >> (sizeof(long) * 8 - 1); - nn = (value + sign_mask) ^ sign_mask; + long sign_mask = value >> (sizeof(long) * 8 - 1); + unsigned long nn = (value + sign_mask) ^ sign_mask; do { buf[i++] = nn % 10 + '0'; } while (nn /= 10); @@ -704,10 +689,9 @@ int swoole_itoa(char *buf, long value) { buf[i] = '\0'; int s_len = i; - char swap; for (i = 0, j = s_len - 1; i < j; ++i, --j) { - swap = buf[i]; + char swap = buf[i]; buf[i] = buf[j]; buf[j] = swap; } @@ -764,10 +748,9 @@ char *swoole_string_format(size_t n, const char *format, ...) { return nullptr; } - int ret; va_list va_list; va_start(va_list, format); - ret = vsnprintf(buf, n, format, va_list); + int ret = vsnprintf(buf, n, format, va_list); va_end(va_list); if (ret >= 0) { return buf; @@ -776,37 +759,45 @@ char *swoole_string_format(size_t n, const char *format, ...) { return nullptr; } -void swoole_random_string(char *buf, size_t size) { - static char characters[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', - 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', - 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - }; +static constexpr char characters[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', +}; + +void swoole_random_string(char *buf, size_t len) { size_t i = 0; - for (; i < size; i++) { + for (; i < len; i++) { buf[i] = characters[swoole_rand(0, sizeof(characters) - 1)]; } buf[i] = '\0'; } -size_t swoole_random_bytes(char *buf, size_t size) { - size_t read_bytes = 0; - ssize_t n; - - while (read_bytes < size) { - size_t amount_to_read = size - read_bytes; - n = getrandom(buf + read_bytes, amount_to_read, 0); - if (n == -1) { - if (errno == EINTR || errno == EAGAIN) { - continue; - } else { - break; - } - } - read_bytes += (size_t) n; +void swoole_random_string(std::string &str, size_t len) { + size_t i = 0; + for (; i < len; i++) { + str.append(1, characters[swoole_rand(0, sizeof(characters) - 1)]); } +} + +uint64_t swoole_random_int() { + static thread_local std::random_device rd; + static thread_local std::mt19937_64 gen(rd()); + static thread_local std::uniform_int_distribution dis; + std::uniform_int_distribution::param_type params(0, UINT64_MAX); + return dis(gen, params); +} - return read_bytes; +int swoole_rand(int min, int max) { + static thread_local std::random_device rd; + static thread_local std::mt19937 gen(rd()); + static thread_local std::uniform_int_distribution dis; + std::uniform_int_distribution::param_type params(min, max); + return dis(gen, params); +} + +int swoole_rand() { + return swoole_rand(0, INT_MAX); } bool swoole_get_env(const char *name, int *value) { @@ -833,12 +824,12 @@ int swoole_get_systemd_listen_fds() { #ifdef HAVE_BOOST_STACKTRACE #include #include -void swoole_print_backtrace(void) { +void swoole_print_backtrace() { std::cout << boost::stacktrace::stacktrace(); } -#elif defined(HAVE_EXECINFO) +#elif defined(HAVE_EXECINFO) && !defined(__ANDROID__) #include -void swoole_print_backtrace(void) { +void swoole_print_backtrace() { int size = 16; void *array[16]; int stack_num = backtrace(array, size); @@ -851,9 +842,18 @@ void swoole_print_backtrace(void) { free(stacktrace); } #else -void swoole_print_backtrace(void) {} +void swoole_print_backtrace() {} #endif +void swoole_print_backtrace_on_error() { + if (SwooleG.print_backtrace_on_error) { + swoole_print_backtrace(); + if (SwooleG.print_backtrace) { + SwooleG.print_backtrace(); + } + } +} + static void swoole_fatal_error_impl(int code, const char *format, ...) { size_t retval = 0; va_list args; @@ -863,7 +863,18 @@ static void swoole_fatal_error_impl(int code, const char *format, ...) { retval += sw_vsnprintf(sw_error + retval, SW_ERROR_MSG_SIZE - retval, format, args); va_end(args); sw_logger()->put(SW_LOG_ERROR, sw_error, retval); - exit(1); + swoole_exit(1); +} + +void swoole_exit(int _status) { +#ifdef SW_THREAD + /** + * If multiple threads call exit simultaneously, it can result in a crash. + * Implementing locking mechanisms can prevent concurrent calls to exit. + */ + std::unique_lock _lock(sw_thread_lock); +#endif + exit(_status); } namespace swoole { @@ -903,61 +914,51 @@ void DataHead::print() { std::string dirname(const std::string &file) { size_t index = file.find_last_of('/'); if (index == std::string::npos) { - return std::string(); + return {}; } else if (index == 0) { return "/"; } return file.substr(0, index); } -int hook_add(void **hooks, int type, const Callback &func, int push_back) { +void hook_add(void **hooks, int type, const Callback &func, int push_back) { if (hooks[type] == nullptr) { hooks[type] = new std::list; } - std::list *l = reinterpret_cast *>(hooks[type]); + auto *l = static_cast *>(hooks[type]); if (push_back) { l->push_back(func); } else { l->push_front(func); } - - return SW_OK; } void hook_call(void **hooks, int type, void *arg) { if (hooks[type] == nullptr) { return; } - std::list *l = reinterpret_cast *>(hooks[type]); - for (auto i = l->begin(); i != l->end(); i++) { - (*i)(arg); + const auto *l = static_cast *>(hooks[type]); + for (auto &i : *l) { + i(arg); } } /** * return the first file of the intersection, in order of vec1 */ -std::string intersection(std::vector &vec1, std::set &vec2) { - std::string result = ""; - - std::find_if(vec1.begin(), vec1.end(), [&](std::string &str) -> bool { - auto iter = std::find(vec2.begin(), vec2.end(), str); - if (iter != vec2.end()) { - result = *iter; - return true; +std::string intersection(const std::vector &vec1, std::set &vec2) { + for (const auto &vec1_item : vec1) { + if (vec2.find(vec1_item) != vec2.end()) { + return vec1_item; } - return false; - }); + } - return result; + return ""; } -double microtime(void) { - struct timeval t; - gettimeofday(&t, nullptr); - return (double) t.tv_sec + ((double) t.tv_usec / 1000000); +double microtime() { + using namespace std::chrono; + return duration_cast>(system_clock::now().time_since_epoch()).count(); } - -//------------------------------------------------------------------------------- }; // namespace swoole diff --git a/src/protocol/base64.cc b/src/core/base64.cc similarity index 98% rename from src/protocol/base64.cc rename to src/core/base64.cc index dd9b452b35..81e74175a0 100644 --- a/src/protocol/base64.cc +++ b/src/core/base64.cc @@ -94,9 +94,9 @@ size_t base64_encode(const unsigned char *in, size_t inlen, char *out) { } size_t base64_decode(const char *in, size_t inlen, char *out) { - size_t i, j; + size_t j; - for (i = j = 0; i < inlen; i++) { + for (size_t i = j = 0; i < inlen; i++) { int c; int s = i % 4; /* from 8/gcd(6, 8) */ @@ -131,4 +131,4 @@ size_t base64_decode(const char *in, size_t inlen, char *out) { return j; } -} +} // namespace swoole diff --git a/src/core/buffer.cc b/src/core/buffer.cc new file mode 100644 index 0000000000..e466a08d36 --- /dev/null +++ b/src/core/buffer.cc @@ -0,0 +1,146 @@ +/* + +----------------------------------------------------------------------+ + | 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 "swoole.h" +#include "swoole_buffer.h" + +namespace swoole { + +BufferChunk::BufferChunk(Type type, uint32_t size) : type(type), size(size) { + if (type == TYPE_DATA && size > 0) { + value.str = new char[size]; + } +} + +BufferChunk::~BufferChunk() { + if (type == TYPE_DATA) { + delete[] value.str; + } + if (destroy) { + destroy(this); + } +} + +Buffer::Buffer(uint32_t _chunk_size) { + chunk_size = _chunk_size == 0 ? INT_MAX : _chunk_size; +} + +BufferChunk *Buffer::alloc(BufferChunk::Type type, uint32_t size) { + auto *chunk = new BufferChunk(type, size); + queue_.push(chunk); + return chunk; +} + +void Buffer::pop() { + BufferChunk *chunk = queue_.front(); + total_length -= chunk->size; + delete chunk; + queue_.pop(); +} + +Buffer::~Buffer() { + while (!queue_.empty()) { + pop(); + } +} + +void Buffer::append(const char *data, uint32_t size) { + uint32_t _length = size; + auto _pos = data; + + assert(size > 0); + + // buffer enQueue + while (_length > 0) { + uint32_t _n = _length >= chunk_size ? chunk_size : _length; + + BufferChunk *chunk = alloc(BufferChunk::TYPE_DATA, _n); + + total_length += _n; + + memcpy(chunk->value.str, _pos, _n); + chunk->length = _n; + + swoole_trace_log( + SW_TRACE_BUFFER, "chunk_n=%lu|size=%u|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); + + _pos += _n; + _length -= _n; + } +} + +void Buffer::append(const iovec *iov, size_t iovcnt, off_t offset) { + size_t _length = 0; + + SW_LOOP_N(iovcnt) { + assert(iov[i].iov_len > 0); + assert(iov[i].iov_base != nullptr); + _length += iov[i].iov_len; + } + + auto pos = static_cast(iov[0].iov_base); + BufferChunk *chunk = nullptr; + size_t iov_remain_len = iov[0].iov_len, chunk_remain_len; + size_t i = 0; + + while (true) { + if (chunk) { + if (chunk->size == chunk->length) { + chunk = nullptr; + continue; + } else { + chunk_remain_len = chunk->size - chunk->length; + } + } else { + if (offset > 0) { + if (offset >= (off_t) iov[i].iov_len) { + offset -= iov[i].iov_len; + i++; + continue; + } else { + pos = static_cast(iov[i].iov_base) + offset; + iov_remain_len = iov[i].iov_len - offset; + offset = 0; + } + } + chunk_remain_len = _length >= chunk_size ? chunk_size : _length; + chunk = alloc(BufferChunk::TYPE_DATA, chunk_remain_len); + } + + size_t _n = std::min(iov_remain_len, chunk_remain_len); + memcpy(chunk->value.str + chunk->length, pos, _n); + total_length += _n; + _length -= _n; + + swoole_trace_log( + SW_TRACE_BUFFER, "chunk_n=%lu|size=%lu|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); + + chunk->length += _n; + iov_remain_len -= _n; + + if (iov_remain_len == 0) { + i++; + if (i == iovcnt) { + break; + } + iov_remain_len = iov[i].iov_len; + pos = (char *) iov[i].iov_base; + } else { + pos += _n; + } + } +} +} // namespace swoole diff --git a/src/core/channel.cc b/src/core/channel.cc index 2ce833790c..ac47eb07ce 100644 --- a/src/core/channel.cc +++ b/src/core/channel.cc @@ -17,7 +17,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_memory.h" #include "swoole_channel.h" #include "swoole_lock.h" @@ -51,8 +50,8 @@ Channel *Channel::make(size_t size, size_t maxlen, int flags) { return nullptr; } - Channel *object = (Channel *) mem; - mem = (char *) mem + sizeof(Channel); + auto *object = static_cast(mem); + mem = static_cast(mem) + sizeof(Channel); *object = {}; @@ -62,12 +61,10 @@ Channel *Channel::make(size_t size, size_t maxlen, int flags) { object->maxlen = maxlen; object->flags = flags; - // use lock if (flags & SW_CHAN_LOCK) { - // init lock - object->lock = new Mutex(Mutex::PROCESS_SHARED); + object->lock = new Mutex(true); } - // use notify + if (flags & SW_CHAN_NOTIFY) { object->notify_pipe = new Pipe(true); if (!object->notify_pipe->ready()) { @@ -96,10 +93,10 @@ int Channel::in(const void *in_data, int data_length) { if ((head - tail) < msize) { return SW_ERR; } - item = (ChannelSlice *) ((char *) mem + tail); + item = reinterpret_cast(static_cast(mem) + tail); tail += msize; } else { - item = (ChannelSlice *) ((char *) mem + tail); + item = reinterpret_cast(static_cast(mem) + tail); tail += msize; if (tail >= (off_t) size) { tail = 0; @@ -121,7 +118,7 @@ int Channel::out(void *out_buf, int buffer_length) { return SW_ERR; } - ChannelSlice *item = (ChannelSlice *) ((char *) mem + head); + auto *item = reinterpret_cast(static_cast(mem) + head); assert(buffer_length >= item->length); memcpy(out_buf, item->data, item->length); head += (item->length + sizeof(item->length)); @@ -137,17 +134,16 @@ int Channel::out(void *out_buf, int buffer_length) { /** * peek data */ -int Channel::peek(void *out, int buffer_length) { +int Channel::peek(void *out, int buffer_length) const { if (empty()) { return SW_ERR; } - int length; lock->lock(); - ChannelSlice *item = (ChannelSlice *) ((char *) mem + head); + auto *item = reinterpret_cast(static_cast(mem) + head); assert(buffer_length >= item->length); memcpy(out, item->data, item->length); - length = item->length; + int length = item->length; lock->unlock(); return length; @@ -156,19 +152,19 @@ int Channel::peek(void *out, int buffer_length) { /** * wait notify */ -int Channel::wait() { +int Channel::wait() const { assert(flags & SW_CHAN_NOTIFY); uint64_t value; - return notify_pipe->read(&value, sizeof(value)); + return notify_pipe->read(&value, sizeof(value)) > 0 ? SW_OK : SW_ERR; } /** * new data coming, notify to customer */ -int Channel::notify() { +int Channel::notify() const { assert(flags & SW_CHAN_NOTIFY); uint64_t value = 1; - return notify_pipe->write(&value, sizeof(value)); + return notify_pipe->write(&value, sizeof(value)) == sizeof(value) ? SW_OK : SW_ERR; } /** @@ -211,7 +207,7 @@ int Channel::pop(void *out_buf, int buffer_length) { return n; } -void Channel::print() { +void Channel::print() const { printf("Channel\n{\n" " off_t head = %ld;\n" " off_t tail = %ld;\n" diff --git a/src/core/crc32.cc b/src/core/crc32.cc index aabf6f784c..f00d66660c 100644 --- a/src/core/crc32.cc +++ b/src/core/crc32.cc @@ -20,7 +20,7 @@ #include "swoole.h" #include "swoole_hash.h" -static const int CRC32_TABLE_SIZE = 256; +static constexpr int CRC32_TABLE_SIZE = 256; static uint32_t crc32_table[CRC32_TABLE_SIZE]; static bool generated = false; @@ -39,7 +39,7 @@ static void generate_table(uint32_t (&table)[CRC32_TABLE_SIZE]) { } } -uint32_t swoole_crc32(const char *data, uint32_t size) { +uint32_t swoole_crc32(const char *data, size_t size) { if (sw_unlikely(!generated)) { generate_table(crc32_table); } diff --git a/src/core/error.cc b/src/core/error.cc index 45b39981a5..18f4b5b62b 100644 --- a/src/core/error.cc +++ b/src/core/error.cc @@ -21,7 +21,7 @@ static std::unordered_set ignored_errors; namespace swoole { -Exception::Exception(int code) throw() : code(code) { +Exception::Exception(const int code) noexcept : code(code) { msg = swoole_strerror(code); } } // namespace swoole @@ -31,7 +31,7 @@ const char *swoole_strerror(int code) { return strerror(code); } /* swstrerror {{{*/ - switch(code) { + switch (code) { case SW_ERROR_MALLOC_FAIL: return "Malloc fail"; case SW_ERROR_SYSTEM_CALL_FAIL: @@ -50,12 +50,24 @@ const char *swoole_strerror(int code) { return "Protocol error"; case SW_ERROR_WRONG_OPERATION: return "Wrong operation"; + case SW_ERROR_PHP_RUNTIME_NOTICE: + return "PHP runtime notice"; + case SW_ERROR_FOR_TEST: + return "For test"; + case SW_ERROR_NO_PAYLOAD: + return "No payload"; + case SW_ERROR_UNDEFINED_BEHAVIOR: + return "Undefined behavior"; + case SW_ERROR_NOT_THREAD_SAFETY: + return "Not thread safety"; case SW_ERROR_FILE_NOT_EXIST: return "File not exist"; case SW_ERROR_FILE_TOO_LARGE: return "File too large"; case SW_ERROR_FILE_EMPTY: return "File empty"; + case SW_ERROR_DIR_NOT_EXIST: + return "Dir not exist"; case SW_ERROR_DNSLOOKUP_DUPLICATE_REQUEST: return "DNS Lookup duplicate request"; case SW_ERROR_DNSLOOKUP_RESOLVE_FAILED: @@ -70,8 +82,20 @@ const char *swoole_strerror(int code) { return "Bad ipv6 address"; case SW_ERROR_UNREGISTERED_SIGNAL: return "Unregistered signal"; - case SW_ERROR_EVENT_SOCKET_REMOVED: - return "Event socket removed"; + case SW_ERROR_BAD_HOST_ADDR: + return "Bad host addr"; + case SW_ERROR_BAD_PORT: + return "Bad port"; + case SW_ERROR_BAD_SOCKET_TYPE: + return "Bad socket type"; + case SW_ERROR_EVENT_REMOVE_FAILED: + return "Event remove failed"; + case SW_ERROR_EVENT_ADD_FAILED: + return "Event add failed"; + case SW_ERROR_EVENT_UPDATE_FAILED: + return "Event update failed"; + case SW_ERROR_EVENT_UNKNOWN_DATA: + return "Event unknown data"; case SW_ERROR_SESSION_CLOSED_BY_SERVER: return "Session closed by server"; case SW_ERROR_SESSION_CLOSED_BY_CLIENT: @@ -108,6 +132,10 @@ const char *swoole_strerror(int code) { return "SSL reset"; case SW_ERROR_SSL_HANDSHAKE_FAILED: return "SSL handshake failed"; + case SW_ERROR_SSL_CREATE_CONTEXT_FAILED: + return "SSL create context failed"; + case SW_ERROR_SSL_CREATE_SESSION_FAILED: + return "SSL create session failed"; case SW_ERROR_PACKAGE_LENGTH_TOO_LARGE: return "Package length too large"; case SW_ERROR_PACKAGE_LENGTH_NOT_FOUND: @@ -132,6 +160,8 @@ const char *swoole_strerror(int code) { return "Http2 stream ignore"; case SW_ERROR_HTTP2_SEND_CONTROL_FRAME_FAILED: return "Http2 send control frame failed"; + case SW_ERROR_HTTP2_INTERNAL_ERROR: + return "Http2 internal error"; case SW_ERROR_AIO_BAD_REQUEST: return "Aio bad request"; case SW_ERROR_AIO_CANCELED: @@ -144,6 +174,8 @@ const char *swoole_strerror(int code) { return "Socket closed"; case SW_ERROR_SOCKET_POLL_TIMEOUT: return "Socket poll timeout"; + case SW_ERROR_SOCKET_NOT_EXISTS: + return "Socket not exists"; case SW_ERROR_SOCKS5_UNSUPPORT_VERSION: return "Socks5 unsupport version"; case SW_ERROR_SOCKS5_UNSUPPORT_METHOD: @@ -154,6 +186,8 @@ const char *swoole_strerror(int code) { return "Socks5 server error"; case SW_ERROR_SOCKS5_HANDSHAKE_FAILED: return "Socks5 handshake failed"; + case SW_ERROR_SOCKS5_CONNECT_FAILED: + return "Socks5 connect failed"; case SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR: return "Http proxy handshake error"; case SW_ERROR_HTTP_INVALID_PROTOCOL: @@ -162,6 +196,12 @@ const char *swoole_strerror(int code) { return "Http proxy handshake failed"; case SW_ERROR_HTTP_PROXY_BAD_RESPONSE: return "Http proxy bad response"; + case SW_ERROR_HTTP_CONFLICT_HEADER: + return "Http conflict header"; + case SW_ERROR_HTTP_CONTEXT_UNAVAILABLE: + return "Http context unavailable"; + case SW_ERROR_HTTP_COOKIE_UNAVAILABLE: + return "Http cookie unavailable"; case SW_ERROR_WEBSOCKET_BAD_CLIENT: return "Websocket bad client"; case SW_ERROR_WEBSOCKET_BAD_OPCODE: @@ -204,6 +244,10 @@ const char *swoole_strerror(int code) { return "Server is not regular file"; case SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT: return "Server send to woker timeout"; + case SW_ERROR_SERVER_INVALID_CALLBACK: + return "Server invalid callback"; + case SW_ERROR_SERVER_UNRELATED_THREAD: + return "Server unrelated thread"; case SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT: return "Server worker exit timeout"; case SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA: @@ -246,6 +290,8 @@ const char *swoole_strerror(int code) { return "Coroutine canceled"; case SW_ERROR_CO_TIMEDOUT: return "Coroutine timedout"; + case SW_ERROR_CO_SOCKET_CLOSE_WAIT: + return "Coroutine socket close wait"; default: static char buffer[32]; #ifndef __MACH__ @@ -266,6 +312,14 @@ void swoole_ignore_error(int code) { ignored_errors.insert(code); } -bool swoole_is_ignored_error(int code) { +bool swoole_is_ignored_error(const int code) { return ignored_errors.find(code) != ignored_errors.end(); } + +void swoole_clear_last_error_msg() { + sw_error[0] = '\0'; +} + +const char *swoole_get_last_error_msg() { + return sw_error; +} diff --git a/src/core/heap.cc b/src/core/heap.cc index 6ffbeb4877..905e72bc92 100644 --- a/src/core/heap.cc +++ b/src/core/heap.cc @@ -23,8 +23,8 @@ namespace swoole { -Heap::Heap(size_t _n, Heap::Type _type) { - if (!(nodes = (HeapNode **) sw_malloc((_n + 1) * sizeof(void *)))) { +Heap::Heap(size_t _n, Type _type) { + if (!((nodes = static_cast(sw_malloc((_n + 1) * sizeof(void *)))))) { throw std::bad_alloc(); } num = 1; @@ -33,18 +33,23 @@ Heap::Heap(size_t _n, Heap::Type _type) { } Heap::~Heap() { + for (uint32_t i = 1; i < num; i++) { + if (nodes[i]) { + delete nodes[i]; + } + } sw_free(nodes); } -int Heap::compare(uint64_t a, uint64_t b) { - if (type == Heap::MIN_HEAP) { +int Heap::compare(uint64_t a, uint64_t b) const { + if (type == MIN_HEAP) { return a > b; } else { return a < b; } } -uint32_t Heap::maxchild(uint32_t i) { +uint32_t Heap::maxchild(uint32_t i) const { uint32_t child_i = left(i); if (child_i >= num) { return 0; @@ -56,7 +61,7 @@ uint32_t Heap::maxchild(uint32_t i) { return child_i; } -void Heap::bubble_up(uint32_t i) { +void Heap::bubble_up(uint32_t i) const { HeapNode *moving_node = nodes[i]; uint32_t parent_i; @@ -70,11 +75,11 @@ void Heap::bubble_up(uint32_t i) { moving_node->position = i; } -void Heap::percolate_down(uint32_t i) { +void Heap::percolate_down(uint32_t i) const { uint32_t child_i; HeapNode *moving_node = nodes[i]; - while ((child_i = maxchild(i)) && compare(moving_node->priority, nodes[child_i]->priority)) { + while (((child_i = maxchild(i))) && compare(moving_node->priority, nodes[child_i]->priority)) { nodes[i] = nodes[child_i]; nodes[i]->position = i; i = child_i; @@ -87,18 +92,17 @@ void Heap::percolate_down(uint32_t i) { HeapNode *Heap::push(uint64_t priority, void *data) { HeapNode **tmp; uint32_t i; - uint32_t newsize; if (num >= size) { - newsize = size * 2; - if (!(tmp = (HeapNode **) sw_realloc(nodes, sizeof(HeapNode *) * newsize))) { + uint32_t newsize = size * 2; + if (!((tmp = static_cast(sw_realloc(nodes, sizeof(HeapNode *) * newsize))))) { return nullptr; } nodes = tmp; size = newsize; } - HeapNode *node = new HeapNode; + auto *node = new HeapNode; node->priority = priority; node->data = data; i = num++; @@ -107,7 +111,7 @@ HeapNode *Heap::push(uint64_t priority, void *data) { return node; } -void Heap::change_priority(uint64_t new_priority, HeapNode *node) { +void Heap::change_priority(uint64_t new_priority, HeapNode *node) const { uint32_t pos = node->position; uint64_t old_pri = node->priority; @@ -132,12 +136,11 @@ void Heap::remove(HeapNode *node) { } void *Heap::pop() { - HeapNode *head; if (count() == 0) { return nullptr; } - head = nodes[1]; + HeapNode *head = nodes[1]; nodes[1] = nodes[--num]; percolate_down(1); @@ -146,7 +149,7 @@ void *Heap::pop() { return data; } -void *Heap::peek() { +void *Heap::peek() const { if (num == 1) { return nullptr; } @@ -157,7 +160,7 @@ void *Heap::peek() { return node->data; } -void Heap::print() { +void Heap::print() const { for (uint32_t i = 1; i < num; i++) { printf("#%u\tpriority=%ld, data=%p\n", i, (long) nodes[i]->priority, nodes[i]->data); } diff --git a/src/core/log.cc b/src/core/log.cc index 2d0c94406a..122677067c 100644 --- a/src/core/log.cc +++ b/src/core/log.cc @@ -14,11 +14,10 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" +#include "swoole_process_pool.h" -#include +#include #include -#include #include #include // NOLINT [build/c++11] @@ -26,13 +25,13 @@ namespace swoole { std::string Logger::get_pretty_name(const std::string &pretty_function, bool strip) { - size_t brackets = pretty_function.find_first_of("("); - if (brackets == pretty_function.npos) { + const size_t brackets = pretty_function.find_first_of('('); + if (brackets == std::string::npos) { return ""; } - size_t begin = pretty_function.substr(0, brackets).rfind(" ") + 1; - size_t end = brackets - begin; + const size_t begin = pretty_function.substr(0, brackets).rfind(' ') + 1; + const size_t end = brackets - begin; if (!strip) { return pretty_function.substr(begin, end); } @@ -41,7 +40,7 @@ std::string Logger::get_pretty_name(const std::string &pretty_function, bool str size_t count = 0, index = method_name.length(); while (true) { index = method_name.rfind("::", index); - if (index == method_name.npos) { + if (index == std::basic_string::npos) { if (count == 1) { return method_name.substr(method_name.rfind("::") + 2); } @@ -70,10 +69,14 @@ bool Logger::open(const char *_log_file) { log_real_file = log_file; } - log_fd = ::open(log_real_file.c_str(), O_APPEND | O_RDWR | O_CREAT, 0666); + auto log_fd = ::open(log_real_file.c_str(), O_APPEND | O_RDWR | O_CREAT, 0666); if (log_fd < 0) { - printf("open(%s) failed. Error: %s[%d]\n", log_real_file.c_str(), strerror(errno), errno); - log_fd = STDOUT_FILENO; + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SYSTEM_CALL_FAIL, + "open('%s') failed. Error: %s[%d]", + log_real_file.c_str(), + strerror(errno), + errno); opened = false; log_file = ""; log_real_file = ""; @@ -81,21 +84,29 @@ bool Logger::open(const char *_log_file) { return false; } else { opened = true; + log_fp = fdopen(log_fd, "a"); return true; } } -void Logger::close(void) { +void Logger::set_stream(FILE *stream) { if (opened) { - ::close(log_fd); - log_fd = STDOUT_FILENO; + close(); + } + log_fp = stream; +} + +void Logger::close() { + if (opened) { + fclose(log_fp); + log_fp = stdout; log_file = ""; opened = false; } } -int Logger::get_level() { +int Logger::get_level() const { return log_level; } @@ -113,7 +124,7 @@ void Logger::set_rotation(int _rotation) { log_rotation = _rotation; } -bool Logger::redirect_stdout_and_stderr(int enable) { +bool Logger::redirect_stdout_and_stderr(bool enable) { if (enable) { if (!opened) { swoole_warning("no log file opened"); @@ -131,7 +142,7 @@ bool Logger::redirect_stdout_and_stderr(int enable) { swoole_sys_warning("dup(STDERR_FILENO) failed"); return false; } - swoole_redirect_stdout(log_fd); + swoole_redirect_stdout(fileno(log_fp)); redirected = true; } else { if (!redirected) { @@ -185,10 +196,7 @@ void Logger::set_date_with_microseconds(bool enable) { date_with_microseconds = enable; } -/** - * reopen log file - */ -void Logger::reopen() { +void Logger::reopen_without_lock() { if (!opened) { return; } @@ -196,23 +204,25 @@ void Logger::reopen() { std::string new_log_file(log_file); close(); open(new_log_file.c_str()); - /** - * redirect STDOUT & STDERR to log file - */ if (redirected) { - swoole_redirect_stdout(log_fd); + swoole_redirect_stdout(fileno(log_fp)); } } +void Logger::reopen() { + std::unique_lock _lock(lock); + reopen_without_lock(); +} + const char *Logger::get_real_file() { return log_real_file.c_str(); } -const char *Logger::get_file() { +const char *Logger::get_file() const { return log_file.c_str(); } -std::string Logger::gen_real_file(const std::string &file) { +std::string Logger::gen_real_file(const std::string &file) const { char date_str[16]; auto now_sec = ::time(nullptr); const char *fmt; @@ -239,7 +249,7 @@ std::string Logger::gen_real_file(const std::string &file) { return real_file; } -bool Logger::is_opened() { +bool Logger::is_opened() const { return opened; } @@ -247,7 +257,6 @@ void Logger::put(int level, const char *content, size_t length) { const char *level_str; char date_str[SW_LOG_DATE_STRLEN]; char log_str[SW_LOG_BUFFER_SIZE]; - int n; if (level < log_level) { return; @@ -281,8 +290,12 @@ void Logger::put(int level, const char *content, size_t length) { if (log_rotation) { std::string tmp = gen_real_file(log_file); - if (tmp != log_real_file) { - reopen(); + /** + * If the current thread fails to acquire the lock, it will forgo executing the log rotation. + */ + if (tmp != log_real_file && lock.try_lock()) { + reopen_without_lock(); + lock.unlock(); } } @@ -292,58 +305,36 @@ void Logger::put(int level, const char *content, size_t length) { date_str + l_data_str, SW_LOG_DATE_STRLEN - l_data_str, "<.%lld>", (long long) now_us - now_sec * 1000000); } - char process_flag = '@'; - int process_id = 0; - - switch (swoole_get_process_type()) { - case SW_PROCESS_MASTER: - process_flag = '#'; - process_id = swoole_get_thread_id(); - break; - case SW_PROCESS_MANAGER: - process_flag = '$'; - break; - case SW_PROCESS_WORKER: - process_flag = '*'; - process_id = swoole_get_process_id(); - break; - case SW_PROCESS_TASKWORKER: - process_flag = '^'; - process_id = swoole_get_process_id(); - break; - default: - break; + int worker_id = swoole_get_worker_id(); + pid_t worker_pid = swoole_get_worker_pid(); + if (worker_pid == 0) { + worker_pid = getpid(); } - - n = sw_snprintf(log_str, - SW_LOG_BUFFER_SIZE, - "[%.*s %c%d.%d]\t%s\t%.*s\n", - static_cast(l_data_str), - date_str, - process_flag, - SwooleG.pid, - process_id, - level_str, - static_cast(length), - content); - - if (opened && flock(log_fd, LOCK_EX) == -1) { - printf("flock(%d, LOCK_EX) failed. Error: %s[%d]\n", log_fd, strerror(errno), errno); - goto _print; - } - if (write(log_fd, log_str, n) < 0) { - _print: - printf("write(log_fd=%d, size=%d) failed. Error: %s[%d].\nMessage: %.*s\n", - log_fd, - n, - strerror(errno), - errno, - n, - log_str); + char worker_symbol = swoole_get_worker_symbol(); + + size_t n = sw_snprintf(log_str, + SW_LOG_BUFFER_SIZE, + "[%.*s %c%d.%d]\t%s\t%.*s\n", + static_cast(l_data_str), + date_str, + worker_symbol, + worker_pid, + worker_id, + level_str, + static_cast(length), + content); + + lock.lock(); + if (opened) { + flockfile(log_fp); } - if (opened && flock(log_fd, LOCK_UN) == -1) { - printf("flock(%d, LOCK_UN) failed. Error: %s[%d]\n", log_fd, strerror(errno), errno); + fwrite(log_str, n, 1, log_fp); + fflush(log_fp); + if (opened) { + funlockfile(log_fp); } + lock.unlock(); + if (display_backtrace_) { swoole_print_backtrace(); } diff --git a/src/core/misc.cc b/src/core/misc.cc new file mode 100644 index 0000000000..b93ea45ef4 --- /dev/null +++ b/src/core/misc.cc @@ -0,0 +1,306 @@ +/* + +----------------------------------------------------------------------+ + | 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 "swoole.h" + +#include + +void sw_spinlock(sw_atomic_t *lock) { + uint32_t i, n; + while (true) { + if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { + return; + } + if (SW_CPU_NUM > 1) { + for (n = 1; n < SW_SPINLOCK_LOOP_N; n <<= 1) { + for (i = 0; i < n; i++) { + sw_atomic_cpu_pause(); + } + + if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { + return; + } + } + } + std::this_thread::yield(); + } +} + +#ifdef HAVE_FUTEX +#include +#include + +int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout) { + if (sw_atomic_cmp_set(atomic, 1, 0)) { + return 0; + } + + int ret; + timespec _timeout; + + if (timeout > 0) { + _timeout.tv_sec = static_cast(timeout); + _timeout.tv_nsec = (timeout - _timeout.tv_sec) * 1000 * 1000 * 1000; + ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, &_timeout, NULL, 0); + } else { + ret = syscall(SYS_futex, atomic, FUTEX_WAIT, 0, NULL, NULL, 0); + } + if (ret == 0 && sw_atomic_cmp_set(atomic, 1, 0)) { + return 0; + } else { + return -1; + } +} + +int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n) { + if (sw_atomic_cmp_set(atomic, 0, 1)) { + return syscall(SYS_futex, atomic, FUTEX_WAKE, n, NULL, NULL, 0); + } else { + return 0; + } +} +#else +int sw_atomic_futex_wait(sw_atomic_t *atomic, double timeout) { + if (sw_atomic_cmp_set(atomic, (sw_atomic_t) 1, (sw_atomic_t) 0)) { + return 0; + } + timeout = timeout <= 0 ? INT_MAX : timeout; + int32_t i = (int32_t) sw_atomic_sub_fetch(atomic, 1); + while (timeout > 0) { + if ((int32_t) *atomic > i) { + return 0; + } else { + usleep(1000); + timeout -= 0.001; + } + } + sw_atomic_fetch_add(atomic, 1); + return -1; +} + +int sw_atomic_futex_wakeup(sw_atomic_t *atomic, int n) { + if (1 == (int32_t) *atomic) { + return 0; + } + sw_atomic_fetch_add(atomic, n); + return 0; +} +#endif + +/* {{{ DJBX33A (Daniel J. Bernstein, Times 33 with Addition) + * + * This is Daniel J. Bernstein's popular `times 33' hash function as + * posted by him years ago on comp->lang.c. It basically uses a function + * like ``hash(i) = hash(i-1) * 33 + str[i]''. This is one of the best + * known hash functions for strings. Because it is both computed very + * fast and distributes very well. + * + * The magic of number 33, i.e. why it works better than many other + * constants, prime or not, has never been adequately explained by + * anyone. So I try an explanation: if one experimentally tests all + * multipliers between 1 and 256 (as RSE did now) one detects that even + * numbers are not useable at all. The remaining 128 odd numbers + * (except for the number 1) work more or less all equally well. They + * all distribute in an acceptable way and this way fill a hash table + * with an average percent of approx. 86%. + * + * If one compares the Chi^2 values of the variants, the number 33 not + * even has the best value. But the number 33 and a few other equally + * good numbers like 17, 31, 63, 127 and 129 have nevertheless a great + * advantage to the remaining numbers in the large set of possible + * multipliers: their multiply operation can be replaced by a faster + * operation based on just one shift plus either a single addition + * or subtraction operation. And because a hash function has to both + * distribute good _and_ has to be very fast to compute, those few + * numbers should be preferred and seems to be the reason why Daniel J. + * Bernstein also preferred it. + * + * -- Ralf S. Engelschall + */ +uint64_t swoole_hash_php(const char *key, size_t len) { + ulong_t hash = 5381; + /* variant with the hash unrolled eight times */ + for (; len >= 8; len -= 8) { + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + hash = ((hash << 5) + hash) + *key++; + } + + switch (len) { + case 7: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 6: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 5: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 4: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 3: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 2: + hash = ((hash << 5) + hash) + *key++; /* fallthrough... */ + /* no break */ + case 1: + hash = ((hash << 5) + hash) + *key++; + break; + case 0: + break; + default: + break; + } + return hash; +} + +#define HASH_JEN_MIX(a, b, c) \ + do { \ + a -= b; \ + a -= c; \ + a ^= (c >> 13); \ + b -= c; \ + b -= a; \ + b ^= (a << 8); \ + c -= a; \ + c -= b; \ + c ^= (b >> 13); \ + a -= b; \ + a -= c; \ + a ^= (c >> 12); \ + b -= c; \ + b -= a; \ + b ^= (a << 16); \ + c -= a; \ + c -= b; \ + c ^= (b >> 5); \ + a -= b; \ + a -= c; \ + a ^= (c >> 3); \ + b -= c; \ + b -= a; \ + b ^= (a << 10); \ + c -= a; \ + c -= b; \ + c ^= (b >> 15); \ + } while (0) + +/** + * MurmurHash2(Austin Appleby) + */ +uint64_t swoole_hash_jenkins(const char *key, size_t keylen) { + unsigned j; + uint64_t hashv = 0xfeedbeef; + unsigned i = j = 0x9e3779b9; + auto k = (unsigned) (keylen); + + while (k >= 12) { + i += (key[0] + ((unsigned) key[1] << 8) + ((unsigned) key[2] << 16) + ((unsigned) key[3] << 24)); + j += (key[4] + ((unsigned) key[5] << 8) + ((unsigned) key[6] << 16) + ((unsigned) key[7] << 24)); + hashv += (key[8] + ((unsigned) key[9] << 8) + ((unsigned) key[10] << 16) + ((unsigned) key[11] << 24)); + + HASH_JEN_MIX(i, j, hashv); + + key += 12; + k -= 12; + } + hashv += keylen; + switch (k) { + case 11: + hashv += ((unsigned) key[10] << 24); + /* no break */ + case 10: + hashv += ((unsigned) key[9] << 16); + /* no break */ + case 9: + hashv += ((unsigned) key[8] << 8); + /* no break */ + case 8: + j += ((unsigned) key[7] << 24); + /* no break */ + case 7: + j += ((unsigned) key[6] << 16); + /* no break */ + case 6: + j += ((unsigned) key[5] << 8); + /* no break */ + case 5: + j += key[4]; + /* no break */ + case 4: + i += ((unsigned) key[3] << 24); + /* no break */ + case 3: + i += ((unsigned) key[2] << 16); + /* no break */ + case 2: + i += ((unsigned) key[1] << 8); + /* no break */ + case 1: + i += key[0]; + } + HASH_JEN_MIX(i, j, hashv); + return hashv; +} + +/** + * MurmurHash2(Austin Appleby) + */ +uint64_t swoole_hash_austin(const char *key, size_t keylen) { + uint64_t h = 0 ^ keylen; + + while (keylen >= 4) { + uint64_t k = key[0]; + k |= key[1] << 8; + k |= key[2] << 16; + k |= key[3] << 24; + + k *= 0x5bd1e995; + k ^= k >> 24; + k *= 0x5bd1e995; + + h *= 0x5bd1e995; + h ^= k; + + key += 4; + keylen -= 4; + } + + switch (keylen) { + case 3: + h ^= key[2] << 16; + /* no break */ + case 2: + h ^= key[1] << 8; + /* no break */ + case 1: + h ^= key[0]; + h *= 0x5bd1e995; + } + + h ^= h >> 13; + h *= 0x5bd1e995; + h ^= h >> 15; + + return h; +} diff --git a/src/core/string.cc b/src/core/string.cc index fab2edb13d..f478fdeed5 100644 --- a/src/core/string.cc +++ b/src/core/string.cc @@ -21,6 +21,107 @@ namespace swoole { +void String::alloc(size_t _size, const Allocator *_allocator) { + if (_allocator == nullptr) { + _allocator = sw_std_allocator(); + } + + _size = SW_MEM_ALIGNED_SIZE(_size); + length = 0; + size = _size; + offset = 0; + str = (char *) _allocator->malloc(_size); + allocator = _allocator; + + if (str == nullptr) { + throw std::bad_alloc(); + } +} + +void String::move(String &&src) { + str = src.str; + length = src.length; + offset = src.offset; + size = src.size; + allocator = src.allocator; + + src.str = nullptr; + src.length = 0; + src.size = 0; + src.offset = 0; +} + +String &String::operator=(const String &src) noexcept { + if (&src == this) { + return *this; + } + if (allocator && str) { + allocator->free(str); + } + copy(src); + return *this; +} + +void String::copy(const String &src) { + alloc(src.size, src.allocator); + memcpy(str, src.str, src.length); + length = src.length; + offset = src.offset; +} + +String &String::operator=(String &&src) noexcept { + if (&src == this) { + return *this; + } + if (allocator && str) { + allocator->free(str); + } + move(std::move(src)); + return *this; +} + +void String::append(const String &append_str) { + size_t new_size = length + append_str.length; + if (new_size > size) { + reserve(new_size); + } + + memcpy(str + length, append_str.str, append_str.length); + length += append_str.length; +} + +void String::write(off_t _offset, const String &write_str) { + write(_offset, write_str.str, write_str.length); +} + +void String::write(off_t _offset, const char *write_str, size_t _length) { + size_t new_length = _offset + _length; + if (new_length > size) { + reserve(swoole_size_align(new_length * 2, swoole_pagesize())); + } + + memcpy(str + _offset, write_str, _length); + if (new_length > length) { + length = new_length; + } +} + +void String::grow(size_t incr_value) { + length += incr_value; + if (length == size) { + reserve(size * 2); + } +} + +String String::substr(size_t _offset, size_t len) const { + if (_offset + len > length) { + return {}; + } + String _substr(len); + _substr.append(str + _offset, len); + return _substr; +} + char *String::pop(size_t init_size) { assert(length >= (size_t) offset); @@ -28,7 +129,7 @@ char *String::pop(size_t init_size) { size_t _length = length - offset; size_t alloc_size = SW_MEM_ALIGNED_SIZE(_length == 0 ? init_size : SW_MAX(_length, init_size)); - char *new_val = (char *) allocator->malloc(alloc_size); + auto new_val = static_cast(allocator->malloc(alloc_size)); if (new_val == nullptr) { return nullptr; } @@ -60,7 +161,7 @@ void String::reduce(off_t _offset) { memmove(str, str + _offset, length); } -void String::print(bool print_value) { +void String::print(bool print_value) const { if (print_value) { printf("String[length=%zu,size=%zu,offset=%jd]=%.*s\n", length, size, (intmax_t) offset, (int) length, str); } else { @@ -68,34 +169,30 @@ void String::print(bool print_value) { } } -int String::append(int value) { +void String::append(int value) { char buf[16]; int s_len = swoole_itoa(buf, value); size_t new_size = length + s_len; if (new_size > size) { - if (!reserve(new_size)) { - return SW_ERR; - } + reserve(new_size); } memcpy(str + length, buf, s_len); length += s_len; - return SW_OK; } -int String::append(const char *append_str, size_t _length) { +void String::append(const char *append_str, size_t _length) { size_t new_size = length + _length; - if (new_size > size and !reserve(new_size)) { - return SW_ERR; + if (new_size > size) { + reserve(new_size); } memcpy(str + length, append_str, _length); length += _length; - return SW_OK; } -int String::append_random_bytes(size_t _length, bool base64) { +bool String::append_random_bytes(size_t _length, bool base64) { size_t new_size = length + _length; size_t base_encode_size; @@ -105,14 +202,12 @@ int String::append_random_bytes(size_t _length, bool base64) { } if (new_size > size) { - if (!reserve(swoole_size_align(new_size * 2, swoole_pagesize()))) { - return SW_ERR; - } + reserve(swoole_size_align(new_size * 2, swoole_pagesize())); } size_t n = swoole_random_bytes(str + length, _length); if (n != _length) { - return SW_ERR; + return false; } if (base64) { @@ -123,45 +218,47 @@ int String::append_random_bytes(size_t _length, bool base64) { length += n; - return SW_OK; + return true; } -bool String::reserve(size_t new_size) { +void String::reserve(size_t new_size) { if (size == 0) { alloc(new_size, nullptr); - return true; + return; } new_size = SW_MEM_ALIGNED_SIZE(new_size); - char *new_str = (char *) allocator->realloc(str, new_size); + auto new_str = static_cast(allocator->realloc(str, new_size)); if (new_str == nullptr) { throw std::bad_alloc(); - return false; } str = new_str; size = new_size; +} - return true; +char *String::release() { + char *tmp = str; + str = nullptr; + size = 0; + length = 0; + offset = 0; + return tmp; } -bool String::repeat(const char *data, size_t len, size_t n) { - if (n <= 0) { - return false; - } +void String::repeat(const char *data, size_t len, size_t n) { + assert(n > 0); if (len == 1) { - if ((size < length + n) && !reserve(length + n)) { - return false; + if ((size < length + n)) { + reserve(length + n); } memset(str + length, data[0], n); length += n; - - return true; - } - for (size_t i = 0; i < n; i++) { - append(data, len); + } else { + for (size_t i = 0; i < n; i++) { + append(data, len); + } } - return true; } /** @@ -178,9 +275,13 @@ ssize_t String::split(const char *delimiter, size_t delimiter_length, const Stri const char *start_addr = str + offset; const char *delimiter_addr = swoole_strnstr(start_addr, length - offset, delimiter, delimiter_length); off_t _offset = offset; - size_t ret; - swoole_trace_log(SW_TRACE_EOF_PROTOCOL, "#[0] count=%d, length=%ld, size=%ld, offset=%jd", count, length, size, (intmax_t) offset); + swoole_trace_log(SW_TRACE_EOF_PROTOCOL, + "#[0] count=%d, length=%ld, size=%ld, offset=%jd", + count, + length, + size, + (intmax_t) offset); while (delimiter_addr) { size_t _length = delimiter_addr - start_addr + delimiter_length; @@ -205,14 +306,15 @@ ssize_t String::split(const char *delimiter, size_t delimiter_length, const Stri offset = length - delimiter_length; } - ret = start_addr - str - _offset; + size_t ret = start_addr - str - _offset; if (ret > 0 && ret < length) { - swoole_trace_log(SW_TRACE_EOF_PROTOCOL, "#[5] count=%d, remaining_length=%zu", count, (size_t) (length - offset)); + swoole_trace_log( + SW_TRACE_EOF_PROTOCOL, "#[5] count=%d, remaining_length=%zu", count, (size_t) (length - offset)); } else if (ret >= length) { - swoole_trace_log(SW_TRACE_EOF_PROTOCOL, "#[3] length=%ld, size=%zu, offset=%jd", length, size, (intmax_t) offset); + swoole_trace_log( + SW_TRACE_EOF_PROTOCOL, "#[3] length=%ld, size=%zu, offset=%jd", length, size, (intmax_t) offset); } return ret; } - } // namespace swoole diff --git a/src/core/timer.cc b/src/core/timer.cc index 519765b0d8..b21b4d99f3 100644 --- a/src/core/timer.cc +++ b/src/core/timer.cc @@ -14,59 +14,40 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_reactor.h" #include "swoole_timer.h" -#if !defined(HAVE_CLOCK_GETTIME) && defined(__MACH__) -#include -#include -#include - -#define ORWL_NANO (+1.0E-9) -#define ORWL_GIGA UINT64_C(1000000000) - -static double orwl_timebase = 0.0; -static uint64_t orwl_timestart = 0; - -int swoole_clock_realtime(struct timespec *t) { - // be more careful in a multithreaded environement - if (!orwl_timestart) { - mach_timebase_info_data_t tb = {0}; - mach_timebase_info(&tb); - orwl_timebase = tb.numer; - orwl_timebase /= tb.denom; - orwl_timestart = mach_absolute_time(); - } - double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase; - t->tv_sec = diff * ORWL_NANO; - t->tv_nsec = diff - (t->tv_sec * ORWL_GIGA); - return 0; -} -#endif - namespace swoole { - -Timer::Timer() : heap(1024, Heap::MIN_HEAP) { +Timer::Timer(bool manually_trigger) : heap(1024, Heap::MIN_HEAP) { _current_id = -1; next_msec_ = -1; _next_id = 1; round = 0; - now(&base_time); + base_time = get_absolute_msec(); + init(manually_trigger); } -bool Timer::init() { - if (now(&base_time) < 0) { - return false; +void Timer::init(bool manually_trigger) { + if (manually_trigger) { + set = [](Timer *, long) -> int { return SW_OK; }; + close = [](Timer *) {}; + return; } if (SwooleTG.reactor) { - return init_reactor(SwooleTG.reactor); + init_with_reactor(SwooleTG.reactor); } else { - return init_system_timer(); + init_with_system_timer(); + } +} + +void Timer::release_node(TimerNode *tnode) { + if (tnode->destructor) { + tnode->destructor(tnode); } + delete tnode; } -bool Timer::init_reactor(Reactor *reactor) { +void Timer::init_with_reactor(Reactor *reactor) { reactor_ = reactor; set = [](Timer *timer, long exec_msec) -> int { timer->reactor_->timeout_msec = exec_msec; @@ -84,22 +65,20 @@ bool Timer::init_reactor(Reactor *reactor) { swoole_timer_free(); } }); - - return true; } -void Timer::reinit(Reactor *reactor) { - init_reactor(reactor); - reactor->timeout_msec = next_msec_; +void Timer::reinit(bool manually_trigger) { + close(this); + init(manually_trigger); + set(this, next_msec_); } Timer::~Timer() { if (close) { close(this); } - for (auto iter = map.begin(); iter != map.end(); iter++) { - auto tnode = iter->second; - delete tnode; + for (const auto &iter : map) { + release_node(iter.second); } } @@ -109,15 +88,11 @@ TimerNode *Timer::add(long _msec, bool persistent, void *data, const TimerCallba return nullptr; } - int64_t now_msec = get_relative_msec(); - if (sw_unlikely(now_msec < 0)) { - return nullptr; - } - - TimerNode *tnode = new TimerNode(); + auto *tnode = new TimerNode(); + tnode->id = _next_id++; tnode->data = data; tnode->type = TimerNode::TYPE_KERNEL; - tnode->exec_msec = now_msec + _msec; + tnode->exec_msec = get_relative_msec() + _msec; tnode->interval = persistent ? _msec : 0; tnode->removed = false; tnode->callback = callback; @@ -129,18 +104,12 @@ TimerNode *Timer::add(long _msec, bool persistent, void *data, const TimerCallba next_msec_ = _msec; } - tnode->id = _next_id++; - if (sw_unlikely(tnode->id < 0)) { - tnode->id = 1; - _next_id = 2; - } - tnode->heap_node = heap.push(tnode->exec_msec, tnode); if (sw_unlikely(tnode->heap_node == nullptr)) { - delete tnode; + release_node(tnode); return nullptr; } - map.emplace(std::make_pair(tnode->id, tnode)); + map.emplace(tnode->id, tnode); swoole_trace_log(SW_TRACE_TIMER, "id=%ld, exec_msec=%" PRId64 ", msec=%ld, round=%" PRIu64 ", exist=%lu", tnode->id, @@ -171,32 +140,25 @@ bool Timer::remove(TimerNode *tnode) { if (tnode->heap_node) { heap.remove(tnode->heap_node); } - if (tnode->destructor) { - tnode->destructor(tnode); - } swoole_trace_log(SW_TRACE_TIMER, "id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu", tnode->id, tnode->exec_msec, tnode->round, count()); - delete tnode; + release_node(tnode); return true; } -int Timer::select() { - int64_t now_msec = get_relative_msec(); - if (sw_unlikely(now_msec < 0)) { - return SW_ERR; - } - +void Timer::select() { + const int64_t now_msec = get_relative_msec(); TimerNode *tnode = nullptr; HeapNode *tmp; - swoole_trace_log(SW_TRACE_TIMER, "timer msec=%" PRId64 ", round=%" PRId64, now_msec, round); + swoole_trace_log(SW_TRACE_TIMER, "select begin: now_msec=%" PRId64 ", round=%" PRId64, now_msec, round); while ((tmp = heap.top())) { - tnode = (TimerNode *) tmp->data; + tnode = static_cast(tmp->data); if (tnode->exec_msec > now_msec || tnode->round == round) { break; } @@ -204,7 +166,7 @@ int Timer::select() { _current_id = tnode->id; if (!tnode->removed) { swoole_trace_log(SW_TRACE_TIMER, - "id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu", + "execute callback [id=%ld, exec_msec=%" PRId64 ", round=%" PRIu64 ", exist=%lu]", tnode->id, tnode->exec_msec, tnode->round, @@ -225,7 +187,8 @@ int Timer::select() { heap.pop(); map.erase(tnode->id); - delete tnode; + release_node(tnode); + tnode = nullptr; } if (!tnode || !tmp) { @@ -239,26 +202,5 @@ int Timer::select() { set(this, next_msec_); } round++; - - return SW_OK; -} - -int Timer::now(struct timeval *time) { -#if defined(SW_USE_MONOTONIC_TIME) && defined(CLOCK_MONOTONIC) - struct timespec _now; - if (clock_gettime(CLOCK_MONOTONIC, &_now) < 0) { - swoole_sys_warning("clock_gettime(CLOCK_MONOTONIC) failed"); - return SW_ERR; - } - time->tv_sec = _now.tv_sec; - time->tv_usec = _now.tv_nsec / 1000; -#else - if (gettimeofday(time, nullptr) < 0) { - swoole_sys_warning("gettimeofday() failed"); - return SW_ERR; - } -#endif - return SW_OK; } - }; // namespace swoole diff --git a/src/coroutine/base.cc b/src/coroutine/base.cc index e2cd51d5a0..2fa72eeb3f 100644 --- a/src/coroutine/base.cc +++ b/src/coroutine/base.cc @@ -15,21 +15,22 @@ */ #include "swoole_coroutine.h" -#include "swoole_coroutine_c_api.h" +#include "swoole_coroutine_api.h" namespace swoole { -Coroutine *Coroutine::current = nullptr; -long Coroutine::last_cid = 0; -std::unordered_map Coroutine::coroutines; -uint64_t Coroutine::peak_num = 0; -bool Coroutine::activated = false; +SW_THREAD_LOCAL Coroutine *Coroutine::current = nullptr; +SW_THREAD_LOCAL long Coroutine::last_cid = 0; +SW_THREAD_LOCAL long Coroutine::socket_bound_cid = 0; +SW_THREAD_LOCAL std::unordered_map Coroutine::coroutines; +SW_THREAD_LOCAL uint64_t Coroutine::peak_num = 0; +SW_THREAD_LOCAL bool Coroutine::activated = false; -size_t Coroutine::stack_size = SW_DEFAULT_C_STACK_SIZE; -Coroutine::SwapCallback Coroutine::on_yield = nullptr; -Coroutine::SwapCallback Coroutine::on_resume = nullptr; -Coroutine::SwapCallback Coroutine::on_close = nullptr; -Coroutine::BailoutCallback Coroutine::on_bailout = nullptr; +SW_THREAD_LOCAL size_t Coroutine::stack_size = SW_DEFAULT_C_STACK_SIZE; +SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_yield = nullptr; +SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_resume = nullptr; +SW_THREAD_LOCAL Coroutine::SwapCallback Coroutine::on_close = nullptr; +SW_THREAD_LOCAL Coroutine::BailoutCallback Coroutine::on_bailout = nullptr; #ifdef SW_USE_THREAD_CONTEXT namespace coroutine { @@ -43,6 +44,7 @@ void Coroutine::activate() { coroutine::thread_context_init(); #endif activated = true; + on_bailout = nullptr; } void Coroutine::deactivate() { @@ -50,6 +52,56 @@ void Coroutine::deactivate() { coroutine::thread_context_clean(); #endif activated = false; + on_bailout = []() { + // The coroutine scheduler has been destroyed, + // Can not resume any coroutine + // Expect that never here + swoole_error("have been bailout, can not resume any coroutine"); + }; +} + +#ifdef SW_CORO_TIME +void Coroutine::calc_execute_usec(Coroutine *yield_coroutine, Coroutine *resume_coroutine) { + long current_usec = time(true); + if (yield_coroutine) { + yield_coroutine->execute_usec += current_usec - yield_coroutine->switch_usec; + } + + if (resume_coroutine) { + resume_coroutine->switch_usec = current_usec; + } +} +#endif + +Coroutine::Coroutine(const CoroutineFunc &fn, void *private_data) : ctx(stack_size, fn, private_data) { + cid = ++last_cid; + coroutines[cid] = this; + if (sw_unlikely(count() > peak_num)) { + peak_num = count(); + } + if (!activated) { + activate(); + } +} + +void Coroutine::check_end() { + if (ctx.is_end()) { + close(); + } else if (sw_unlikely(on_bailout)) { + SW_ASSERT(current == nullptr); + on_bailout(); + } +} + +long Coroutine::run() { + const long _cid = cid; + origin = current; + current = this; + CALC_EXECUTE_USEC(origin, nullptr); + state = STATE_RUNNING; + ctx.swap_in(); + check_end(); + return _cid; } void Coroutine::yield() { @@ -79,7 +131,7 @@ bool Coroutine::yield_ex(double timeout) { }; if (timeout > 0) { - timer = swoole_timer_add((long) (timeout * 1000), false, timer_callback, nullptr); + timer = swoole_timer_add(timeout, false, timer_callback, nullptr); } CancelFunc cancel_fn = [](Coroutine *co) { @@ -147,9 +199,9 @@ void Coroutine::close() { } void Coroutine::print_list() { - for (auto i = coroutines.begin(); i != coroutines.end(); i++) { + for (auto &coroutine : coroutines) { const char *state; - switch (i->second->state) { + switch (coroutine.second->state) { case STATE_INIT: state = "[INIT]"; break; @@ -166,23 +218,34 @@ void Coroutine::print_list() { abort(); return; } - printf("Coroutine\t%ld\t%s\n", i->first, state); + sw_printf("Coroutine\t%ld\t%s\n", coroutine.first, state); } } -void Coroutine::set_on_yield(SwapCallback func) { +void Coroutine::print_socket_bound_error(int sock_fd, const char *event_str, long bound_cid) { + socket_bound_cid = bound_cid; + swoole_fatal_error(SW_ERROR_CO_HAS_BEEN_BOUND, + "Socket#%d has already been bound to another coroutine#%ld, " + "%s of the same socket in coroutine#%ld at the same time is not allowed", + sock_fd, + socket_bound_cid, + event_str, + get_current_cid()); +} + +void Coroutine::set_on_yield(const SwapCallback func) { on_yield = func; } -void Coroutine::set_on_resume(SwapCallback func) { +void Coroutine::set_on_resume(const SwapCallback func) { on_resume = func; } -void Coroutine::set_on_close(SwapCallback func) { +void Coroutine::set_on_close(const SwapCallback func) { on_close = func; } -void Coroutine::bailout(BailoutCallback func) { +void Coroutine::bailout(const BailoutCallback &func) { Coroutine *co = current; if (!co) { // marks that it can no longer resume any coroutine @@ -194,7 +257,7 @@ void Coroutine::bailout(BailoutCallback func) { return; } if (!func) { - swoole_error("bailout without callback function"); + swoole_error("bailout without callback function"); } on_bailout = func; // find the coroutine which is closest to the main @@ -225,7 +288,22 @@ uint8_t swoole_coroutine_is_in() { return !!swoole::Coroutine::get_current(); } -long swoole_coroutine_get_current_id() { +long swoole_coroutine_create(void (*routine)(void *), void *arg) { + if (sw_likely(swoole_event_is_available())) { + return swoole::Coroutine::create(routine, arg); + } else { + if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { + return -1; + } + swoole::Coroutine::activate(); + long cid = swoole::Coroutine::create(routine, arg); + swoole_event_wait(); + swoole::Coroutine::deactivate(); + return cid; + } +} + +long swoole_coroutine_get_id() { return swoole::Coroutine::get_current_cid(); } @@ -245,18 +323,17 @@ size_t swoole_coroutine_count() { /** * for gdb */ -static std::unordered_map::iterator _gdb_iterator; +static std::unordered_map::iterator gdb_iterator_; void swoole_coroutine_iterator_reset() { - _gdb_iterator = swoole::Coroutine::coroutines.begin(); + gdb_iterator_ = swoole::Coroutine::coroutines.begin(); } swoole::Coroutine *swoole_coroutine_iterator_each() { - if (_gdb_iterator == swoole::Coroutine::coroutines.end()) { + if (gdb_iterator_ == swoole::Coroutine::coroutines.end()) { return nullptr; - } else { - swoole::Coroutine *co = _gdb_iterator->second; - _gdb_iterator++; - return co; } + swoole::Coroutine *co = gdb_iterator_->second; + ++gdb_iterator_; + return co; } diff --git a/src/coroutine/channel.cc b/src/coroutine/channel.cc index 44eaaab013..fc02f2ddb5 100644 --- a/src/coroutine/channel.cc +++ b/src/coroutine/channel.cc @@ -20,7 +20,7 @@ namespace swoole { namespace coroutine { void Channel::timer_callback(Timer *timer, TimerNode *tnode) { - TimeoutMessage *msg = (TimeoutMessage *) tnode->data; + auto *msg = static_cast(tnode->data); msg->error = true; msg->timer = nullptr; if (msg->type == CONSUMER) { @@ -31,7 +31,7 @@ void Channel::timer_callback(Timer *timer, TimerNode *tnode) { msg->co->resume(); } -void Channel::yield(enum Opcode type) { +void Channel::yield(Opcode type) { Coroutine *co = Coroutine::get_current_safe(); if (type == PRODUCER) { producer_queue.push_back(co); @@ -63,11 +63,10 @@ void *Channel::pop(double timeout) { msg.error = false; msg.timer = nullptr; if (timeout > 0) { - long msec = (long) (timeout * 1000); msg.chan = this; msg.type = CONSUMER; msg.co = current_co; - msg.timer = swoole_timer_add(msec, false, timer_callback, &msg); + msg.timer = swoole_timer_add(timeout, false, timer_callback, &msg); } yield(CONSUMER); @@ -114,11 +113,10 @@ bool Channel::push(void *data, double timeout) { msg.error = false; msg.timer = nullptr; if (timeout > 0) { - long msec = (long) (timeout * 1000); msg.chan = this; msg.type = PRODUCER; msg.co = current_co; - msg.timer = swoole_timer_add(msec, false, timer_callback, &msg); + msg.timer = swoole_timer_add(timeout, false, timer_callback, &msg); } yield(PRODUCER); diff --git a/src/coroutine/context.cc b/src/coroutine/context.cc index 50fc294d83..182cdf1e2c 100644 --- a/src/coroutine/context.cc +++ b/src/coroutine/context.cc @@ -31,15 +31,15 @@ namespace swoole { namespace coroutine { -Context::Context(size_t stack_size, const CoroutineFunc &fn, void *private_data) - : fn_(fn), stack_size_(stack_size), private_data_(private_data) { +Context::Context(size_t stack_size, CoroutineFunc fn, void *private_data) + : fn_(std::move(fn)), stack_size_(stack_size), private_data_(private_data) { end_ = false; #ifdef SW_CONTEXT_PROTECT_STACK_PAGE int mapflags = MAP_PRIVATE | MAP_ANONYMOUS; #ifdef __OpenBSD__ // no-op for Linux and NetBSD, not to enable on FreeBSD as the semantic differs. - // However necessary on OpenBSD. + // However, necessary on OpenBSD. mapflags |= MAP_STACK; #endif stack_ = (char *) ::mmap(0, stack_size_, PROT_READ | PROT_WRITE, mapflags, -1, 0); @@ -57,7 +57,7 @@ Context::Context(size_t stack_size, const CoroutineFunc &fn, void *private_data) valgrind_stack_id = VALGRIND_STACK_REGISTER(sp, stack_); #endif -#if USE_UCONTEXT +#ifdef USE_UCONTEXT if (-1 == getcontext(&ctx_)) { swoole_throw_error(SW_ERROR_CO_GETCONTEXT_FAILED); sw_free(stack_); @@ -68,7 +68,7 @@ Context::Context(size_t stack_size, const CoroutineFunc &fn, void *private_data) ctx_.uc_link = nullptr; makecontext(&ctx_, (void (*)(void)) & context_func, 1, this); #else - ctx_ = swoole_make_fcontext(sp, stack_size_, (void (*)(intptr_t)) & context_func); + ctx_ = swoole_make_fcontext(sp, stack_size_, (void (*)(transfer_t)) & context_func); swap_ctx_ = nullptr; #endif @@ -120,25 +120,32 @@ ssize_t Context::get_stack_usage() { #endif bool Context::swap_in() { -#if USE_UCONTEXT +#ifdef USE_UCONTEXT return 0 == swapcontext(&swap_ctx_, &ctx_); #else - swoole_jump_fcontext(&swap_ctx_, ctx_, (intptr_t) this, true); + coroutine_transfer_t transfer_data = swoole_jump_fcontext(ctx_, (void *) this); + ctx_ = transfer_data.fctx; return true; #endif } bool Context::swap_out() { -#if USE_UCONTEXT +#ifdef USE_UCONTEXT return 0 == swapcontext(&ctx_, &swap_ctx_); #else - swoole_jump_fcontext(&ctx_, swap_ctx_, (intptr_t) this, true); + coroutine_transfer_t transfer_data = swoole_jump_fcontext(swap_ctx_, (void *) this); + swap_ctx_ = transfer_data.fctx; return true; #endif } -void Context::context_func(void *arg) { - Context *_this = (Context *) arg; +void Context::context_func(coroutine_transfer_t arg) { +#if defined(USE_UCONTEXT) || defined(SW_USE_THREAD_CONTEXT) + auto *_this = (Context *) arg; +#else + auto *_this = (Context *) arg.data; + _this->swap_ctx_ = arg.fctx; +#endif _this->fn_(_this->private_data_); _this->end_ = true; _this->swap_out(); diff --git a/src/coroutine/file.cc b/src/coroutine/file.cc new file mode 100644 index 0000000000..587f524893 --- /dev/null +++ b/src/coroutine/file.cc @@ -0,0 +1,74 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "swoole_file.h" +#include "swoole_coroutine_api.h" + +namespace swoole { +AsyncFile::AsyncFile(const std::string &path, int flags, int mode) { + open(path, flags, mode); +} + +AsyncFile::~AsyncFile() { + close(); +} + +bool AsyncFile::open(const std::string &path, int flags, mode_t mode) { + close(); + + flags_ = flags; + mode_ = mode; + path_ = path; + fd = swoole_coroutine_open(path.c_str(), flags, mode); + return fd > 0; +} + +bool AsyncFile::close() const { + if (sw_unlikely(fd == -1)) { + return false; + } + + return swoole_coroutine_close(fd) == 0; +} + +ssize_t AsyncFile::read(void *buf, size_t count) const { + return swoole_coroutine_read(fd, buf, count); +} + +ssize_t AsyncFile::write(const void *buf, size_t count) const { + return swoole_coroutine_write(fd, buf, count); +} + +bool AsyncFile::sync() const { + return swoole_coroutine_fsync(fd) == 0; +} + +bool AsyncFile::truncate(off_t length) const { + return swoole_coroutine_ftruncate(fd, length) == 0; +} + +bool AsyncFile::stat(FileStatus *statbuf) const { + return swoole_coroutine_fstat(fd, statbuf) == 0; +} + +off_t AsyncFile::get_offset() const { + return swoole_coroutine_lseek(fd, 0, SEEK_CUR); +} + +off_t AsyncFile::set_offset(off_t offset) const { + return swoole_coroutine_lseek(fd, offset, SEEK_SET); +} +} // namespace swoole diff --git a/src/coroutine/file_lock.cc b/src/coroutine/file_lock.cc index af040bbdc1..6a4aacf13e 100644 --- a/src/coroutine/file_lock.cc +++ b/src/coroutine/file_lock.cc @@ -16,130 +16,59 @@ #include -#include - #include "swoole_coroutine.h" -#include "swoole_coroutine_c_api.h" +#include "swoole_coroutine_api.h" using swoole::Coroutine; +using swoole::coroutine::wait_for; -class LockManager { - public: - bool lock_ex = false; - bool lock_sh = false; - std::queue queue_; -}; - -static std::unordered_map lock_pool; - -static inline LockManager *get_manager(const char *filename) { - std::string key(filename); - auto i = lock_pool.find(key); - LockManager *lm; - if (i == lock_pool.end()) { - lm = new LockManager; - lock_pool[key] = lm; - } else { - lm = i->second; - } - return lm; -} - -static inline int lock_ex(const char *filename, int fd) { - LockManager *lm = get_manager(filename); - if (lm->lock_ex || lm->lock_sh) { - Coroutine *co = Coroutine::get_current(); - lm->queue_.push(co); - co->yield(); - } - lm->lock_ex = true; - if (swoole_coroutine_flock(fd, LOCK_EX) < 0) { - lm->lock_ex = false; - return -1; - } else { - return 0; - } +static inline int do_lock(int fd, int operation) { + int retval = 0; + auto success = wait_for([&retval, operation, fd]() { + auto rv = flock(fd, operation | LOCK_NB); + if (rv == 0) { + retval = 0; + } else if (rv == -1 && errno == EWOULDBLOCK) { + return false; + } else { + retval = -1; + } + return true; + }); + return success ? retval : -1; } -static inline int lock_sh(const char *filename, int fd) { - LockManager *lm = get_manager(filename); - if (lm->lock_ex) { - Coroutine *co = Coroutine::get_current(); - lm->queue_.push(co); - co->yield(); - } - lm->lock_sh = true; - if (swoole_coroutine_flock(fd, LOCK_SH) < 0) { - lm->lock_sh = false; - return -1; - } else { - return 0; - } +static inline int lock_ex(int fd) { + return do_lock(fd, LOCK_EX); } -static inline int lock_release(const char *filename, int fd) { - std::string key(filename); - auto i = lock_pool.find(key); - if (i == lock_pool.end()) { - return swoole_coroutine_flock(fd, LOCK_UN); - } - LockManager *lm = i->second; - if (lm->queue_.empty()) { - delete lm; - lock_pool.erase(i); - return swoole_coroutine_flock(fd, LOCK_UN); - } else { - Coroutine *co = lm->queue_.front(); - lm->queue_.pop(); - int retval = swoole_coroutine_flock(fd, LOCK_UN); - co->resume(); - return retval; - } +static inline int lock_sh(int fd) { + return do_lock(fd, LOCK_SH); } -#ifdef LOCK_NB -static inline int lock_nb(const char *filename, int fd, int operation) { - int retval = ::flock(fd, operation | LOCK_NB); - if (retval == 0) { - LockManager *lm = get_manager(filename); - if (operation == LOCK_EX) { - lm->lock_ex = true; - } else { - lm->lock_sh = true; - } - } - return retval; +static inline int lock_release(int fd) { + return flock(fd, LOCK_UN); } -#endif -int swoole_coroutine_flock_ex(const char *filename, int fd, int operation) { +int swoole_coroutine_flock(int fd, int operation) { Coroutine *co = Coroutine::get_current(); if (sw_unlikely(SwooleTG.reactor == nullptr || !co)) { return ::flock(fd, operation); } - - const char *real = realpath(filename, sw_tg_buffer()->str); - if (real == nullptr) { - errno = ENOENT; - swoole_set_last_error(ENOENT); - return -1; + if (operation & LOCK_NB) { + return ::flock(fd, operation); } - switch (operation) { case LOCK_EX: - return lock_ex(real, fd); + return lock_ex(fd); case LOCK_SH: - return lock_sh(real, fd); + return lock_sh(fd); case LOCK_UN: - return lock_release(real, fd); + return lock_release(fd); default: -#ifdef LOCK_NB - if (operation & LOCK_NB) { - return lock_nb(real, fd, operation & (~LOCK_NB)); - } -#endif - return -1; + break; } - - return 0; + errno = EINVAL; + swoole_set_last_error(EINVAL); + return -1; } diff --git a/src/coroutine/hook.cc b/src/coroutine/hook.cc index 0e506e810d..964a906d2e 100644 --- a/src/coroutine/hook.cc +++ b/src/coroutine/hook.cc @@ -24,53 +24,74 @@ #include #include -#include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" +#include "swoole_socket_impl.h" +#include "swoole_iouring.h" using swoole::AsyncEvent; using swoole::Coroutine; +using swoole::EventType; +using swoole::translate_events_from_poll; +using swoole::translate_events_to_poll; using swoole::async::dispatch; -using swoole::coroutine::Socket; -using swoole::coroutine::System; using swoole::coroutine::async; +using swoole::coroutine::PollSocket; +using swoole::coroutine::System; + +#ifdef SW_USE_IOURING +using swoole::Iouring; +#else +#define SW_USE_ASYNC 1 +#endif -static std::unordered_map socket_map; +static std::unordered_map> socket_map; static std::mutex socket_map_lock; +#if defined(__APPLE__) || defined(__MACH__) +static int fdatasync(int fd) { + return fcntl(fd, F_FULLFSYNC); +} +#endif + static sw_inline bool is_no_coro() { return SwooleTG.reactor == nullptr || !Coroutine::get_current(); } -static sw_inline Socket *get_socket(int sockfd) { +static sw_inline std::shared_ptr get_socket(int sockfd) { std::unique_lock _lock(socket_map_lock); auto socket_iterator = socket_map.find(sockfd); if (socket_iterator == socket_map.end()) { + errno = ENOTSOCK; return nullptr; } return socket_iterator->second; } -static sw_inline Socket *get_socket_ex(int sockfd) { +static sw_inline std::shared_ptr get_socket_ex(int sockfd) { if (sw_unlikely(is_no_coro())) { + errno = EWOULDBLOCK; return nullptr; } return get_socket(sockfd); } -Socket *swoole_coroutine_get_socket_object(int sockfd) { +std::shared_ptr swoole_coroutine_get_socket_object(int sockfd) { return get_socket(sockfd); } -SW_EXTERN_C_BEGIN +std::shared_ptr swoole_coroutine_get_socket_object_ex(int sockfd) { + return get_socket_ex(sockfd); +} +SW_EXTERN_C_BEGIN int swoole_coroutine_socket(int domain, int type, int protocol) { if (sw_unlikely(is_no_coro())) { return ::socket(domain, type, protocol); } - Socket *socket = new Socket(domain, type, protocol); + auto socket = std::make_shared(domain, type, protocol); int fd = socket->get_fd(); if (sw_unlikely(fd < 0)) { - delete socket; + return -1; } else { std::unique_lock _lock(socket_map_lock); socket_map[fd] = socket; @@ -79,32 +100,32 @@ int swoole_coroutine_socket(int domain, int type, int protocol) { } ssize_t swoole_coroutine_send(int sockfd, const void *buf, size_t len, int flags) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { return ::send(sockfd, buf, len, flags); } return socket->send(buf, len); } ssize_t swoole_coroutine_sendmsg(int sockfd, const struct msghdr *msg, int flags) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { return ::sendmsg(sockfd, msg, flags); } return socket->sendmsg(msg, flags); } ssize_t swoole_coroutine_recvmsg(int sockfd, struct msghdr *msg, int flags) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { return ::recvmsg(sockfd, msg, flags); } return socket->recvmsg(msg, flags); } ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { return ::recv(sockfd, buf, len, flags); } if (flags & MSG_PEEK) { @@ -114,31 +135,41 @@ ssize_t swoole_coroutine_recv(int sockfd, void *buf, size_t len, int flags) { } } -int swoole_coroutine_close(int sockfd) { - Socket *socket = get_socket(sockfd); - if (socket == NULL) { - return ::close(sockfd); - } - if (socket->close()) { - delete socket; - std::unique_lock _lock(socket_map_lock); - socket_map.erase(sockfd); - } - return 0; -} - int swoole_coroutine_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { return ::connect(sockfd, addr, addrlen); } return socket->connect(addr, addrlen) ? 0 : -1; } -#if 1 -int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout) { - Socket *socket; - if (sw_unlikely(nfds != 1 || timeout == 0 || (socket = get_socket_ex(fds[0].fd)) == NULL)) { +int swoole_coroutine_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { + return ::accept(sockfd, addr, addrlen); + } + + auto conn = socket->accept(0); + if (!conn) { + return -1; + } + + *addrlen = conn->get_socket()->info.len; + memcpy(addr, &conn->get_socket()->info.addr.ss, *addrlen); + + std::unique_lock _lock(socket_map_lock); + socket_map.emplace(conn->get_fd(), std::shared_ptr(conn)); + return conn->get_fd(); +} + +int swoole_coroutine_poll_fake(struct pollfd *fds, nfds_t nfds, int timeout) { + if (nfds != 1) { + swoole_set_last_error(SW_ERROR_INVALID_PARAMS); + swoole_warning("fake poll() implementation, only supports one fd"); + return -1; + } + auto socket = get_socket_ex(fds[0].fd); + if (sw_unlikely(timeout == 0 || socket == nullptr)) { return poll(fds, nfds, timeout); } socket->set_timeout((double) timeout / 1000); @@ -150,26 +181,32 @@ int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout) { } return 1; } -#else + int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout) { - if (sw_unlikely(is_no_coro() || nfds != 1 || timeout == 0)) { + if (sw_unlikely(is_no_coro() || timeout == 0)) { return poll(fds, nfds, timeout); } - std::unordered_map _fds; - for (int i = 0; i < nfds; i++) { - _fds.emplace(std::make_pair(fds[i].fd, swoole::socket_poll_fd(fds[i].events, &fds[i]))); +#ifdef SW_USE_IOURING + if (nfds == 1) { + return Iouring::poll(fds, nfds, timeout); + } +#endif + + std::unordered_map _fds; + for (nfds_t i = 0; i < nfds; i++) { + _fds.emplace(fds[i].fd, PollSocket(translate_events_from_poll(fds[i].events), &fds[i])); } if (!System::socket_poll(_fds, (double) timeout / 1000)) { return -1; } - int retval; + int retval = 0; for (auto &i : _fds) { int revents = i.second.revents; - struct pollfd *_fd = (struct pollfd *) i.second.ptr; - _fd->revents = revents; + auto *_fd = static_cast(i.second.ptr); + _fd->revents = translate_events_to_poll(revents); if (revents > 0) { retval++; } @@ -177,155 +214,38 @@ int swoole_coroutine_poll(struct pollfd *fds, nfds_t nfds, int timeout) { return retval; } -#endif - -int swoole_coroutine_open(const char *pathname, int flags, mode_t mode) { - if (sw_unlikely(is_no_coro())) { - return open(pathname, flags, mode); - } - - int ret = -1; - async([&]() { ret = open(pathname, flags, mode); }); - return ret; -} int swoole_coroutine_socket_create(int fd) { if (sw_unlikely(is_no_coro())) { return -1; } - Socket *socket = new Socket(fd, SW_SOCK_RAW); + auto socket = std::make_shared(fd, SW_SOCK_RAW); int _fd = socket->get_fd(); if (sw_unlikely(_fd < 0)) { - delete socket; - } else { - std::unique_lock _lock(socket_map_lock); - socket_map[fd] = socket; + return -1; } + socket->get_socket()->set_nonblock(); + std::unique_lock _lock(socket_map_lock); + socket_map[fd] = socket; return 0; } -uint8_t swoole_coroutine_socket_exists(int fd) { - return socket_map.find(fd) != socket_map.end(); -} - -ssize_t swoole_coroutine_read(int sockfd, void *buf, size_t count) { - if (sw_unlikely(is_no_coro())) { - return read(sockfd, buf, count); - } - - Socket *socket = get_socket(sockfd); - if (socket) { - return socket->read(buf, count); - } - - ssize_t ret = -1; - async([&]() { ret = read(sockfd, buf, count); }); - return ret; -} - -ssize_t swoole_coroutine_write(int sockfd, const void *buf, size_t count) { +int swoole_coroutine_socket_unwrap(int fd) { if (sw_unlikely(is_no_coro())) { - return write(sockfd, buf, count); - } - - Socket *socket = get_socket(sockfd); - if (socket) { - return socket->write(buf, count); - } - - ssize_t ret = -1; - async([&]() { ret = write(sockfd, buf, count); }); - return ret; -} - -off_t swoole_coroutine_lseek(int fd, off_t offset, int whence) { - if (sw_unlikely(is_no_coro())) { - return lseek(fd, offset, whence); - } - - off_t retval = -1; - async([&]() { retval = lseek(fd, offset, whence); }); - return retval; -} - -int swoole_coroutine_fstat(int fd, struct stat *statbuf) { - if (sw_unlikely(is_no_coro())) { - return fstat(fd, statbuf); - } - - int retval = -1; - async([&]() { retval = fstat(fd, statbuf); }); - return retval; -} - -int swoole_coroutine_readlink(const char *pathname, char *buf, size_t len) { - if (sw_unlikely(is_no_coro())) { - return readlink(pathname, buf, len); - } - - int retval = -1; - async([&]() { retval = readlink(pathname, buf, len); }); - return retval; -} - -int swoole_coroutine_unlink(const char *pathname) { - if (sw_unlikely(is_no_coro())) { - return unlink(pathname); - } - - int retval = -1; - async([&]() { retval = unlink(pathname); }); - return retval; -} - -int swoole_coroutine_statvfs(const char *path, struct statvfs *buf) { - if (sw_unlikely(is_no_coro())) { - return statvfs(path, buf); - } - - int retval = -1; - async([&]() { retval = statvfs(path, buf); }); - return retval; -} - -int swoole_coroutine_mkdir(const char *pathname, mode_t mode) { - if (sw_unlikely(is_no_coro())) { - return mkdir(pathname, mode); - } - - int retval = -1; - async([&]() { retval = mkdir(pathname, mode); }); - return retval; -} - -int swoole_coroutine_rmdir(const char *pathname) { - if (sw_unlikely(is_no_coro())) { - return rmdir(pathname); + return -1; } - - int retval = -1; - async([&]() { retval = rmdir(pathname); }); - return retval; -} - -int swoole_coroutine_rename(const char *oldpath, const char *newpath) { - if (sw_unlikely(is_no_coro())) { - return rename(oldpath, newpath); + auto socket = get_socket(fd); + if (socket == nullptr) { + return -1; } - - int retval = -1; - async([&]() { retval = rename(oldpath, newpath); }); - return retval; + std::unique_lock _lock(socket_map_lock); + socket->move_fd(); + socket_map.erase(fd); + return 0; } -int swoole_coroutine_access(const char *pathname, int mode) { - if (sw_unlikely(is_no_coro())) { - return access(pathname, mode); - } - - int retval = -1; - async([&]() { retval = access(pathname, mode); }); - return retval; +uint8_t swoole_coroutine_socket_exists(int fd) { + return socket_map.find(fd) != socket_map.end(); } FILE *swoole_coroutine_fopen(const char *pathname, const char *mode) { @@ -408,23 +328,23 @@ int swoole_coroutine_feof(FILE *stream) { return retval; } -int swoole_coroutine_fclose(FILE *stream) { +int swoole_coroutine_fflush(FILE *stream) { if (sw_unlikely(is_no_coro())) { - return fclose(stream); + return fflush(stream); } int retval = -1; - async([&]() { retval = fclose(stream); }); + async([&]() { retval = fflush(stream); }); return retval; } -int swoole_coroutine_flock(int fd, int operation) { +int swoole_coroutine_fclose(FILE *stream) { if (sw_unlikely(is_no_coro())) { - return flock(fd, operation); + return fclose(stream); } int retval = -1; - async([&]() { retval = flock(fd, operation); }); + async([&]() { retval = fclose(stream); }); return retval; } @@ -444,11 +364,7 @@ struct dirent *swoole_coroutine_readdir(DIR *dirp) { } struct dirent *retval; - - async([&retval, dirp]() { - retval = readdir(dirp); - }); - + async([&retval, dirp]() { retval = readdir(dirp); }); return retval; } @@ -471,16 +387,16 @@ void swoole_coroutine_usleep(int usec) { } int swoole_coroutine_socket_set_timeout(int sockfd, int which, double timeout) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { errno = EINVAL; return -1; } if (which == SO_RCVTIMEO) { - socket->set_timeout(timeout, Socket::TIMEOUT_READ); + socket->set_timeout(timeout, SW_TIMEOUT_READ); return 0; } else if (which == SO_SNDTIMEO) { - socket->set_timeout(timeout, Socket::TIMEOUT_WRITE); + socket->set_timeout(timeout, SW_TIMEOUT_WRITE); return 0; } else { errno = EINVAL; @@ -489,40 +405,39 @@ int swoole_coroutine_socket_set_timeout(int sockfd, int which, double timeout) { } int swoole_coroutine_socket_set_connect_timeout(int sockfd, double timeout) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { errno = EINVAL; return -1; } - socket->set_timeout(timeout, Socket::TIMEOUT_DNS | Socket::TIMEOUT_CONNECT); + socket->set_timeout(timeout, SW_TIMEOUT_DNS | SW_TIMEOUT_CONNECT); return 0; } int swoole_coroutine_socket_wait_event(int sockfd, int event, double timeout) { - Socket *socket = get_socket_ex(sockfd); - if (sw_unlikely(socket == NULL)) { - errno = EINVAL; - return -1; - } - double ori_timeout = socket->get_timeout(event == SW_EVENT_READ ? Socket::TIMEOUT_READ : Socket::TIMEOUT_WRITE); + auto socket = get_socket_ex(sockfd); + if (sw_unlikely(socket == nullptr)) { + pollfd poll_ev{}; + poll_ev.fd = sockfd; + poll_ev.events = translate_events_to_poll(event); + return poll(&poll_ev, 1, (int) (timeout * 1000)) == 1 ? SW_OK : SW_ERR; + } + double ori_timeout = socket->get_timeout(event == SW_EVENT_READ ? SW_TIMEOUT_READ : SW_TIMEOUT_WRITE); socket->set_timeout(timeout); - bool retval = socket->poll((enum swEventType) event); + bool retval = socket->poll(static_cast(event)); socket->set_timeout(ori_timeout); return retval ? SW_OK : SW_ERR; } -int swoole_coroutine_getaddrinfo(const char *name, - const char *service, - const struct addrinfo *req, - struct addrinfo **pai) { +int swoole_coroutine_getaddrinfo(const char *name, const char *service, const addrinfo *req, addrinfo **pai) { int retval = -1; async([&]() { retval = getaddrinfo(name, service, req, pai); }); return retval; } -struct hostent *swoole_coroutine_gethostbyname(const char *name) { - struct hostent *retval = nullptr; - int _tmp_h_errno; +hostent *swoole_coroutine_gethostbyname(const char *name) { + hostent *retval = nullptr; + int _tmp_h_errno = 0; async([&]() { retval = gethostbyname(name); _tmp_h_errno = h_errno; @@ -531,4 +446,275 @@ struct hostent *swoole_coroutine_gethostbyname(const char *name) { return retval; } +int swoole_coroutine_open(const char *pathname, int flags, mode_t mode) { + if (sw_unlikely(is_no_coro())) { + return open(pathname, flags, mode); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = open(pathname, flags, mode); }); + return ret; +#else + return Iouring::open(pathname, flags, mode); +#endif +} + +int swoole_coroutine_close(int sockfd) { + if (sw_unlikely(is_no_coro())) { + return close(sockfd); + } + + auto socket = get_socket(sockfd); + if (socket != nullptr) { + if (socket->close()) { + std::unique_lock _lock(socket_map_lock); + socket_map.erase(sockfd); + return 0; + } + return -1; + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = close(sockfd); }); + return ret; +#else + return Iouring::close(sockfd); +#endif +} + +ssize_t swoole_coroutine_read(int sockfd, void *buf, size_t count) { + if (sw_unlikely(is_no_coro())) { + return read(sockfd, buf, count); + } + + auto socket = get_socket(sockfd); + if (socket != nullptr) { + return socket->read(buf, count); + } + +#ifdef SW_USE_ASYNC + ssize_t ret = -1; + NetSocket sock = {}; + sock.fd = sockfd; + sock.nonblock = 1; + sock.read_timeout = -1; + async([&]() { ret = sock.read_sync(buf, count); }); + return ret; +#else + return Iouring::read(sockfd, buf, count); +#endif +} + +ssize_t swoole_coroutine_write(int sockfd, const void *buf, size_t count) { + if (sw_unlikely(is_no_coro())) { + return write(sockfd, buf, count); + } + + auto socket = get_socket(sockfd); + if (socket != nullptr) { + return socket->write(buf, count); + } + +#ifdef SW_USE_ASYNC + ssize_t ret = -1; + NetSocket sock = {}; + sock.fd = sockfd; + sock.nonblock = 1; + sock.write_timeout = -1; + async([&]() { ret = sock.write_sync(buf, count); }); + return ret; +#else + return Iouring::write(sockfd, buf, count); +#endif +} + +int swoole_coroutine_fstat(int fd, struct stat *statbuf) { + if (sw_unlikely(is_no_coro())) { + return fstat(fd, statbuf); + } + +#if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) + int ret = -1; + async([&]() { ret = fstat(fd, statbuf); }); + return ret; +#else + return Iouring::fstat(fd, statbuf); +#endif +} + +int swoole_coroutine_stat(const char *path, struct stat *statbuf) { + if (sw_unlikely(is_no_coro())) { + return stat(path, statbuf); + } + +#if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) + int ret = -1; + async([&]() { ret = stat(path, statbuf); }); + return ret; +#else + return Iouring::stat(path, statbuf); +#endif +} + +int swoole_coroutine_lstat(const char *path, struct stat *statbuf) { + if (sw_unlikely(is_no_coro())) { + return lstat(path, statbuf); + } + +#if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_STATX) + int ret = -1; + async([&]() { ret = lstat(path, statbuf); }); + return ret; +#else + return Iouring::stat(path, statbuf); +#endif +} + +int swoole_coroutine_unlink(const char *pathname) { + if (sw_unlikely(is_no_coro())) { + return unlink(pathname); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = unlink(pathname); }); + return ret; +#else + return Iouring::unlink(pathname); +#endif +} + +int swoole_coroutine_mkdir(const char *pathname, mode_t mode) { + if (sw_unlikely(is_no_coro())) { + return mkdir(pathname, mode); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = mkdir(pathname, mode); }); + return ret; +#else + return Iouring::mkdir(pathname, mode); +#endif +} + +int swoole_coroutine_rmdir(const char *pathname) { + if (sw_unlikely(is_no_coro())) { + return rmdir(pathname); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = rmdir(pathname); }); + return ret; +#else + return Iouring::rmdir(pathname); +#endif +} + +int swoole_coroutine_rename(const char *oldpath, const char *newpath) { + if (sw_unlikely(is_no_coro())) { + return rename(oldpath, newpath); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = rename(oldpath, newpath); }); + return ret; +#else + return Iouring::rename(oldpath, newpath); +#endif +} + +int swoole_coroutine_fsync(int fd) { + if (sw_unlikely(is_no_coro())) { + return fsync(fd); + } + +#ifdef SW_USE_ASYNC + int ret = -1; + async([&]() { ret = fsync(fd); }); + return ret; +#else + return Iouring::fsync(fd); +#endif +} + +int swoole_coroutine_fdatasync(int fd) { + if (sw_unlikely(is_no_coro())) { +#ifdef HAVE_FDATASYNC + return fdatasync(fd); +#else + return fsync(fd); +#endif + } + +#ifdef SW_USE_ASYNC + int ret = -1; +#ifdef HAVE_FDATASYNC + async([&]() { ret = fdatasync(fd); }); +#else + async([&]() { ret = fsync(fd); }); +#endif + return ret; +#else + return Iouring::fdatasync(fd); +#endif +} + +int swoole_coroutine_ftruncate(int fd, off_t length) { + if (sw_unlikely(is_no_coro())) { + return ftruncate(fd, length); + } + +#if defined(SW_USE_ASYNC) || !defined(HAVE_IOURING_FTRUNCATE) + int ret = -1; + async([&]() { ret = ftruncate(fd, length); }); + return ret; +#else + return Iouring::ftruncate(fd, length); +#endif +} + +off_t swoole_coroutine_lseek(int fd, off_t offset, int whence) { + if (sw_unlikely(is_no_coro())) { + return lseek(fd, offset, whence); + } + + off_t ret = -1; + async([&]() { ret = lseek(fd, offset, whence); }); + return ret; +} + +ssize_t swoole_coroutine_readlink(const char *pathname, char *buf, size_t len) { + if (sw_unlikely(is_no_coro())) { + return readlink(pathname, buf, len); + } + + ssize_t ret = -1; + async([&]() { ret = readlink(pathname, buf, len); }); + return ret; +} + +int swoole_coroutine_statvfs(const char *path, struct statvfs *buf) { + if (sw_unlikely(is_no_coro())) { + return statvfs(path, buf); + } + + int ret = -1; + async([&]() { ret = statvfs(path, buf); }); + return ret; +} + +int swoole_coroutine_access(const char *pathname, int mode) { + if (sw_unlikely(is_no_coro())) { + return access(pathname, mode); + } + + int ret = -1; + async([&]() { ret = access(pathname, mode); }); + return ret; +} SW_EXTERN_C_END diff --git a/src/coroutine/iouring.cc b/src/coroutine/iouring.cc new file mode 100644 index 0000000000..cca2e03437 --- /dev/null +++ b/src/coroutine/iouring.cc @@ -0,0 +1,856 @@ +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author NathanFreeman | + | Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "swoole_iouring.h" +#include "swoole_coroutine_system.h" + +#ifdef SW_USE_IOURING +#ifdef HAVE_IOURING_FUTEX +#ifndef FUTEX2_SIZE_U32 +#define FUTEX2_SIZE_U32 0x02 +#endif +#include +#endif + +#if IO_URING_VERSION_MAJOR < 2 || (IO_URING_VERSION_MAJOR == 2 && IO_URING_VERSION_MINOR < 8) +#error "The version of liburing required must be greater than or equal to 2.8." +#endif + +#include + +#define DOUBLE_TO_TIMESPEC(seconds, ts) \ + do { \ + double __int_part; \ + double __frac_part = modf((seconds), &__int_part); \ + (ts)->tv_sec = (__kernel_time64_t) __int_part; \ + (ts)->tv_nsec = (long long) (__frac_part * 1000000000.0); \ + if ((ts)->tv_nsec >= 1000000000) { \ + (ts)->tv_sec += 1; \ + (ts)->tv_nsec = 0; \ + } \ + } while (0) + +#define TIMEOUT_EVENT (-1) + +using swoole::Coroutine; +using swoole::coroutine::System; + +namespace swoole { +//------------------------------------------------------------------------------- +enum IouringEventFlag { + SW_URING_TIMEOUT = 1U << 0, +}; + +struct IouringEvent { + Coroutine *coroutine; + io_uring_sqe data; + ssize_t result; + IouringTimeout timeout; + int flags; + + void set_timeout(double seconds) { + if (seconds > 0) { + flags |= SW_URING_TIMEOUT; + DOUBLE_TO_TIMESPEC(seconds, &timeout); + } + } +}; + +static void parse_kernel_version(const char *release, int *major, int *minor) { + char copy[SW_STRUCT_MEMBER_SIZE(utsname, release)]; + strcpy(copy, release); + + char *token = strtok(copy, ".-"); + *major = token ? sw_atoi(token) : 0; + + token = strtok(nullptr, ".-"); + *minor = token ? sw_atoi(token) : 0; +} + +Iouring::Iouring(Reactor *_reactor) { + reactor = _reactor; + if (SwooleG.iouring_entries > 0) { + uint32_t i = 6; + while ((1U << i) < SwooleG.iouring_entries) { + i++; + } + entries = 1 << i; + } + + int ret = + io_uring_queue_init(entries, &ring, (SwooleG.iouring_flag == IORING_SETUP_SQPOLL ? IORING_SETUP_SQPOLL : 0)); + if (ret < 0) { + errno = -ret; + swoole_sys_error("Failed to initialize io_uring instance"); + return; + } + + if (SwooleG.iouring_workers > 0) { + uint32_t workers[2] = {SwooleG.iouring_workers, SwooleG.iouring_workers}; + ret = io_uring_register_iowq_max_workers(&ring, workers); + if (ret < 0) { + errno = -ret; + swoole_sys_error("Failed to set the maximum of io_uring async workers"); + return; + } + } + + int major, minor; + parse_kernel_version(SwooleG.uname.release, &major, &minor); + +#ifdef HAVE_IOURING_FUTEX + if (!(major >= 6 && minor >= 7)) { + swoole_error("The Iouring::futex_wait()/Iouring::futex_wakeup() requires `6.7` or higher Linux kernel"); + } +#endif + +#ifdef HAVE_IOURING_FTRUNCATE + if (!(major >= 6 && minor >= 9)) { + swoole_error("The Iouring::ftruncate() requires `6.9` or higher Linux kernel"); + } +#endif + + ring_socket = make_socket(ring.ring_fd, SW_FD_IOURING); + ring_socket->object = this; + + reactor->set_exit_condition(Reactor::EXIT_CONDITION_IOURING, [](Reactor *reactor, size_t &event_num) -> bool { + if (SwooleTG.iouring && SwooleTG.iouring->get_task_num() == 0 && SwooleTG.iouring->is_empty_waiting_tasks()) { + event_num--; + } + return true; + }); + + reactor->add_destroy_callback([](void *data) { + if (!SwooleTG.iouring) { + return; + } + delete SwooleTG.iouring; + SwooleTG.iouring = nullptr; + }); + + reactor->set_end_callback(Reactor::PRIORITY_IOURING_SUBMIT, [](Reactor *reactor) { + if (!SwooleTG.iouring) { + return; + } + SwooleTG.iouring->submit(true); + }); + + reactor->iouring_interrupt_handler = [this](Reactor *reactor) { wakeup(); }; + + if (reactor->add(ring_socket, SW_EVENT_READ) == SW_ERR) { + swoole_sys_error("Failed to add io_uring ring fd to the event loop"); + } +} + +Iouring::~Iouring() { + if (!ring_socket) { + return; + } + + if (!ring_socket->removed) { + reactor->del(ring_socket); + } + ring_socket->move_fd(); + ring_socket->free(); + ring_socket = nullptr; + + io_uring_queue_exit(&ring); +} + +bool Iouring::ready() const { + return ring_socket && reactor->exists(ring_socket); +} + +void Iouring::yield(IouringEvent *event) { + ++task_num; + Coroutine::CancelFunc cancel_fn = [event, this](Coroutine *) { return cancel(event); }; + event->coroutine->yield(&cancel_fn); +} + +void Iouring::resume(IouringEvent *event) { + --task_num; + event->coroutine->resume(); +} + +bool Iouring::wakeup() { + IouringEvent *waiting_task = nullptr; + + while (true) { + auto count = io_uring_peek_batch_cqe(&ring, cqes, SW_IOURING_QUEUE_SIZE); + if (count == 0) { + break; + } + + uint32_t ready_count = 0; + SW_LOOP_N(count) { + auto cqe = cqes[i]; + auto event = static_cast(io_uring_cqe_get_data(cqe)); + // The user data for the timeout request is -1, this event should be ignored. + if (event == reinterpret_cast(TIMEOUT_EVENT)) { + swoole_trace( + "timeout, cqe.flags=%d, ceq.res=%d, error=`%s`", cqe->flags, cqe->res, strerror(-cqe->res)); + continue; + } + + event->result = cqe->res; + ready_events[ready_count++] = event; + + swoole_trace("opcode=%s, cqe.flags=%d, ceq.res=%d, error=`%s`", + get_opcode_name((io_uring_op) event->data.opcode), + cqe->flags, + cqe->res, + strerror(cqe->res < 0 ? errno : 0)); + } + io_uring_cq_advance(&ring, count); + + /** + * After the harvest is completed, the operating system's unprocessed task queue is reduced. + * Before resuming the coroutine ready for IO, it should extract the queued SQE to the SQEs queue for processing + * by the operating system. This can achieve a relatively good balance. If the submission is made after resuming + * the coroutine, the kernel may become idle. + */ + SW_LOOP_N(ready_count) { + if (get_sq_space_left() == 0 || is_empty_waiting_tasks()) { + break; + } + waiting_task = waiting_tasks.front(); + waiting_tasks.pop(); + dispatch(waiting_task); + } + + SW_LOOP_N(ready_count) { + auto event = ready_events[i]; + if (event->result < 0) { + errno = -(event->result); + /** + * After a timeout, iouring will set errno to `ECANCELED`, but in the async implementation, + * the errno after a timeout is `ETIMEDOUT`. + * To maintain compatibility, numerical conversion is necessary. + */ + errno = errno == ECANCELED ? ETIMEDOUT : errno; + event->result = -1; + } + resume(ready_events[i]); + } + } + + return true; +} + +const char *Iouring::get_opcode_name(io_uring_op opcode) { + switch (opcode) { + case IORING_OP_SOCKET: + return "SOCKET"; + case IORING_OP_OPENAT: + return "OPENAT"; + case IORING_OP_ACCEPT: + return "ACCEPT"; + case IORING_OP_CONNECT: + return "CONNECT"; + case IORING_OP_BIND: + return "BIND"; + case IORING_OP_LISTEN: + return "LISTEN"; + case IORING_OP_SEND: + return "SEND"; + case IORING_OP_RECV: + return "RECV"; + case IORING_OP_READV: + return "READV"; + case IORING_OP_WRITEV: + return "WRITEV"; + case IORING_OP_SENDMSG: + return "SENDMSG"; + case IORING_OP_RECVMSG: + return "RECVMSG"; + case IORING_OP_SHUTDOWN: + return "SHUTDOWN"; + case IORING_OP_CLOSE: + return "CLOSE"; + case IORING_OP_STATX: + return "STATX"; + case IORING_OP_READ: + return "READ"; + case IORING_OP_WRITE: + return "WRITE"; + case IORING_OP_RENAMEAT: + return "RENAMEAT"; + case IORING_OP_MKDIRAT: + return "MKDIRAT"; + case IORING_OP_UNLINKAT: + return "UNLINKAT"; + case IORING_OP_FSYNC: + return "FSYNC"; +#ifdef HAVE_IOURING_FUTEX + case IORING_OP_FUTEX_WAIT: + return "FUTEX_WAIT"; + case IORING_OP_FUTEX_WAKE: + return "FUTEX_WAKE"; +#endif +#ifdef HAVE_IOURING_FTRUNCATE + case IORING_OP_FTRUNCATE: + return "FTRUNCATE"; +#endif + case IORING_OP_POLL_ADD: + return "POLL_ADD"; + case IORING_OP_POLL_REMOVE: + return "POLL_REMOVE"; + case IORING_OP_ASYNC_CANCEL: + return "CANCEL"; + default: + return "unknown"; + } +} + +std::unordered_map Iouring::list_all_opcode() { + std::unordered_map opcodes; + for (int i = IORING_OP_NOP; i < IORING_OP_LAST; i++) { + auto name = get_opcode_name((io_uring_op) i); + if (strcmp(name, "unknown") == 0) { + continue; + } + opcodes[name] = i; + } + return opcodes; +} + +void Iouring::submit(bool immediately) { + /** + * Submit SQEs in batches to reduce the number of system calls. + * If the number of SQEs is still less than SW_IOURING_SQE_BATCH_SIZE at the end of this event loop, + * it will be automatically submitted at the end of the event loop. + */ + if (!immediately && swoole_event_is_running() && get_sq_used() < SW_IOURING_SQE_BATCH_SIZE) { + return; + } + + do { + /** + * If the value returned by io_uring_submit() is less than the current length of the SQE queue, + * it indicates that only a part of the SQEs has been submitted and needs to be retried after the next event + * loop. + */ + int ret = io_uring_submit(&ring); + if (ret < 0) { + /** + * Returned EAGAIN error, ignoring this error, will retry on the next submit. + */ + if (-ret == EAGAIN) { + return; + } else if (-ret == EBUSY) { + usleep(10000); + continue; + } else if (-ret == EINTR) { + continue; + } else { + swoole_sys_error("io_uring_submit() failed"); + return; + } + } + break; + } while (true); +} + +Iouring *Iouring::get_instance() { + if (sw_unlikely(!SwooleTG.iouring)) { + if (!swoole_event_is_available()) { + swoole_error("The event loop is unavailable, unable to create io_uring instance."); + } + SwooleTG.iouring = new Iouring(sw_reactor()); + } + return SwooleTG.iouring; +} + +ssize_t Iouring::execute(IouringEvent *event) { + auto iouring = get_instance(); + iouring->dispatch(event); + iouring->yield(event); + return event->result; +} + +void Iouring::dispatch(IouringEvent *event) { + io_uring_sqe *sqe = alloc_sqe(); + if (sw_unlikely(!sqe)) { + waiting_tasks.push(event); + return; + } + + swoole_trace("opcode=%s, timeout[tv_sec=%ld, tv_nsec=%ld]", + get_opcode_name((io_uring_op) event->data.opcode), + event->timeout.tv_sec, + event->timeout.tv_nsec); + + memcpy(sqe, &event->data, sizeof(event->data)); + io_uring_sqe_set_data(sqe, (void *) event); + + if (event->flags & SW_URING_TIMEOUT) { + auto timeout_sqe = alloc_sqe(); + if (sw_likely(timeout_sqe)) { + memset(timeout_sqe, 0, sizeof(*timeout_sqe)); + io_uring_prep_link_timeout(timeout_sqe, reinterpret_cast<__kernel_timespec *>(&event->timeout), 0); + io_uring_sqe_set_data(timeout_sqe, reinterpret_cast(TIMEOUT_EVENT)); + sqe->flags |= IOSQE_IO_LINK; + } else { + swoole_warning("timeout setting failed, the iouring queue[%d] is full", ring.ring_fd); + } + } + + submit(false); +} + +#define INIT_EVENT(op) \ + IouringEvent event{}; \ + event.coroutine = Coroutine::get_current_safe(); + +int Iouring::open(const char *pathname, int flags, mode_t mode) { + INIT_EVENT(IORING_OP_OPENAT); + io_uring_prep_open(&event.data, pathname, flags | O_CLOEXEC, mode); + return static_cast(execute(&event)); +} + +bool Iouring::cancel(IouringEvent *prev_event) { + INIT_EVENT(IORING_OP_ASYNC_CANCEL); + io_uring_prep_cancel(&event.data, (void *) prev_event, 0); + return static_cast(execute(&event)) == 0; +} + +int Iouring::socket(int domain, int type, int protocol, int flags) { + INIT_EVENT(IORING_OP_SOCKET); + io_uring_prep_socket(&event.data, domain, type, protocol, flags); + return static_cast(execute(&event)); +} + +int Iouring::connect(int fd, const struct sockaddr *addr, socklen_t len, double timeout) { + INIT_EVENT(IORING_OP_CONNECT); + io_uring_prep_connect(&event.data, fd, addr, len); + event.set_timeout(timeout); + return static_cast(execute(&event)); +} + +int Iouring::bind(int fd, const struct sockaddr *addr, socklen_t len) { +#if 1 + return ::bind(fd, addr, len); +#else + INIT_EVENT(IORING_OP_BIND); + io_uring_prep_bind(&event.data, fd, (struct sockaddr *) addr, len); + return static_cast(execute(&event)); +#endif +} + +int Iouring::listen(int fd, int backlog) { +#if 1 + return ::listen(fd, backlog); +#else + io_uring_prep_listen(sqe, fd, backlog); +#endif +} + +int Iouring::sleep(double seconds) { + IouringTimeout ts; + DOUBLE_TO_TIMESPEC(seconds, &ts); + return sleep(ts.tv_sec, ts.tv_nsec, 0); +} + +int Iouring::sleep(int tv_sec, int tv_nsec, int flags) { + struct __kernel_timespec ts { + tv_sec, tv_nsec, + }; + + INIT_EVENT(IORING_OP_TIMEOUT); + io_uring_prep_timeout(&event.data, &ts, 0, flags); + execute(&event); + return errno == ETIME ? SW_OK : SW_ERR; +} + +int Iouring::accept(int fd, struct sockaddr *addr, socklen_t *len, int flags, double timeout) { + INIT_EVENT(IORING_OP_ACCEPT); + io_uring_prep_accept(&event.data, fd, addr, len, flags); + event.set_timeout(timeout); + return static_cast(execute(&event)); +} + +ssize_t Iouring::recv(int fd, void *buf, size_t len, int flags, double timeout) { + INIT_EVENT(IORING_OP_RECV); + io_uring_prep_recv(&event.data, fd, buf, len, flags); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::send(int fd, const void *buf, size_t len, int flags, double timeout) { + INIT_EVENT(IORING_OP_SEND); + io_uring_prep_send(&event.data, fd, buf, len, flags); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::recvmsg(int fd, struct msghdr *message, int flags, double timeout) { + INIT_EVENT(IORING_OP_RECVMSG); + io_uring_prep_recvmsg(&event.data, fd, message, flags); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::sendmsg(int fd, const struct msghdr *message, int flags, double timeout) { + INIT_EVENT(IORING_OP_SENDMSG); + io_uring_prep_sendmsg(&event.data, fd, message, flags); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::sendto( + int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t len, double timeout) { + INIT_EVENT(IORING_OP_SENDTO); + io_uring_prep_sendto(&event.data, fd, buf, n, flags, addr, len); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::recvfrom(int fd, void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen, double timeout) { + auto rv = recv(fd, _buf, _n, MSG_PEEK, timeout); + if (rv > 0) { + return ::recvfrom(fd, _buf, _n, MSG_DONTWAIT, _addr, _socklen); + } else { + return rv; + } +} + +ssize_t Iouring::readv(int fd, const struct iovec *iovec, int count, double timeout) { + INIT_EVENT(IORING_OP_READV); + io_uring_prep_readv(&event.data, fd, iovec, count, -1); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::writev(int fd, const struct iovec *iovec, int count, double timeout) { + INIT_EVENT(IORING_OP_WRITEV); + io_uring_prep_writev(&event.data, fd, iovec, count, -1); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::sendfile(int out_fd, int in_fd, off_t *offset, size_t size, double timeout) { + if (size == 0) { + return 0; + } + + INIT_EVENT(IORING_OP_SPLICE); + event.set_timeout(timeout); + +#ifndef MSG_SPLICE_PAGES + int pipe_fds[2]; + if (pipe(pipe_fds) < 0) { + return -1; + } + + fcntl(pipe_fds[1], F_SETPIPE_SZ, 1024 * 1024); + int pipe_size = fcntl(pipe_fds[0], F_GETPIPE_SZ); + if (pipe_size < 0) { + pipe_size = 65536; + } + + size_t total_sent = 0; + off_t current_offset = offset ? *offset : 0; + + while (total_sent < size) { + size_t remaining = size - total_sent; + size_t to_send = std::min(remaining, (size_t) pipe_size); + + event.data = {}; + io_uring_prep_splice( + &event.data, in_fd, offset ? (current_offset + total_sent) : -1, pipe_fds[1], -1, to_send, 0); + + ssize_t ret1 = execute(&event); + if (ret1 <= 0) { + if (total_sent > 0) { + break; + } + int saved_errno = errno; + close(pipe_fds[0]); + close(pipe_fds[1]); + errno = saved_errno; + return -1; + } + + event.data = {}; + io_uring_prep_splice(&event.data, pipe_fds[0], -1, out_fd, -1, ret1, 0); + + ssize_t ret2 = execute(&event); + if (ret2 <= 0) { + if (total_sent > 0) { + break; + } + int saved_errno = errno; + close(pipe_fds[0]); + close(pipe_fds[1]); + errno = saved_errno; + return -1; + } + + total_sent += ret2; + + if (ret2 < ret1) { + break; + } + } + + close(pipe_fds[0]); + close(pipe_fds[1]); + + if (total_sent > 0 && offset) { + *offset += total_sent; + } + + return total_sent; + +#else + struct msghdr msg = {0}; + struct iovec iov = {0}; + + iov.iov_base = NULL; + iov.iov_len = size; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + io_uring_prep_sendmsg(&event.data, out_fd, &msg, MSG_SPLICE_PAGES); + + event.data.len = size; + event.data.splice_fd_in = in_fd; + event.data.addr2 = offset ? (__u64) *offset : (__u64) -1; + + ssize_t ret = execute(&event); + + if (ret > 0 && offset) { + *offset += ret; + } + + return ret; +#endif +} + +int Iouring::shutdown(int fd, int how) { + INIT_EVENT(IORING_OP_SHUTDOWN); + io_uring_prep_shutdown(&event.data, fd, how); + return static_cast(execute(&event)); +} + +int Iouring::close(int fd) { + INIT_EVENT(IORING_OP_CLOSE); + io_uring_prep_close(&event.data, fd); + return static_cast(execute(&event)); +} + +ssize_t Iouring::read(int fd, void *buf, size_t size, double timeout) { + INIT_EVENT(IORING_OP_READ); + io_uring_prep_read(&event.data, fd, buf, size, -1); + event.set_timeout(timeout); + return execute(&event); +} + +ssize_t Iouring::write(int fd, const void *buf, size_t size, double timeout) { + INIT_EVENT(IORING_OP_WRITE); + io_uring_prep_write(&event.data, fd, buf, size, -1); + event.set_timeout(timeout); + return execute(&event); +} + +int Iouring::rename(const char *oldpath, const char *newpath) { + INIT_EVENT(IORING_OP_RENAMEAT); + io_uring_prep_rename(&event.data, oldpath, newpath); + return static_cast(execute(&event)); +} + +int Iouring::mkdir(const char *pathname, mode_t mode) { + INIT_EVENT(IORING_OP_MKDIRAT); + io_uring_prep_mkdir(&event.data, pathname, mode); + return static_cast(execute(&event)); +} + +int Iouring::unlink(const char *pathname) { + INIT_EVENT(IORING_OP_UNLINK_FILE); + io_uring_prep_unlink(&event.data, pathname, 0); + return static_cast(execute(&event)); +} + +int Iouring::rmdir(const char *pathname) { + INIT_EVENT(IORING_OP_UNLINK_DIR); + io_uring_prep_unlink(&event.data, pathname, AT_REMOVEDIR); + return static_cast(execute(&event)); +} + +int Iouring::fsync(int fd) { + INIT_EVENT(IORING_OP_FSYNC); + io_uring_prep_fsync(&event.data, fd, 0); + return static_cast(execute(&event)); +} + +int Iouring::fdatasync(int fd) { + INIT_EVENT(IORING_OP_FDATASYNC); + io_uring_prep_fsync(&event.data, fd, IORING_FSYNC_DATASYNC); + return static_cast(execute(&event)); +} + +#ifdef HAVE_IOURING_FTRUNCATE +int Iouring::ftruncate(int fd, off_t length) { + INIT_EVENT(IORING_OP_FTRUNCATE); + io_uring_prep_ftruncate(&event.data, fd, length); + return static_cast(execute(&event)); +} +#endif + +static inline int siginfo_to_status(const siginfo_t *info) { + int status = 0; + + switch (info->si_code) { + case CLD_EXITED: + status = (info->si_status & 0xFF) << 8; + break; + case CLD_KILLED: + status = info->si_status & 0x7F; + break; + case CLD_DUMPED: + status = (info->si_status & 0x7F) | 0x80; + break; + case CLD_STOPPED: + status = ((info->si_status & 0xFF) << 8) | 0x7F; + break; + case CLD_CONTINUED: + status = 0xFFFF; + break; + } + + return status; +} + +#ifdef HAVE_IOURING_STATX +static void swoole_statx_to_stat(const struct statx *statxbuf, struct stat *statbuf) { + statbuf->st_dev = (((unsigned int) statxbuf->stx_dev_major) << 8) | (unsigned int) statxbuf->stx_dev_minor; + statbuf->st_mode = statxbuf->stx_mode; + statbuf->st_nlink = statxbuf->stx_nlink; + statbuf->st_uid = statxbuf->stx_uid; + statbuf->st_gid = statxbuf->stx_gid; + statbuf->st_rdev = (((unsigned int) statxbuf->stx_rdev_major) << 8) | (unsigned int) statxbuf->stx_rdev_minor; + statbuf->st_ino = statxbuf->stx_ino; + statbuf->st_size = statxbuf->stx_size; + statbuf->st_blksize = statxbuf->stx_blksize; + statbuf->st_blocks = statxbuf->stx_blocks; + statbuf->st_atim.tv_sec = statxbuf->stx_atime.tv_sec; + statbuf->st_atim.tv_nsec = statxbuf->stx_atime.tv_nsec; + statbuf->st_mtim.tv_sec = statxbuf->stx_mtime.tv_sec; + statbuf->st_mtim.tv_nsec = statxbuf->stx_mtime.tv_nsec; + statbuf->st_ctim.tv_sec = statxbuf->stx_ctime.tv_sec; + statbuf->st_ctim.tv_nsec = statxbuf->stx_ctime.tv_nsec; +} + +int Iouring::fstat(int fd, struct stat *statbuf) { + struct statx statxbuf; + INIT_EVENT(IORING_OP_FSTAT); + io_uring_prep_statx(&event.data, fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS | STATX_BTIME, &statxbuf); + + auto retval = execute(&event); + if (retval == 0) { + swoole_statx_to_stat(&statxbuf, statbuf); + } + return retval; +} + +int Iouring::stat(const char *path, struct stat *statbuf) { + struct statx statxbuf; + INIT_EVENT(IORING_OP_FSTAT); + io_uring_prep_statx( + &event.data, AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, &statxbuf); + + auto retval = execute(&event); + if (retval == 0) { + swoole_statx_to_stat(&statxbuf, statbuf); + } + return retval; +} +#endif + +#ifdef HAVE_IOURING_FUTEX +int Iouring::futex_wait(uint32_t *futex) { + INIT_EVENT(IORING_OP_FUTEX_WAIT); + io_uring_prep_futex_wait(&event.data, futex, 1, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0); + return static_cast(execute(&event)); +} + +int Iouring::futex_wakeup(uint32_t *futex) { + INIT_EVENT(IORING_OP_FUTEX_WAKE); + io_uring_prep_futex_wake(&event.data, futex, 1, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0); + return static_cast(execute(&event)); +} +#endif + +pid_t Iouring::wait(int *stat_loc, double timeout) { + return waitpid(-1, stat_loc, 0, timeout); +} + +pid_t Iouring::waitpid(pid_t _pid, int *stat_loc, int options, double timeout) { + if (options & WNOHANG) { + return ::waitpid(_pid, stat_loc, options); + } + + INIT_EVENT(IORING_OP_WAITID); + siginfo_t info{}; + idtype_t idtype = _pid > 0 ? P_PID : P_ALL; + id_t id = _pid > 0 ? _pid : 0; + options = options == 0 ? WEXITED : options; + io_uring_prep_waitid(&event.data, idtype, id, &info, options, 0); + + event.set_timeout(timeout); + int rc = static_cast(execute(&event)); + if (rc != -1) { + *stat_loc = siginfo_to_status(&info); + return info.si_pid; + } + return rc; +} + +int Iouring::poll(struct pollfd *fds, nfds_t nfds, int timeout) { + if (nfds != 1) { + errno = EINVAL; + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "incomplete poll() implementation, only supports one fd"); + return -1; + } + + if (timeout == 0) { + return ::poll(fds, nfds, timeout); + } + + INIT_EVENT(IORING_OP_POLL); + io_uring_prep_poll_add(&event.data, fds[0].fd, fds[0].events); + event.set_timeout((double) timeout / 1000); + + int rc = static_cast(execute(&event)); + if (rc > 0) { + fds[0].revents = rc; + return 1; + } + return rc; +} + +int Iouring::callback(Reactor *reactor, Event *event) { + auto *iouring = static_cast(event->socket->object); + return iouring->wakeup() ? SW_OK : SW_ERR; +} +} // namespace swoole +#endif diff --git a/src/coroutine/socket.cc b/src/coroutine/socket.cc index 799e779b5d..577dc0bd32 100644 --- a/src/coroutine/socket.cc +++ b/src/coroutine/socket.cc @@ -20,7 +20,6 @@ #include "swoole_string.h" #include "swoole_util.h" #include "swoole_reactor.h" -#include "swoole_buffer.h" #include "swoole_base64.h" #include "swoole_coroutine_socket.h" @@ -28,11 +27,10 @@ namespace swoole { namespace coroutine { - -enum Socket::TimeoutType Socket::timeout_type_list[4] = {TIMEOUT_DNS, TIMEOUT_CONNECT, TIMEOUT_READ, TIMEOUT_WRITE}; +TimeoutType Socket::timeout_type_list[4] = {SW_TIMEOUT_DNS, SW_TIMEOUT_CONNECT, SW_TIMEOUT_READ, SW_TIMEOUT_WRITE}; void Socket::timer_callback(Timer *timer, TimerNode *tnode) { - Socket *socket = (Socket *) tnode->data; + auto *socket = static_cast(tnode->data); socket->set_err(ETIMEDOUT); if (sw_likely(tnode == socket->read_timer)) { socket->read_timer = nullptr; @@ -46,16 +44,13 @@ void Socket::timer_callback(Timer *timer, TimerNode *tnode) { } int Socket::readable_event_callback(Reactor *reactor, Event *event) { - Socket *socket = (Socket *) event->socket->object; + auto *socket = static_cast(event->socket->object); socket->set_err(0); -#ifdef SW_USE_OPENSSL if (sw_unlikely(socket->want_event != SW_EVENT_NULL)) { if (socket->want_event == SW_EVENT_READ) { socket->write_co->resume(); } - } else -#endif - { + } else { if (socket->recv_barrier && (*socket->recv_barrier)() && !event->socket->event_hup) { return SW_OK; } @@ -66,16 +61,13 @@ int Socket::readable_event_callback(Reactor *reactor, Event *event) { } int Socket::writable_event_callback(Reactor *reactor, Event *event) { - Socket *socket = (Socket *) event->socket->object; + auto *socket = static_cast(event->socket->object); socket->set_err(0); -#ifdef SW_USE_OPENSSL if (sw_unlikely(socket->want_event != SW_EVENT_NULL)) { if (socket->want_event == SW_EVENT_WRITE) { socket->read_co->resume(); } - } else -#endif - { + } else { if (socket->send_barrier && (*socket->send_barrier)() && !event->socket->event_hup) { return SW_OK; } @@ -86,7 +78,7 @@ int Socket::writable_event_callback(Reactor *reactor, Event *event) { } int Socket::error_event_callback(Reactor *reactor, Event *event) { - Socket *socket = (Socket *) event->socket->object; + auto *socket = static_cast(event->socket->object); if (socket->write_co) { socket->set_err(0); socket->write_co->resume(); @@ -112,6 +104,28 @@ bool Socket::add_event(const EventType event) { return ret; } +#ifdef SW_LOG_TRACE_OPEN +static const char *get_trigger_event_name(Socket *socket, EventType added_event) { + if (socket->is_closed()) { + return "CLOSE"; + } + if (socket->errCode) { + return socket->errCode == ETIMEDOUT ? "TIMEOUT" : "ERROR"; + } + return added_event == SW_EVENT_READ ? "READ" : "WRITE"; +} + +static const char *get_wait_event_name(Socket *socket, EventType event) { + if (socket->get_socket()->ssl_want_read) { + return "SSL READ"; + } else if (socket->get_socket()->ssl_want_write) { + return "SSL WRITE"; + } else { + return event == SW_EVENT_READ ? "READ" : "WRITE"; + } +} +#endif + /** * If an exception occurs while waiting for an event, false is returned. * For example, when waiting for a read event, timeout, connection closed, are exceptions to the interrupt event. @@ -119,16 +133,32 @@ bool Socket::add_event(const EventType event) { * We only need to set the errCode for the socket operation when wait_event returns true, * which means that the exception's error code priority is greater than the current event error priority. */ -bool Socket::wait_event(const EventType event, const void **__buf, size_t __n) { +bool Socket::wait_event(const EventType event, const void **_buf, size_t _n) { EventType added_event = event; Coroutine *co = Coroutine::get_current_safe(); if (!co) { return false; } + if (sw_unlikely(socket->close_wait)) { + set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); + return false; + } + + if (socket->has_kernel_nobufs()) { + if (sw_likely(event == SW_EVENT_READ)) { + read_co = co; + System::sleep(0.01); + read_co = nullptr; + } else { + write_co = co; + System::sleep(0.01); + write_co = nullptr; + } + return !is_closed(); + } // clear the last errCode set_err(0); -#ifdef SW_USE_OPENSSL if (sw_unlikely(socket->ssl && ((event == SW_EVENT_READ && socket->ssl_want_write) || (event == SW_EVENT_WRITE && socket->ssl_want_read)))) { if (sw_likely(socket->ssl_want_write && add_event(SW_EVENT_WRITE))) { @@ -139,20 +169,14 @@ bool Socket::wait_event(const EventType event, const void **__buf, size_t __n) { return false; } added_event = want_event; - } else -#endif - if (sw_unlikely(!add_event(event))) { + } else if (sw_unlikely(!add_event(event))) { return false; } swoole_trace_log(SW_TRACE_SOCKET, "socket#%d blongs to cid#%ld is waiting for %s event", sock_fd, co->get_cid(), -#ifdef SW_USE_OPENSSL - socket->ssl_want_read ? "SSL READ" - : socket->ssl_want_write ? "SSL WRITE" : -#endif - event == SW_EVENT_READ ? "READ" : "WRITE"); + get_wait_event_name(this, event)); Coroutine::CancelFunc cancel_fn = [this, event](Coroutine *co) { return cancel(event); }; @@ -161,13 +185,10 @@ bool Socket::wait_event(const EventType event, const void **__buf, size_t __n) { read_co->yield(&cancel_fn); read_co = nullptr; } else if (event == SW_EVENT_WRITE) { - if (sw_unlikely(!zero_copy && __n > 0 && *__buf != get_write_buffer()->str)) { + if (sw_unlikely(!zero_copy && _n > 0 && *_buf != get_write_buffer()->str)) { write_buffer->clear(); - if (write_buffer->append((const char *) *__buf, __n) != SW_OK) { - set_err(ENOMEM); - goto _failed; - } - *__buf = write_buffer->str; + write_buffer->append(static_cast(*_buf), _n); + *_buf = write_buffer->str; } write_co = co; write_co->yield(&cancel_fn); @@ -176,12 +197,8 @@ bool Socket::wait_event(const EventType event, const void **__buf, size_t __n) { assert(0); return false; } -_failed: -#ifdef SW_USE_OPENSSL // maybe read_co and write_co are all waiting for the same event when we use SSL - if (sw_likely(want_event == SW_EVENT_NULL || !has_bound())) -#endif - { + if (sw_likely(want_event == SW_EVENT_NULL || !has_bound())) { Reactor *reactor = SwooleTG.reactor; if (sw_likely(added_event == SW_EVENT_READ)) { reactor->remove_read_event(socket); @@ -189,195 +206,51 @@ bool Socket::wait_event(const EventType event, const void **__buf, size_t __n) { reactor->remove_write_event(socket); } } -#ifdef SW_USE_OPENSSL want_event = SW_EVENT_NULL; -#endif swoole_trace_log(SW_TRACE_SOCKET, "socket#%d blongs to cid#%ld trigger %s event", sock_fd, co->get_cid(), - closed ? "CLOSE" - : errCode ? errCode == ETIMEDOUT ? "TIMEOUT" : "ERROR" - : added_event == SW_EVENT_READ ? "READ" : "WRITE"); - return !closed && !errCode; + get_trigger_event_name(this, added_event)); + return !is_closed() && !errCode; } bool Socket::socks5_handshake() { - Socks5Proxy *ctx = socks5_proxy; - char *p; - ssize_t n; - uchar version, method, result; - - Socks5Proxy::pack(ctx->buf, !socks5_proxy->username.empty() ? 0x02 : 0x00); - socks5_proxy->state = SW_SOCKS5_STATE_HANDSHAKE; - if (send(ctx->buf, 3) != 3) { - return false; - } - n = recv(ctx->buf, sizeof(ctx->buf)); - if (n <= 0) { + Socks5Proxy *ctx = socks5_proxy.get(); + const auto len = ctx->pack_negotiate_request(); + if (send(ctx->buf, len) < 0) { return false; } - version = ctx->buf[0]; - method = ctx->buf[1]; - if (version != SW_SOCKS5_VERSION_CODE) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return false; - } - if (method != ctx->method) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_METHOD, "SOCKS authentication method is not supported"); - return false; - } - // authentication - if (method == SW_SOCKS5_METHOD_AUTH) { - p = ctx->buf; - // username - p[0] = 0x01; - p[1] = ctx->username.length(); - p += 2; - if (!ctx->username.empty()) { - memcpy(p, ctx->username.c_str(), ctx->username.length()); - p += ctx->username.length(); - } - // password - p[0] = ctx->password.length(); - p += 1; - if (!ctx->password.empty()) { - memcpy(p, ctx->password.c_str(), ctx->password.length()); - p += ctx->password.length(); - } - // auth request - ctx->state = SW_SOCKS5_STATE_AUTH; - if (send(ctx->buf, p - ctx->buf) != p - ctx->buf) { - return false; - } - // auth response - n = recv(ctx->buf, sizeof(ctx->buf)); - if (n <= 0) { - return false; - } - uchar version = ctx->buf[0]; - uchar status = ctx->buf[1]; - if (version != 0x01) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return false; - } - if (status != 0x00) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS username/password authentication failed"); - return false; - } - } - // send connect request - ctx->state = SW_SOCKS5_STATE_CONNECT; - p = ctx->buf; - p[0] = SW_SOCKS5_VERSION_CODE; - p[1] = 0x01; - p[2] = 0x00; - p += 3; - if (ctx->dns_tunnel) { - p[0] = 0x03; - p[1] = ctx->target_host.length(); - p += 2; - memcpy(p, ctx->target_host.c_str(), ctx->target_host.length()); - p += ctx->target_host.length(); - *(uint16_t *) p = htons(ctx->target_port); - p += 2; - if (send(ctx->buf, p - ctx->buf) != p - ctx->buf) { - return false; - } - } else { - p[0] = 0x01; - p += 1; - *(uint32_t *) p = htons(ctx->target_host.length()); - p += 4; - *(uint16_t *) p = htons(ctx->target_port); - p += 2; - if (send(ctx->buf, p - ctx->buf) != p - ctx->buf) { - return false; + auto send_fn = [this](const char *buf, size_t len) { return send(buf, len); }; + char recv_buf[512]; + ctx->state = SW_SOCKS5_STATE_HANDSHAKE; + while (true) { + const ssize_t n = recv(recv_buf, sizeof(recv_buf)); + if (n > 0 && ctx->handshake(recv_buf, n, send_fn)) { + if (ctx->state == SW_SOCKS5_STATE_READY) { + return true; + } + continue; } + break; } - // recv response - n = recv(ctx->buf, sizeof(ctx->buf)); - if (n <= 0) { - return false; - } - version = ctx->buf[0]; - if (version != SW_SOCKS5_VERSION_CODE) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return false; - } - result = ctx->buf[1]; -#if 0 - uchar reg = buf[2]; - uchar type = buf[3]; - uint32_t ip = *(uint32_t *) (buf + 4); - uint16_t port = *(uint16_t *) (buf + 8); -#endif - if (result == 0) { - ctx->state = SW_SOCKS5_STATE_READY; - return true; - } else { - swoole_error_log(SW_LOG_NOTICE, - SW_ERROR_SOCKS5_SERVER_ERROR, - "Socks5 server error, reason: %s", - Socks5Proxy::strerror(result)); - return false; - } + return false; } bool Socket::http_proxy_handshake() { -#define HTTP_PROXY_FMT \ - "CONNECT %.*s:%d HTTP/1.1\r\n" \ - "Host: %.*s:%d\r\n" \ - "User-Agent: Swoole/" SWOOLE_VERSION "\r\n" \ - "Proxy-Connection: Keep-Alive\r\n" - - // CONNECT - int n; - const char *host = http_proxy->target_host.c_str(); - int host_len = http_proxy->target_host.length(); -#ifdef SW_USE_OPENSSL - if (ssl_context && !ssl_context->tls_host_name.empty()) { - host = ssl_context->tls_host_name.c_str(); - host_len = ssl_context->tls_host_name.length(); - } -#endif + auto target_host = get_http_proxy_host_name(); String *send_buffer = get_write_buffer(); ON_SCOPE_EXIT { send_buffer->clear(); }; - if (!http_proxy->password.empty()) { - auto auth_str = http_proxy->get_auth_str(); - n = sw_snprintf(send_buffer->str, - send_buffer->size, - HTTP_PROXY_FMT "Proxy-Authorization: Basic %s\r\n\r\n", - (int) http_proxy->target_host.length(), - http_proxy->target_host.c_str(), - http_proxy->target_port, - host_len, - host, - http_proxy->target_port, - auth_str.c_str()); - } else { - n = sw_snprintf(send_buffer->str, - send_buffer->size, - HTTP_PROXY_FMT "\r\n", - (int) http_proxy->target_host.length(), - http_proxy->target_host.c_str(), - http_proxy->target_port, - host_len, - host, - http_proxy->target_port); - } - - swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy request: <str); - + size_t n = http_proxy->pack(send_buffer, target_host); send_buffer->length = n; - if (send(send_buffer->str, n) != n) { + swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy request: <str); + + if (send(send_buffer->str, n) != (ssize_t) n) { return false; } @@ -392,70 +265,30 @@ bool Socket::http_proxy_handshake() { protocol.package_eof_len = sizeof("\r\n\r\n") - 1; memcpy(protocol.package_eof, SW_STRS("\r\n\r\n")); - n = recv_packet(); - if (n <= 0) { + if (recv_packet() <= 0) { return false; } - swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy response: <str); + swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy response: <str); - bool ret = false; - char *buf = recv_buffer->str; - int len = n; - int state = 0; - char *p = buf; - char *pe = buf + len; - for (; p < buf + len; p++) { - if (state == 0) { - if (SW_STRCASECT(p, pe - p, "HTTP/1.1") || SW_STRCASECT(p, pe - p, "HTTP/1.0")) { - state = 1; - p += sizeof("HTTP/1.x") - 1; - } else { - break; - } - } else if (state == 1) { - if (isspace(*p)) { - continue; - } else { - if (SW_STRCASECT(p, pe - p, "200")) { - state = 2; - p += sizeof("200") - 1; - } else { - break; - } - } - } else if (state == 2) { - ret = true; - break; -#if 0 - if (isspace(*p)) { - continue; - } else { - if (SW_STRCASECT(p, pe - p, "Connection established")) { - ret = true; - } - break; - } -#endif - } - } - - if (!ret) { + if (!http_proxy->handshake(recv_buffer)) { set_err(SW_ERROR_HTTP_PROXY_BAD_RESPONSE, std::string("wrong http_proxy response received, \n[Request]: ") + send_buffer->to_std_string() + - "\n[Response]: " + std::string(buf, len)); + "\n[Response]: " + send_buffer->to_std_string()); + return false; } - return ret; + return true; } -void Socket::init_sock_type(SocketType _sw_type) { - type = _sw_type; - network::Socket::get_domain_and_type(_sw_type, &sock_domain, &sock_type); +void Socket::init_sock_type(SocketType _type) { + type = _type; + network::Socket::get_domain_and_type(_type, &sock_domain, &sock_type); } bool Socket::init_sock() { - socket = make_socket(type, SW_FD_CO_SOCKET, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); + socket = + make_socket(type, SW_FD_CO_SOCKET, sock_domain, sock_type, sock_protocol, SW_SOCK_NONBLOCK | SW_SOCK_CLOEXEC); if (socket == nullptr) { return false; } @@ -465,8 +298,30 @@ bool Socket::init_sock() { return true; } +bool Socket::reinit_sock(SocketType _type) { + int _sock_domain; + int _sock_type; + network::Socket::get_domain_and_type(_type, &_sock_domain, &_sock_type); + + auto new_sock = make_socket( + _type, SW_FD_CO_SOCKET, _sock_domain, _sock_type, sock_protocol, SW_SOCK_NONBLOCK | SW_SOCK_CLOEXEC); + if (socket == nullptr) { + return false; + } + + socket->free(); + socket = new_sock; + socket->object = this; + socket->info.type = type; + sock_domain = _sock_domain; + sock_type = _sock_type; + type = _type; + sock_fd = socket->fd; + return true; +} + bool Socket::init_reactor_socket(int _fd) { - socket = swoole::make_socket(_fd, SW_FD_CO_SOCKET); + socket = make_socket(_fd, SW_FD_CO_SOCKET); sock_fd = _fd; socket->object = this; socket->socket_type = type; @@ -478,7 +333,7 @@ bool Socket::init_reactor_socket(int _fd) { Socket::Socket(int _domain, int _type, int _protocol) : sock_domain(_domain), sock_type(_type), sock_protocol(_protocol) { - type = network::Socket::convert_to_type(_domain, _type, _protocol); + type = network::Socket::convert_to_type(_domain, _type); if (sw_unlikely(!init_sock())) { return; } @@ -498,13 +353,16 @@ Socket::Socket(int _fd, SocketType _type) { if (sw_unlikely(!init_reactor_socket(_fd))) { return; } + if (_type == SW_SOCK_RAW) { + return; + } socket->set_nonblock(); init_options(); } Socket::Socket(int _fd, int _domain, int _type, int _protocol) : sock_domain(_domain), sock_type(_type), sock_protocol(_protocol) { - type = network::Socket::convert_to_type(_domain, _type, _protocol); + type = network::Socket::convert_to_type(_domain, _type); if (sw_unlikely(!init_reactor_socket(_fd))) { return; } @@ -515,7 +373,7 @@ Socket::Socket(int _fd, int _domain, int _type, int _protocol) /** * Only used as accept member method */ -Socket::Socket(network::Socket *sock, Socket *server_sock) { +Socket::Socket(network::Socket *sock, const Socket *server_sock) { type = server_sock->type; sock_domain = server_sock->sock_domain; sock_type = server_sock->sock_type; @@ -527,27 +385,29 @@ Socket::Socket(network::Socket *sock, Socket *server_sock) { socket->fd_type = SW_FD_CO_SOCKET; init_options(); /* inherits server socket options */ - dns_timeout = server_sock->dns_timeout; - connect_timeout = server_sock->connect_timeout; - read_timeout = server_sock->read_timeout; - write_timeout = server_sock->write_timeout; + socket->dns_timeout = server_sock->get_socket()->dns_timeout; + socket->connect_timeout = server_sock->get_socket()->connect_timeout; + socket->read_timeout = server_sock->get_socket()->read_timeout; + socket->write_timeout = server_sock->get_socket()->write_timeout; open_length_check = server_sock->open_length_check; open_eof_check = server_sock->open_eof_check; http2 = server_sock->http2; protocol = server_sock->protocol; connected = true; -#ifdef SW_USE_OPENSSL ssl_context = server_sock->ssl_context; ssl_is_server = server_sock->ssl_is_server; if (server_sock->ssl_is_enable() && !ssl_create(server_sock->get_ssl_context())) { close(); } -#endif } -bool Socket::getsockname(network::Address *sa) { +bool Socket::getsockname() const { + return socket->get_name() == SW_OK; +} + +bool Socket::getpeername(network::Address *sa) { sa->len = sizeof(sa->addr); - if (::getsockname(sock_fd, (struct sockaddr *) &sa->addr, &sa->len) != 0) { + if (::getpeername(sock_fd, reinterpret_cast(&sa->addr), &sa->len) != 0) { set_err(errno); return false; } @@ -555,17 +415,90 @@ bool Socket::getsockname(network::Address *sa) { return true; } -bool Socket::getpeername(network::Address *sa) { - sa->len = sizeof(sa->addr); - if (::getpeername(sock_fd, (struct sockaddr *) &sa->addr, &sa->len) != 0) { - set_err(errno); +double Socket::get_timeout(TimeoutType _type) const { + return socket->get_timeout(_type); +} + +String *Socket::get_read_buffer() { + if (sw_unlikely(!read_buffer)) { + read_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); + } + return read_buffer; +} + +String *Socket::get_write_buffer() { + if (sw_unlikely(!write_buffer)) { + write_buffer = make_string(SW_BUFFER_SIZE_BIG, buffer_allocator); + } + return write_buffer; +} + +String *Socket::pop_read_buffer() { + if (sw_unlikely(!read_buffer)) { + return nullptr; + } + auto tmp = read_buffer; + read_buffer = nullptr; + return tmp; +} + +String *Socket::pop_write_buffer() { + if (sw_unlikely(!write_buffer)) { + return nullptr; + } + auto tmp = write_buffer; + write_buffer = nullptr; + return tmp; +} + +void Socket::set_timeout(double timeout, int _type) const { + socket->set_timeout(timeout, _type); +} + +const char *Socket::get_event_str(const EventType event) const { + if (event == SW_EVENT_READ) { + return "reading"; + } else if (event == SW_EVENT_WRITE) { + return "writing"; + } else { + return read_co && write_co ? "reading or writing" : (read_co ? "reading" : "writing"); + } +} + +bool Socket::set_option(int level, int optname, int optval) const { + return set_option(level, optname, &optval, sizeof(optval)); +} + +bool Socket::get_option(int level, int optname, int *optval) const { + socklen_t optval_size = sizeof(*optval); + return get_option(level, optname, optval, &optval_size); +} + +bool Socket::set_option(int level, int optname, const void *optval, socklen_t optlen) const { + if (socket->set_option(level, optname, optval, optlen) < 0) { + swoole_sys_warning("setsockopt(%d, %d, %d, %u) failed", sock_fd, level, optname, optlen); return false; } - sa->type = type; return true; } -bool Socket::connect(const struct sockaddr *addr, socklen_t addrlen) { +bool Socket::get_option(int level, int optname, void *optval, socklen_t *optlen) const { + if (socket->get_option(level, optname, optval, optlen) < 0) { + swoole_sys_warning("getsockopt(%d, %d, %d) failed", sock_fd, level, optname); + return false; + } + return true; +} + +void Socket::set_socks5_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { + socks5_proxy.reset(Socks5Proxy::create(type, host, port, user, pwd)); +} + +void Socket::set_http_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { + http_proxy.reset(HttpProxy::create(host, port, user, pwd)); +} + +bool Socket::connect(const sockaddr *addr, socklen_t addrlen) { if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } @@ -578,9 +511,9 @@ bool Socket::connect(const struct sockaddr *addr, socklen_t addrlen) { set_err(errno); return false; } else { - TimerController timer(&write_timer, connect_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->connect_timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_WRITE)) { - if (closed) { + if (is_closed()) { set_err(ECONNABORTED); } return false; @@ -593,77 +526,70 @@ bool Socket::connect(const struct sockaddr *addr, socklen_t addrlen) { } } connected = true; + socket->get_name(); set_err(0); return true; } -bool Socket::connect(std::string _host, int _port, int flags) { +bool Socket::connect(const std::string &_host, int _port, int flags) { if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } -#ifdef SW_USE_OPENSSL if (ssl_context && (socks5_proxy || http_proxy)) { /* If the proxy is enabled, the host will be replaced with the proxy ip, * so we have to handle the host first, - * if the host is not a ip, assign it to ssl_host_name + * if the host is not an ip, assign it to ssl_host_name */ - union { - struct in_addr sin; - struct in6_addr sin6; - } addr; - if ((sock_domain == AF_INET && !inet_pton(AF_INET, _host.c_str(), &addr.sin)) || - (sock_domain == AF_INET6 && !inet_pton(AF_INET6, _host.c_str(), &addr.sin6))) { + if (!network::Address::verify_ip(sock_domain, _host)) { ssl_host_name = _host; } } -#endif if (socks5_proxy) { socks5_proxy->target_host = _host; socks5_proxy->target_port = _port; - _host = socks5_proxy->host; - _port = socks5_proxy->port; + connect_host = socks5_proxy->host; + connect_port = socks5_proxy->port; } else if (http_proxy) { http_proxy->target_host = _host; http_proxy->target_port = _port; - _host = http_proxy->proxy_host; - _port = http_proxy->proxy_port; + connect_host = http_proxy->host; + connect_port = http_proxy->port; + } else { + connect_host = _host; + connect_port = _port; } - if (sock_domain == AF_INET6 || sock_domain == AF_INET) { - if (_port == -1) { - set_err(EINVAL, "Socket of type AF_INET/AF_INET6 requires port argument"); - return false; - } else if (_port == 0 || _port >= 65536) { - set_err(EINVAL, std_string::format("Invalid port [%d]", _port)); - return false; + if (socks5_proxy || http_proxy) { + if (socket->is_inet6()) { + if (network::Address::verify_ip(AF_INET, connect_host) && !reinit_sock(SW_SOCK_TCP)) { + return false; + } + } else if (socket->is_inet4()) { + if (network::Address::verify_ip(AF_INET6, connect_host) && !reinit_sock(SW_SOCK_TCP6)) { + return false; + } } } - connect_host = _host; - connect_port = _port; - - struct sockaddr *_target_addr = nullptr; NameResolver::Context *ctx = resolve_context_; NameResolver::Context _ctx{}; if (ctx == nullptr) { ctx = &_ctx; } - ctx->timeout = dns_timeout; + ctx->timeout = socket->dns_timeout; std::once_flag oc; - auto name_resolve_fn = [ctx, &oc, this](int type) -> bool { - ctx->type = type; -#ifdef SW_USE_OPENSSL + auto name_resolve_fn = [ctx, &oc, this](int _type) -> bool { + ctx->type = _type; std::call_once(oc, [this]() { if (ssl_context && !(socks5_proxy || http_proxy)) { ssl_host_name = connect_host; } }); -#endif /* locked like wait_event */ read_co = write_co = Coroutine::get_current_safe(); ON_SCOPE_EXIT { @@ -675,9 +601,9 @@ bool Socket::connect(std::string _host, int _port, int flags) { return false; } if (ctx->with_port) { - char delimiter = type == AF_INET6 ? '@' : ':'; + char delimiter = _type == AF_INET6 ? '@' : ':'; auto port_pos = addr.find_first_of(delimiter); - if (port_pos != addr.npos) { + if (port_pos != std::string::npos) { connect_port = std::stoi(addr.substr(port_pos + 1)); connect_host = addr.substr(0, port_pos); return true; @@ -687,91 +613,50 @@ bool Socket::connect(std::string _host, int _port, int flags) { return true; }; - for (int i = 0; i < 2; i++) { - if (sock_domain == AF_INET) { - socket->info.addr.inet_v4.sin_family = AF_INET; - socket->info.addr.inet_v4.sin_port = htons(connect_port); - - if (!inet_pton(AF_INET, connect_host.c_str(), &socket->info.addr.inet_v4.sin_addr)) { - if (!name_resolve_fn(AF_INET)) { - set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); - return false; - } - continue; - } else { - socket->info.len = sizeof(socket->info.addr.inet_v4); - _target_addr = (struct sockaddr *) &socket->info.addr.inet_v4; - break; - } - } else if (sock_domain == AF_INET6) { - socket->info.addr.inet_v6.sin6_family = AF_INET6; - socket->info.addr.inet_v6.sin6_port = htons(connect_port); + network::Address server_addr; - if (!inet_pton(AF_INET6, connect_host.c_str(), &socket->info.addr.inet_v6.sin6_addr)) { - if (!name_resolve_fn(AF_INET6)) { - set_err(swoole_get_last_error()); - return false; - } - continue; - } else { - socket->info.len = sizeof(socket->info.addr.inet_v6); - _target_addr = (struct sockaddr *) &socket->info.addr.inet_v6; - break; + for (int i = 0; i < 2; i++) { + if (!server_addr.assign(type, connect_host, connect_port, false)) { + if (swoole_get_last_error() != SW_ERROR_BAD_HOST_ADDR) { + set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); + return false; } - } else if (sock_domain == AF_UNIX) { - if (connect_host.size() >= sizeof(socket->info.addr.un.sun_path)) { - set_err(EINVAL, "unix socket file is too large"); + if (!name_resolve_fn(sock_domain)) { + set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); return false; } - socket->info.addr.un.sun_family = AF_UNIX; - memcpy(&socket->info.addr.un.sun_path, connect_host.c_str(), connect_host.size()); - socket->info.len = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + connect_host.size()); - _target_addr = (struct sockaddr *) &socket->info.addr.un; - break; - } else { - set_err(EINVAL, "unknown protocol[%d]"); - return false; + continue; } + break; } - if (_target_addr == nullptr) { - set_err(EINVAL, "bad target host"); - return false; - } - if (connect(_target_addr, socket->info.len) == false) { + + if (!connect(&server_addr.addr.ss, server_addr.len)) { return false; } // socks5 proxy - if (socks5_proxy && socks5_handshake() == false) { - if (errCode == 0) { - set_err(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); - } + if (socks5_proxy && !socks5_handshake()) { + set_err(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); return false; } // http proxy - if (http_proxy && !http_proxy->dont_handshake && http_proxy_handshake() == false) { - if (errCode == 0) { - set_err(SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); - } + if (http_proxy && !http_proxy->dont_handshake && !http_proxy_handshake()) { + set_err(SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); return false; } -#ifdef SW_USE_OPENSSL ssl_is_server = false; - if (ssl_context) { - if (!ssl_handshake()) { - if (errCode == 0) { - set_err(SW_ERROR_SSL_HANDSHAKE_FAILED); - } - return false; + if (ssl_context && !ssl_handshake()) { + if (swoole_get_last_error() == 0) { + set_err(SW_ERROR_SSL_HANDSHAKE_FAILED); } + return false; } -#endif return true; } bool Socket::check_liveness() { - if (closed) { - set_err(ECONNRESET); + if (is_closed()) { + set_err(EBADF); return false; } if (!socket->check_liveness()) { @@ -782,68 +667,72 @@ bool Socket::check_liveness() { return true; } -ssize_t Socket::peek(void *__buf, size_t __n) { - ssize_t retval = socket->peek(__buf, __n, 0); +ssize_t Socket::peek(void *_buf, size_t _n) { + ssize_t retval = socket->peek(_buf, _n, 0); check_return_value(retval); return retval; } -bool Socket::poll(EventType type) { - if (sw_unlikely(!is_available(type))) { - return -1; +bool Socket::poll(EventType _type, double timeout) { + if (sw_unlikely(!is_available(_type))) { + return false; } - TimerController timer(&read_timer, read_timeout, this, timer_callback); - if (timer.start() && wait_event(type)) { + TimerNode **timer_pp = _type == SW_EVENT_READ ? &read_timer : &write_timer; + if (timeout == 0) { + timeout = _type == SW_EVENT_READ ? socket->read_timeout : socket->write_timeout; + } + TimerController timer(timer_pp, timeout, this, timer_callback); + if (timer.start() && wait_event(_type)) { return true; } else { return false; } } -ssize_t Socket::recv(void *__buf, size_t __n) { +ssize_t Socket::recv(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { - retval = socket->recv(__buf, __n, 0); + retval = socket->recv(_buf, _n, 0); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } -ssize_t Socket::send(const void *__buf, size_t __n) { +ssize_t Socket::send(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { - retval = socket->send(__buf, __n, 0); + retval = socket->send(_buf, _n, 0); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && - wait_event(SW_EVENT_WRITE, &__buf, __n)); + wait_event(SW_EVENT_WRITE, &_buf, _n)); check_return_value(retval); return retval; } -ssize_t Socket::read(void *__buf, size_t __n) { +ssize_t Socket::read(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { - retval = socket->read(__buf, __n); + retval = socket->read(_buf, _n); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); check_return_value(retval); return retval; } -ssize_t Socket::recv_line(void *__buf, size_t maxlen) { +ssize_t Socket::recv_line(void *_buf, size_t maxlen) { size_t n = 0; ssize_t m = 0; - char *t = (char *) __buf; + auto t = static_cast(_buf); *t = '\0'; while (*t != '\n' && *t != '\r' && n < maxlen) { @@ -866,7 +755,7 @@ ssize_t Socket::recv_line(void *__buf, size_t maxlen) { return n; } -ssize_t Socket::recv_with_buffer(void *__buf, size_t __n) { +ssize_t Socket::recv_with_buffer(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } @@ -874,14 +763,14 @@ ssize_t Socket::recv_with_buffer(void *__buf, size_t __n) { String *buffer = get_read_buffer(); size_t buffer_bytes = buffer->length - buffer->offset; - if (__n <= buffer_bytes) { - memcpy(__buf, buffer->str + buffer->offset, __n); - buffer->offset += __n; - return __n; + if (_n <= buffer_bytes) { + memcpy(_buf, buffer->str + buffer->offset, _n); + buffer->offset += _n; + return _n; } if (buffer_bytes > 0) { - memcpy(__buf, buffer->str + buffer->offset, buffer_bytes); + memcpy(_buf, buffer->str + buffer->offset, buffer_bytes); buffer->offset += buffer_bytes; } @@ -895,23 +784,23 @@ ssize_t Socket::recv_with_buffer(void *__buf, size_t __n) { } buffer->length += retval; - size_t copy_bytes = SW_MIN(__n - buffer_bytes, buffer->length - buffer->offset); - memcpy((char *) __buf + buffer_bytes, buffer->str + buffer->offset, copy_bytes); + size_t copy_bytes = SW_MIN(_n - buffer_bytes, buffer->length - buffer->offset); + memcpy((char *) _buf + buffer_bytes, buffer->str + buffer->offset, copy_bytes); buffer->offset += copy_bytes; return buffer_bytes + copy_bytes; } -ssize_t Socket::write(const void *__buf, size_t __n) { +ssize_t Socket::write(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { - retval = socket->write((void *) __buf, __n); + retval = socket->write((void *) _buf, _n); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && - wait_event(SW_EVENT_WRITE, &__buf, __n)); + wait_event(SW_EVENT_WRITE, &_buf, _n)); check_return_value(retval); return retval; } @@ -921,7 +810,7 @@ ssize_t Socket::readv(network::IOVector *io_vector) { return -1; } ssize_t retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->readv(io_vector); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); @@ -934,10 +823,10 @@ ssize_t Socket::readv_all(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } - ssize_t retval, total_bytes = 0; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + ssize_t total_bytes = 0; + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); - retval = socket->readv(io_vector); + ssize_t retval = socket->readv(io_vector); swoole_trace_log(SW_TRACE_SOCKET, "readv %ld bytes, errno=%d", retval, errno); if (retval < 0 && socket->catch_read_error(errno) != SW_WAIT) { @@ -983,7 +872,7 @@ ssize_t Socket::writev(network::IOVector *io_vector) { return -1; } ssize_t retval; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = socket->writev(io_vector); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE)); @@ -996,10 +885,10 @@ ssize_t Socket::writev_all(network::IOVector *io_vector) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } - ssize_t retval, total_bytes = 0; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + ssize_t total_bytes = 0; + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); - retval = socket->writev(io_vector); + ssize_t retval = socket->writev(io_vector); swoole_trace_log(SW_TRACE_SOCKET, "writev %ld bytes, errno=%d", retval, errno); if (retval < 0 && socket->catch_write_error(errno) != SW_WAIT) { @@ -1040,17 +929,17 @@ ssize_t Socket::writev_all(network::IOVector *io_vector) { return total_bytes; } -ssize_t Socket::recv_all(void *__buf, size_t __n) { +ssize_t Socket::recv_all(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval = 0; size_t total_bytes = 0; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); - retval = socket->recv(__buf, __n, 0); + retval = socket->recv(_buf, _n, 0); - if (retval == 0 || retval == (ssize_t) __n) { + if (retval == 0 || retval == (ssize_t) _n) { return retval; } if (retval < 0 && socket->catch_read_error(errno) != SW_WAIT) { @@ -1061,10 +950,10 @@ ssize_t Socket::recv_all(void *__buf, size_t __n) { retval = -1; - EventBarrier barrier = [&__n, &total_bytes, &retval, &__buf, this]() -> bool { - retval = socket->recv((char *) __buf + total_bytes, __n - total_bytes, 0); + EventBarrier barrier = [&_n, &total_bytes, &retval, &_buf, this]() -> bool { + retval = socket->recv((char *) _buf + total_bytes, _n - total_bytes, 0); return (retval < 0 && socket->catch_read_error(errno) == SW_WAIT) || - (retval > 0 && (total_bytes += retval) < __n); + (retval > 0 && (total_bytes += retval) < _n); }; recv_barrier = &barrier; @@ -1076,17 +965,17 @@ ssize_t Socket::recv_all(void *__buf, size_t __n) { return retval < 0 && total_bytes == 0 ? -1 : total_bytes; } -ssize_t Socket::send_all(const void *__buf, size_t __n) { +ssize_t Socket::send_all(const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval = 0; size_t total_bytes = 0; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); - retval = socket->send(__buf, __n, 0); + retval = socket->send(_buf, _n, 0); - if (retval == 0 || retval == (ssize_t) __n) { + if (retval == 0 || retval == (ssize_t) _n) { return retval; } if (retval < 0 && socket->catch_write_error(errno) != SW_WAIT) { @@ -1097,10 +986,10 @@ ssize_t Socket::send_all(const void *__buf, size_t __n) { retval = -1; - EventBarrier barrier = [&__n, &total_bytes, &retval, &__buf, this]() -> bool { - retval = socket->send((char *) __buf + total_bytes, __n - total_bytes, 0); + EventBarrier barrier = [&_n, &total_bytes, &retval, &_buf, this]() -> bool { + retval = socket->send((char *) _buf + total_bytes, _n - total_bytes, 0); return (retval < 0 && socket->catch_write_error(errno) == SW_WAIT) || - (retval > 0 && (total_bytes += retval) < __n); + (retval > 0 && (total_bytes += retval) < _n); }; send_barrier = &barrier; @@ -1117,7 +1006,7 @@ ssize_t Socket::recvmsg(struct msghdr *msg, int flags) { return -1; } ssize_t retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = ::recvmsg(sock_fd, msg, flags); } while (retval < 0 && socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)); @@ -1133,7 +1022,7 @@ ssize_t Socket::sendmsg(const struct msghdr *msg, int flags) { return -1; } ssize_t retval; - TimerController timer(&write_timer, write_timeout, this, timer_callback); + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); do { retval = ::sendmsg(sock_fd, msg, flags); } while (retval < 0 && socket->catch_write_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_WRITE)); @@ -1141,25 +1030,32 @@ ssize_t Socket::sendmsg(const struct msghdr *msg, int flags) { return retval; } -bool Socket::bind(const struct sockaddr *sa, socklen_t len) { - return ::bind(sock_fd, (struct sockaddr *) sa, len) == 0; +bool Socket::bind(const sockaddr *sa, socklen_t len) { + if (socket->bind(sa, len) < 0) { + set_err(); + return false; + } + return true; } -bool Socket::bind(std::string address, int port) { +bool Socket::bind(const std::string &address, const int port) { if (sw_unlikely(!is_available(SW_EVENT_NULL))) { return false; } - if ((sock_domain == AF_INET || sock_domain == AF_INET6) && (port < 0 || port > 65535)) { - set_err(EINVAL, std_string::format("Invalid port [%d]", port)); - return false; + + swoole_clear_last_error(); + + if (socket->set_reuse_addr() < 0) { + swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", get_fd()); } - bind_address = address; - bind_port = port; - bind_address_info.type = type; + if (socket->bind(address, port) < 0) { + set_err(); + return false; + } - if (socket->bind(address, &bind_port) != 0) { - set_err(errno); + if (socket->get_name() < 0) { + set_err(); return false; } @@ -1175,13 +1071,7 @@ bool Socket::listen(int backlog) { set_err(errno); return false; } - if (socket->get_name(&socket->info) < 0) { - set_err(errno); - return false; - } -#ifdef SW_USE_OPENSSL ssl_is_server = true; -#endif return true; } @@ -1189,9 +1079,12 @@ Socket *Socket::accept(double timeout) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return nullptr; } + if (ssl_is_enable() && sw_unlikely(ssl_context->context == nullptr) && !ssl_context_create()) { + return nullptr; + } network::Socket *conn = socket->accept(); if (conn == nullptr && errno == EAGAIN) { - TimerController timer(&read_timer, timeout == 0 ? read_timeout : timeout, this, timer_callback); + TimerController timer(&read_timer, timeout == 0 ? socket->read_timeout : timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_READ)) { return nullptr; } @@ -1202,7 +1095,7 @@ Socket *Socket::accept(double timeout) { return nullptr; } - Socket *client_sock = new Socket(conn, this); + auto *client_sock = new Socket(conn, this); if (sw_unlikely(client_sock->get_fd() < 0)) { swoole_sys_warning("new Socket() failed"); set_err(errno); @@ -1213,11 +1106,7 @@ Socket *Socket::accept(double timeout) { return client_sock; } -#ifdef SW_USE_OPENSSL -bool Socket::ssl_check_context() { - if (socket->ssl || (get_ssl_context() && get_ssl_context()->get_context())) { - return true; - } +bool Socket::ssl_context_create() { if (socket->is_dgram()) { #ifdef SW_SUPPORT_DTLS socket->dtls = 1; @@ -1230,7 +1119,7 @@ bool Socket::ssl_check_context() { } ssl_context->http_v2 = http2; if (!ssl_context->create()) { - swoole_warning("swSSL_get_context() error"); + set_err(); return false; } socket->ssl_send_ = 1; @@ -1242,6 +1131,7 @@ bool Socket::ssl_create(SSLContext *ssl_context) { return true; } if (socket->ssl_create(ssl_context, 0) < 0) { + set_err(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } #ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER @@ -1259,25 +1149,34 @@ bool Socket::ssl_create(SSLContext *ssl_context) { bool Socket::ssl_handshake() { if (ssl_handshaked) { + set_err(SW_ERROR_WRONG_OPERATION); return false; } if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { return false; } - if (!ssl_check_context()) { + /** + * If the ssl_context is empty, it indicates that this socket was not a connection + * returned by a server socket accept, and a new ssl_context needs to be created. + */ + if (ssl_context->context == nullptr && !ssl_context_create()) { return false; } if (!ssl_create(get_ssl_context())) { return false; } + /** + * The server will use ssl_accept to complete the SSL handshake, + * while the client will use ssl_connect. + */ if (!ssl_is_server) { while (true) { if (socket->ssl_connect() < 0) { - set_err(errno); + set_err(); return false; } if (socket->ssl_state == SW_SSL_STATE_WAIT_STREAM) { - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); if (!timer.start() || !wait_event(SW_EVENT_READ)) { return false; } @@ -1287,7 +1186,7 @@ bool Socket::ssl_handshake() { } } else { ReturnCode retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { retval = socket->ssl_accept(); @@ -1324,12 +1223,12 @@ bool Socket::ssl_verify(bool allow_self_signed) { std::string Socket::ssl_get_peer_cert() { if (!socket->ssl_get_peer_certificate(sw_tg_buffer())) { + set_err(SW_ERROR_SSL_EMPTY_PEER_CERTIFICATE); return ""; } else { return sw_tg_buffer()->to_std_string(); } } -#endif bool Socket::sendfile(const char *filename, off_t offset, size_t length) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { @@ -1354,18 +1253,10 @@ bool Socket::sendfile(const char *filename, off_t offset, size_t length) { length = offset + length; } - TimerController timer(&write_timer, write_timeout, this, timer_callback); - int n, sendn; + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); while ((size_t) offset < length) { - sendn = (length - offset > SW_SENDFILE_CHUNK_SIZE) ? SW_SENDFILE_CHUNK_SIZE : length - offset; -#ifdef SW_USE_OPENSSL - if (socket->ssl) { - n = socket->ssl_sendfile(file, &offset, sendn); - } else -#endif - { - n = ::swoole_sendfile(sock_fd, file.get_fd(), &offset, sendn); - } + ssize_t sent_bytes = (length - offset > SW_SENDFILE_CHUNK_SIZE) ? SW_SENDFILE_CHUNK_SIZE : length - offset; + ssize_t n = socket->sendfile(file, &offset, sent_bytes); if (n > 0) { continue; } else if (n == 0) { @@ -1382,96 +1273,63 @@ bool Socket::sendfile(const char *filename, off_t offset, size_t length) { return true; } -ssize_t Socket::sendto(const std::string &host, int port, const void *__buf, size_t __n) { +ssize_t Socket::sendto(const std::string &host, int port, const void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { return -1; } ssize_t retval = 0; - union { - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_un un; - } addr = {}; - size_t addr_size = 0; - - std::string ip = host; - - for (size_t i = 0; i < 2; i++) { - if (type == SW_SOCK_UDP) { - if (::inet_pton(AF_INET, ip.c_str(), &addr.in.sin_addr) == 0) { - read_co = write_co = Coroutine::get_current_safe(); - ip = System::gethostbyname(host, sock_domain, dns_timeout); - read_co = write_co = nullptr; - if (ip.empty()) { - set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); - return -1; - } - continue; - } else { - addr.in.sin_family = AF_INET; - addr.in.sin_port = htons(port); - addr_size = sizeof(addr.in); - break; - } - } else if (type == SW_SOCK_UDP6) { - if (::inet_pton(AF_INET6, ip.c_str(), &addr.in6.sin6_addr) == 0) { - read_co = write_co = Coroutine::get_current_safe(); - ip = System::gethostbyname(host, sock_domain, dns_timeout); - read_co = write_co = nullptr; - if (ip.empty()) { - set_err(swoole_get_last_error(), swoole_strerror(swoole_get_last_error())); - return -1; + network::Address addr; + + if (!socket->is_dgram()) { + set_err(EPROTONOSUPPORT); + return -1; + } + + auto ip_addr = host; + + SW_LOOP_N(2) { + if (!addr.assign(type, ip_addr, port, false)) { + if (swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { + ip_addr = System::gethostbyname(host, sock_domain, socket->dns_timeout); + if (!ip_addr.empty()) { + continue; } - continue; - } else { - addr.in6.sin6_port = (uint16_t) htons(port); - addr.in6.sin6_family = AF_INET6; - addr_size = sizeof(addr.in6); - break; } - } else if (type == SW_SOCK_UNIX_DGRAM) { - addr.un.sun_family = AF_UNIX; - swoole_strlcpy(addr.un.sun_path, host.c_str(), sizeof(addr.un.sun_path)); - addr_size = sizeof(addr.un); - break; - } else { - set_err(EPROTONOSUPPORT); - retval = -1; - break; + set_err(); + return -1; } + break; } - if (addr_size > 0) { - TimerController timer(&write_timer, write_timeout, this, timer_callback); - do { - retval = ::sendto(sock_fd, __buf, __n, 0, (struct sockaddr *) &addr, addr_size); - swoole_trace_log(SW_TRACE_SOCKET, "sendto %ld/%ld bytes, errno=%d", retval, __n, errno); - } while (retval < 0 && (errno == EINTR || (socket->catch_write_error(errno) == SW_WAIT && timer.start() && - wait_event(SW_EVENT_WRITE, &__buf, __n)))); - check_return_value(retval); - } + TimerController timer(&write_timer, socket->write_timeout, this, timer_callback); + do { + retval = socket->sendto(addr, _buf, _n, 0); + swoole_trace_log(SW_TRACE_SOCKET, "sendto %ld/%ld bytes, errno=%d", retval, _n, errno); + } while (retval < 0 && (errno == EINTR || (socket->catch_write_error(errno) == SW_WAIT && timer.start() && + wait_event(SW_EVENT_WRITE, &_buf, _n)))); + check_return_value(retval); return retval; } -ssize_t Socket::recvfrom(void *__buf, size_t __n) { +ssize_t Socket::recvfrom(void *_buf, size_t _n) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } socket->info.len = sizeof(socket->info.addr); - return recvfrom(__buf, __n, (struct sockaddr *) &socket->info.addr, &socket->info.len); + return recvfrom(_buf, _n, reinterpret_cast(&socket->info.addr), &socket->info.len); } -ssize_t Socket::recvfrom(void *__buf, size_t __n, struct sockaddr *_addr, socklen_t *_socklen) { +ssize_t Socket::recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) { if (sw_unlikely(!is_available(SW_EVENT_READ))) { return -1; } ssize_t retval; - TimerController timer(&read_timer, read_timeout, this, timer_callback); + TimerController timer(&read_timer, socket->read_timeout, this, timer_callback); do { - retval = ::recvfrom(sock_fd, __buf, __n, 0, _addr, _socklen); - swoole_trace_log(SW_TRACE_SOCKET, "recvfrom %ld/%ld bytes, errno=%d", retval, __n, errno); + retval = ::recvfrom(sock_fd, _buf, _n, 0, _addr, _socklen); + swoole_trace_log(SW_TRACE_SOCKET, "recvfrom %ld/%ld bytes, errno=%d", retval, _n, errno); } while (retval < 0 && ((errno == EINTR) || (socket->catch_read_error(errno) == SW_WAIT && timer.start() && wait_event(SW_EVENT_READ)))); check_return_value(retval); @@ -1521,8 +1379,8 @@ ssize_t Socket::recv_packet_with_length_protocol() { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, "packet length is too big, remote_addr=%s:%d, length=%zu", - socket->info.get_ip(), - socket->info.get_port(), + socket->get_addr(), + socket->get_port(), packet_len); set_err(SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, sw_error); return -1; @@ -1535,11 +1393,7 @@ ssize_t Socket::recv_packet_with_length_protocol() { } if ((size_t) packet_len > read_buffer->size) { - if (!read_buffer->extend(packet_len)) { - read_buffer->clear(); - set_err(ENOMEM); - return -1; - } + read_buffer->extend(packet_len); } retval = recv_all(read_buffer->str + read_buffer->length, packet_len - read_buffer->length); @@ -1564,7 +1418,7 @@ ssize_t Socket::recv_packet_with_eof_protocol() { goto _find_eof; } - while (1) { + while (true) { buf = read_buffer->str + read_buffer->length; l_buf = read_buffer->size - read_buffer->length; @@ -1599,11 +1453,7 @@ ssize_t Socket::recv_packet_with_eof_protocol() { if (new_size > protocol.package_max_length) { new_size = protocol.package_max_length; } - if (!read_buffer->extend(new_size)) { - read_buffer->clear(); - set_err(ENOMEM); - return -1; - } + read_buffer->extend(new_size); } } assert(0); @@ -1627,7 +1477,7 @@ ssize_t Socket::recv_packet(double timeout) { return -1; } - TimerController timer(&read_timer, timeout == 0 ? read_timeout : timeout, this, timer_callback); + TimerController timer(&read_timer, timeout == 0 ? socket->read_timeout : timeout, this, timer_callback); if (sw_unlikely(!timer.start())) { return 0; } @@ -1657,22 +1507,20 @@ ssize_t Socket::recv_packet(double timeout) { return recv_bytes; } -bool Socket::shutdown(int __how) { +bool Socket::shutdown(int _how) { set_err(0); - if (!is_connected() || (__how == SHUT_RD && shutdown_read) || (__how == SHUT_WR && shutdown_write)) { + if (!is_connected() || (_how == SHUT_RD && shutdown_read) || (_how == SHUT_WR && shutdown_write)) { errno = ENOTCONN; } else { -#ifdef SW_USE_OPENSSL if (socket->ssl) { socket->ssl_shutdown(); } -#endif - if (::shutdown(sock_fd, __how) == 0 || errno == ENOTCONN) { + if (::shutdown(sock_fd, _how) == 0 || errno == ENOTCONN) { if (errno == ENOTCONN) { // connection reset by server side - __how = SHUT_RDWR; + _how = SHUT_RDWR; } - switch (__how) { + switch (_how) { case SHUT_RD: shutdown_read = true; break; @@ -1693,14 +1541,9 @@ bool Socket::shutdown(int __how) { return false; } -#ifdef SW_USE_OPENSSL -bool Socket::ssl_shutdown() { - if (socket->ssl) { - socket->ssl_close(); - } - return true; +void Socket::ssl_close() const { + socket->ssl_close(); } -#endif bool Socket::cancel(const EventType event) { if (!has_bound(event)) { @@ -1715,41 +1558,39 @@ bool Socket::cancel(const EventType event) { write_co->resume(); return true; } else { + set_err(EINVAL); return false; } } /** - * @return bool (whether it can be freed) - * you can access errCode member to get error information + * @return bool + * If true is returned, the related resources of this socket can be released + * If false is returned, it means that other coroutines are still referencing this socket, + * and need to wait for the coroutine bound to readable or writable event to execute close, + * and release when all references are 0 */ bool Socket::close() { - if (sock_fd < 0) { + if (is_closed()) { set_err(EBADF); - return true; + return false; } if (connected) { shutdown(); } if (sw_unlikely(has_bound())) { - if (closed) { - // close operation is in processing - set_err(EINPROGRESS); - return false; - } - closed = true; - if (write_co) { - set_err(ECONNRESET); - write_co->resume(); - } - if (read_co) { - set_err(ECONNRESET); - read_co->resume(); - } + socket->close_wait = 1; + cancel(SW_EVENT_WRITE); + cancel(SW_EVENT_READ); + set_err(SW_ERROR_CO_SOCKET_CLOSE_WAIT); return false; } else { - sock_fd = -1; - closed = true; + sock_fd = SW_BAD_SOCKET; + if (dtor_ != nullptr) { + auto dtor = dtor_; + dtor_ = nullptr; + dtor(this); + } return true; } } @@ -1758,8 +1599,8 @@ bool Socket::close() { * Warn: * the destructor should only be called in following two cases: * 1. construct failed - * 2. called close() and it return true - * 3. called close() and it return false but it will not be accessed anywhere else + * 2. called close() and it returns true + * 3. called close() and it returns false, but it will not be accessed anywhere else */ Socket::~Socket() { #ifdef SW_DEBUG @@ -1767,49 +1608,85 @@ Socket::~Socket() { SW_ASSERT(!has_bound() && socket->removed); } #endif - if (read_buffer) { - delete read_buffer; - } - if (write_buffer) { - delete write_buffer; - } - if (socks5_proxy) { - delete socks5_proxy; - } - if (http_proxy) { - delete http_proxy; + if (dtor_ != nullptr) { + dtor_(this); } + delete read_buffer; + delete write_buffer; if (socket == nullptr) { return; } /* {{{ release socket resources */ -#ifdef SW_USE_OPENSSL - ssl_shutdown(); -#endif - if (sock_domain == AF_UNIX && !bind_address.empty()) { - ::unlink(bind_address_info.addr.un.sun_path); - bind_address_info = {}; - } - if (socket->socket_type == SW_SOCK_UNIX_DGRAM) { - ::unlink(socket->info.addr.un.sun_path); + if (socket->ssl) { + ssl_close(); } socket->free(); } -} // namespace coroutine +bool Socket::TimerController::start() { + if (timeout != 0 && !*timer_pp) { + enabled = true; + if (timeout > 0) { + *timer_pp = swoole_timer_add(timeout, false, callback, socket_); + return *timer_pp != nullptr; + } + *timer_pp = reinterpret_cast(-1); + } + return true; +} + +Socket::TimerController::~TimerController() { + if (enabled && *timer_pp) { + if (*timer_pp != reinterpret_cast(-1)) { + swoole_timer_del(*timer_pp); + } + *timer_pp = nullptr; + } +} + +Socket::TimeoutSetter::TimeoutSetter(Socket *socket, double _timeout, TimeoutType _type) + : socket_(socket), timeout(_timeout), type(_type) { + if (_timeout == 0) { + return; + } + for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { + if (_type & timeout_type_list[i]) { + original_timeout[i] = socket->get_timeout(timeout_type_list[i]); + if (_timeout != original_timeout[i]) { + socket->set_timeout(_timeout, timeout_type_list[i]); + } + } + } +} -std::string HttpProxy::get_auth_str() { - char auth_buf[256]; - char encode_buf[512]; - size_t n = sw_snprintf(auth_buf, - sizeof(auth_buf), - "%.*s:%.*s", - (int) username.length(), - username.c_str(), - (int) password.length(), - password.c_str()); - base64_encode((unsigned char *) auth_buf, n, encode_buf); - return std::string(encode_buf); +Socket::TimeoutSetter::~TimeoutSetter() { + if (timeout == 0) { + return; + } + for (uint8_t i = 0; i < SW_ARRAY_SIZE(timeout_type_list); i++) { + if (type & timeout_type_list[i]) { + if (timeout != original_timeout[i]) { + socket_->set_timeout(original_timeout[i], timeout_type_list[i]); + } + } + } } +bool Socket::TimeoutController::has_timedout(TimeoutType _type) { + SW_ASSERT_1BYTE(_type); + if (timeout > 0) { + if (sw_unlikely(startup_time == 0)) { + startup_time = microtime(); + } else { + double used_time = microtime() - startup_time; + if (sw_unlikely(timeout - used_time < SW_TIMER_MIN_SEC)) { + socket_->set_err(ETIMEDOUT); + return true; + } + socket_->set_timeout(timeout - used_time, _type); + } + } + return false; +} +} // namespace coroutine } // namespace swoole diff --git a/src/coroutine/system.cc b/src/coroutine/system.cc index 17eb2bad11..e6ef9c2f54 100644 --- a/src/coroutine/system.cc +++ b/src/coroutine/system.cc @@ -15,70 +15,65 @@ */ #include "swoole_coroutine_system.h" -#include "swoole_coroutine_socket.h" #include "swoole_lru_cache.h" #include "swoole_signal.h" +#include "swoole_iouring.h" +#include "swoole_socket_impl.h" namespace swoole { namespace coroutine { -static size_t dns_cache_capacity = 1000; -static time_t dns_cache_expire = 60; -static LRUCache *dns_cache = nullptr; +static struct { + size_t capacity; + time_t expire; + LRUCache *data; + size_t miss_count; + size_t hit_count; +} dns_cache = { + 1000, + 60, + nullptr, + 0, + 0, +}; void System::set_dns_cache_expire(time_t expire) { - dns_cache_expire = expire; + dns_cache.expire = expire; } void System::set_dns_cache_capacity(size_t capacity) { - dns_cache_capacity = capacity; - delete dns_cache; - dns_cache = nullptr; + dns_cache.capacity = capacity; + clear_dns_cache(); + delete dns_cache.data; + dns_cache.data = nullptr; } void System::clear_dns_cache() { - if (dns_cache) { - dns_cache->clear(); + if (dns_cache.data) { + dns_cache.data->clear(); } + dns_cache.miss_count = 0; + dns_cache.hit_count = 0; } -static void sleep_callback(Coroutine *co, bool *canceled) { - if (*canceled == false) { - co->resume(); +float System::get_dns_cache_hit_ratio() { + auto total = dns_cache.hit_count + dns_cache.miss_count; + if (total == 0) { + return 0; } - delete canceled; + return (float) dns_cache.hit_count / (float) total; } int System::sleep(double sec) { +#if SW_USE_IOURING + return Iouring::sleep(sec); +#endif Coroutine *co = Coroutine::get_current_safe(); - - bool *canceled = new bool(false); - TimerNode *tnode = nullptr; - if (sec < SW_TIMER_MIN_SEC) { - swoole_event_defer([co, canceled](void *data) { sleep_callback(co, canceled); }, nullptr); - } else { - auto fn = [canceled](Timer *timer, TimerNode *tnode) { sleep_callback((Coroutine *) tnode->data, canceled); }; - tnode = swoole_timer_add((long) (sec * 1000), false, fn, co); - if (tnode == nullptr) { - delete canceled; - return -1; - } + sec = SW_TIMER_MIN_SEC; } - Coroutine::CancelFunc cancel_fn = [canceled, tnode](Coroutine *co) { - *canceled = true; - if (tnode) { - swoole_timer_del(tnode); - } - co->resume(); - return true; - }; - co->yield(&cancel_fn); - if (co->is_canceled()) { - swoole_set_last_error(SW_ERROR_CO_CANCELED); - return SW_ERR; - } - return SW_OK; + co->yield_ex(sec); + return co->is_canceled() ? SW_ERR : SW_OK; } std::shared_ptr System::read_file(const char *file, bool lock) { @@ -109,7 +104,7 @@ std::shared_ptr System::read_file(const char *file, bool lock) { return result; } -ssize_t System::write_file(const char *file, char *buf, size_t length, bool lock, int flags) { +ssize_t System::write_file(const char *file, const char *buf, size_t length, bool lock, int flags) { ssize_t retval = -1; int file_flags = flags | O_CREAT | O_WRONLY; async([&]() { @@ -136,21 +131,8 @@ ssize_t System::write_file(const char *file, char *buf, size_t length, bool lock std::string gethostbyname_impl_with_async(const std::string &hostname, int domain, double timeout) { AsyncEvent ev{}; - - if (hostname.size() < SW_IP_MAX_LENGTH) { - ev.nbytes = SW_IP_MAX_LENGTH + 1; - } else { - ev.nbytes = hostname.size() + 1; - } - - ev.buf = sw_malloc(ev.nbytes); - if (!ev.buf) { - return ""; - } - - memcpy(ev.buf, hostname.c_str(), hostname.size()); - ((char *) ev.buf)[hostname.size()] = 0; - ev.flags = domain; + auto req = new GethostbynameRequest(hostname, domain); + ev.data = std::shared_ptr(req); ev.retval = 1; coroutine::async(async::handler_gethostbyname, ev, timeout); @@ -162,27 +144,32 @@ std::string gethostbyname_impl_with_async(const std::string &hostname, int domai swoole_set_last_error(ev.error); return ""; } else { - std::string addr((char *) ev.buf); - sw_free(ev.buf); - return addr; + return req->addr; } } std::string System::gethostbyname(const std::string &hostname, int domain, double timeout) { - if (dns_cache == nullptr && dns_cache_capacity != 0) { - dns_cache = new LRUCache(dns_cache_capacity); + if (dns_cache.data == nullptr && dns_cache.capacity != 0) { + dns_cache.data = new LRUCache(dns_cache.capacity); } std::string cache_key; std::string result; - if (dns_cache) { - cache_key.append(domain == AF_INET ? "4_" : "6_"); + if (dns_cache.data) { + /** + * The cache key must end with a prefix that uses a dot. + * The domain name cannot contain the `.` symbol, and other characters are considered unsafe. + */ + cache_key.append(domain == AF_INET ? "IPv4." : "IPv6."); cache_key.append(hostname); - auto cache = dns_cache->get(cache_key); + auto cache = dns_cache.data->get(cache_key); if (cache) { + dns_cache.hit_count++; return *(std::string *) cache.get(); + } else { + dns_cache.miss_count++; } } @@ -190,7 +177,7 @@ std::string System::gethostbyname(const std::string &hostname, int domain, doubl auto result_list = dns_lookup_impl_with_cares(hostname.c_str(), domain, timeout); if (!result_list.empty()) { if (SwooleG.dns_lookup_random) { - result = result_list[rand() % result_list.size()]; + result = result_list[swoole_rand() % result_list.size()]; } else { result = result_list[0]; } @@ -199,8 +186,8 @@ std::string System::gethostbyname(const std::string &hostname, int domain, doubl result = gethostbyname_impl_with_async(hostname, domain, timeout); #endif - if (dns_cache && !result.empty()) { - dns_cache->set(cache_key, std::make_shared(result), dns_cache_expire); + if (dns_cache.data && !result.empty()) { + dns_cache.data->set(cache_key, std::make_shared(result), dns_cache.expire); } return result; @@ -212,126 +199,123 @@ std::vector System::getaddrinfo( assert(family == AF_INET || family == AF_INET6); AsyncEvent ev{}; - network::GetaddrinfoRequest req{}; - - ev.req = &req; - - struct sockaddr_in6 result_buffer[SW_DNS_HOST_BUFFER_SIZE]; - - req.hostname = hostname.c_str(); - req.family = family; - req.socktype = socktype; - req.protocol = protocol; - req.service = service.empty() ? nullptr : service.c_str(); - req.result = result_buffer; + auto req = new GetaddrinfoRequest(hostname, family, socktype, protocol, service); + ev.data = std::shared_ptr(req); coroutine::async(async::handler_getaddrinfo, ev, timeout); std::vector retval; - if (ev.retval == -1 || req.error != 0) { + if (ev.retval == -1 || req->error != 0) { if (ev.error == SW_ERROR_AIO_TIMEOUT) { ev.error = SW_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT; } swoole_set_last_error(ev.error); } else { - req.parse_result(retval); + req->parse_result(retval); } return retval; } +struct SignalListener { + Coroutine *co; + int signo; +}; + +/** + * Only the main thread should listen for signals, + * without modifying it to a thread-local variable. + */ +static SignalListener *listeners[SW_SIGNO_MAX]; + +int System::wait_signal(int signal, double timeout) { + std::vector signals = {signal}; + return wait_signal(signals, timeout); +} + /** * @error: swoole_get_last_error() */ -bool System::wait_signal(int signo, double timeout) { - static Coroutine *listeners[SW_SIGNO_MAX]; - Coroutine *co = Coroutine::get_current_safe(); +int System::wait_signal(const std::vector &signals, double timeout) { + SignalListener listener = { + Coroutine::get_current_safe(), + -1, + }; - if (SwooleTG.signal_listener_num > 0) { + if (SwooleG.signal_listener_num > 0) { swoole_set_last_error(EBUSY); - return false; + return -1; } - if (signo < 0 || signo >= SW_SIGNO_MAX || signo == SIGCHLD) { - swoole_set_last_error(EINVAL); - return false; + + auto callback_fn = [](int signo) { + auto listener = listeners[signo]; + if (listener) { + listeners[signo] = nullptr; + listener->signo = signo; + listener->co->resume(); + } + }; + + for (auto &signo : signals) { + if (signo < 0 || signo >= SW_SIGNO_MAX || signo == SIGCHLD) { + swoole_set_last_error(EINVAL); + return -1; + } + + /* resgiter signal */ + listeners[signo] = &listener; + +#ifdef SW_USE_THREAD_CONTEXT + swoole_event_defer([signo, &callback_fn](void *) { swoole_signal_set(signo, callback_fn); }, nullptr); +#else + swoole_signal_set(signo, callback_fn); +#endif } - /* resgiter signal */ - listeners[signo] = co; // exit condition if (!sw_reactor()->isset_exit_condition(Reactor::EXIT_CONDITION_CO_SIGNAL_LISTENER)) { sw_reactor()->set_exit_condition( Reactor::EXIT_CONDITION_CO_SIGNAL_LISTENER, - [](Reactor *reactor, size_t &event_num) -> bool { return SwooleTG.co_signal_listener_num == 0; }); + [](Reactor *reactor, size_t &event_num) -> bool { return SwooleG.signal_async_listener_num == 0; }); } - swoole_signal_set(signo, [](int signo) { - Coroutine *co = listeners[signo]; - if (co) { - listeners[signo] = nullptr; - co->resume(); - } - }); - SwooleTG.co_signal_listener_num++; - TimerNode *timer = nullptr; - if (timeout > 0) { - timer = swoole_timer_add( - timeout * 1000, - 0, - [](Timer *timer, TimerNode *tnode) { - Coroutine *co = (Coroutine *) tnode->data; - co->resume(); - }, - co); - } - - Coroutine::CancelFunc cancel_fn = [timer](Coroutine *co) { - if (timer) { - swoole_timer_del(timer); - } - co->resume(); - return true; - }; - co->yield(&cancel_fn); + SwooleG.signal_async_listener_num++; - swoole_signal_set(signo, nullptr); - SwooleTG.co_signal_listener_num--; + bool retval = listener.co->yield_ex(timeout); - if (listeners[signo] != nullptr) { + for (auto &signo : signals) { +#ifdef SW_USE_THREAD_CONTEXT + swoole_event_defer([signo](void *) { swoole_signal_set(signo, nullptr); }, nullptr); +#else + swoole_signal_set(signo, nullptr); +#endif listeners[signo] = nullptr; - swoole_set_last_error(co->is_canceled() ? SW_ERROR_CO_CANCELED : ETIMEDOUT); - return false; } - if (timer) { - swoole_timer_del(timer); - } + SwooleG.signal_async_listener_num--; - return !co->is_canceled(); + return retval ? listener.signo : -1; } struct CoroPollTask { - std::unordered_map *fds; + std::unordered_map *fds = nullptr; Coroutine *co = nullptr; TimerNode *timer = nullptr; bool success = false; bool wait = true; }; -static inline void socket_poll_clean(CoroPollTask *task) { - for (auto i = task->fds->begin(); i != task->fds->end(); i++) { - network::Socket *socket = i->second.socket; +static inline void socket_poll_clean(const CoroPollTask *task) { + for (auto &fd : *task->fds) { + network::Socket *socket = fd.second.socket; if (!socket) { continue; } - int retval = swoole_event_del(i->second.socket); - /** - * Temporary socket, fd marked -1, skip close - */ - socket->fd = -1; + int retval = swoole_event_del(fd.second.socket); + socket->move_fd(); socket->free(); - i->second.socket = nullptr; + fd.second.socket = nullptr; if (retval < 0) { continue; } @@ -339,7 +323,7 @@ static inline void socket_poll_clean(CoroPollTask *task) { } static void socket_poll_timeout(Timer *timer, TimerNode *tnode) { - CoroPollTask *task = (CoroPollTask *) tnode->data; + auto *task = static_cast(tnode->data); task->timer = nullptr; task->success = false; task->wait = false; @@ -348,7 +332,7 @@ static void socket_poll_timeout(Timer *timer, TimerNode *tnode) { } static void socket_poll_completed(void *data) { - CoroPollTask *task = (CoroPollTask *) data; + auto *task = static_cast(data); socket_poll_clean(task); task->co->resume(); } @@ -391,54 +375,24 @@ static int socket_poll_error_callback(Reactor *reactor, Event *event) { return SW_OK; } -static int translate_events_to_poll(int events) { - int poll_events = 0; - - if (events & SW_EVENT_READ) { - poll_events |= POLLIN; - } - if (events & SW_EVENT_WRITE) { - poll_events |= POLLOUT; - } - - return poll_events; -} - -static int translate_events_from_poll(int events) { - int sw_events = 0; - - if (events & POLLIN) { - sw_events |= SW_EVENT_READ; - } - if (events & POLLOUT) { - sw_events |= SW_EVENT_WRITE; - } - // ignore ERR and HUP, because event is already processed at IN and OUT handler. - if ((((events & POLLERR) || (events & POLLHUP)) && !((events & POLLIN) || (events & POLLOUT)))) { - sw_events |= SW_EVENT_ERROR; - } - - return sw_events; -} - bool System::socket_poll(std::unordered_map &fds, double timeout) { if (timeout == 0) { - struct pollfd *event_list = (struct pollfd *) sw_calloc(fds.size(), sizeof(struct pollfd)); + auto *event_list = static_cast(sw_calloc(fds.size(), sizeof(struct pollfd))); if (!event_list) { swoole_warning("calloc() failed"); return false; } int n = 0; - for (auto i = fds.begin(); i != fds.end(); i++, n++) { + for (auto i = fds.begin(); i != fds.end(); ++i, n++) { event_list[n].fd = i->first; event_list[n].events = translate_events_to_poll(i->second.events); event_list[n].revents = 0; } int retval = ::poll(event_list, n, 0); if (retval > 0) { - int n = 0; - for (auto i = fds.begin(); i != fds.end(); i++, n++) { - i->second.revents = translate_events_from_poll(event_list[n].revents); + int _n = 0; + for (auto i = fds.begin(); i != fds.end(); ++i, _n++) { + i->second.revents = translate_events_from_poll(event_list[_n].revents); } } sw_free(event_list); @@ -450,13 +404,17 @@ bool System::socket_poll(std::unordered_map &fds, double timeou task.fds = &fds; task.co = Coroutine::get_current_safe(); - for (auto i = fds.begin(); i != fds.end(); i++) { - i->second.socket = swoole::make_socket(i->first, SW_FD_CO_POLL); - if (swoole_event_add(i->second.socket, i->second.events) < 0) { - i->second.socket->free(); + for (auto &fd : fds) { + fd.second.socket = make_socket(fd.first, SW_FD_CO_POLL); + if (swoole_event_add(fd.second.socket, fd.second.events) < 0) { + // socket_poll() is not the owner of the socket, + // so the socket should not be closed upon failure or successful return; + // it is necessary to release control over the fd. + fd.second.socket->move_fd(); + fd.second.socket->free(); continue; } - i->second.socket->object = &task; + fd.second.socket->object = &task; tasked_num++; } @@ -465,10 +423,7 @@ bool System::socket_poll(std::unordered_map &fds, double timeou } if (timeout > 0) { - if (timeout < 0.001) { - timeout = 0.001; - } - task.timer = swoole_timer_add((long) (timeout * 1000), false, socket_poll_timeout, &task); + task.timer = swoole_timer_add(timeout, false, socket_poll_timeout, &task); } task.co->yield(); @@ -478,7 +433,6 @@ bool System::socket_poll(std::unordered_map &fds, double timeou struct EventWaiter { network::Socket *socket; - TimerNode *timer; Coroutine *co; int revents; int error_; @@ -487,43 +441,17 @@ struct EventWaiter { error_ = revents = 0; socket = swoole::make_socket(fd, SW_FD_CO_EVENT); socket->object = this; - timer = nullptr; co = Coroutine::get_current_safe(); - Coroutine::CancelFunc cancel_fn = [this](Coroutine *) { - if (timer) { - swoole_timer_del(timer); - } - error_ = SW_ERROR_CO_CANCELED; - co->resume(); - return true; - }; - if (swoole_event_add(socket, events) < 0) { - swoole_set_last_error(errno); + error_ = swoole_get_last_error(); goto _done; } - if (timeout > 0) { - timer = swoole_timer_add((long) (timeout * 1000), - false, - [](Timer *timer, TimerNode *tnode) { - EventWaiter *waiter = (EventWaiter *) tnode->data; - waiter->timer = nullptr; - waiter->error_ = ETIMEDOUT; - waiter->co->resume(); - }, - this); + if (!co->yield_ex(timeout)) { + error_ = swoole_get_last_error(); } - co->yield(&cancel_fn); - - if (timer != nullptr) { - swoole_timer_del(timer); - } - if (error_) { - swoole_set_last_error(error_); - } swoole_event_del(socket); _done: socket->fd = -1; /* skip close */ @@ -560,28 +488,30 @@ int System::wait_event(int fd, int events, double timeout) { events &= SW_EVENT_READ | SW_EVENT_WRITE; if (events == 0) { swoole_set_last_error(EINVAL); - return 0; + return -1; } if (timeout == 0) { - struct pollfd pfd; + pollfd pfd; pfd.fd = fd; pfd.events = translate_events_to_poll(events); pfd.revents = 0; int retval = ::poll(&pfd, 1, 0); if (retval == 1) { + if (pfd.revents & POLLNVAL) { + swoole_set_last_error(EBADF); + return -1; + } return translate_events_from_poll(pfd.revents); } - if (retval < 0) { - swoole_set_last_error(errno); - } - return 0; + swoole_set_last_error(retval < 0 ? errno : ETIMEDOUT); + return -1; } EventWaiter waiter(fd, events, timeout); if (waiter.error_) { - errno = swoole_get_last_error(); + errno = waiter.error_; return SW_ERR; } @@ -599,23 +529,53 @@ int System::wait_event(int fd, int events, double timeout) { return revents; } +bool System::exec(const char *command, bool get_error_stream, std::shared_ptr buffer, int *status) { + Coroutine::get_current_safe(); + + pid_t pid; + int fd = swoole_shell_exec(command, &pid, get_error_stream); + if (fd < 0) { + swoole_sys_warning("Unable to execute '%s'", command); + return false; + } + + SocketImpl socket(fd, SW_SOCK_UNIX_STREAM); + while (true) { + ssize_t retval = socket.read(buffer->str + buffer->length, buffer->size - buffer->length); + if (retval > 0) { + buffer->length += retval; + if (buffer->length == buffer->size) { + buffer->extend(); + } + } else { + break; + } + } + socket.close(); + + return waitpid_safe(pid, status, 0) == pid; +} + void System::init_reactor(Reactor *reactor) { - reactor->set_handler(SW_FD_CO_POLL | SW_EVENT_READ, socket_poll_read_callback); - reactor->set_handler(SW_FD_CO_POLL | SW_EVENT_WRITE, socket_poll_write_callback); - reactor->set_handler(SW_FD_CO_POLL | SW_EVENT_ERROR, socket_poll_error_callback); + reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_READ, socket_poll_read_callback); + reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_WRITE, socket_poll_write_callback); + reactor->set_handler(SW_FD_CO_POLL, SW_EVENT_ERROR, socket_poll_error_callback); - reactor->set_handler(SW_FD_CO_EVENT | SW_EVENT_READ, event_waiter_read_callback); - reactor->set_handler(SW_FD_CO_EVENT | SW_EVENT_WRITE, event_waiter_write_callback); - reactor->set_handler(SW_FD_CO_EVENT | SW_EVENT_ERROR, event_waiter_error_callback); + reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_READ, event_waiter_read_callback); + reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_WRITE, event_waiter_write_callback); + reactor->set_handler(SW_FD_CO_EVENT, SW_EVENT_ERROR, event_waiter_error_callback); - reactor->set_handler(SW_FD_AIO | SW_EVENT_READ, AsyncThreads::callback); + reactor->set_handler(SW_FD_AIO, SW_EVENT_READ, AsyncThreads::callback); +#ifdef SW_USE_IOURING + reactor->set_handler(SW_FD_IOURING, SW_EVENT_READ, Iouring::callback); +#endif } static void async_task_completed(AsyncEvent *event) { if (event->canceled) { return; } - Coroutine *co = (Coroutine *) event->object; + auto *co = static_cast(event->object); co->resume(); } @@ -641,7 +601,7 @@ bool async(async::Handler handler, AsyncEvent &event, double timeout) { return false; } else { event.canceled = _ev->canceled; - event.error = errno = _ev->error; + event.error = errno = _ev->error; event.retval = _ev->retval; return true; } @@ -649,25 +609,22 @@ bool async(async::Handler handler, AsyncEvent &event, double timeout) { struct AsyncLambdaTask { Coroutine *co; - std::function fn; + std::function fn; }; static void async_lambda_handler(AsyncEvent *event) { - AsyncLambdaTask *task = reinterpret_cast(event->object); + auto *task = static_cast(event->object); task->fn(); event->error = errno; event->retval = 0; } static void async_lambda_callback(AsyncEvent *event) { - if (event->canceled) { - return; - } - AsyncLambdaTask *task = reinterpret_cast(event->object); + auto *task = static_cast(event->object); task->co->resume(); } -bool async(const std::function &fn, double timeout) { +bool async(const std::function &fn) { AsyncEvent event{}; AsyncLambdaTask task{Coroutine::get_current_safe(), fn}; @@ -680,14 +637,26 @@ bool async(const std::function &fn, double timeout) { return false; } - if (!task.co->yield_ex(timeout)) { - _ev->canceled = true; - errno = swoole_get_last_error(); - return false; - } else { - errno = _ev->error; - return true; + task.co->yield(); + errno = _ev->error; + return true; +} + +bool wait_for(const std::function &fn) { + double second = SW_FILE_LOCK_DEFAULT_SECOND; + while (true) { + if (fn()) { + break; + } + if (System::sleep(second) != SW_OK) { + return false; + } + // Limit the maximum waiting time to 0.1 second; otherwise, this exponential time waiting + // will make it increasingly difficult to acquire the lock. + second = (second >= SW_FILE_LOCK_MAX_SECOND) ? SW_FILE_LOCK_DEFAULT_SECOND + : SW_MIN(second * 2, SW_FILE_LOCK_MAX_SECOND); } + return true; } } // namespace coroutine diff --git a/src/coroutine/thread_context.cc b/src/coroutine/thread_context.cc index bde7181161..ec55b8ba8b 100644 --- a/src/coroutine/thread_context.cc +++ b/src/coroutine/thread_context.cc @@ -14,7 +14,7 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" +#include "swoole_signal.h" #include "swoole_async.h" #include "swoole_coroutine_context.h" @@ -32,9 +32,13 @@ static std::mutex *current_lock = nullptr; void thread_context_init() { if (!swoole_timer_is_available()) { - swoole_timer_add(1, false, [](Timer *timer, TimerNode *tnode) { - // do nothing - }, nullptr); + swoole_timer_add( + 1L, + false, + [](Timer *timer, TimerNode *tnode) { + // do nothing + }, + nullptr); } if (SwooleTG.async_threads == nullptr) { SwooleTG.async_threads = new AsyncThreads(); @@ -56,8 +60,8 @@ void thread_context_clean() { g_lock.unlock(); } -Context::Context(size_t stack_size, const CoroutineFunc &fn, void *private_data) - : fn_(fn), private_data_(private_data) { +Context::Context(size_t stack_size, CoroutineFunc fn, void *private_data) + : fn_(std::move(fn)), private_data_(private_data) { end_ = false; lock_.lock(); swap_lock_ = nullptr; @@ -83,7 +87,8 @@ bool Context::swap_out() { return true; } -void Context::context_func(void *arg) { +void Context::context_func(coroutine_transfer_t arg) { + swoole_signal_block_all(); Context *_this = (Context *) arg; SwooleTG.reactor = g_reactor; SwooleTG.timer = g_timer; diff --git a/src/coroutine/uring_socket.cc b/src/coroutine/uring_socket.cc new file mode 100644 index 0000000000..1dd0a03386 --- /dev/null +++ b/src/coroutine/uring_socket.cc @@ -0,0 +1,668 @@ + + +/* + +----------------------------------------------------------------------+ + | 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. | + +----------------------------------------------------------------------+ + | @link https://www.swoole.com/ | + | @contact team@swoole.com | + | @license https://github.com/swoole/swoole-src/blob/master/LICENSE | + | @Author Tianfeng Han | + +----------------------------------------------------------------------+ +*/ + +#include "swoole_uring_socket.h" +#include "swoole_coroutine_socket.h" +#include "swoole_coroutine_system.h" +#include "swoole_util.h" +#include "swoole_iouring.h" + +typedef swoole::network::Socket NetSocket; + +#ifdef SW_USE_IOURING +namespace swoole { +namespace coroutine { +bool UringSocket::connect(const sockaddr *addr, socklen_t addrlen) { + if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { + return false; + } + + write_co = read_co = Coroutine::get_current_safe(); + int retval = Iouring::connect(socket->get_fd(), addr, addrlen, socket->connect_timeout); + write_co = read_co = nullptr; + if (retval < 0) { + return false; + } + + connected = true; + socket->get_name(); + set_err(0); + return true; +} + +UringSocket *UringSocket::accept(double timeout) { + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return nullptr; + } + if (ssl_is_enable() && sw_unlikely(ssl_context->context == nullptr) && !ssl_context_create()) { + return nullptr; + } + + read_co = Coroutine::get_current_safe(); + network::Socket *conn = uring_accept(timeout == 0 ? socket->read_timeout : timeout); + read_co = nullptr; + + if (conn == nullptr) { + set_err(errno); + return nullptr; + } + + auto *client_sock = new UringSocket(conn, this); + if (sw_unlikely(client_sock->get_fd() < 0)) { + swoole_sys_warning("new Socket() failed"); + set_err(errno); + delete client_sock; + return nullptr; + } + + return client_sock; +} + +NetSocket *UringSocket::uring_accept(double timeout) { + auto *client_socket = new NetSocket(); + int fd = Iouring::accept(socket->get_fd(), + reinterpret_cast(&client_socket->info.addr), + &client_socket->info.len, + SOCK_CLOEXEC | SOCK_NONBLOCK, + socket->read_timeout); + if (fd < 0) { + delete client_socket; + return nullptr; + } + + client_socket->fd = fd; + client_socket->removed = 1; + client_socket->info.type = socket->socket_type; + client_socket->nonblock = 1; + client_socket->cloexec = 1; + + return client_socket; +} + +ssize_t UringSocket::uring_send(const void *_buf, size_t _n) { + return Iouring::send(socket->get_fd(), _buf, _n, 0, socket->write_timeout); +} + +ssize_t UringSocket::uring_recv(void *_buf, size_t _n) { + return Iouring::recv(socket->get_fd(), _buf, _n, 0, socket->read_timeout); +} + +ssize_t UringSocket::uring_readv(const struct iovec *iovec, int count) { + return Iouring::readv(socket->get_fd(), iovec, count, socket->read_timeout); +} + +ssize_t UringSocket::uring_writev(const struct iovec *iovec, int count) { + return Iouring::writev(socket->get_fd(), iovec, count, socket->write_timeout); +} + +ssize_t UringSocket::uring_sendfile(const File &file, off_t *offset, size_t size) { + return Iouring::sendfile(socket->get_fd(), file.get_fd(), offset, size, socket->write_timeout); +} + +ssize_t UringSocket::read(void *_buf, size_t _n) { + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return -1; + } + read_co = Coroutine::get_current_safe(); + ssize_t retval = Iouring::read(socket->get_fd(), _buf, _n, socket->read_timeout); + read_co = nullptr; + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::write(const void *_buf, size_t _n) { + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return -1; + } + write_co = Coroutine::get_current_safe(); + ssize_t retval = Iouring::write(socket->get_fd(), _buf, _n, socket->write_timeout); + write_co = nullptr; + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::recvmsg(msghdr *msg, int flags) { + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return -1; + } + read_co = Coroutine::get_current_safe(); + ssize_t retval = Iouring::recvmsg(socket->get_fd(), msg, flags, socket->read_timeout); + read_co = nullptr; + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::sendmsg(const msghdr *msg, int flags) { + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return -1; + } + write_co = Coroutine::get_current_safe(); + ssize_t retval = Iouring::sendmsg(socket->get_fd(), msg, flags, socket->write_timeout); + write_co = nullptr; + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::recvfrom(void *_buf, size_t _n, sockaddr *_addr, socklen_t *_socklen) { + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return -1; + } + read_co = Coroutine::get_current_safe(); + ssize_t retval = Iouring::recvfrom(socket->get_fd(), _buf, _n, _addr, _socklen, socket->read_timeout); + read_co = nullptr; + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::sendto(const std::string &host, int port, const void *_buf, size_t _n) { + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return -1; + } + + ssize_t retval = 0; + network::Address addr; + + if (!socket->is_dgram()) { + set_err(EPROTONOSUPPORT); + return -1; + } + + auto ip_addr = host; + + SW_LOOP_N(2) { + if (!addr.assign(type, ip_addr, port, false)) { + if (swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { + ip_addr = System::gethostbyname(host, sock_domain, socket->dns_timeout); + if (!ip_addr.empty()) { + continue; + } + } + set_err(); + return -1; + } + break; + } + + write_co = Coroutine::get_current_safe(); + retval = Iouring::sendto(socket->get_fd(), _buf, _n, 0, &addr.addr.ss, addr.len, socket->write_timeout); + write_co = nullptr; + + swoole_trace_log(SW_TRACE_SOCKET, "sendto %ld/%ld bytes, errno=%d", retval, _n, errno); + + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::recv(void *_buf, size_t _n) { + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return -1; + } + + ssize_t retval; + read_co = Coroutine::get_current_safe(); + if (is_ssl()) { + retval = ssl_recv(_buf, _n); + } else { + retval = uring_recv(_buf, _n); + } + read_co = nullptr; + + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::send(const void *_buf, size_t _n) { + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return -1; + } + + ssize_t retval; + write_co = Coroutine::get_current_safe(); + if (is_ssl()) { + retval = ssl_send(_buf, _n); + } else { + retval = uring_send(_buf, _n); + } + write_co = nullptr; + + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::recv_all(void *_buf, size_t _n) { + ssize_t retval = 0; + size_t total_bytes = 0; + + do { + retval = recv((char *) _buf + total_bytes, _n - total_bytes); + if (retval <= 0) { + break; + } + total_bytes += retval; + } while (total_bytes < _n); + + check_return_value(retval); + return retval < 0 && total_bytes == 0 ? -1 : total_bytes; +} + +ssize_t UringSocket::send_all(const void *_buf, size_t _n) { + ssize_t retval = 0; + size_t total_bytes = 0; + + do { + retval = send((char *) _buf + total_bytes, _n - total_bytes); + if (retval <= 0) { + break; + } + total_bytes += retval; + } while (total_bytes < _n); + + check_return_value(retval); + return retval < 0 && total_bytes == 0 ? -1 : total_bytes; +} + +bool UringSocket::poll(EventType _type, double timeout) { + if (sw_unlikely(!is_available(_type))) { + return false; + } + + struct pollfd fds[1]; + fds[0].events = translate_events_to_poll(_type); + fds[0].fd = socket->get_fd(); + fds[0].revents = 0; + + auto rc = Iouring::poll(fds, 1, timeout > 0 ? timeout * 1000 : timeout) == 1; + if (rc != 1) { + set_err(rc == 0 ? ETIMEDOUT : errno); + return false; + } + return true; +} + +bool UringSocket::sendfile(const char *filename, off_t offset, size_t length) { + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return false; + } + + File file(filename, O_RDONLY); + if (!file.ready()) { + set_err(errno, std_string::format("open(%s) failed, %s", filename, strerror(errno))); + return false; + } + + if (length == 0) { + FileStatus file_stat; + if (!file.stat(&file_stat)) { + set_err(errno, std_string::format("fstat(%s) failed, %s", filename, strerror(errno))); + return false; + } + length = file_stat.st_size; + } else { + // total length of the file + length = offset + length; + } + + ssize_t retval; + + write_co = Coroutine::get_current_safe(); + if (is_ssl()) { + retval = ssl_sendfile(file, &offset, length); + } else { + retval = uring_sendfile(file, &offset, length); + } + write_co = nullptr; + + return retval == (ssize_t) length; +} + +ssize_t UringSocket::readv(network::IOVector *io_vector) { + ssize_t retval; + if (sw_unlikely(!is_available(SW_EVENT_READ))) { + return -1; + } + + read_co = Coroutine::get_current_safe(); + do { + if (is_ssl()) { + retval = ssl_readv(io_vector); + } else { + retval = uring_readv(io_vector->get_iterator(), io_vector->get_remain_count()); + } + io_vector->update_iterator(retval); + } while (retval < 0 && errno == EINTR); + read_co = nullptr; + + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::writev(network::IOVector *io_vector) { + ssize_t retval; + if (sw_unlikely(!is_available(SW_EVENT_WRITE))) { + return -1; + } + + write_co = Coroutine::get_current_safe(); + do { + if (is_ssl()) { + retval = ssl_writev(io_vector); + } else { + retval = uring_writev(io_vector->get_iterator(), io_vector->get_remain_count()); + } + io_vector->update_iterator(retval); + } while (retval < 0 && errno == EINTR); + write_co = nullptr; + + check_return_value(retval); + return retval; +} + +ssize_t UringSocket::readv_all(network::IOVector *io_vector) { + ssize_t retval = 0; + size_t total_bytes = 0; + + do { + retval = readv(io_vector); + if (retval <= 0) { + break; + } + total_bytes += retval; + } while (retval > 0 && io_vector->get_remain_count() > 0); + + check_return_value(retval); + return retval < 0 && total_bytes == 0 ? -1 : total_bytes; +} + +ssize_t UringSocket::writev_all(network::IOVector *io_vector) { + ssize_t retval = 0; + size_t total_bytes = 0; + + do { + retval = writev(io_vector); + if (retval <= 0) { + break; + } + total_bytes += retval; + } while (retval > 0 && io_vector->get_remain_count() > 0); + + check_return_value(retval); + return retval < 0 && total_bytes == 0 ? -1 : total_bytes; +} + +bool UringSocket::ssl_bio_write() { + auto buf = get_write_buffer(); + + while (true) { + size_t pending = BIO_ctrl_pending(wbio); + if (pending == 0) { + break; + } + + int nread = BIO_read(wbio, buf->str, buf->size); + if (nread <= 0) { + set_err(SW_ERROR_SSL_BAD_PROTOCOL); + return false; + } + + ssize_t written = 0; + while (written < nread) { + ssize_t n = uring_send(buf->str + written, nread - written); + if (n > 0) { + written += n; + } else if (n == 0) { + errno = SW_ERROR_SSL_RESET; + return false; + } else { + if (errno == EINTR) { + continue; + } + set_err(errno); + return false; + } + } + } + + return true; +} + +bool UringSocket::ssl_bio_read() { + auto buf = get_read_buffer(); + ssize_t rv = uring_recv(buf->str, buf->size); + if (rv > 0) { + int written = BIO_write(rbio, buf->str, rv); + if (written != rv) { + set_err(SW_ERROR_SSL_BAD_PROTOCOL); + return false; + } + return true; + } else if (rv == 0) { + set_err(SW_ERROR_SSL_RESET); + return false; + } else { + if (errno == EINTR) { + return ssl_bio_read(); + } + set_err(errno); + return false; + } +} + +bool UringSocket::ssl_bio_prepare() { + if (BIO_ctrl_pending(wbio) > 0) { + if (!ssl_bio_write()) { + check_return_value(-1); + return false; + } + } + return true; +} + +bool UringSocket::ssl_bio_perform(int rc, const char *fn) { + if (!ssl_bio_prepare()) { + return false; + } + + int error = SSL_get_error(socket->ssl, rc); + if (error == SSL_ERROR_WANT_WRITE) { + if (ssl_bio_write()) { + _error: + check_return_value(-1); + return false; + } + return true; + } else if (error == SSL_ERROR_WANT_READ) { + if (!ssl_bio_read()) { + goto _error; + } + return true; + } else if (error == SSL_ERROR_ZERO_RETURN) { + swoole_debug("%s(fd=%d) return zero value", fn, socket->get_fd()); + error = SW_ERROR_SSL_RESET; + goto _error; + } else if (error == SSL_ERROR_SYSCALL) { + goto _error; + } else { + ulong_t err_code = ERR_get_error(); + if (err_code) { + char error_buf[512]; + ERR_error_string_n(err_code, error_buf, sizeof(error_buf)); + swoole_notice("%s(fd=%d) to server[%s:%d] failed. Error: %s[%d|%d]", + fn, + socket->get_fd(), + socket->info.get_addr(), + socket->info.get_port(), + error_buf, + error, + ERR_GET_REASON(err_code)); + set_err(SW_ERROR_SSL_BAD_PROTOCOL, error_buf); + } else { + set_err(SW_ERROR_SSL_BAD_PROTOCOL); + } + return false; + } +} + +bool UringSocket::ssl_handshake() { + if (ssl_handshaked) { + set_err(SW_ERROR_WRONG_OPERATION); + return false; + } + if (sw_unlikely(!is_available(SW_EVENT_RDWR))) { + return false; + } + + /** + * If the ssl_context is empty, it indicates that this socket was not a connection + * returned by a server socket accept, and a new ssl_context needs to be created. + */ + if (ssl_context->context == nullptr && !ssl_context_create()) { + return false; + } + if (!ssl_create(get_ssl_context())) { + return false; + } + rbio = BIO_new(BIO_s_mem()); + wbio = BIO_new(BIO_s_mem()); + SSL_set_bio(socket->ssl, rbio, wbio); + + const char *fn; + if (ssl_is_server) { + fn = "ssl_accept"; + SSL_set_accept_state(socket->ssl); + } else { + fn = "ssl_connect"; + SSL_set_connect_state(socket->ssl); + } + + while (true) { + auto rs = SSL_do_handshake(socket->ssl); + if (rs == 1) { + break; + } + if (ssl_bio_perform(rs, fn)) { + continue; + } else { + return false; + } + } + + if (ssl_context->verify_peer) { + if (!ssl_verify(ssl_context->allow_self_signed)) { + return false; + } + } + ssl_handshaked = true; + + return true; +} + +ssize_t UringSocket::ssl_readv(network::IOVector *io_vector) { + ssize_t retval, total_bytes = 0; + + do { + retval = ssl_recv(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); + total_bytes += retval > 0 ? retval : 0; + io_vector->update_iterator(retval); + } while (retval > 0 && io_vector->get_remain_count() > 0); + + return total_bytes > 0 ? total_bytes : retval; +} + +ssize_t UringSocket::ssl_writev(network::IOVector *io_vector) { + ssize_t retval, total_bytes = 0; + + do { + retval = ssl_send(io_vector->get_iterator()->iov_base, io_vector->get_iterator()->iov_len); + total_bytes += retval > 0 ? retval : 0; + io_vector->update_iterator(retval); + } while (retval > 0 && io_vector->get_remain_count() > 0); + + return total_bytes > 0 ? total_bytes : retval; +} + +ssize_t UringSocket::ssl_recv(void *_buf, size_t _n) { + while (true) { + int n = SSL_read(socket->ssl, _buf, _n); + if (!ssl_bio_prepare()) { + return -1; + } + if (n > 0) { + return n; + } + if (!ssl_bio_perform(n, "ssl_recv")) { + return -1; + } + } +} + +ssize_t UringSocket::ssl_send(const void *_buf, size_t _n) { + while (true) { + int n = SSL_write(socket->ssl, _buf, _n); + if (!ssl_bio_prepare()) { + return -1; + } + if (n > 0) { + return n; + } + if (!ssl_bio_perform(n, "ssl_send")) { + return -1; + } + } +} + +ssize_t UringSocket::ssl_sendfile(const File &file, off_t *offset, size_t size) { + char buf[SW_BUFFER_SIZE_BIG]; + size_t total = 0; + + while (total < size) { + ssize_t readn = size > sizeof(buf) ? sizeof(buf) : size; + ssize_t n = file.pread(buf, readn, *offset); + if (n <= 0) { + swoole_sys_warning("pread() failed"); + break; + } + + ssize_t ret = ssl_send(buf, n); + if (ret > 0) { + *offset += ret; + total += ret; + swoole_trace_log(SW_TRACE_REACTOR, "fd=%d, readn=%ld, n=%ld, ret=%ld", socket->get_fd(), readn, n, ret); + } else if (ret == 0) { + return total; + } else { + switch (socket->catch_write_error(errno)) { + case SW_ERROR: + swoole_sys_warning("write() failed"); + return total; + case SW_CLOSE: + return total; + case SW_WAIT: + default: + break; + } + } + } + + return total; +} +}; // namespace coroutine +}; // namespace swoole +#endif diff --git a/src/lock/barrier.cc b/src/lock/barrier.cc new file mode 100644 index 0000000000..74e44804e4 --- /dev/null +++ b/src/lock/barrier.cc @@ -0,0 +1,62 @@ +/* + +----------------------------------------------------------------------+ + | 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 "swoole_lock.h" + +namespace swoole { + +#define BARRIER_USEC 10000 + +void Barrier::init(bool shared, int count) { +#ifdef SW_USE_PTHREAD_BARRIER + if (shared) { + pthread_barrierattr_setpshared(&barrier_attr_, PTHREAD_PROCESS_SHARED); + pthread_barrier_init(&barrier_, &barrier_attr_, count); + } else { + pthread_barrier_init(&barrier_, nullptr, count); + } + shared_ = shared; +#else + barrier_ = 0; + count_ = count; +#endif +} + +void Barrier::wait() { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_wait(&barrier_); +#else + sw_atomic_add_fetch(&barrier_, 1); + SW_LOOP { + if (barrier_ == count_) { + break; + } + usleep(BARRIER_USEC); + sw_atomic_memory_barrier(); + } +#endif +} + +void Barrier::destroy() { +#ifdef SW_USE_PTHREAD_BARRIER + pthread_barrier_destroy(&barrier_); + if (shared_) { + pthread_barrierattr_destroy(&barrier_attr_); + } +#endif +} + +}; // namespace swoole diff --git a/src/lock/coroutine_lock.cc b/src/lock/coroutine_lock.cc new file mode 100644 index 0000000000..cb64df4d3b --- /dev/null +++ b/src/lock/coroutine_lock.cc @@ -0,0 +1,120 @@ +/* + +----------------------------------------------------------------------+ + | 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: NathanFreeman | + +----------------------------------------------------------------------+ + */ + +#include "swoole_lock.h" + +#ifdef HAVE_IOURING_FUTEX +#include "swoole_iouring.h" +#else +#include "swoole_coroutine_system.h" +using swoole::coroutine::System; +#endif + +namespace swoole { +CoroutineLock::CoroutineLock(bool shared) : Lock(COROUTINE_LOCK, shared) { + if (shared) { + value = (sw_atomic_t *) sw_mem_pool()->alloc(sizeof(sw_atomic_t)); + } else { + value = new sw_atomic_t; + } + *value = 0; +} + +CoroutineLock::~CoroutineLock() { + if (shared_) { + sw_mem_pool()->free((void *) value); + } else { + delete value; + } + value = nullptr; +} + +int CoroutineLock::lock(int operation, int _) { + if (operation & LOCK_NB) { + return lock_impl(false); + } else { + return lock_impl(true); + } +} + +int CoroutineLock::unlock() { + Coroutine *current_coroutine = Coroutine::get_current(); + if (current_coroutine == nullptr) { + swoole_warning("The coroutine lock can only be used in a coroutine environment"); + return SW_ERROR_CO_OUT_OF_COROUTINE; + } + + if (*value == 0) { + return 0; + } + + *value = 0; + cid = 0; + coroutine = nullptr; + +#ifdef HAVE_IOURING_FUTEX + return Iouring::futex_wakeup((uint32_t *) value) >= 0 ? 0 : errno; +#else + return 0; +#endif +} + +int CoroutineLock::lock_impl(bool blocking) { + Coroutine *current_coroutine = Coroutine::get_current(); + if (current_coroutine == nullptr) { + swoole_warning("The coroutine lock can only be used in a coroutine environment"); + return SW_ERROR_CO_OUT_OF_COROUTINE; + } + + if (current_coroutine == static_cast(coroutine) && current_coroutine->get_cid() == cid) { + return 0; + } + + int result = 0; +#ifndef HAVE_IOURING_FUTEX + double second = SW_FILE_LOCK_DEFAULT_SECOND; +#endif + + while (true) { + if (sw_atomic_cmp_set(value, 0, 1)) { + break; + } + + if (!blocking) { + return EBUSY; + } + +#ifdef HAVE_IOURING_FUTEX + result = Iouring::futex_wait((uint32_t *) value); + if (result != 0) { + return errno; + } +#else + if (System::sleep(second) != SW_OK) { + return SW_ERROR_CO_CANCELED; + } + // Limit the maximum waiting time to 0.1 second; otherwise, this exponential time waiting + // will make it increasingly difficult to acquire the lock. + second = (second >= SW_FILE_LOCK_MAX_SECOND) ? SW_FILE_LOCK_DEFAULT_SECOND + : SW_MIN(second * 2, SW_FILE_LOCK_MAX_SECOND); +#endif + } + + cid = current_coroutine->get_cid(); + coroutine = (void *) current_coroutine; + return result; +} +} // namespace swoole diff --git a/src/lock/mutex.cc b/src/lock/mutex.cc index 4e7ffaa9d4..4e73a8b00a 100644 --- a/src/lock/mutex.cc +++ b/src/lock/mutex.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_lock.h" namespace swoole { @@ -24,95 +23,46 @@ struct MutexImpl { pthread_mutexattr_t attr_; }; -Mutex::Mutex(int flags) : Lock() { - if (flags & PROCESS_SHARED) { +Mutex::Mutex(bool shared) : Lock(MUTEX, shared) { + if (shared) { impl = (MutexImpl *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } - shared_ = true; } else { impl = new MutexImpl(); - shared_ = false; } - type_ = MUTEX; pthread_mutexattr_init(&impl->attr_); - - if (flags & PROCESS_SHARED) { -#ifdef HAVE_PTHREAD_MUTEXATTR_SETPSHARED + if (shared) { pthread_mutexattr_setpshared(&impl->attr_, PTHREAD_PROCESS_SHARED); -#else - swoole_warning("PTHREAD_MUTEX_PSHARED is not supported"); -#endif - } - - if (flags & ROBUST) { -#ifdef HAVE_PTHREAD_MUTEXATTR_SETROBUST - pthread_mutexattr_setrobust(&impl->attr_, PTHREAD_MUTEX_ROBUST); -#else - swoole_warning("PTHREAD_MUTEX_ROBUST is not supported"); -#endif } - - if (pthread_mutex_init(&impl->lock_, &impl->attr_) < 0) { + if (pthread_mutex_init(&impl->lock_, &impl->attr_) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_mutex_init() failed"); } } -int Mutex::lock() { - int retval = pthread_mutex_lock(&impl->lock_); -#ifdef HAVE_PTHREAD_MUTEX_CONSISTENT - if (retval == EOWNERDEAD) { - retval = pthread_mutex_consistent(&impl->lock_); +int Mutex::lock(int operation, int timeout_msec) { + if (operation & LOCK_NB) { + return pthread_mutex_trylock(&impl->lock_); } + if (timeout_msec > 0) { +#ifdef HAVE_MUTEX_TIMEDLOCK + timespec timeo; + realtime_get(&timeo); + realtime_add(&timeo, timeout_msec); + return pthread_mutex_timedlock(&impl->lock_, &timeo); +#else + return sw_wait_for([this]() { return pthread_mutex_trylock(&impl->lock_) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; #endif - return retval; -} - -int Mutex::lock_rd() { - return lock(); + } + return pthread_mutex_lock(&impl->lock_); } int Mutex::unlock() { return pthread_mutex_unlock(&impl->lock_); } -int Mutex::trylock() { - return pthread_mutex_trylock(&impl->lock_); -} - -int Mutex::trylock_rd() { - return trylock(); -} - -#ifdef HAVE_MUTEX_TIMEDLOCK -int Mutex::lock_wait(int timeout_msec) { - struct timespec timeo = swoole_time_until(timeout_msec); - return pthread_mutex_timedlock(&impl->lock_, &timeo); -} -#else -int Mutex::lock_wait(int timeout_msec) { - int sub = 1; - int sleep_ms = 1000; - - if (timeout_msec > 100) { - sub = 10; - sleep_ms = 10000; - } - - while (timeout_msec > 0) { - if (pthread_mutex_trylock(&impl->lock_) == 0) { - return 0; - } else { - usleep(sleep_ms); - timeout_msec -= sub; - } - } - return ETIMEDOUT; -} -#endif - Mutex::~Mutex() { pthread_mutexattr_destroy(&impl->attr_); pthread_mutex_destroy(&impl->lock_); diff --git a/src/lock/rw_lock.cc b/src/lock/rw_lock.cc index cd725dd981..5d10c55e95 100644 --- a/src/lock/rw_lock.cc +++ b/src/lock/rw_lock.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_lock.h" #ifdef HAVE_RWLOCK @@ -22,54 +21,82 @@ namespace swoole { struct RWLockImpl { - pthread_rwlock_t _lock; - pthread_rwlockattr_t attr; + pthread_rwlock_t lock_; + pthread_rwlockattr_t attr_; }; -RWLock::RWLock(int use_in_process) : Lock() { - if (use_in_process) { +RWLock::RWLock(bool shared) : Lock(RW_LOCK, shared) { + if (shared) { impl = (RWLockImpl *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } - shared_ = true; } else { impl = new RWLockImpl(); - shared_ = false; } - type_ = RW_LOCK; - pthread_rwlockattr_init(&impl->attr); - if (use_in_process == 1) { - pthread_rwlockattr_setpshared(&impl->attr, PTHREAD_PROCESS_SHARED); + pthread_rwlockattr_init(&impl->attr_); + if (shared) { + pthread_rwlockattr_setpshared(&impl->attr_, PTHREAD_PROCESS_SHARED); } - if (pthread_rwlock_init(&impl->_lock, &impl->attr) < 0) { + if (pthread_rwlock_init(&impl->lock_, &impl->attr_) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_rwlock_init() failed"); } } -int RWLock::lock_rd() { - return pthread_rwlock_rdlock(&impl->_lock); -} - -int RWLock::lock() { - return pthread_rwlock_wrlock(&impl->_lock); +static int rwlock_timed_lock_rd(pthread_rwlock_t *rwlock, int timeout_msec) { +#ifdef HAVE_RWLOCK_TIMEDRDLOCK + timespec timeo; + realtime_get(&timeo); + realtime_add(&timeo, timeout_msec); + return pthread_rwlock_timedrdlock(rwlock, &timeo); +#else + return sw_wait_for([rwlock]() { return pthread_rwlock_tryrdlock(rwlock) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; +#endif } -int RWLock::unlock() { - return pthread_rwlock_unlock(&impl->_lock); +static int rwlock_timed_lock_wr(pthread_rwlock_t *rwlock, int timeout_msec) { +#ifdef HAVE_RWLOCK_TIMEDRDLOCK + timespec timeo; + realtime_get(&timeo); + realtime_add(&timeo, timeout_msec); + return pthread_rwlock_timedwrlock(rwlock, &timeo); +#else + return sw_wait_for([rwlock]() { return pthread_rwlock_trywrlock(rwlock) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; +#endif } -int RWLock::trylock_rd() { - return pthread_rwlock_tryrdlock(&impl->_lock); +int RWLock::lock(int operation, int timeout_msec) { + if (operation & LOCK_NB) { + if (operation & LOCK_SH) { + return pthread_rwlock_tryrdlock(&impl->lock_); + } else { + return pthread_rwlock_trywrlock(&impl->lock_); + } + } else { + if (timeout_msec > 0) { + if (operation & LOCK_SH) { + return rwlock_timed_lock_rd(&impl->lock_, timeout_msec); + } else { + return rwlock_timed_lock_wr(&impl->lock_, timeout_msec); + } + } else { + if (operation & LOCK_SH) { + return pthread_rwlock_rdlock(&impl->lock_); + } else { + return pthread_rwlock_wrlock(&impl->lock_); + } + } + } } -int RWLock::trylock() { - return pthread_rwlock_trywrlock(&impl->_lock); +int RWLock::unlock() { + return pthread_rwlock_unlock(&impl->lock_); } RWLock::~RWLock() { - pthread_rwlock_destroy(&impl->_lock); + pthread_rwlockattr_destroy(&impl->attr_); + pthread_rwlock_destroy(&impl->lock_); if (shared_) { sw_mem_pool()->free(impl); } else { @@ -78,5 +105,4 @@ RWLock::~RWLock() { } } // namespace swoole - #endif diff --git a/src/lock/spin_lock.cc b/src/lock/spin_lock.cc index aa058410f6..ee5b7b4077 100644 --- a/src/lock/spin_lock.cc +++ b/src/lock/spin_lock.cc @@ -14,51 +14,39 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_lock.h" #ifdef HAVE_SPINLOCK - namespace swoole { - -SpinLock::SpinLock(int use_in_process) : Lock() { - if (use_in_process) { +SpinLock::SpinLock(bool shared) : Lock(SPIN_LOCK, shared) { + if (shared) { impl = (pthread_spinlock_t *) sw_mem_pool()->alloc(sizeof(*impl)); if (impl == nullptr) { throw std::bad_alloc(); } - shared_ = true; } else { impl = new pthread_spinlock_t(); - shared_ = false; } - type_ = SPIN_LOCK; - if (pthread_spin_init(impl, use_in_process) < 0) { + if (pthread_spin_init(impl, shared) != 0) { throw std::system_error(errno, std::generic_category(), "pthread_spin_init() failed"); } } -int SpinLock::lock() { +int SpinLock::lock(int operation, int timeout_msec) { + if (operation & LOCK_NB) { + return pthread_spin_trylock(impl); + } + if (timeout_msec > 0) { + return sw_wait_for([this]() { return pthread_spin_trylock(impl) == 0; }, timeout_msec) ? 0 : ETIMEDOUT; + } return pthread_spin_lock(impl); } -int SpinLock::lock_rd() { - return lock(); -} - int SpinLock::unlock() { return pthread_spin_unlock(impl); } -int SpinLock::trylock() { - return pthread_spin_trylock(impl); -} - -int SpinLock::trylock_rd() { - return trylock(); -} - SpinLock::~SpinLock() { pthread_spin_destroy(impl); if (shared_) { diff --git a/src/memory/buffer.cc b/src/memory/buffer.cc deleted file mode 100644 index 51fa67b3f8..0000000000 --- a/src/memory/buffer.cc +++ /dev/null @@ -1,145 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 "swoole.h" -#include "swoole_buffer.h" - -namespace swoole { - -Buffer::Buffer(uint32_t _chunk_size) { - chunk_size = _chunk_size == 0 ? INT_MAX : _chunk_size; -} - -BufferChunk *Buffer::alloc(BufferChunk::Type type, uint32_t size) { - BufferChunk *chunk = new BufferChunk(); - - if (type == BufferChunk::TYPE_DATA && size > 0) { - chunk->size = size; - chunk->value.ptr = new char[size]; - } - - chunk->type = type; - queue_.push(chunk); - - return chunk; -} - -void Buffer::pop() { - BufferChunk *chunk = queue_.front(); - - total_length -= chunk->size; - if (chunk->type == BufferChunk::TYPE_DATA) { - delete[] chunk->value.ptr; - } - if (chunk->destroy) { - chunk->destroy(chunk); - } - delete chunk; - queue_.pop(); -} - -Buffer::~Buffer() { - while (!queue_.empty()) { - pop(); - } -} - -void Buffer::append(const void *data, uint32_t size) { - uint32_t _length = size; - char *_pos = (char *) data; - uint32_t _n; - - assert(size > 0); - - // buffer enQueue - while (_length > 0) { - _n = _length >= chunk_size ? chunk_size : _length; - - BufferChunk *chunk = alloc(BufferChunk::TYPE_DATA, _n); - - total_length += _n; - - memcpy(chunk->value.ptr, _pos, _n); - chunk->length = _n; - - swoole_trace_log(SW_TRACE_BUFFER, "chunk_n=%lu|size=%u|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); - - _pos += _n; - _length -= _n; - } -} - -void Buffer::append(const struct iovec *iov, size_t iovcnt, off_t offset) { - size_t _length = 0; - - SW_LOOP_N(iovcnt) { - assert(iov[i].iov_len > 0); - assert(iov[i].iov_base != nullptr); - _length += iov[i].iov_len; - } - - char *pos = (char *) iov[0].iov_base; - BufferChunk *chunk = nullptr; - size_t iov_remain_len = iov[0].iov_len, chunk_remain_len; - size_t i = 0; - - while (true) { - if (chunk) { - if (chunk->size == chunk->length) { - chunk = nullptr; - continue; - } else { - chunk_remain_len = chunk->size - chunk->length; - } - } else { - if (offset > 0) { - if (offset >= (off_t) iov[i].iov_len) { - offset -= iov[i].iov_len; - i++; - continue; - } else { - offset = 0; - pos += offset; - iov_remain_len -= offset; - } - } - chunk_remain_len = _length >= chunk_size ? chunk_size : _length; - chunk = alloc(BufferChunk::TYPE_DATA, chunk_remain_len); - } - - size_t _n = std::min(iov_remain_len, chunk_remain_len); - memcpy(chunk->value.ptr + chunk->length, pos, _n); - total_length += _n; - _length -= _n; - - swoole_trace_log(SW_TRACE_BUFFER, "chunk_n=%lu|size=%lu|chunk_len=%u|chunk=%p", count(), _n, chunk->length, chunk); - - chunk->length += _n; - iov_remain_len -= _n; - - if (iov_remain_len == 0) { - i++; - if (i == iovcnt) { - break; - } - iov_remain_len = iov[i].iov_len; - pos = (char *) iov[i].iov_base; - } else { - pos += _n; - } - } -} -} // namespace swoole diff --git a/src/memory/fixed_pool.cc b/src/memory/fixed_pool.cc index 7e7daa4d4e..e7bfd9aa97 100644 --- a/src/memory/fixed_pool.cc +++ b/src/memory/fixed_pool.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_memory.h" namespace swoole { @@ -62,8 +61,8 @@ FixedPool::FixedPool(uint32_t slice_num, uint32_t slice_size, bool shared) { throw std::bad_alloc(); } - impl = (FixedPoolImpl *) memory; - memory = (char *) memory + sizeof(*impl); + impl = static_cast(memory); + memory = static_cast(memory) + sizeof(*impl); sw_memset_zero(impl, sizeof(*impl)); impl->shared = shared; @@ -107,11 +106,10 @@ size_t FixedPool::sizeof_struct_impl() { * linked list */ void FixedPoolImpl::init() { - FixedPoolSlice *slice; void *cur = memory; void *max = (char *) memory + size; do { - slice = (FixedPoolSlice *) cur; + auto *slice = static_cast(cur); sw_memset_zero(slice, sizeof(FixedPoolSlice)); if (head != nullptr) { @@ -125,24 +123,24 @@ void FixedPoolImpl::init() { cur = (char *) cur + (sizeof(FixedPoolSlice) + slice_size); if (cur < max) { - slice->prev = (FixedPoolSlice *) cur; + slice->prev = static_cast(cur); } else { slice->prev = nullptr; break; } - } while (1); + } while (true); } -uint32_t FixedPool::get_number_of_spare_slice() { +uint32_t FixedPool::get_number_of_spare_slice() const { return impl->slice_num - impl->slice_use; } -uint32_t FixedPool::get_number_of_total_slice() { +uint32_t FixedPool::get_number_of_total_slice() const { return impl->slice_num; } -uint32_t FixedPool::get_slice_size() { +uint32_t FixedPool::get_slice_size() const { return impl->slice_size; } @@ -171,9 +169,9 @@ void *FixedPool::alloc(uint32_t size) { } void FixedPool::free(void *ptr) { - FixedPoolSlice *slice = (FixedPoolSlice *) ((char *) ptr - sizeof(FixedPoolSlice)); + auto *slice = reinterpret_cast(static_cast(ptr) - sizeof(FixedPoolSlice)); - assert(ptr > impl->memory && (char *) ptr < (char *) impl->memory + impl->size); + assert(ptr > impl->memory && static_cast(ptr) < static_cast(impl->memory) + impl->size); assert(slice->lock == 1); impl->slice_use--; @@ -209,7 +207,7 @@ FixedPool::~FixedPool() { } } -void FixedPool::debug(int max_lines) { +void FixedPool::debug(int max_lines) const { int line = 0; FixedPoolSlice *slice = impl->head; diff --git a/src/memory/global_memory.cc b/src/memory/global_memory.cc index d03837ea05..af822f6b97 100644 --- a/src/memory/global_memory.cc +++ b/src/memory/global_memory.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_memory.h" #include @@ -29,11 +28,11 @@ struct GlobalMemoryImpl { uint32_t pagesize; std::mutex lock; std::vector pages; - uint32_t alloc_offset; + uint32_t alloc_offset = 0; pid_t create_pid; - public: GlobalMemoryImpl(uint32_t _pagesize, bool _shared); + ~GlobalMemoryImpl(); char *new_page(); }; @@ -55,15 +54,21 @@ GlobalMemory::GlobalMemory(uint32_t pagesize, bool shared) { GlobalMemoryImpl::GlobalMemoryImpl(uint32_t _pagesize, bool _shared) { shared = _shared; pagesize = SW_MEM_ALIGNED_SIZE_EX(_pagesize, swoole_pagesize()); - create_pid = SwooleG.pid; + create_pid = getpid(); if (new_page() == nullptr) { throw std::bad_alloc(); } } +GlobalMemoryImpl::~GlobalMemoryImpl() { + for (auto page : pages) { + shared ? ::sw_shm_free(page) : ::sw_free(page); + } +} + char *GlobalMemoryImpl::new_page() { - char *page = (char *) (shared ? sw_shm_malloc(pagesize) : sw_malloc(pagesize)); + auto page = static_cast(shared ? sw_shm_malloc(pagesize) : sw_malloc(pagesize)); if (page == nullptr) { return nullptr; } @@ -103,7 +108,7 @@ void *GlobalMemory::alloc(uint32_t size) { } } - block = (MemoryBlock *) (impl->pages.back() + impl->alloc_offset); + block = reinterpret_cast(impl->pages.back() + impl->alloc_offset); impl->alloc_offset += alloc_size; block->size = size; @@ -114,17 +119,11 @@ void *GlobalMemory::alloc(uint32_t size) { void GlobalMemory::free(void *ptr) {} -void GlobalMemory::destroy() { - for (auto page : impl->pages) { - impl->shared ? ::sw_shm_free(page) : ::sw_free(page); - } -} - -size_t GlobalMemory::capacity() { +size_t GlobalMemory::capacity() const { return impl->pagesize - impl->alloc_offset; } -size_t GlobalMemory::get_memory_size() { +size_t GlobalMemory::get_memory_size() const { return impl->pagesize * impl->pages.size(); } diff --git a/src/memory/ring_buffer.cc b/src/memory/ring_buffer.cc index b53a3cae27..20569881fa 100644 --- a/src/memory/ring_buffer.cc +++ b/src/memory/ring_buffer.cc @@ -14,11 +14,10 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_memory.h" namespace swoole { - +struct RingBufferItem; struct RingBufferImpl { void *memory; bool shared; @@ -30,6 +29,7 @@ struct RingBufferImpl { sw_atomic_t free_count; void collect(); + RingBufferItem *get_item(uint32_t offset) const; }; struct RingBufferItem { @@ -61,22 +61,26 @@ RingBuffer::RingBuffer(uint32_t size, bool shared) { throw std::bad_alloc(); } - impl = (RingBufferImpl *) mem; - mem = (char *) mem + sizeof(*impl); + impl = static_cast(mem); + mem = static_cast(mem) + sizeof(*impl); sw_memset_zero(impl, sizeof(*impl)); - impl->size = size - sizeof(impl); + impl->size = size - sizeof(*impl); impl->shared = shared; impl->memory = mem; swoole_debug("memory: ptr=%p", mem); } +RingBufferItem *RingBufferImpl::get_item(uint32_t offset) const { + return reinterpret_cast(static_cast(memory) + offset); +} + void RingBufferImpl::collect() { for (uint32_t i = 0; i < free_count; i++) { - RingBufferItem *item = (RingBufferItem*) ((char*) memory + collect_offset); + const auto *item = get_item(collect_offset); if (item->lock == 0) { - uint32_t n_size = item->length + sizeof(RingBufferItem); + const uint32_t n_size = item->length + sizeof(RingBufferItem); collect_offset += n_size; if (collect_offset + sizeof(RingBufferItem) > size || collect_offset >= size) { collect_offset = 0; @@ -106,7 +110,7 @@ void *RingBuffer::alloc(uint32_t size) { if (impl->alloc_offset + alloc_size >= (impl->size - sizeof(RingBufferItem))) { uint32_t skip_n = impl->size - impl->alloc_offset; if (skip_n >= sizeof(RingBufferItem)) { - item = (RingBufferItem *) ((char *) impl->memory + impl->alloc_offset); + item = impl->get_item(impl->alloc_offset); item->lock = 0; item->length = skip_n - sizeof(RingBufferItem); sw_atomic_t *free_count = &impl->free_count; @@ -126,7 +130,7 @@ void *RingBuffer::alloc(uint32_t size) { return nullptr; } - item = (RingBufferItem *) ((char *) impl->memory + impl->alloc_offset); + item = impl->get_item(impl->alloc_offset); item->lock = 1; item->length = size; item->index = impl->alloc_count; @@ -140,10 +144,10 @@ void *RingBuffer::alloc(uint32_t size) { } void RingBuffer::free(void *ptr) { - RingBufferItem *item = (RingBufferItem *) ((char *) ptr - sizeof(RingBufferItem)); + auto *item = reinterpret_cast(static_cast(ptr) - sizeof(RingBufferItem)); assert(ptr >= impl->memory); - assert((char *) ptr <= (char *) impl->memory + impl->size); + assert(static_cast(ptr) <= static_cast(impl->memory) + impl->size); assert(item->lock == 1); if (item->lock != 1) { @@ -166,4 +170,4 @@ RingBuffer::~RingBuffer() { } } -} +} // namespace swoole diff --git a/src/memory/shared_memory.cc b/src/memory/shared_memory.cc index a93873effd..8188278c9e 100644 --- a/src/memory/shared_memory.cc +++ b/src/memory/shared_memory.cc @@ -14,8 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" -#include "swoole_file.h" #include "swoole_memory.h" #include @@ -33,7 +31,7 @@ struct SharedMemory { static void free(void *ptr); static SharedMemory *fetch_object(void *ptr) { - return (SharedMemory *) ((char *) ptr - sizeof(SharedMemory)); + return reinterpret_cast(static_cast(ptr) - sizeof(SharedMemory)); } }; @@ -67,7 +65,7 @@ void *SharedMemory::alloc(size_t size) { } else { object.size_ = size; memcpy(mem, &object, sizeof(object)); - return (char *) mem + sizeof(object); + return static_cast(mem) + sizeof(object); } } diff --git a/src/memory/table.cc b/src/memory/table.cc index dcc3ccce40..5718280901 100644 --- a/src/memory/table.cc +++ b/src/memory/table.cc @@ -15,12 +15,16 @@ */ #include "swoole_table.h" +#include "swoole_hash.h" +#include "swoole_util.h" + +#include namespace swoole { Table *Table::make(uint32_t rows_size, float conflict_proportion) { - if (rows_size >= 0x80000000) { - rows_size = 0x80000000; + if (rows_size >= SW_TABLE_MAX_ROW_SIZE) { + rows_size = SW_TABLE_MAX_ROW_SIZE; } else { uint32_t i = 6; while ((1U << i) < rows_size) { @@ -35,11 +39,11 @@ Table *Table::make(uint32_t rows_size, float conflict_proportion) { conflict_proportion = SW_TABLE_CONFLICT_PROPORTION; } - Table *table = (Table *) sw_mem_pool()->alloc(sizeof(*table)); + auto table = static_cast(sw_mem_pool()->alloc(sizeof(Table))); if (table == nullptr) { return nullptr; } - table->mutex = new Mutex(Mutex::PROCESS_SHARED); + table->mutex = new Mutex(true); table->iterator = nullptr; table->column_map = new std::unordered_map; table->column_list = new std::vector; @@ -55,22 +59,13 @@ Table *Table::make(uint32_t rows_size, float conflict_proportion) { return table; } -void Table::free() { - delete mutex; - if (iterator) { - delete iterator; - } - delete column_map; - delete column_list; -} - bool Table::add_column(const std::string &_name, enum TableColumn::Type _type, size_t _size) { if (_type < TableColumn::TYPE_INT || _type > TableColumn::TYPE_STRING) { swoole_warning("unknown column type"); return false; } - TableColumn *col = new TableColumn(_name, _type, _size); + auto col = new TableColumn(_name, _type, _size); col->index = item_size; item_size += col->size; column_map->emplace(_name, col); @@ -79,10 +74,46 @@ bool Table::add_column(const std::string &_name, enum TableColumn::Type _type, s return true; } -size_t Table::get_memory_size() { - if (memory_size > 0) { - return memory_size; +TableColumn *Table::get_column(const std::string &key) const { + auto i = column_map->find(key); + if (i == column_map->end()) { + return nullptr; + } else { + return i->second; + } +} + +bool Table::exists(const char *key, uint16_t keylen) const { + TableRow *_rowlock = nullptr; + const TableRow *row = get(key, keylen, &_rowlock); + _rowlock->unlock(); + return row != nullptr; +} + +TableIterator::TableIterator(size_t row_size) { + current_ = (TableRow *) sw_malloc(row_size); + if (!current_) { + throw std::bad_alloc(); + } + mutex_ = new Mutex(true); + row_memory_size_ = row_size; + reset(); +} + +void TableIterator::reset() { + absolute_index = 0; + collision_index = 0; + sw_memset_zero(current_, row_memory_size_); +} + +TableIterator::~TableIterator() { + if (current_) { + sw_free(current_); } + delete mutex_; +} + +size_t Table::calc_memory_size() const { /** * table size + conflict size */ @@ -108,21 +139,23 @@ size_t Table::get_memory_size() { */ _memory_size += size * sizeof(TableRow *); - memory_size = _memory_size; - swoole_trace("_memory_size=%lu, _row_num=%lu, _row_memory_size=%lu", _memory_size, _row_num, _row_memory_size); return _memory_size; } -uint32_t Table::get_available_slice_num() { +size_t Table::get_memory_size() const { + return memory_size; +} + +uint32_t Table::get_available_slice_num() const { lock(); uint32_t num = pool->get_number_of_spare_slice(); unlock(); return num; } -uint32_t Table::get_total_slice_num() { +uint32_t Table::get_total_slice_num() const { return pool->get_number_of_total_slice(); } @@ -131,7 +164,7 @@ bool Table::create() { return false; } - size_t _memory_size = get_memory_size(); + size_t _memory_size = calc_memory_size(); size_t _row_memory_size = sizeof(TableRow) + item_size; void *_memory = sw_shm_malloc(_memory_size); @@ -140,19 +173,20 @@ bool Table::create() { } memory = _memory; - rows = (TableRow **) _memory; - _memory = (char *) _memory + size * sizeof(TableRow *); + rows = static_cast(_memory); + _memory = static_cast(_memory) + size * sizeof(TableRow *); _memory_size -= size * sizeof(TableRow *); for (size_t i = 0; i < size; i++) { - rows[i] = (TableRow *) ((char *) _memory + (_row_memory_size * i)); + rows[i] = reinterpret_cast(static_cast(_memory) + (_row_memory_size * i)); memset(rows[i], 0, sizeof(TableRow)); } - _memory = (char *) _memory + _row_memory_size * size; + _memory = static_cast(_memory) + _row_memory_size * size; _memory_size -= _row_memory_size * size; pool = new FixedPool(_row_memory_size, _memory, _memory_size, true); iterator = new TableIterator(_row_memory_size); + memory_size = _memory_size; created = true; return true; @@ -174,9 +208,7 @@ void Table::destroy() { } delete column_map; delete column_list; - if (iterator) { - delete iterator; - } + delete iterator; delete pool; if (memory) { sw_shm_free(memory); @@ -191,10 +223,10 @@ void TableRow::lock() { uint32_t i, n; long t = 0; - while (1) { + while (true) { if (*lock == 0 && sw_atomic_cmp_set(lock, 0, 1)) { _success: - lock_pid = SwooleG.pid; + lock_pid = getpid(); return; } if (SW_CPU_NUM > 1) { @@ -233,11 +265,11 @@ void TableRow::lock() { swoole_warning("timeout, force unlock"); goto _success; } - sw_yield(); + std::this_thread::yield(); } } -void Table::forward() { +void Table::forward() const { iterator->lock(); for (; iterator->absolute_index < size; iterator->absolute_index++) { TableRow *row = get_by_index(iterator->absolute_index); @@ -275,7 +307,7 @@ void Table::forward() { iterator->unlock(); } -TableRow *Table::get(const char *key, uint16_t keylen, TableRow **rowlock) { +TableRow *Table::get(const char *key, uint16_t keylen, TableRow **rowlock) const { check_key_length(&keylen); TableRow *row = hash(key, keylen); @@ -308,13 +340,10 @@ TableRow *Table::set(const char *key, uint16_t keylen, TableRow **rowlock, int * row->lock(); int _out_flags = 0; - uint32_t _conflict_level = 1; - if (row->active) { - for (;;) { - if (sw_mem_equal(row->key, row->key_len, key, keylen)) { - break; - } else if (row->next == nullptr) { + uint32_t _conflict_level = 1; + while (!sw_mem_equal(row->key, row->key_len, key, keylen)) { + if (row->next == nullptr) { conflict_count++; if (_conflict_level > conflict_max_level) { conflict_max_level = _conflict_level; @@ -367,7 +396,6 @@ bool Table::del(const char *key, uint16_t keylen) { if (row->next == nullptr) { if (sw_mem_equal(row->key, row->key_len, key, keylen)) { row->clear(); - goto _delete_element; } else { goto _not_exists; } @@ -402,7 +430,6 @@ bool Table::del(const char *key, uint16_t keylen) { free_row(tmp); } -_delete_element: sw_atomic_fetch_add(&(delete_count), 1); sw_atomic_fetch_sub(&(row_num), 1); row->unlock(); @@ -410,7 +437,27 @@ bool Table::del(const char *key, uint16_t keylen) { return true; } -void TableColumn::clear(TableRow *row) { +TableColumn::TableColumn(const std::string &_name, Type _type, size_t _size) { + index = 0; + name = _name; + type = _type; + switch (_type) { + case TYPE_INT: + size = sizeof(long); + break; + case TYPE_FLOAT: + size = sizeof(double); + break; + case TYPE_STRING: + size = _size + sizeof(TableStringLength); + break; + default: + abort(); + break; + } +} + +void TableColumn::clear(TableRow *row) const { if (type == TYPE_STRING) { row->set_value(this, nullptr, 0); } else if (type == TYPE_FLOAT) { @@ -422,7 +469,7 @@ void TableColumn::clear(TableRow *row) { } } -void TableRow::set_value(TableColumn *col, void *value, size_t vlen) { +void TableRow::set_value(const TableColumn *col, const void *value, size_t vlen) { switch (col->type) { case TableColumn::TYPE_INT: memcpy(data + col->index, value, sizeof(long)); @@ -446,15 +493,15 @@ void TableRow::set_value(TableColumn *col, void *value, size_t vlen) { } } -void TableRow::get_value(TableColumn *col, double *dval) { +void TableRow::get_value(const TableColumn *col, double *dval) const { memcpy(dval, data + col->index, sizeof(*dval)); } -void TableRow::get_value(TableColumn *col, long *lval) { +void TableRow::get_value(const TableColumn *col, long *lval) const { memcpy(lval, data + col->index, sizeof(*lval)); } -void TableRow::get_value(TableColumn *col, char **value, TableStringLength *len) { +void TableRow::get_value(const TableColumn *col, char **value, TableStringLength *len) { memcpy(len, data + col->index, sizeof(*len)); *value = data + col->index + sizeof(*len); } diff --git a/src/network/address.cc b/src/network/address.cc index bace224f62..04e04624d9 100644 --- a/src/network/address.cc +++ b/src/network/address.cc @@ -16,63 +16,178 @@ #include "swoole_socket.h" +#include + +static bool IN_IS_ADDR_LOOPBACK(const in_addr *a) { + return a->s_addr == htonl(INADDR_LOOPBACK); +} + namespace swoole { namespace network { - static thread_local char tmp_address[INET6_ADDRSTRLEN]; -const char *Address::get_addr() { - if (type == SW_SOCK_TCP || type == SW_SOCK_UDP) { - if (inet_ntop(AF_INET, &addr.inet_v4.sin_addr, tmp_address, sizeof(tmp_address))) { - return tmp_address; - } - } else if (type == SW_SOCK_TCP6 || type == SW_SOCK_UDP6) { - if (inet_ntop(AF_INET6, &addr.inet_v6.sin6_addr, tmp_address, sizeof(tmp_address))) { - return tmp_address; - } - } else if (type == SW_SOCK_UNIX_STREAM || type == SW_SOCK_UNIX_DGRAM) { +const char *Address::addr_str(int family, const void *addr) { + if (inet_ntop(family, addr, tmp_address, sizeof(tmp_address))) { + return tmp_address; + } + return nullptr; +} + +bool Address::verify_ip(int family, const std::string &str) { + return inet_pton(family, str.c_str(), tmp_address) == 1; +} + +bool Address::verify_port(const int port, const bool for_connect) { + if (port < 0 || port > 65535) { + return false; + } + if (for_connect && port == 0) { + return false; + } + return true; +} + +const char *Address::get_addr() const { + if (Socket::is_inet4(type)) { + return addr_str(AF_INET, &addr.inet_v4.sin_addr); + } + if (Socket::is_inet6(type)) { + return addr_str(AF_INET6, &addr.inet_v6.sin6_addr); + } + if (Socket::is_local(type)) { return addr.un.sun_path; } return "unknown"; } -int Address::get_port() { - if (type == SW_SOCK_TCP || type == SW_SOCK_UDP) { +bool Address::empty() const { + return type == 0; +} + +int Address::get_port() const { + if (Socket::is_inet4(type)) { return ntohs(addr.inet_v4.sin_port); - } else if (type == SW_SOCK_TCP6 || type == SW_SOCK_UDP6) { + } + if (Socket::is_inet6(type)) { return ntohs(addr.inet_v6.sin6_port); - } else { - return 0; } + return 0; } -bool Address::assign(SocketType _type, const std::string &_host, int _port) { +void Address::set_port(int _port) { + if (Socket::is_inet4(type)) { + addr.inet_v4.sin_port = htons(_port); + } else if (Socket::is_inet6(type)) { + addr.inet_v6.sin6_port = htons(_port); + } +} + +bool Address::assign(SocketType _type, const std::string &_host, int _port, bool _resolve_name) { type = _type; const char *host = _host.c_str(); - if (_type == SW_SOCK_TCP || _type == SW_SOCK_UDP) { + + if (!verify_port(_port)) { + swoole_set_last_error(SW_ERROR_BAD_PORT); + return false; + } + + if (Socket::is_inet4(_type)) { addr.inet_v4.sin_family = AF_INET; addr.inet_v4.sin_port = htons(_port); len = sizeof(addr.inet_v4); - if (inet_pton(AF_INET, host, &addr.inet_v4.sin_addr.s_addr)) { - return true; + + if (inet_pton(AF_INET, host, &addr.inet_v4.sin_addr.s_addr) != 1) { + if (!_resolve_name) { + swoole_set_last_error(SW_ERROR_BAD_HOST_ADDR); + return false; + } + if (gethostbyname(AF_INET, host, reinterpret_cast(&addr.inet_v4.sin_addr)) < 0) { + swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + return false; + } } - } else if (_type == SW_SOCK_TCP6 || _type == SW_SOCK_UDP6) { + } else if (Socket::is_inet6(_type)) { addr.inet_v6.sin6_family = AF_INET6; addr.inet_v6.sin6_port = htons(_port); len = sizeof(addr.inet_v6); - if (inet_pton(AF_INET6, host, addr.inet_v6.sin6_addr.s6_addr)) { - return true; + if (inet_pton(AF_INET6, host, addr.inet_v6.sin6_addr.s6_addr) != 1) { + if (!_resolve_name) { + swoole_set_last_error(SW_ERROR_BAD_HOST_ADDR); + return false; + } + if (gethostbyname(AF_INET6, host, reinterpret_cast(&addr.inet_v6.sin6_addr)) < 0) { + swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + return false; + } + } + } else if (Socket::is_local(_type)) { + if (_host.length() >= sizeof(addr.un.sun_path) - 1) { + swoole_set_last_error(SW_ERROR_NAME_TOO_LONG); + return false; } - } else if (_type == SW_SOCK_UNIX_STREAM || _type == SW_SOCK_UNIX_DGRAM) { addr.un.sun_family = AF_UNIX; swoole_strlcpy(addr.un.sun_path, host, sizeof(addr.un.sun_path)); addr.un.sun_path[sizeof(addr.un.sun_path) - 1] = 0; len = sizeof(addr.un.sun_path); - return true; + } else { + swoole_set_last_error(SW_ERROR_BAD_SOCKET_TYPE); + return false; } + return true; +} + +const char *Address::type_str(SocketType type) { + if (Socket::is_inet4(type)) { + return "IPv4"; + } + if (Socket::is_inet6(type)) { + return "IPv6"; + } + if (Socket::is_local(type)) { + return "UnixSocket"; + } + return "Unknown"; +} + +bool Address::assign(const std::string &url) { + static const std::regex unix_pattern(R"(^(unix|udg)://(/[^?#]+))"); + static const std::regex inet4_pattern(R"(^(tcp|udp)://([^:\[]+):(\d+)$)"); + static const std::regex inet6_pattern(R"(^(tcp|udp)://\[([^\]]+)\]:(\d+)$)"); + std::smatch match; + + if (std::regex_match(url, match, unix_pattern)) { + std::string proto = match[1]; + std::string path = match[2]; + type = proto == "unix" ? SW_SOCK_UNIX_STREAM : SW_SOCK_UNIX_DGRAM; + return assign(type, path, 0); + } + if (std::regex_match(url, match, inet4_pattern)) { + std::string proto = match[1]; + std::string host = match[2]; + int port = std::stoi(match[3]); + type = proto == "tcp" ? SW_SOCK_TCP : SW_SOCK_UDP; + return assign(type, host, port); + } + if (std::regex_match(url, match, inet6_pattern)) { + std::string proto = match[1]; + std::string host = match[2]; + int port = std::stoi(match[3]); + type = proto == "tcp" ? SW_SOCK_TCP6 : SW_SOCK_UDP6; + return assign(type, host, port); + } + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_BAD_HOST_ADDR, "Invalid address '%s'", url.c_str()); return false; } +bool Address::is_loopback_addr() const { + if (Socket::is_inet4(type)) { + return IN_IS_ADDR_LOOPBACK(&addr.inet_v4.sin_addr); + } + if (Socket::is_inet6(type)) { + return IN6_IS_ADDR_LOOPBACK(&addr.inet_v6.sin6_addr); + } + return false; +} } // namespace network } // namespace swoole diff --git a/src/network/client.cc b/src/network/client.cc index e19fc6da4a..56678c753f 100644 --- a/src/network/client.cc +++ b/src/network/client.cc @@ -14,24 +14,20 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_socket.h" -#include "swoole_reactor.h" #include "swoole_timer.h" #include "swoole_protocol.h" #include "swoole_client.h" #include "swoole_proxy.h" #include "swoole_async.h" -#include +#include namespace swoole { namespace network { - static int Client_inet_addr(Client *cli, const char *host, int port); -static int Client_tcp_connect_sync(Client *cli, const char *host, int port, double _timeout, int udp_connect); +static int Client_tcp_connect_sync(Client *cli, const char *host, int port, double _timeout, int nonblock); static int Client_tcp_connect_async(Client *cli, const char *host, int port, double timeout, int nonblock); static int Client_udp_connect(Client *cli, const char *host, int port, double _timeout, int udp_connect); @@ -42,8 +38,8 @@ static ssize_t Client_udp_send(Client *cli, const char *data, size_t length, int static int Client_tcp_sendfile_sync(Client *cli, const char *filename, off_t offset, size_t length); static int Client_tcp_sendfile_async(Client *cli, const char *filename, off_t offset, size_t length); -static ssize_t Client_tcp_recv_no_buffer(Client *cli, char *data, size_t len, int flags); -static ssize_t Client_udp_recv(Client *cli, char *data, size_t len, int waitall); +static ssize_t Client_tcp_recv_sync(Client *cli, char *data, size_t len, int flags); +static ssize_t Client_udp_recv(Client *cli, char *data, size_t len, int flags); static int Client_onDgramRead(Reactor *reactor, Event *event); static int Client_onStreamRead(Reactor *reactor, Event *event); @@ -62,15 +58,15 @@ static sw_inline void execute_onConnect(Client *cli) { } void Client::init_reactor(Reactor *reactor) { - reactor->set_handler(SW_FD_STREAM_CLIENT | SW_EVENT_READ, Client_onStreamRead); - reactor->set_handler(SW_FD_DGRAM_CLIENT | SW_EVENT_READ, Client_onDgramRead); - reactor->set_handler(SW_FD_STREAM_CLIENT | SW_EVENT_WRITE, Client_onWrite); - reactor->set_handler(SW_FD_STREAM_CLIENT | SW_EVENT_ERROR, Client_onError); + reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_READ, Client_onStreamRead); + reactor->set_handler(SW_FD_DGRAM_CLIENT, SW_EVENT_READ, Client_onDgramRead); + reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_WRITE, Client_onWrite); + reactor->set_handler(SW_FD_STREAM_CLIENT, SW_EVENT_ERROR, Client_onError); } Client::Client(SocketType _type, bool _async) : async(_async) { fd_type = Socket::is_stream(_type) ? SW_FD_STREAM_CLIENT : SW_FD_DGRAM_CLIENT; - socket = swoole::make_socket(_type, fd_type, (async ? SW_SOCK_NONBLOCK : 0) | SW_SOCK_CLOEXEC); + socket = make_socket(_type, fd_type, (async ? SW_SOCK_NONBLOCK : 0) | SW_SOCK_CLOEXEC); if (socket == nullptr) { swoole_sys_warning("socket() failed"); return; @@ -81,24 +77,24 @@ Client::Client(SocketType _type, bool _async) : async(_async) { socket->chunk_size = SW_SEND_BUFFER_SIZE; if (socket->is_stream()) { - recv = Client_tcp_recv_no_buffer; + recv_ = Client_tcp_recv_sync; if (async) { - connect = Client_tcp_connect_async; - send = Client_tcp_send_async; - sendfile = Client_tcp_sendfile_async; - socket->dontwait = SwooleG.socket_dontwait; + connect_ = Client_tcp_connect_async; + send_ = Client_tcp_send_async; + sendfile_ = Client_tcp_sendfile_async; } else { - connect = Client_tcp_connect_sync; - send = Client_tcp_send_sync; - sendfile = Client_tcp_sendfile_sync; + connect_ = Client_tcp_connect_sync; + send_ = Client_tcp_send_sync; + sendfile_ = Client_tcp_sendfile_sync; } } else { - connect = Client_udp_connect; - recv = Client_udp_recv; - send = Client_udp_send; + connect_ = Client_udp_connect; + recv_ = Client_udp_recv; + send_ = Client_udp_send; } - Socket::get_domain_and_type(_type, &_sock_domain, &_sock_type); + Socket::get_domain_and_type(_type, &sock_domain_, &sock_type_); + remote_addr.type = _type; protocol.package_length_type = 'N'; protocol.package_length_size = 4; @@ -107,6 +103,33 @@ Client::Client(SocketType _type, bool _async) : async(_async) { protocol.onPackage = Client_onPackage; } +int Client::bind(const std::string &addr, int port) const { + if (socket->set_reuse_addr() < 0) { + swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", socket->get_fd()); + } + if (socket->bind(addr, port) < 0) { + swoole_set_last_error(errno); + return SW_ERR; + } + return SW_OK; +} + +void Client::set_timeout(double timeout, TimeoutType type) const { + socket->set_timeout(timeout, type); +} + +bool Client::has_timedout() const { + return socket->has_timedout(); +} + +void Client::set_socks5_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { + socks5_proxy.reset(Socks5Proxy::create(get_socket_type(), host, port, user, pwd)); +} + +void Client::set_http_proxy(const std::string &host, int port, const std::string &user, const std::string &pwd) { + http_proxy.reset(HttpProxy::create(host, port, user, pwd)); +} + int Client::sleep() { int ret; if (socket->events & SW_EVENT_WRITE) { @@ -133,141 +156,79 @@ int Client::wakeup() { return ret; } -int Client::shutdown(int __how) { +int Client::sendto(const std::string &host, int port, const char *data, size_t len) const { + if (!socket->is_dgram()) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + swoole_warning("only supports SWOOLE_SOCK_(UDP/UDP6/UNIX_DGRAM)"); + return SW_ERR; + } + + Address tmp_addr; + if (!tmp_addr.assign(socket->socket_type, host, port, !async)) { + return SW_ERR; + } + + if (socket->sendto(tmp_addr, data, len, 0) < 0) { + swoole_set_last_error(errno); + return SW_ERR; + } + + return SW_OK; +} + +int Client::get_peer_name(Address *addr) const { + if (socket->is_dgram()) { + *addr = remote_addr; + return SW_OK; + } else { + return socket->get_peer_name(addr); + } +} + +int Client::shutdown(int _how) { if (!socket || closed) { return SW_ERR; } - if (__how == SHUT_RD) { - if (shutdown_read || shutdow_rw || ::shutdown(socket->fd, SHUT_RD)) { + swoole_trace_log(SW_TRACE_CLIENT, "how=%d, fd=%d", _how, socket->fd); + if (_how == SHUT_RD) { + if (shutdown_read || shutdown_rw || ::shutdown(socket->fd, SHUT_RD)) { return SW_ERR; } else { - shutdown_read = 1; + shutdown_read = true; return SW_OK; } - } else if (__how == SHUT_WR) { - if (shutdown_write || shutdow_rw || ::shutdown(socket->fd, SHUT_WR) < 0) { + } else if (_how == SHUT_WR) { + if (shutdown_write || shutdown_rw || ::shutdown(socket->fd, SHUT_WR) < 0) { return SW_ERR; } else { - shutdown_write = 1; + shutdown_write = true; return SW_OK; } - } else if (__how == SHUT_RDWR) { - if (shutdow_rw || ::shutdown(socket->fd, SHUT_RDWR) < 0) { + } else if (_how == SHUT_RDWR) { + if (shutdown_rw || ::shutdown(socket->fd, SHUT_RDWR) < 0) { return SW_ERR; } else { - shutdown_read = 1; + shutdown_read = true; return SW_OK; } } else { + swoole_set_last_error(EINVAL); return SW_ERR; } } -int Client::socks5_handshake(const char *recv_data, size_t length) { - Socks5Proxy *ctx = socks5_proxy; - char *buf = ctx->buf; - uchar version, status, result, method; - - if (ctx->state == SW_SOCKS5_STATE_HANDSHAKE) { - version = recv_data[0]; - method = recv_data[1]; - if (version != SW_SOCKS5_VERSION_CODE) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return SW_ERR; - } - if (method != ctx->method) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_METHOD, "SOCKS authentication method is not supported"); - return SW_ERR; - } - // authenticate request - if (method == SW_SOCKS5_METHOD_AUTH) { - buf[0] = 0x01; - buf[1] = ctx->username.length(); - - buf += 2; - memcpy(buf, ctx->username.c_str(), ctx->username.length()); - buf += ctx->username.length(); - buf[0] = ctx->password.length(); - memcpy(buf + 1, ctx->password.c_str(), ctx->password.length()); - - ctx->state = SW_SOCKS5_STATE_AUTH; - - return send(this, ctx->buf, ctx->username.length() + ctx->password.length() + 3, 0); - } - // send connect request - else { - _send_connect_request: - buf[0] = SW_SOCKS5_VERSION_CODE; - buf[1] = 0x01; - buf[2] = 0x00; - - ctx->state = SW_SOCKS5_STATE_CONNECT; - - if (ctx->dns_tunnel) { - buf[3] = 0x03; - buf[4] = ctx->target_host.length(); - buf += 5; - memcpy(buf, ctx->target_host.c_str(), ctx->target_host.length()); - buf += ctx->target_host.length(); - *(uint16_t *) buf = htons(ctx->target_port); - return send(this, ctx->buf, ctx->target_host.length() + 7, 0); - } else { - buf[3] = 0x01; - buf += 4; - *(uint32_t *) buf = htons(ctx->target_host.length()); - buf += 4; - *(uint16_t *) buf = htons(ctx->target_port); - return send(this, ctx->buf, ctx->target_host.length() + 7, 0); - } - } - } else if (ctx->state == SW_SOCKS5_STATE_AUTH) { - version = recv_data[0]; - status = recv_data[1]; - if (version != 0x01) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return SW_ERR; - } - if (status != 0) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS username/password authentication failed"); - return SW_ERR; - } - goto _send_connect_request; - } else if (ctx->state == SW_SOCKS5_STATE_CONNECT) { - version = recv_data[0]; - if (version != SW_SOCKS5_VERSION_CODE) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); - return SW_ERR; - } - result = recv_data[1]; -#if 0 - uchar reg = recv_data[2]; - uchar type = recv_data[3]; - uint32_t ip = *(uint32_t *) (recv_data + 4); - uint16_t port = *(uint16_t *) (recv_data + 8); -#endif - if (result == 0) { - ctx->state = SW_SOCKS5_STATE_READY; - } else { - swoole_error_log(SW_LOG_NOTICE, - SW_ERROR_SOCKS5_SERVER_ERROR, - "Socks5 server error, reason :%s", - Socks5Proxy::strerror(result)); - } - return result; - } - return SW_OK; +bool Client::socks5_handshake(const char *recv_data, size_t length) { + auto send_fn = [this](const char *buf, size_t len) { return send(buf, len); }; + return socks5_proxy->handshake(recv_data, length, send_fn); } -#ifdef SW_USE_OPENSSL #ifdef SW_SUPPORT_DTLS void Client::enable_dtls() { ssl_context->protocols = SW_SSL_DTLS; socket->dtls = 1; socket->chunk_size = SW_SSL_BUFFER_SIZE; - send = Client_tcp_send_sync; - recv = Client_tcp_recv_no_buffer; + send_ = Client_tcp_send_sync; + recv_ = Client_tcp_recv_sync; } #endif @@ -275,7 +236,7 @@ int Client::enable_ssl_encrypt() { if (ssl_context) { return SW_ERR; } - ssl_context.reset(new swoole::SSLContext()); + ssl_context = std::make_shared(); open_ssl = true; #ifdef SW_SUPPORT_DTLS if (socket->is_dgram()) { @@ -290,7 +251,7 @@ int Client::enable_ssl_encrypt() { return SW_OK; } -int Client::ssl_handshake() { +int Client::ssl_handshake() const { if (socket->ssl_state == SW_SSL_STATE_READY) { return SW_ERR; } @@ -303,6 +264,7 @@ int Client::ssl_handshake() { if (!socket->ssl) { socket->ssl_send_ = 1; if (socket->ssl_create(ssl_context.get(), SW_SSL_CLIENT) < 0) { + swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return SW_ERR; } #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME @@ -322,7 +284,7 @@ int Client::ssl_handshake() { return SW_OK; } -int Client::ssl_verify(int allow_self_signed) { +int Client::ssl_verify(int allow_self_signed) const { if (!socket->ssl_verify(allow_self_signed)) { return SW_ERR; } @@ -334,80 +296,41 @@ int Client::ssl_verify(int allow_self_signed) { return SW_OK; } -#endif - static int Client_inet_addr(Client *cli, const char *host, int port) { - // enable socks5 proxy - if (cli->socks5_proxy) { - cli->socks5_proxy->target_host = host; - cli->socks5_proxy->target_port = port; - - host = cli->socks5_proxy->host.c_str(); - port = cli->socks5_proxy->port; - } - - // enable http proxy - if (cli->http_proxy) { - cli->http_proxy->target_host = host; - cli->http_proxy->target_port = port; - - host = cli->http_proxy->proxy_host.c_str(); - port = cli->http_proxy->proxy_port; - } + if (!cli->host_preseted) { + // enable socks5 proxy + if (cli->socks5_proxy) { + cli->socks5_proxy->target_host = host; + cli->socks5_proxy->target_port = port; - cli->server_host = host; - cli->server_port = port; + host = cli->socks5_proxy->host.c_str(); + port = cli->socks5_proxy->port; + } - void *addr = nullptr; - if (cli->socket->is_inet4()) { - cli->server_addr.addr.inet_v4.sin_family = AF_INET; - cli->server_addr.addr.inet_v4.sin_port = htons(port); - cli->server_addr.len = sizeof(cli->server_addr.addr.inet_v4); - addr = &cli->server_addr.addr.inet_v4.sin_addr.s_addr; + // enable http proxy + if (cli->http_proxy) { + cli->http_proxy->target_host = host; + cli->http_proxy->target_port = port; - if (inet_pton(AF_INET, host, addr)) { - return SW_OK; + host = cli->http_proxy->host.c_str(); + port = cli->http_proxy->port; } - } else if (cli->socket->is_inet6()) { - cli->server_addr.addr.inet_v6.sin6_family = AF_INET6; - cli->server_addr.addr.inet_v6.sin6_port = htons(port); - cli->server_addr.len = sizeof(cli->server_addr.addr.inet_v6); - addr = cli->server_addr.addr.inet_v6.sin6_addr.s6_addr; - if (inet_pton(AF_INET6, host, addr)) { - return SW_OK; - } - } else if (cli->socket->is_local()) { - cli->server_addr.addr.un.sun_family = AF_UNIX; - swoole_strlcpy(cli->server_addr.addr.un.sun_path, host, sizeof(cli->server_addr.addr.un.sun_path)); - cli->server_addr.addr.un.sun_path[sizeof(cli->server_addr.addr.un.sun_path) - 1] = 0; - cli->server_addr.len = sizeof(cli->server_addr.addr.un.sun_path); - return SW_OK; - } else { - return SW_ERR; + cli->server_host = host; + cli->server_port = port; + + cli->host_preseted = true; } - if (!cli->async) { - if (swoole::network::gethostbyname(cli->_sock_domain, host, (char *) addr) < 0) { - swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + + if (!cli->server_addr.assign(cli->socket->socket_type, host, port, !cli->async)) { + if (!cli->dns_completed && swoole_get_last_error() == SW_ERROR_BAD_HOST_ADDR) { + cli->wait_dns = true; + } else { return SW_ERR; } - } else { - cli->wait_dns = 1; } - return SW_OK; -} -void Client::destroy() { - if (destroyed) { - return; - } - destroyed = true; - swoole_event_defer( - [](void *data) { - Client *object = (Client *) data; - delete object; - }, - this); + return SW_OK; } Client::~Client() { @@ -424,38 +347,31 @@ Client::~Client() { delete buffer; buffer = nullptr; } - if (server_str) { - ::sw_free((void *) server_str); - } - if (socks5_proxy) { - delete socks5_proxy; - } - if (http_proxy) { - delete http_proxy; - } if (async) { + // The object must be set to empty here, indicating that the client has been released in the onClose callback + socket->object = nullptr; socket->free(); } else { delete socket; } } +/** + * There are two situations, 1 While maintaining the connection, the user actively calls the close function to close the + * connection. 2 Received readable or writable, close event, and executed the close function at the underlying level + */ int Client::close() { if (socket == nullptr || closed) { return SW_ERR; } - closed = 1; + closed = true; + auto _socket = socket; - int fd = socket->fd; - assert(fd != 0); - -#ifdef SW_USE_OPENSSL if (open_ssl && ssl_context) { if (socket->ssl) { socket->ssl_close(); } } -#endif if (socket->socket_type == SW_SOCK_UNIX_DGRAM) { unlink(socket->info.addr.un.sun_path); @@ -469,142 +385,120 @@ int Client::close() { swoole_timer_del(timer); timer = nullptr; } - // onClose callback + // execute `onClose` callback if (active) { - active = 0; + active = false; + // In the `onClose` callback, the destructor may be executed and the C++ object will be released onClose(this); } } else { - active = 0; + active = false; } - /** - * fd marked -1, prevent double close - */ + if (_socket->object == nullptr) { + return SW_OK; + } + + // Set `socket->fd` to -1 to prevent duplicate closure of file descriptors + const int fd = socket->fd; socket->fd = -1; + swoole_trace_log(SW_TRACE_CLIENT, "fd=%d", fd); return ::close(fd); } static int Client_tcp_connect_sync(Client *cli, const char *host, int port, double timeout, int nonblock) { - int ret; - - cli->timeout = timeout; + cli->set_timeout(timeout); + if (timeout > 0) { + cli->socket->set_kernel_timeout(timeout); + } if (Client_inet_addr(cli, host, port) < 0) { return SW_ERR; } if (nonblock) { - cli->socket->set_nonblock(); - } else { - if (cli->timeout > 0) { - cli->socket->set_timeout(timeout); - } -#ifndef HAVE_KQUEUE - cli->socket->set_block(); -#endif - } - while (1) { -#ifdef HAVE_KQUEUE - if (nonblock == 2) { - // special case on MacOS - ret = cli->socket->connect(cli->server_addr); - } else { - cli->socket->set_nonblock(); - ret = cli->socket->connect(cli->server_addr); - if (ret < 0) { - if (errno != EINPROGRESS) { - return SW_ERR; - } - if (cli->socket->wait_event(timeout > 0 ? (int) (timeout * 1000) : timeout, SW_EVENT_WRITE) < 0) { - swoole_set_last_error(ETIMEDOUT); - return SW_ERR; - } - int err; - socklen_t len = sizeof(len); - ret = cli->socket->get_option(SOL_SOCKET, SO_ERROR, &err, &len); - if (ret < 0) { - swoole_set_last_error(errno); - return SW_ERR; - } - if (err != 0) { - swoole_set_last_error(err); - return SW_ERR; - } - cli->socket->set_block(); - ret = 0; - } + auto rc = cli->socket->connect_async(cli->server_addr); + if (rc == SW_READY) { + return SW_OK; } -#else - ret = cli->socket->connect(cli->server_addr); -#endif - if (ret < 0) { - if (errno == EINTR) { - continue; - } else if (errno == EINPROGRESS) { - if (nonblock) { - cli->async_connect = true; - } else { - errno = ETIMEDOUT; - } - } - swoole_set_last_error(errno); + if (rc == SW_WAIT) { + cli->async_connect = true; } - break; + return SW_ERR; } + int ret = cli->socket->connect_sync(cli->server_addr); if (ret >= 0) { - cli->active = 1; + cli->active = true; + auto recv_buf = sw_tg_buffer(); - // socks5 proxy if (cli->socks5_proxy) { - char buf[1024]; - Socks5Proxy::pack(buf, cli->socks5_proxy->username.empty() ? 0x00 : 0x02); - if (cli->send(cli, buf, 3, 0) < 0) { + const auto ctx = cli->socks5_proxy.get(); + const auto len = ctx->pack_negotiate_request(); + if (cli->send(ctx->buf, len) < 0) { return SW_ERR; } - cli->socks5_proxy->state = SW_SOCKS5_STATE_HANDSHAKE; - while (1) { - ssize_t n = cli->recv(cli, buf, sizeof(buf), 0); - if (n > 0) { - if (cli->socks5_handshake(buf, n) < 0) { - return SW_ERR; - } + ctx->state = SW_SOCKS5_STATE_HANDSHAKE; + while (true) { + const ssize_t n = cli->recv(recv_buf->str, recv_buf->size, 0); + if (n > 0 && cli->socks5_handshake(recv_buf->str, n)) { if (cli->socks5_proxy->state == SW_SOCKS5_STATE_READY) { break; - } else { - continue; } + continue; } return SW_ERR; } + } else if (cli->http_proxy) { + auto target_host = cli->get_http_proxy_host_name(); + const size_t n_write = cli->http_proxy->pack(recv_buf, target_host); + if (cli->send(recv_buf->str, n_write, 0) < 0) { + return SW_ERR; + } + const ssize_t n_read = cli->recv(recv_buf->str, recv_buf->size, 0); + if (n_read <= 0) { + return SW_ERR; + } + recv_buf->length = n_read; + if (!cli->http_proxy->handshake(recv_buf)) { + return SW_ERR; + } } -#ifdef SW_USE_OPENSSL if (cli->open_ssl && cli->ssl_handshake() < 0) { return SW_ERR; } -#endif } return ret; } -static int Client_tcp_connect_async(Client *cli, const char *host, int port, double timeout, int nonblock) { - int ret; +static int Client_dns_lookup(Client *cli) { + AsyncEvent ev{}; + auto req = new GethostbynameRequest(cli->server_host, cli->sock_domain_); + ev.data = std::shared_ptr(req); + ev.object = cli; + ev.handler = async::handler_gethostbyname; + ev.callback = Client_onResolveCompleted; - cli->timeout = timeout; + return async::dispatch(&ev) == nullptr ? SW_ERR : SW_OK; +} - if (!cli->buffer) { - cli->buffer = new String(cli->input_buffer_size); - } +static int Client_tcp_connect_async(Client *cli, const char *host, int port, double timeout, int nonblock) { + int ret; if (!(cli->onConnect && cli->onError && cli->onClose && cli->onReceive)) { swoole_warning("onConnect/onError/onReceive/onClose callback have not set"); return SW_ERR; } + cli->set_timeout(timeout); + + if (!cli->buffer) { + cli->buffer = new String(cli->input_buffer_size); + } + if (cli->onBufferFull && cli->buffer_high_watermark == 0) { cli->buffer_high_watermark = cli->socket->buffer_size * 0.8; } @@ -614,64 +508,40 @@ static int Client_tcp_connect_async(Client *cli, const char *host, int port, dou } if (cli->wait_dns) { - AsyncEvent ev{}; - - size_t len = strlen(cli->server_host); - if (len < SW_IP_MAX_LENGTH) { - ev.nbytes = SW_IP_MAX_LENGTH; - } else { - ev.nbytes = len + 1; - } - - ev.buf = sw_malloc(ev.nbytes); - if (!ev.buf) { - swoole_warning("malloc failed"); - return SW_ERR; - } - - memcpy(ev.buf, cli->server_host, len); - ((char *) ev.buf)[len] = 0; - ev.flags = cli->_sock_domain; - ev.object = cli; - ev.fd = cli->socket->fd; - ev.handler = async::handler_gethostbyname; - ev.callback = Client_onResolveCompleted; - - if (swoole::async::dispatch(&ev) == nullptr) { - sw_free(ev.buf); - return SW_ERR; - } else { - return SW_OK; - } + return Client_dns_lookup(cli); } - while (1) { + do { ret = cli->socket->connect(cli->server_addr); - if (ret < 0) { - if (errno == EINTR) { - continue; + if (ret < 0 && errno == EINTR) { + continue; + } + if ((ret < 0 && errno == EINPROGRESS) || ret == 0) { + /** + * A return value of 0 indicates that the connection has been successfully established, + * and there is no need to add a timer to check for connection timeouts. + */ + if (ret < 0 && timeout > 0) { + cli->timer = swoole_timer_add(timeout, false, Client_onTimeout, cli); + if (!cli->timer) { + return SW_ERR; + } } - swoole_set_last_error(errno); + /** + * Regardless of whether the connection has been successfully established or is still in progress, + * listen for writable events to handle the proxy and SSL handshake within those events. + */ + return swoole_event_add(cli->socket, SW_EVENT_WRITE); } - break; - } - - if ((ret < 0 && errno == EINPROGRESS) || ret == 0) { - if (swoole_event_add(cli->socket, SW_EVENT_WRITE) < 0) { - return SW_ERR; - } - if (timeout > 0) { - cli->timer = swoole_timer_add((long) (timeout * 1000), false, Client_onTimeout, cli); - } - return SW_OK; - } else { - cli->active = 0; + cli->active = false; cli->socket->removed = 1; cli->close(); if (cli->onError) { + cli->onerror_called = true; cli->onError(cli); } - } + break; + } while (true); return ret; } @@ -681,48 +551,25 @@ static ssize_t Client_tcp_send_async(Client *cli, const char *data, size_t lengt if (swoole_event_write(cli->socket, data, length) < 0) { if (swoole_get_last_error() == SW_ERROR_OUTPUT_BUFFER_OVERFLOW) { n = -1; - cli->high_watermark = 1; + cli->high_watermark = true; } else { return SW_ERR; } } - if (cli->onBufferFull && cli->socket->out_buffer && cli->high_watermark == 0 && - cli->socket->out_buffer->length() >= cli->buffer_high_watermark) { - cli->high_watermark = 1; + if (cli->onBufferFull && !cli->high_watermark && + cli->socket->get_out_buffer_length() >= cli->buffer_high_watermark) { + cli->high_watermark = true; cli->onBufferFull(cli); } return n; } static ssize_t Client_tcp_send_sync(Client *cli, const char *data, size_t length, int flags) { - size_t written = 0; - ssize_t n; - - assert(length > 0); - assert(data != nullptr); - - while (written < length) { - n = cli->socket->send(data, length - written, flags); - if (n < 0) { - if (errno == EINTR) { - continue; - } else if (errno == EAGAIN) { - cli->socket->wait_event(1000, SW_EVENT_WRITE); - continue; - } else { - swoole_set_last_error(errno); - return SW_ERR; - } - } - written += n; - data += n; - } - - return written; + return cli->socket->send_sync(data, length, flags); } static int Client_tcp_sendfile_sync(Client *cli, const char *filename, off_t offset, size_t length) { - if (cli->socket->sendfile_blocking(filename, offset, length, cli->timeout) < 0) { + if (cli->socket->sendfile_sync(filename, offset, length) < 0) { swoole_set_last_error(errno); return SW_ERR; } @@ -730,7 +577,7 @@ static int Client_tcp_sendfile_sync(Client *cli, const char *filename, off_t off } static int Client_tcp_sendfile_async(Client *cli, const char *filename, off_t offset, size_t length) { - if (cli->socket->sendfile(filename, offset, length) < 0) { + if (cli->socket->sendfile_async(filename, offset, length) < 0) { swoole_set_last_error(errno); return SW_ERR; } @@ -740,85 +587,68 @@ static int Client_tcp_sendfile_async(Client *cli, const char *filename, off_t of return SW_OK; } -/** - * Only for synchronous client - */ -static ssize_t Client_tcp_recv_no_buffer(Client *cli, char *data, size_t len, int flag) { - ssize_t ret; - - while (1) { -#ifdef HAVE_KQUEUE - int timeout_ms = (int) (cli->timeout * 1000); -#ifdef SW_USE_OPENSSL - if (cli->socket->ssl) { - timeout_ms = 0; - } -#endif - if (timeout_ms > 0 && cli->socket->wait_event(timeout_ms, SW_EVENT_READ) < 0) { - return -1; - } -#endif - ret = cli->socket->recv(data, len, flag); - if (ret >= 0) { - break; - } - if (errno == EINTR) { - if (cli->interrupt_time <= 0) { - cli->interrupt_time = microtime(); - continue; - } else if (microtime() > cli->interrupt_time + cli->timeout) { - break; - } else { - continue; - } - } -#ifdef SW_USE_OPENSSL - if (cli->socket->catch_read_error(errno) == SW_WAIT && cli->socket->ssl) { - int timeout_ms = (int) (cli->timeout * 1000); - if (cli->socket->ssl_want_read && cli->socket->wait_event(timeout_ms, SW_EVENT_READ) == SW_OK) { - continue; - } else if (cli->socket->ssl_want_write && cli->socket->wait_event(timeout_ms, SW_EVENT_WRITE) == SW_OK) { - continue; - } - } -#endif - break; +static ssize_t Client_tcp_recv_sync(Client *cli, char *data, size_t len, int flags) { + auto rv = cli->socket->recv_sync(data, len, flags); + /** + * To maintain forward compatibility, the recv system call returns EAGAIN after a timeout, + * while the poll() function returns 0 on timeout without setting errno, + * which should be set to either EAGAIN or ETIMEDOUT. + */ + if (rv == -1 && swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT) { + errno = EAGAIN; } - - return ret; + return rv; } static int Client_udp_connect(Client *cli, const char *host, int port, double timeout, int udp_connect) { + cli->set_timeout(timeout); + if (!cli->async && timeout > 0) { + cli->socket->set_kernel_timeout(timeout); + } + cli->sock_flags_ = udp_connect; + if (Client_inet_addr(cli, host, port) < 0) { return SW_ERR; } if (cli->async && !cli->onReceive) { - swoole_warning("onReceive callback have not set"); + swoole_warning("`onReceive` callback have not set"); return SW_ERR; } - cli->active = 1; - cli->timeout = timeout; - int bufsize = Socket::default_buffer_size; + if (cli->wait_dns && cli->async) { + /** + * Domain name resolution is required, and UDP connect cannot return immediately. + * If an `onError` callback is not set, the caller will not receive any notification in case of a resolution + * failure. Therefore, it is essential to set the onError callback; otherwise, an error will be returned + * immediately. + */ + if (!cli->onError) { + swoole_warning("`onError` callback have not set"); + return SW_ERR; + } + return Client_dns_lookup(cli); + } + + cli->active = true; + cli->socket->set_buffer_size(Socket::default_buffer_size); if (timeout > 0) { cli->socket->set_timeout(timeout); } if (cli->socket->socket_type == SW_SOCK_UNIX_DGRAM) { - struct sockaddr_un *client_addr = &cli->socket->info.addr.un; + sockaddr_un *client_addr = &cli->socket->info.addr.un; sprintf(client_addr->sun_path, "/tmp/swoole-client.%d.%d.sock", getpid(), cli->socket->fd); client_addr->sun_family = AF_UNIX; unlink(client_addr->sun_path); - if (bind(cli->socket->fd, (struct sockaddr *) client_addr, sizeof(cli->socket->info.addr.un)) < 0) { + if (bind(cli->socket->fd, reinterpret_cast(client_addr), sizeof(cli->socket->info.addr.un)) < 0) { swoole_sys_warning("bind(%s) failed", client_addr->sun_path); return SW_ERR; } } -#ifdef SW_USE_OPENSSL if (cli->open_ssl) #ifdef SW_SUPPORT_DTLS { @@ -830,7 +660,6 @@ static int Client_udp_connect(Client *cli, const char *host, int port, double ti swoole_warning("DTLS support require openssl-1.1 or later"); return SW_ERR; } -#endif #endif if (udp_connect != 1) { @@ -840,27 +669,22 @@ static int Client_udp_connect(Client *cli, const char *host, int port, double ti if (cli->socket->connect(cli->server_addr) == 0) { cli->socket->clean(); _connect_ok: - - cli->socket->set_option(SOL_SOCKET, SO_SNDBUF, bufsize); - cli->socket->set_option(SOL_SOCKET, SO_RCVBUF, bufsize); - if (cli->async && cli->onConnect) { if (swoole_event_add(cli->socket, SW_EVENT_READ) < 0) { return SW_ERR; } execute_onConnect(cli); } -#ifdef SW_USE_OPENSSL if (cli->open_ssl && cli->ssl_handshake() < 0) { return SW_ERR; } -#endif return SW_OK; } else { - cli->active = 0; + cli->active = false; cli->socket->removed = 1; cli->close(); if (cli->async && cli->onError) { + cli->onerror_called = true; cli->onError(cli); } return SW_ERR; @@ -868,7 +692,7 @@ static int Client_udp_connect(Client *cli, const char *host, int port, double ti } static ssize_t Client_udp_send(Client *cli, const char *data, size_t len, int flags) { - ssize_t n = sendto(cli->socket->fd, data, len, 0, (struct sockaddr *) &cli->server_addr.addr, cli->server_addr.len); + ssize_t n = cli->socket->sendto(cli->server_addr, data, len, 0); if (n < 0 || n < (ssize_t) len) { return SW_ERR; } else { @@ -877,146 +701,75 @@ static ssize_t Client_udp_send(Client *cli, const char *data, size_t len, int fl } static ssize_t Client_udp_recv(Client *cli, char *data, size_t length, int flags) { -#ifdef HAVE_KQUEUE - if (!cli->async) { - int timeout_ms = (int) (cli->timeout * 1000); - if (cli->socket->wait_event(timeout_ms, SW_EVENT_READ) < 0) { - return -1; - } - } -#endif - ssize_t ret = cli->socket->recvfrom(data, length, flags, &cli->remote_addr); - if (ret < 0) { - if (errno == EINTR) { - ret = cli->socket->recvfrom(data, length, flags, &cli->remote_addr); - } else { - return SW_ERR; - } - } - return ret; -} - -#ifdef SW_USE_OPENSSL -static int Client_https_proxy_handshake(Client *cli) { - char *buf = cli->buffer->str; - size_t len = cli->buffer->length; - int state = 0; - char *p = buf; - char *pe = buf + len; - for (; p < pe; p++) { - if (state == 0) { - if (SW_STRCASECT(p, pe - p, "HTTP/1.1") || SW_STRCASECT(p, pe - p, "HTTP/1.0")) { - state = 1; - p += sizeof("HTTP/1.x") - 1; - } else { - break; - } - } else if (state == 1) { - if (isspace(*p)) { - continue; - } else { - if (SW_STRCASECT(p, pe - p, "200")) { - state = 2; - p += sizeof("200") - 1; - } else { - break; - } - } - } else if (state == 2) { - if (isspace(*p)) { - continue; - } else { - if (SW_STRCASECT(p, pe - p, "Connection established")) { - return SW_OK; - } else { - break; - } - } - } + if (cli->async) { + return cli->socket->recvfrom_sync(data, length, flags, &cli->remote_addr); + } else { + return cli->socket->recvfrom(data, length, flags, &cli->remote_addr); } - return SW_ERR; } -#endif static int Client_onPackage(const Protocol *proto, Socket *conn, const RecvData *rdata) { - Client *cli = (Client *) conn->object; + auto *cli = static_cast(conn->object); cli->onReceive(cli, rdata->data, rdata->info.len); return conn->close_wait ? SW_ERR : SW_OK; } static int Client_onStreamRead(Reactor *reactor, Event *event) { - ssize_t n = -1; - Client *cli = (Client *) event->socket->object; + ssize_t n; + auto *cli = (Client *) event->socket->object; char *buf = cli->buffer->str + cli->buffer->length; ssize_t buf_size = cli->buffer->size - cli->buffer->length; + bool do_ssl_handshake = cli->open_ssl; if (cli->http_proxy && cli->http_proxy->state != SW_HTTP_PROXY_STATE_READY) { -#ifdef SW_USE_OPENSSL - if (cli->open_ssl) { - n = event->socket->recv(buf, buf_size, 0); - if (n <= 0) { - goto __close; - } - cli->buffer->length += n; - if (cli->buffer->length < sizeof(SW_HTTPS_PROXY_HANDSHAKE_RESPONSE) - 1) { - return SW_OK; - } - if (Client_https_proxy_handshake(cli) < 0) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR, "failed to handshake with http proxy"); - goto _connect_fail; - } else { - cli->http_proxy->state = SW_HTTP_PROXY_STATE_READY; - cli->buffer->clear(); - } - if (cli->ssl_handshake() < 0) { - goto _connect_fail; - } else { - if (cli->socket->ssl_state == SW_SSL_STATE_READY) { - execute_onConnect(cli); - } else if (cli->socket->ssl_state == SW_SSL_STATE_WAIT_STREAM && cli->socket->ssl_want_write) { - swoole_event_set(event->socket, SW_EVENT_WRITE); - } + n = event->socket->recv(buf, buf_size, 0); + if (n <= 0) { + swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); + _connect_fail: + cli->active = false; + cli->close(); + if (cli->onError) { + cli->onError(cli); } return SW_OK; } -#endif + cli->buffer->length += n; + if (!cli->http_proxy->handshake(cli->buffer)) { + swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_ERROR); + goto _connect_fail; + } + cli->http_proxy->state = SW_HTTP_PROXY_STATE_READY; + cli->buffer->clear(); + if (!do_ssl_handshake) { + execute_onConnect(cli); + return SW_OK; + } } + if (cli->socks5_proxy && cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { n = event->socket->recv(buf, buf_size, 0); if (n <= 0) { - goto __close; + swoole_set_last_error(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); + goto _connect_fail; } - if (cli->socks5_handshake(buf, buf_size) < 0) { - goto __close; + cli->buffer->length += n; + if (!cli->socks5_handshake(buf, buf_size)) { + swoole_set_last_error(SW_ERROR_SOCKS5_HANDSHAKE_FAILED); + goto _connect_fail; } if (cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { return SW_OK; } -#ifdef SW_USE_OPENSSL - if (cli->open_ssl) { - if (cli->ssl_handshake() < 0) { - _connect_fail: - cli->active = 0; - cli->close(); - if (cli->onError) { - cli->onError(cli); - } - } else { - cli->socket->ssl_state = SW_SSL_STATE_WAIT_STREAM; - return swoole_event_set(event->socket, SW_EVENT_WRITE); - } - } else -#endif - { + cli->buffer->clear(); + if (!do_ssl_handshake) { execute_onConnect(cli); + return SW_OK; } - return SW_OK; } -#ifdef SW_USE_OPENSSL - if (cli->open_ssl && cli->socket->ssl_state == SW_SSL_STATE_WAIT_STREAM) { + if (cli->open_ssl && cli->socket->ssl_state != SW_SSL_STATE_READY) { if (cli->ssl_handshake() < 0) { + swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } if (cli->socket->ssl_state != SW_SSL_STATE_READY) { @@ -1026,7 +779,6 @@ static int Client_onStreamRead(Reactor *reactor, Event *event) { return SW_OK; } } -#endif if (cli->open_eof_check || cli->open_length_check) { Socket *conn = cli->socket; @@ -1046,15 +798,12 @@ static int Client_onStreamRead(Reactor *reactor, Event *event) { } else { if (conn->removed == 0 && cli->remove_delay) { cli->sleep(); - cli->remove_delay = 0; + cli->remove_delay = false; } return SW_OK; } } -#ifdef SW_CLIENT_RECV_AGAIN -_recv_again: -#endif n = event->socket->recv(buf, buf_size, 0); if (n < 0) { switch (event->socket->catch_read_error(errno)) { @@ -1073,18 +822,13 @@ static int Client_onStreamRead(Reactor *reactor, Event *event) { return cli->close(); } else { cli->onReceive(cli, buf, n); -#ifdef SW_CLIENT_RECV_AGAIN - if (n == buf_size) { - goto _recv_again; - } -#endif return SW_OK; } return SW_OK; } static int Client_onDgramRead(Reactor *reactor, Event *event) { - Client *cli = (Client *) event->socket->object; + auto *cli = (Client *) event->socket->object; char buffer[SW_BUFFER_SIZE_UDP]; int n = Client_udp_recv(cli, buffer, sizeof(buffer), 0); @@ -1097,7 +841,7 @@ static int Client_onDgramRead(Reactor *reactor, Event *event) { } static int Client_onError(Reactor *reactor, Event *event) { - Client *cli = (Client *) event->socket->object; + auto *cli = (Client *) event->socket->object; if (cli->active) { return cli->close(); } else { @@ -1107,18 +851,18 @@ static int Client_onError(Reactor *reactor, Event *event) { } static void Client_onTimeout(Timer *timer, TimerNode *tnode) { - Client *cli = (Client *) tnode->data; + auto *cli = (Client *) tnode->data; swoole_set_last_error(ETIMEDOUT); -#ifdef SW_USE_OPENSSL + cli->timer = nullptr; + if (cli->open_ssl && cli->socket->ssl_state != SW_SSL_STATE_READY) { - cli->active = 0; + cli->active = false; } -#endif if (cli->socks5_proxy && cli->socks5_proxy->state != SW_SOCKS5_STATE_READY) { - cli->active = 0; + cli->active = false; } else if (cli->http_proxy && cli->http_proxy->state != SW_HTTP_PROXY_STATE_READY) { - cli->active = 0; + cli->active = false; } cli->close(); @@ -1128,37 +872,44 @@ static void Client_onTimeout(Timer *timer, TimerNode *tnode) { } static void Client_onResolveCompleted(AsyncEvent *event) { - if (event->canceled) { - sw_free(event->buf); - return; - } + auto *req = dynamic_cast(event->data.get()); - Client *cli = (Client *) event->object; - cli->wait_dns = 0; + auto *cli = static_cast(event->object); + cli->wait_dns = false; + cli->dns_completed = true; if (event->error == 0) { - Client_tcp_connect_async(cli, (char *) event->buf, cli->server_port, cli->timeout, 1); + /** + * In the callback function, the application layer cannot obtain the return value of the connect function, + * so it must call `onError` to notify the caller. + */ + double timeout = cli->socket->get_timeout(SW_TIMEOUT_CONNECT); + if (cli->connect(req->addr.c_str(), cli->server_port, timeout, cli->sock_flags_) == SW_ERR && + !cli->onerror_called) { + goto _error; + } } else { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + _error: cli->socket->removed = 1; cli->close(); if (cli->onError) { + cli->onerror_called = true; cli->onError(cli); } } - sw_free(event->buf); } static int Client_onWrite(Reactor *reactor, Event *event) { - Client *cli = (Client *) event->socket->object; + auto *cli = (Client *) event->socket->object; Socket *_socket = cli->socket; int ret; int err; if (cli->active) { -#ifdef SW_USE_OPENSSL if (cli->open_ssl && _socket->ssl_state == SW_SSL_STATE_WAIT_STREAM) { if (cli->ssl_handshake() < 0) { + swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } else if (_socket->ssl_state == SW_SSL_STATE_READY) { goto _connect_success; @@ -1169,12 +920,12 @@ static int Client_onWrite(Reactor *reactor, Event *event) { return SW_OK; } } -#endif if (Reactor::_writable_callback(reactor, event) < 0) { return SW_ERR; } - if (cli->onBufferEmpty && cli->high_watermark && _socket->out_buffer->length() <= cli->buffer_low_watermark) { - cli->high_watermark = 0; + if (cli->onBufferEmpty && cli->high_watermark && + _socket->get_out_buffer_length() <= cli->buffer_low_watermark) { + cli->high_watermark = false; cli->onBufferEmpty(cli); } return SW_OK; @@ -1192,31 +943,24 @@ static int Client_onWrite(Reactor *reactor, Event *event) { // listen read event swoole_event_set(event->socket, SW_EVENT_READ); // connected - cli->active = 1; + cli->active = true; // socks5 proxy if (cli->socks5_proxy && cli->socks5_proxy->state == SW_SOCKS5_STATE_WAIT) { - char buf[3]; - Socks5Proxy::pack(buf, cli->socks5_proxy->username.empty() ? 0x00 : 0x02); + const auto len = cli->socks5_proxy->pack_negotiate_request(); cli->socks5_proxy->state = SW_SOCKS5_STATE_HANDSHAKE; - return cli->send(cli, buf, sizeof(buf), 0); + return cli->send(cli->socks5_proxy->buf, len, 0); } // http proxy if (cli->http_proxy && cli->http_proxy->state == SW_HTTP_PROXY_STATE_WAIT) { -#ifdef SW_USE_OPENSSL - if (cli->open_ssl) { - cli->http_proxy->state = SW_HTTP_PROXY_STATE_HANDSHAKE; - int n = sw_snprintf(cli->http_proxy->buf, - sizeof(cli->http_proxy->buf), - "CONNECT %s:%d HTTP/1.1\r\n\r\n", - cli->http_proxy->target_host.c_str(), - cli->http_proxy->target_port); - return cli->send(cli, cli->http_proxy->buf, n, 0); - } -#endif + auto proxy_buf = sw_tg_buffer(); + auto host_name = cli->get_http_proxy_host_name(); + size_t n = cli->http_proxy->pack(proxy_buf, host_name); + swoole_trace_log(SW_TRACE_HTTP_CLIENT, "proxy request: <str); + return cli->send(proxy_buf->str, n, 0); } -#ifdef SW_USE_OPENSSL if (cli->open_ssl) { if (cli->ssl_handshake() < 0) { + swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); goto _connect_fail; } else { _socket->ssl_state = SW_SSL_STATE_WAIT_STREAM; @@ -1224,13 +968,10 @@ static int Client_onWrite(Reactor *reactor, Event *event) { return SW_OK; } _connect_success: -#endif execute_onConnect(cli); } else { -#ifdef SW_USE_OPENSSL _connect_fail: -#endif - cli->active = 0; + cli->active = false; cli->close(); cli->onError(cli); } diff --git a/src/network/dns.cc b/src/network/dns.cc index 0d407bf5e1..6f2945e9d6 100644 --- a/src/network/dns.cc +++ b/src/network/dns.cc @@ -14,9 +14,9 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" +#include "swoole_coroutine_api.h" #include "swoole_util.h" #include @@ -34,6 +34,7 @@ using swoole::NameResolver; using swoole::coroutine::System; +using swoole::network::Address; SW_API bool swoole_load_resolv_conf() { FILE *fp; @@ -61,6 +62,29 @@ SW_API bool swoole_load_resolv_conf() { return true; } +SW_API void swoole_set_dns_server(const std::string &server) { + char *_port; + int dns_server_port = SW_DNS_SERVER_PORT; + char dns_server_host[32]; + strcpy(dns_server_host, server.c_str()); + if ((_port = strchr(const_cast(server.c_str()), ':'))) { + dns_server_port = sw_atoi(_port + 1); + if (!Address::verify_port(dns_server_port, true)) { + dns_server_port = SW_DNS_SERVER_PORT; + } + dns_server_host[_port - server.c_str()] = '\0'; + } + SwooleG.dns_server.host = dns_server_host; + SwooleG.dns_server.port = dns_server_port; +} + +SW_API swoole::DnsServer swoole_get_dns_server() { + if (SwooleG.dns_server.host.empty()) { + swoole_load_resolv_conf(); + } + return SwooleG.dns_server; +} + SW_API void swoole_set_hosts_path(const std::string &hosts_file) { SwooleG.dns_hosts_path = hosts_file; } @@ -74,18 +98,13 @@ SW_API void swoole_name_resolver_add(const NameResolver &resolver, bool append) } SW_API void swoole_name_resolver_each( - const std::function::iterator &iter)> &fn) { - for (auto iter = SwooleG.name_resolvers.begin(); iter != SwooleG.name_resolvers.end(); iter++) { - enum swTraverseOperation op = fn(iter); - switch (op) { - case SW_TRAVERSE_REMOVE: + const std::function::iterator &iter)> &fn) { + for (auto iter = SwooleG.name_resolvers.begin(); iter != SwooleG.name_resolvers.end(); ++iter) { + const swTraverseOperation op = fn(iter); + if (op == SW_TRAVERSE_REMOVE) { SwooleG.name_resolvers.erase(iter++); - continue; - case SW_TRAVERSE_STOP: + } else if (op == SW_TRAVERSE_STOP) { break; - default: - case SW_TRAVERSE_KEEP: - continue; } } } @@ -94,8 +113,8 @@ SW_API std::string swoole_name_resolver_lookup(const std::string &host_name, Nam if (SwooleG.name_resolvers.empty()) { goto _dns_lookup; } - for (auto iter = SwooleG.name_resolvers.begin(); iter != SwooleG.name_resolvers.end(); iter++) { - std::string result = iter->resolve(host_name, ctx, iter->private_data); + for (auto &name_resolver : SwooleG.name_resolvers) { + std::string result = name_resolver.resolve(host_name, ctx, name_resolver.private_data); if (!result.empty() || ctx->final_) { return result; } @@ -107,16 +126,7 @@ SW_API std::string swoole_name_resolver_lookup(const std::string &host_name, Nam if (swoole_coroutine_is_in()) { return System::gethostbyname(host_name, ctx->type, ctx->timeout); } else { - char addr[SW_IP_MAX_LENGTH]; - if (swoole::network::gethostbyname(ctx->type, host_name.c_str(), sw_tg_buffer()->str) < 0) { - swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); - return ""; - } - if (!inet_ntop(ctx->type, sw_tg_buffer()->str, addr, sizeof(addr))) { - swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); - return ""; - } - return std::string(addr); + return swoole::network::gethostbyname(ctx->type, host_name); } } @@ -222,7 +232,7 @@ std::string get_ip_by_hosts(const std::string &search_domain) { } static std::string parse_ip_address(void *vaddr, int type) { - auto addr = reinterpret_cast(vaddr); + auto addr = static_cast(vaddr); std::string ip_addr; if (type == AF_INET) { char buff[4 * 4 + 3 + 1]; @@ -244,19 +254,18 @@ static std::string parse_ip_address(void *vaddr, int type) { } std::vector dns_lookup_impl_with_socket(const char *domain, int family, double timeout) { - char *_domain_name; Q_FLAGS *qflags = nullptr; char packet[SW_BUFFER_SIZE_STD]; RecordHeader *header = nullptr; int steps = 0; std::vector result; - if (SwooleG.dns_server_host.empty() && !swoole_load_resolv_conf()) { + if (SwooleG.dns_server.host.empty() && !swoole_load_resolv_conf()) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_NO_SERVER); return result; } - header = (RecordHeader *) packet; + header = reinterpret_cast(packet); int _request_id = dns_request_id++; header->id = htons(_request_id); header->qr = 0; @@ -274,9 +283,9 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam steps = sizeof(RecordHeader); - _domain_name = &packet[steps]; + char *_domain_name = &packet[steps]; - int len = strlen(domain); + const int len = strlen(domain); if (domain_encode(domain, len, _domain_name) < 0) { swoole_warning("invalid domain[%s]", domain); return result; @@ -284,7 +293,7 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam steps += (strlen((const char *) _domain_name) + 1); - qflags = (Q_FLAGS *) &packet[steps]; + qflags = reinterpret_cast(&packet[steps]); qflags->qtype = htons(family == AF_INET6 ? SW_DNS_AAAA_RECORD : SW_DNS_A_RECORD); qflags->qclass = htons(0x0001); steps += sizeof(Q_FLAGS); @@ -293,7 +302,7 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam if (timeout > 0) { _sock.set_timeout(timeout); } - if (!_sock.sendto(SwooleG.dns_server_host, SwooleG.dns_server_port, (char *) packet, steps)) { + if (!_sock.sendto(SwooleG.dns_server.host, SwooleG.dns_server.port, (char *) packet, steps)) { swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); return result; } @@ -309,11 +318,10 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam uint32_t type[10]; sw_memset_zero(rdata, sizeof(rdata)); - char *temp; steps = 0; char name[10][254]; - int i, j; + int i; auto ret = _sock.recv(packet, sizeof(packet) - 1); if (ret <= 0) { @@ -322,14 +330,14 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam } packet[ret] = 0; - header = (RecordHeader *) packet; + header = reinterpret_cast(packet); steps = sizeof(RecordHeader); _domain_name = &packet[steps]; domain_decode(_domain_name); steps = steps + (strlen(_domain_name) + 2); - qflags = (Q_FLAGS *) &packet[steps]; + qflags = reinterpret_cast(&packet[steps]); (void) qflags; steps = steps + sizeof(Q_FLAGS); @@ -341,10 +349,10 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam for (i = 0; i < ancount; ++i) { type[i] = 0; /* Parsing the NAME portion of the RR */ - temp = &packet[steps]; - j = 0; + char *temp = &packet[steps]; + int j = 0; while (*temp != 0) { - if ((uchar)(*temp) == 0xc0) { + if ((uchar) (*temp) == 0xc0) { ++temp; temp = &packet[(uint8_t) *temp]; } else { @@ -373,7 +381,7 @@ std::vector dns_lookup_impl_with_socket(const char *domain, int fam temp = &packet[steps]; j = 0; while (*temp != 0) { - if ((uchar)(*temp) == 0xc0) { + if ((uchar) (*temp) == 0xc0) { ++temp; temp = &packet[(uint8_t) *temp]; } else { @@ -417,7 +425,6 @@ static int domain_encode(const char *src, int n, char *dest) { } int pos = 0; - int i; int len = 0; memcpy(dest + 1, src, n + 1); dest[n + 1] = '.'; @@ -425,7 +432,7 @@ static int domain_encode(const char *src, int n, char *dest) { src = dest + 1; n++; - for (i = 0; i < n; i++) { + for (int i = 0; i < n; i++) { if (src[i] == '.') { len = i - pos; dest[pos] = len; @@ -441,10 +448,10 @@ static int domain_encode(const char *src, int n, char *dest) { * (i.e. 3www5apple3com0 into www.apple.com) */ static void domain_decode(char *str) { - size_t i, j; + size_t i; for (i = 0; i < strlen(str); i++) { uint32_t len = str[i]; - for (j = 0; j < len; j++) { + for (size_t j = 0; j < len; j++) { str[i] = str[i + 1]; i++; } @@ -467,16 +474,16 @@ struct ResolvContext { }; std::vector dns_lookup_impl_with_cares(const char *domain, int family, double timeout) { - if (!swoole_event_isset_handler(SW_FD_CARES)) { + if (!swoole_event_isset_handler(SW_FD_CARES, SW_EVENT_READ)) { ares_library_init(ARES_LIB_INIT_ALL); - swoole_event_set_handler(SW_FD_CARES | SW_EVENT_READ, [](Reactor *reactor, Event *event) -> int { - auto ctx = reinterpret_cast(event->socket->object); + swoole_event_set_handler(SW_FD_CARES, SW_EVENT_READ, [](Reactor *reactor, Event *event) -> int { + auto ctx = static_cast(event->socket->object); swoole_trace_log(SW_TRACE_CARES, "[event callback] readable event, fd=%d", event->socket->fd); ares_process_fd(ctx->channel, event->fd, ARES_SOCKET_BAD); return SW_OK; }); - swoole_event_set_handler(SW_FD_CARES | SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { - auto ctx = reinterpret_cast(event->socket->object); + swoole_event_set_handler(SW_FD_CARES, SW_EVENT_WRITE, [](Reactor *reactor, Event *event) -> int { + auto ctx = static_cast(event->socket->object); swoole_trace_log(SW_TRACE_CARES, "[event callback] writable event, fd=%d", event->socket->fd); ares_process_fd(ctx->channel, ARES_SOCKET_BAD, event->fd); return SW_OK; @@ -496,7 +503,7 @@ std::vector dns_lookup_impl_with_cares(const char *domain, int fami ctx.ares_opts.tries = SwooleG.dns_tries; ctx.ares_opts.sock_state_cb_data = &ctx; ctx.ares_opts.sock_state_cb = [](void *arg, int fd, int readable, int writable) { - auto ctx = reinterpret_cast(arg); + auto ctx = static_cast(arg); int events = 0; if (readable) { events |= SW_EVENT_READ; @@ -543,14 +550,14 @@ std::vector dns_lookup_impl_with_cares(const char *domain, int fami goto _return; } - if (!SwooleG.dns_server_host.empty()) { + if (!SwooleG.dns_server.host.empty()) { #if (ARES_VERSION >= 0x010b00) struct ares_addr_port_node servers; servers.family = AF_INET; servers.next = nullptr; - inet_pton(AF_INET, SwooleG.dns_server_host.c_str(), &servers.addr.addr4); + inet_pton(AF_INET, SwooleG.dns_server.host.c_str(), &servers.addr.addr4); servers.tcp_port = 0; - servers.udp_port = SwooleG.dns_server_port; + servers.udp_port = SwooleG.dns_server.port; ares_set_servers_ports(ctx.channel, &servers); #elif (ARES_VERSION >= 0x010701) struct ares_addr_node servers; @@ -571,7 +578,7 @@ std::vector dns_lookup_impl_with_cares(const char *domain, int fami domain, family, [](void *data, int status, int timeouts, struct hostent *hostent) { - auto ctx = reinterpret_cast(data); + auto ctx = static_cast(data); swoole_trace_log(SW_TRACE_CARES, "[cares callback] status=%d, timeouts=%d", status, timeouts); @@ -600,7 +607,7 @@ std::vector dns_lookup_impl_with_cares(const char *domain, int fami if (*_cancelled) { return; } - Coroutine *co = reinterpret_cast(data); + auto *co = static_cast(data); co->resume(); }, ctx->co); @@ -646,6 +653,7 @@ std::vector dns_lookup_impl_with_cares(const char *domain, int fami #endif std::vector dns_lookup(const char *domain, int family, double timeout) { + family = family == AF_INET6 ? AF_INET6 : AF_INET; // only support IPv4 and IPv6 #ifdef SW_USE_CARES return dns_lookup_impl_with_cares(domain, family, timeout); #else @@ -667,21 +675,21 @@ static std::mutex g_gethostbyname2_lock; #ifdef HAVE_GETHOSTBYNAME2_R int gethostbyname(int flags, const char *name, char *addr) { - int __af = flags & (~SW_DNS_LOOKUP_RANDOM); + int _af = flags & (~SW_DNS_LOOKUP_RANDOM); int index = 0; int rc, err; int buf_len = 256; - struct hostent hbuf; - struct hostent *result; + hostent hbuf{}; + hostent *result; - char *buf = (char *) sw_malloc(buf_len); + char *buf = static_cast(sw_malloc(buf_len)); if (!buf) { return SW_ERR; } memset(buf, 0, buf_len); - while ((rc = ::gethostbyname2_r(name, __af, &hbuf, buf, buf_len, &result, &err)) == ERANGE) { + while ((rc = ::gethostbyname2_r(name, _af, &hbuf, buf, buf_len, &result, &err)) == ERANGE) { buf_len *= 2; - char *tmp = (char *) sw_realloc(buf, buf_len); + char *tmp = static_cast(sw_realloc(buf, buf_len)); if (nullptr == tmp) { sw_free(buf); return SW_ERR; @@ -705,13 +713,13 @@ int gethostbyname(int flags, const char *name, char *addr) { if (hbuf.h_addr_list[i] == nullptr) { break; } - if (__af == AF_INET) { + if (_af == AF_INET) { memcpy(addr_list[i].v4, hbuf.h_addr_list[i], hbuf.h_length); } else { memcpy(addr_list[i].v6, hbuf.h_addr_list[i], hbuf.h_length); } } - if (__af == AF_INET) { + if (_af == AF_INET) { memcpy(addr, addr_list[index].v4, hbuf.h_length); } else { memcpy(addr, addr_list[index].v6, hbuf.h_length); @@ -758,65 +766,80 @@ int gethostbyname(int flags, const char *name, char *addr) { } #endif +std::string gethostbyname(int type, const std::string &name) { + char addr[sizeof(in6_addr)]; + if (gethostbyname(type, name.c_str(), addr) == SW_OK) { + return Address::addr_str(type, addr); + } + swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + return {}; +} + int getaddrinfo(GetaddrinfoRequest *req) { - struct addrinfo *result = nullptr; - struct addrinfo *ptr = nullptr; - struct addrinfo hints {}; + addrinfo *result = nullptr; + addrinfo *ptr = nullptr; + addrinfo hints{}; hints.ai_family = req->family; hints.ai_socktype = req->socktype; hints.ai_protocol = req->protocol; - int ret = ::getaddrinfo(req->hostname, req->service, &hints, &result); + int ret = ::getaddrinfo(req->hostname.c_str(), req->service.c_str(), &hints, &result); if (ret != 0) { req->error = ret; return SW_ERR; } - void *buffer = req->result; int i = 0; - for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) { + for (ptr = result; ptr != nullptr; ptr = ptr->ai_next, i++) { + } + req->count = SW_MIN(i, SW_DNS_HOST_BUFFER_SIZE); + req->results.resize(req->count); + + for (ptr = result, i = 0; ptr != nullptr; ptr = ptr->ai_next, i++) { switch (ptr->ai_family) { case AF_INET: - memcpy((char *) buffer + (i * sizeof(struct sockaddr_in)), ptr->ai_addr, sizeof(struct sockaddr_in)); + memcpy(&req->results[i], ptr->ai_addr, sizeof(struct sockaddr_in)); break; case AF_INET6: - memcpy((char *) buffer + (i * sizeof(struct sockaddr_in6)), ptr->ai_addr, sizeof(struct sockaddr_in6)); + memcpy(&req->results[i], ptr->ai_addr, sizeof(struct sockaddr_in6)); break; default: swoole_warning("unknown socket family[%d]", ptr->ai_family); break; } - i++; if (i == SW_DNS_HOST_BUFFER_SIZE) { break; } } ::freeaddrinfo(result); req->error = 0; - req->count = i; + return SW_OK; } -void GetaddrinfoRequest::parse_result(std::vector &retval) { - struct sockaddr_in *addr_v4; - struct sockaddr_in6 *addr_v6; - - char tmp[INET6_ADDRSTRLEN]; - const char *r; +int gethostbyname(GethostbynameRequest *req) { + const auto rv = gethostbyname(req->family, req->name); + if (rv.empty()) { + swoole_set_last_error(SW_ERROR_DNSLOOKUP_RESOLVE_FAILED); + return SW_ERR; + } + req->addr = rv; + return SW_OK; +} +} // namespace network - for (int i = 0; i < count; i++) { - if (family == AF_INET) { - addr_v4 = (struct sockaddr_in *) ((char *) result + (i * sizeof(struct sockaddr_in))); - r = inet_ntop(AF_INET, (const void *) &addr_v4->sin_addr, tmp, sizeof(tmp)); +void GetaddrinfoRequest::parse_result(std::vector &retval) const { + for (auto &addr : results) { + const char *addr_str; + if (family == AF_INET6) { + addr_str = Address::addr_str(family, &addr.sin6_addr); } else { - addr_v6 = (struct sockaddr_in6 *) ((char *) result + (i * sizeof(struct sockaddr_in6))); - r = inet_ntop(AF_INET6, (const void *) &addr_v6->sin6_addr, tmp, sizeof(tmp)); + addr_str = Address::addr_str(family, &((sockaddr_in *) &addr)->sin_addr); } - if (r) { - retval.push_back(tmp); + if (addr_str) { + retval.emplace_back(addr_str); } } } -} // namespace network } // namespace swoole diff --git a/src/network/socket.cc b/src/network/socket.cc index 5108225f5d..b764bcc404 100644 --- a/src/network/socket.cc +++ b/src/network/socket.cc @@ -15,12 +15,13 @@ */ #include "swoole_socket.h" - -#include - -#include "swoole_api.h" +#include "swoole_signal.h" #include "swoole_util.h" #include "swoole_string.h" +#include "swoole_timer.h" + +#include +#include namespace swoole { namespace network { @@ -31,7 +32,7 @@ double Socket::default_read_timeout = SW_SOCKET_DEFAULT_READ_TIMEOUT; double Socket::default_write_timeout = SW_SOCKET_DEFAULT_WRITE_TIMEOUT; uint32_t Socket::default_buffer_size = SW_SOCKET_BUFFER_SIZE; -IOVector::IOVector(struct iovec *_iov, int _iovcnt) { +IOVector::IOVector(const iovec *_iov, int _iovcnt) { iov = new iovec[_iovcnt + _iovcnt]; iov_iterator = iov + _iovcnt; count = remain_count = _iovcnt; @@ -44,19 +45,19 @@ IOVector::~IOVector() { delete[] iov; } -void IOVector::update_iterator(ssize_t __n) { +void IOVector::update_iterator(ssize_t _n) { size_t total_bytes = 0; size_t _offset_bytes = 0; int _index = 0; - if (__n <= 0 || remain_count == 0) { + if (_n <= 0 || remain_count == 0) { return; } SW_LOOP_N(remain_count) { total_bytes += iov_iterator[i].iov_len; - if ((ssize_t) total_bytes >= __n) { - _offset_bytes = iov_iterator[i].iov_len - (total_bytes - __n); + if (static_cast(total_bytes) >= _n) { + _offset_bytes = iov_iterator[i].iov_len - (total_bytes - _n); _index = i; if (_offset_bytes == iov_iterator[i].iov_len) { @@ -73,78 +74,218 @@ void IOVector::update_iterator(ssize_t __n) { return; } iov_iterator += _index; - iov_iterator->iov_base = reinterpret_cast(iov_iterator->iov_base) + _offset_bytes; + iov_iterator->iov_base = static_cast(iov_iterator->iov_base) + _offset_bytes; iov_iterator->iov_len = iov_iterator->iov_len - _offset_bytes; return; } } - // represents the length of __n greater than total_bytes + // represents the length of _n greater than total_bytes abort(); } -int Socket::sendfile_blocking(const char *filename, off_t offset, size_t length, double timeout) { - int timeout_ms = timeout < 0 ? -1 : timeout * 1000; +static bool check_sendfile_parameters(const File *file, off_t begin, size_t length, off_t *end) { + auto filename = file->get_path().c_str(); + if (!file->ready()) { + swoole_sys_warning("open('%s') failed", filename); + return false; + } - File file(filename, O_RDONLY); - if (!file.ready()) { - swoole_sys_warning("open(%s) failed", filename); - return SW_ERR; + FileStatus file_stat; + if (!file->stat(&file_stat)) { + swoole_sys_warning("fstat('%s') failed", filename); + return false; + } + + if (file_stat.st_size == 0) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_EMPTY, "cannot send empty file '%s'", filename); + return false; } if (length == 0) { - FileStatus file_stat; - if (!file.stat(&file_stat)) { - return SW_ERR; - } - length = file_stat.st_size; + *end = file_stat.st_size; } else { - length = offset + length; + *end = begin + static_cast(length); } - int n, sendn; - while (offset < (off_t) length) { - if (wait_event(timeout_ms, SW_EVENT_WRITE) < 0) { - return SW_ERR; - } else { - sendn = (length - offset > SW_SENDFILE_CHUNK_SIZE) ? SW_SENDFILE_CHUNK_SIZE : length - offset; - n = ::swoole_sendfile(fd, file.get_fd(), &offset, sendn); - if (n <= 0) { - swoole_sys_warning("sendfile(%d, %s) failed", fd, filename); - return SW_ERR; - } else { + if (begin < 0 || *end > file_stat.st_size) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "length[%ld] or offset[%ld] is invalid", length, (long) begin); + return false; + } + + return true; +} + +static size_t get_sendfile_chunk_size(off_t begin, off_t end) { + size_t real_length = end - begin; + return real_length > SW_SENDFILE_CHUNK_SIZE ? SW_SENDFILE_CHUNK_SIZE : real_length; +} + +int Socket::what_event_want(int default_event) const { + if (ssl && (ssl_want_write || ssl_want_read)) { + return ssl_want_write ? SW_EVENT_WRITE : SW_EVENT_READ; + } + return default_event; +} + +#define CHECK_RETURN_VALUE(rv, be_zero_return) \ + if (rv < 0) { \ + if (errno == EINTR || catch_error(errno) == SW_WAIT) { \ + return SW_CONTINUE; \ + } \ + swoole_set_last_error(errno); \ + return SW_ERROR; \ + } else if (rv == 0) { \ + return be_zero_return; \ + } + +bool Socket::wait_for(const std::function &fn, int event, int timeout_msec) { + double began_at = 0; + if (timeout_msec > 0) { + began_at = microtime(); + } + + if (!nonblock) { + set_nonblock(); + } + + if (ssl) { + ssl_clear_error(); + } + + while (true) { + switch (fn()) { + case SW_ERROR: + return false; + case SW_READY: + return true; + case SW_CONTINUE: + /** + * The ENOBUFS error indicates that the operating system currently lacks sufficient available memory, + * requiring waiting for the kernel to reclaim memory. Event listening for writes is also ineffective—the + * only recourse is to sleep for 10 milliseconds while awaiting kernel memory recovery. + */ + if (has_kernel_nobufs()) { + usleep(10 * 1000); continue; } + break; + default: + break; + } + + auto rv = wait_event(timeout_msec, what_event_want(event)); + if (rv == SW_ERR && ((errno == EINTR && dont_restart) || errno != EINTR)) { + return false; + } + + if (timeout_msec > 0) { + timeout_msec -= sec2msec(microtime() - began_at); + swoole_trace_log(SW_TRACE_CLIENT, "timeout_ms=%d", timeout_msec); + if (timeout_msec <= 0) { + swoole_set_last_error(ETIMEDOUT); + errno = ETIMEDOUT; + return false; + } } } - return SW_OK; + + return false; } -ssize_t Socket::writev_blocking(const struct iovec *iov, size_t iovcnt) { - while (1) { - ssize_t n = writev(iov, iovcnt); - if (n < 0) { +int Socket::sendfile_sync(const char *filename, off_t offset, size_t length) { + off_t end; + File file(filename, O_RDONLY); + + if (!check_sendfile_parameters(&file, offset, length, &end)) { + return SW_ERR; + } + + auto corked = false; + if (end - offset > SW_SOCKET_CORK_MIN_SIZE) { + corked = cork(); + } + + auto rv = wait_for( + [this, &file, &offset, end]() { + size_t sent_bytes = get_sendfile_chunk_size(offset, end); + ssize_t n = sendfile(file, &offset, sent_bytes); + CHECK_RETURN_VALUE(n, SW_ERROR); + return offset < end ? SW_CONTINUE : SW_READY; + }, + SW_EVENT_WRITE, + sec2msec(write_timeout)); + + if (corked) { + uncork(); + } + + return rv ? SW_OK : SW_ERR; +} + +ssize_t Socket::writev_sync(const struct iovec *iov, size_t iovcnt) { + ssize_t bytes = 0; + + auto rv = wait_for( + [this, &bytes, iov, iovcnt]() { + ssize_t n = writev(iov, iovcnt); + CHECK_RETURN_VALUE(n, SW_READY); + bytes = n; + return SW_READY; + }, + SW_EVENT_WRITE, + sec2msec(write_timeout)); + + return rv ? bytes : -1; +} + +swReturnCode Socket::connect_async(const Address &sa) { + set_nonblock(); + while (true) { + auto ret = connect(sa); + if (ret < 0) { if (errno == EINTR) { continue; - } else if (catch_write_error(errno) == SW_WAIT && - wait_event((int) (send_timeout_ * 1000), SW_EVENT_WRITE) == SW_OK) { - continue; - } else { - swoole_sys_warning("send %lu bytes failed", iov[1].iov_len); - return SW_ERR; } - } else { - return n; + swoole_set_last_error(errno); + return errno == EINPROGRESS ? SW_WAIT : SW_ERROR; } + break; } - return -1; + return SW_READY; +} + +int Socket::connect_sync(const Address &sa) { + auto rc = connect_async(sa); + if (rc != SW_WAIT) { + return rc == SW_READY ? SW_OK : SW_ERR; + } + double timeout = connect_timeout; + if (wait_event(timeout > 0 ? sec2msec(timeout) : timeout, SW_EVENT_WRITE) < 0) { + swoole_set_last_error(ETIMEDOUT); + return SW_ERR; + } + int err; + socklen_t len = sizeof(len); + int ret = get_option(SOL_SOCKET, SO_ERROR, &err, &len); + if (ret < 0) { + swoole_set_last_error(errno); + return SW_ERR; + } + if (err != 0) { + swoole_set_last_error(err); + return SW_ERR; + } + set_block(); + return SW_OK; } /** * clear socket buffer. */ -void Socket::clean() { +void Socket::clean() const { char buf[2048]; while (::recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) { }; @@ -153,94 +294,83 @@ void Socket::clean() { /** * Wait socket can read or write. */ -int Socket::wait_event(int timeout_ms, int events) { - struct pollfd event; +int Socket::wait_event(int timeout_ms, int _events) const { + pollfd event; event.fd = fd; - event.events = 0; + event.events = translate_events_to_poll(_events); if (timeout_ms < 0) { timeout_ms = -1; } - if (events & SW_EVENT_READ) { - event.events |= POLLIN; - } - if (events & SW_EVENT_WRITE) { - event.events |= POLLOUT; - } - while (1) { + while (true) { int ret = poll(&event, 1, timeout_ms); if (ret == 0) { swoole_set_last_error(SW_ERROR_SOCKET_POLL_TIMEOUT); + errno = EAGAIN; return SW_ERR; - } else if (ret < 0 && errno != EINTR) { - swoole_sys_warning("poll() failed"); - return SW_ERR; - } else { - return SW_OK; } - } - return SW_OK; -} - -ssize_t Socket::send_blocking(const void *__data, size_t __len) { - ssize_t n = 0; - ssize_t written = 0; - - while (written < (ssize_t) __len) { -#ifdef SW_USE_OPENSSL - if (ssl) { - n = ssl_send((char *) __data + written, __len - written); - } else -#endif - { - n = ::send(fd, (char *) __data + written, __len - written, 0); - } - if (n < 0) { - if (errno == EINTR) { - continue; - } else if (catch_write_error(errno) == SW_WAIT && - wait_event((int) (send_timeout_ * 1000), SW_EVENT_WRITE) == SW_OK) { - continue; - } else { - swoole_sys_warning("send %lu bytes failed", __len); + if (ret < 0) { + if (errno != EINTR) { + swoole_set_last_error(errno); return SW_ERR; } + if (dont_restart) { + swoole_set_last_error(errno); + return SW_ERR; + } + swoole_signal_dispatch(); + continue; } - written += n; + return SW_OK; } - return written; + return SW_OK; } -ssize_t Socket::recv_blocking(void *__data, size_t __len, int flags) { - ssize_t ret; - size_t read_bytes = 0; +ssize_t Socket::send_sync(const void *_data, size_t _len, int flags) { + ssize_t bytes = 0; - while (read_bytes != __len) { - errno = 0; - ret = ::recv(fd, (char *) __data + read_bytes, __len - read_bytes, flags); - if (ret > 0) { - read_bytes += ret; - } else if (ret == 0) { - return read_bytes; - } else if (ret < 0) { - if (errno == EINTR) { - continue; - } - if (catch_read_error(errno) == SW_WAIT && - wait_event((int) (recv_timeout_ * 1000), SW_EVENT_READ) == SW_OK) { - continue; + auto rv = wait_for( + [this, _data, _len, flags, &bytes]() { + ssize_t n = send((char *) _data + bytes, _len - bytes, flags); + CHECK_RETURN_VALUE(n, SW_READY); + bytes += n; + return bytes == (ssize_t) _len ? SW_READY : SW_CONTINUE; + }, + SW_EVENT_WRITE, + sec2msec(write_timeout)); + + return rv ? bytes : -1; +} + +ssize_t Socket::recv_sync(void *_data, size_t _len, int flags) { + ssize_t bytes = 0; + + auto rv = wait_for( + [this, _data, _len, flags, &bytes]() { + /** + * In non-blocking mode, the MSG_WAITALL flag must be cleared. Otherwise, receiving a small amount of data + * may cause the poll listener to return 1 for readable events, yet the recv operation returns -1 with errno + * set to EAGAIN, resulting in an infinite loop. + */ + ssize_t n = recv((char *) _data + bytes, _len - bytes, flags & ~MSG_WAITALL); + CHECK_RETURN_VALUE(n, SW_READY); + bytes += n; + if (flags & MSG_WAITALL) { + return bytes == (ssize_t) _len ? SW_READY : SW_CONTINUE; + } else { + return SW_READY; } - return ret; - } - } + }, + SW_EVENT_READ, + sec2msec(read_timeout)); - return read_bytes; + return rv ? bytes : -1; } Socket *Socket::accept() { - Socket *socket = new Socket(); + auto *socket = new Socket(); socket->removed = 1; socket->socket_type = socket_type; socket->info.len = sizeof(socket->info); @@ -249,7 +379,7 @@ Socket *Socket::accept() { if (nonblock) { flags |= SOCK_NONBLOCK; } - socket->fd = ::accept4(fd, (struct sockaddr *) &socket->info.addr, &socket->info.len, flags); + socket->fd = ::accept4(fd, reinterpret_cast(&socket->info.addr), &socket->info.len, flags); #else socket->fd = ::accept(fd, (struct sockaddr *) &socket->info.addr, &socket->info.len); if (socket->fd >= 0) { @@ -266,48 +396,59 @@ Socket *Socket::accept() { return socket; } -ssize_t Socket::sendto_blocking(const Address &sa, const void *__buf, size_t __n, int flags) { - ssize_t n = 0; +ssize_t Socket::sendto_sync(const Address &sa, const void *_buf, size_t _n, int flags) { + ssize_t bytes = 0; - for (int i = 0; i < SW_SOCKET_RETRY_COUNT; i++) { - n = sendto(sa, __buf, __n, flags); - if (n >= 0) { - break; - } - if (errno == EINTR) { - continue; - } - if (catch_write_error(errno) == SW_WAIT && wait_event((int) (send_timeout_ * 1000), SW_EVENT_WRITE) == SW_OK) { - continue; - } - break; - } + auto rv = wait_for( + [this, &sa, _buf, _n, flags, &bytes]() { + ssize_t n = sendto(sa, _buf, _n, flags); + CHECK_RETURN_VALUE(n, SW_READY); + bytes = n; + return SW_READY; + }, + SW_EVENT_WRITE, + sec2msec(write_timeout)); - return n; + return rv ? bytes : -1; } -ssize_t Socket::recvfrom_blocking(char *__buf, size_t __len, int flags, Address *sa) { +ssize_t Socket::recvfrom(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) const { ssize_t n = 0; - - for (int i = 0; i < SW_SOCKET_RETRY_COUNT; i++) { - n = recvfrom(__buf, __len, flags, sa); - if (n >= 0) { - break; - } - if (errno == EINTR) { - continue; - } - if (catch_read_error(errno) == SW_WAIT && wait_event((int) (recv_timeout_ * 1000), SW_EVENT_READ) == SW_OK) { + SW_LOOP_N(SW_SOCKET_RETRY_COUNT) { + n = ::recvfrom(fd, buf, len, flags, addr, addr_len); + if (n < 0 && errno == EINTR) { continue; } break; } - return n; } +ssize_t Socket::recvfrom_sync(char *buf, size_t len, int flags, Address *sa) { + return recvfrom_sync(buf, len, flags, &sa->addr.ss, &sa->len); +} + +ssize_t Socket::recvfrom_sync(char *buf, size_t len, int flags, sockaddr *addr, socklen_t *addr_len) { + ssize_t bytes = 0; + + auto rv = wait_for( + [this, buf, len, flags, addr, addr_len, &bytes]() { + ssize_t n = recvfrom(buf, len, flags, addr, addr_len); + CHECK_RETURN_VALUE(n, SW_READY); + bytes = n; + return SW_READY; + }, + SW_EVENT_READ, + sec2msec(read_timeout)); + + return rv ? bytes : -1; +} + static void socket_free_defer(void *ptr) { - Socket *sock = (Socket *) ptr; + auto *sock = static_cast(ptr); + if (sock->is_local() && sock->bound) { + ::unlink(sock->get_addr()); + } if (sock->fd != -1 && close(sock->fd) != 0) { swoole_sys_warning("close(%d) failed", sock->fd); } @@ -321,127 +462,150 @@ void Socket::free() { if (send_timer) { swoole_timer_del(send_timer); } - if (in_buffer) { - delete in_buffer; - } - if (out_buffer) { - delete out_buffer; - } + + delete in_buffer; + delete out_buffer; + if (swoole_event_is_available()) { - removed = 1; + if (fd != -1 && !removed) { + swoole_event_del(this); + } swoole_event_defer(socket_free_defer, this); } else { socket_free_defer(this); } } -int Socket::bind(const std::string &_host, int *port) { - int ret; - Address address = {}; - size_t l_host = _host.length(); - const char *host = _host.c_str(); - - if (set_reuse_addr() < 0) { - swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", fd); +int Socket::get_name() { + info.len = sizeof(info.addr); + if (getsockname(fd, &info.addr.ss, &info.len) < 0) { + return -1; } - // UnixSocket - if (socket_type == SW_SOCK_UNIX_DGRAM || socket_type == SW_SOCK_UNIX_STREAM) { - if (l_host == 0 || l_host > sizeof(address.addr.un) - 1) { - swoole_warning("bad unix socket file"); - errno = EINVAL; - return SW_ERR; - } - unlink(host); - address.addr.un.sun_family = AF_UNIX; - swoole_strlcpy(address.addr.un.sun_path, host, sizeof(address.addr.un.sun_path)); - ret = ::bind(fd, (struct sockaddr *) &address.addr.un, sizeof(address.addr.un)); - } - // IPv6 - else if (socket_type == SW_SOCK_TCP6 || socket_type == SW_SOCK_UDP6) { - if (l_host == 0) { - host = "::"; - } - if (inet_pton(AF_INET6, host, &address.addr.inet_v6.sin6_addr) < 0) { - swoole_sys_warning("inet_pton(AF_INET6, %s) failed", host); - return SW_ERR; - } - address.addr.inet_v6.sin6_port = htons(*port); - address.addr.inet_v6.sin6_family = AF_INET6; - ret = ::bind(fd, (struct sockaddr *) &address.addr.inet_v6, sizeof(address.addr.inet_v6)); - if (ret == 0 && *port == 0) { - address.len = sizeof(address.addr.inet_v6); - if (getsockname(fd, (struct sockaddr *) &address.addr.inet_v6, &address.len) != -1) { - *port = ntohs(address.addr.inet_v6.sin6_port); - } - } + info.type = socket_type; + return 0; +} + +int Socket::get_peer_name(Address *sa) const { + sa->len = sizeof(sa->addr); + sa->type = socket_type; + if (::getpeername(fd, &sa->addr.ss, &sa->len) != 0) { + return SW_ERR; } - // IPv4 - else if (socket_type == SW_SOCK_UDP || socket_type == SW_SOCK_TCP) { - if (l_host == 0) { - host = "0.0.0.0"; - } - if (inet_pton(AF_INET, host, &address.addr.inet_v4.sin_addr) < 0) { - swoole_sys_warning("inet_pton(AF_INET, %s) failed", host); - return SW_ERR; - } - address.addr.inet_v4.sin_port = htons(*port); - address.addr.inet_v4.sin_family = AF_INET; - ret = ::bind(fd, (struct sockaddr *) &address.addr.inet_v4, sizeof(address.addr.inet_v4)); - if (ret == 0 && *port == 0) { - address.len = sizeof(address.addr.inet_v4); - if (getsockname(fd, (struct sockaddr *) &address.addr.inet_v4, &address.len) != -1) { - *port = ntohs(address.addr.inet_v4.sin_port); - } - } - } else { - errno = EINVAL; + return SW_OK; +} + +int Socket::set_tcp_nopush(int nopush) { +#ifdef TCP_CORK + if (set_option(IPPROTO_TCP, TCP_CORK, nopush) == SW_ERR) { return -1; + } else { + tcp_nopush = nopush; + return 0; } +#else + return -1; +#endif +} - // bind failed - if (ret < 0) { +int Socket::bind(const std::string &_host, int port) { + Address addr; + if (!addr.assign(socket_type, _host, port, false)) { return SW_ERR; } + return bind(addr); +} - return ret; +int Socket::bind(const struct sockaddr *sa, socklen_t len) { + if (::bind(fd, sa, len) < 0) { + return SW_ERR; + } + bound = 1; + return SW_OK; } -bool Socket::set_buffer_size(uint32_t _buffer_size) { - if (!set_send_buffer_size(_buffer_size)) { - return false; +int Socket::listen(int backlog) { + if (::listen(fd, backlog <= 0 ? SW_BACKLOG : backlog) < 0) { + return SW_ERR; } - if (!set_recv_buffer_size(_buffer_size)) { + listened = 1; + return SW_OK; +} + +bool Socket::set_buffer_size(uint32_t _buffer_size) const { + return set_send_buffer_size(_buffer_size) && set_recv_buffer_size(_buffer_size); +} + +bool Socket::set_recv_buffer_size(uint32_t _buffer_size) const { + return set_option(SOL_SOCKET, SO_RCVBUF, _buffer_size) == 0; +} + +bool Socket::set_send_buffer_size(uint32_t _buffer_size) const { + return set_option(SOL_SOCKET, SO_SNDBUF, _buffer_size) == 0; +} + +bool Socket::check_liveness() { + char buf; + errno = 0; + ssize_t retval = peek(&buf, sizeof(buf), MSG_DONTWAIT); + return !(retval == 0 || (retval < 0 && catch_read_error(errno) == SW_CLOSE)); +} + +bool Socket::set_tcp_nodelay(int nodelay) { + if (set_option(IPPROTO_TCP, TCP_NODELAY, nodelay) == SW_ERR) { return false; + } else { + tcp_nodelay = nodelay; + return true; } - return true; } -bool Socket::set_recv_buffer_size(uint32_t _buffer_size) { - if (set_option(SOL_SOCKET, SO_RCVBUF, _buffer_size) != 0) { - swoole_sys_warning("setsockopt(%d, SOL_SOCKET, SO_SNDBUF, %d) failed", fd, _buffer_size); +bool Socket::cork() { + if (tcp_nopush) { + return false; + } +#ifdef TCP_CORK + if (set_tcp_nopush(1) < 0) { + swoole_sys_warning("set_tcp_nopush(fd=%d, ON) failed", fd); return false; } +#endif + // Need to turn off tcp nodelay when using nopush + if (tcp_nodelay && !set_tcp_nodelay(0)) { + swoole_sys_warning("set_tcp_nodelay(fd=%d, OFF) failed", fd); + } return true; } -bool Socket::set_send_buffer_size(uint32_t _buffer_size) { - if (set_option(SOL_SOCKET, SO_SNDBUF, _buffer_size) != 0) { - swoole_sys_warning("setsockopt(%d, SOL_SOCKET, SO_RCVBUF, %d) failed", fd, _buffer_size); +bool Socket::uncork() { + if (!tcp_nopush) { + return false; + } +#ifdef TCP_CORK + if (set_tcp_nopush(0) < 0) { + swoole_sys_warning("set_tcp_nopush(fd=%d, OFF) failed", fd); + return false; + } +#endif + // Restore tcp_nodelay setting + if (enable_tcp_nodelay && tcp_nodelay == 0 && !set_tcp_nodelay(1)) { + swoole_sys_warning("set_tcp_nodelay(fd=%d, ON) failed", fd); return false; } return true; } -bool Socket::set_timeout(double timeout) { - return set_recv_timeout(timeout) and set_send_timeout(timeout); +Socket *Socket::dup() const { + auto *_socket = new Socket(); + *_socket = *this; + _socket->fd = ::dup(fd); + return _socket; } static bool _set_timeout(int fd, int type, double timeout) { - int ret; - struct timeval timeo; + timeval timeo; timeo.tv_sec = (int) timeout; timeo.tv_usec = (int) ((timeout - timeo.tv_sec) * 1000 * 1000); - ret = setsockopt(fd, SOL_SOCKET, type, (void *) &timeo, sizeof(timeo)); + int ret = setsockopt(fd, SOL_SOCKET, type, &timeo, sizeof(timeo)); if (ret < 0) { swoole_sys_warning("setsockopt(SO_SNDTIMEO, %s) failed", type == SO_SNDTIMEO ? "SEND" : "RECV"); return false; @@ -518,18 +682,60 @@ bool Socket::set_fd_option(int _nonblock, int _cloexec) { } } -bool Socket::set_recv_timeout(double timeout) { +void Socket::set_timeout(double timeout, int type) { + if (timeout == 0) { + return; + } + if (type & SW_TIMEOUT_DNS) { + dns_timeout = timeout; + } + if (type & SW_TIMEOUT_CONNECT) { + connect_timeout = timeout; + } + if (type & SW_TIMEOUT_READ) { + read_timeout = timeout; + } + if (type & SW_TIMEOUT_WRITE) { + write_timeout = timeout; + } +} + +double Socket::get_timeout(TimeoutType type) const { + SW_ASSERT_1BYTE(type); + if (type == SW_TIMEOUT_DNS) { + return dns_timeout; + } else if (type == SW_TIMEOUT_CONNECT) { + return connect_timeout; + } else if (type == SW_TIMEOUT_READ) { + return read_timeout; + } else if (type == SW_TIMEOUT_WRITE) { + return write_timeout; + } else { + assert(0); + return -1; + } +} + +bool Socket::has_timedout() const { + return errno == EAGAIN || errno == ETIMEDOUT || swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT; +} + +bool Socket::has_kernel_nobufs() { + return std::exchange(kernel_nobufs, 0); +} + +bool Socket::set_kernel_read_timeout(double timeout) { if (_set_timeout(fd, SO_SNDTIMEO, timeout)) { - send_timeout_ = timeout; + write_timeout = timeout; return true; } else { return false; } } -bool Socket::set_send_timeout(double timeout) { +bool Socket::set_kernel_write_timeout(double timeout) { if (_set_timeout(fd, SO_RCVTIMEO, timeout)) { - recv_timeout_ = timeout; + read_timeout = timeout; return true; } else { return false; @@ -537,34 +743,27 @@ bool Socket::set_send_timeout(double timeout) { } int Socket::handle_sendfile() { - int ret; Buffer *buffer = out_buffer; BufferChunk *chunk = buffer->front(); - SendfileRequest *task = (SendfileRequest *) chunk->value.object; + auto *task = (SendfileRequest *) chunk->value.ptr; - if (task->offset == 0) { - cork(); + if (task->corked == 0) { + if (task->end - task->begin > SW_SOCKET_CORK_MIN_SIZE) { + task->corked = cork() ? 1 : -1; + } else { + task->corked = -1; + } } - size_t sendn = - (task->length - task->offset > SW_SENDFILE_CHUNK_SIZE) ? SW_SENDFILE_CHUNK_SIZE : task->length - task->offset; - -#ifdef SW_USE_OPENSSL - if (ssl) { - ret = ssl_sendfile(task->file, &task->offset, sendn); - } else -#endif - { - ret = ::swoole_sendfile(fd, task->file.get_fd(), &task->offset, sendn); - } + size_t sendn = get_sendfile_chunk_size(task->begin, task->end); + ssize_t rv = sendfile(task->file, &task->begin, sendn); - swoole_trace("ret=%d|task->offset=%ld|sendn=%lu|filesize=%lu", ret, (long) task->offset, sendn, task->length); + swoole_trace("rv=%ld|begin=%ld|sendn=%lu|end=%lu", rv, (long) task->begin, sendn, task->end); - if (ret <= 0) { + if (rv <= 0) { switch (catch_write_error(errno)) { case SW_ERROR: - swoole_sys_warning( - "sendfile(%s, %ld, %zu) failed", task->file.get_path().c_str(), (long) task->offset, sendn); + swoole_sys_warning("sendfile(%s, %ld, %zu) failed", task->get_filename(), (long) task->begin, sendn); buffer->pop(); return SW_OK; case SW_CLOSE: @@ -583,9 +782,12 @@ int Socket::handle_sendfile() { } // sendfile completed - if ((size_t) task->offset >= task->length) { + if (task->begin == task->end) { + if (task->corked == 1) { + uncork(); + task->corked = 0; + } buffer->pop(); - uncork(); } return SW_OK; @@ -604,7 +806,7 @@ int Socket::handle_send() { return SW_OK; } - ssize_t ret = send(chunk->value.ptr + chunk->offset, sendn, 0); + ssize_t ret = send(chunk->value.str + chunk->offset, sendn, 0); if (ret < 0) { switch (catch_write_error(errno)) { case SW_ERROR: @@ -622,7 +824,7 @@ int Socket::handle_send() { return SW_OK; } // chunk full send - else if (ret == sendn || sendn == 0) { + else if (ret == sendn) { buffer->pop(); } else { chunk->offset += ret; @@ -636,61 +838,44 @@ int Socket::handle_send() { } static void Socket_sendfile_destructor(BufferChunk *chunk) { - SendfileRequest *task = (SendfileRequest *) chunk->value.object; + auto *task = static_cast(chunk->value.ptr); delete task; } -int Socket::sendfile(const char *filename, off_t offset, size_t length) { - std::unique_ptr task(new SendfileRequest(filename, offset, length)); - if (!task->file.ready()) { - swoole_sys_warning("open(%s) failed", filename); - return SW_OK; +ssize_t Socket::sendfile(const File &fp, off_t *offset, size_t length) { + if (ssl) { + return ssl_sendfile(fp, offset, length); + } else { + return swoole_sendfile(fd, fp.get_fd(), offset, length); } +} - FileStatus file_stat; - if (!task->file.stat(&file_stat)) { - swoole_sys_warning("fstat(%s) failed", filename); - return SW_ERR; - } +int Socket::sendfile_async(const char *filename, off_t offset, size_t length) { + std::unique_ptr task(new SendfileRequest(filename, offset)); - if (file_stat.st_size == 0) { - swoole_warning("empty file[%s]", filename); + if (!check_sendfile_parameters(&task->file, offset, length, &task->end)) { return SW_ERR; } if (out_buffer == nullptr) { out_buffer = new Buffer(SW_SEND_BUFFER_SIZE); - if (out_buffer == nullptr) { - return SW_ERR; - } - } - - if (offset < 0 || (length + offset > (size_t) file_stat.st_size)) { - swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "length or offset is invalid"); - return SW_OK; - } - if (length == 0) { - task->length = file_stat.st_size; - } else { - task->length = length + offset; } BufferChunk *chunk = out_buffer->alloc(BufferChunk::TYPE_SENDFILE, 0); - chunk->value.object = task.release(); + chunk->value.ptr = task.release(); chunk->destroy = Socket_sendfile_destructor; return SW_OK; } -ssize_t Socket::recv(void *__buf, size_t __n, int __flags) { +ssize_t Socket::recv(void *_buf, size_t _n, int _flags) { ssize_t total_bytes = 0; do { -#ifdef SW_USE_OPENSSL if (ssl) { ssize_t retval = 0; - while ((size_t) total_bytes < __n) { - retval = ssl_recv(((char *) __buf) + total_bytes, __n - total_bytes); + while (static_cast(total_bytes) < _n) { + retval = ssl_recv(static_cast(_buf) + total_bytes, _n - total_bytes); if (retval <= 0) { if (total_bytes == 0) { total_bytes = retval; @@ -698,17 +883,15 @@ ssize_t Socket::recv(void *__buf, size_t __n, int __flags) { break; } else { total_bytes += retval; - if (!(nonblock || (__flags & MSG_WAITALL))) { + if (!(nonblock || (_flags & MSG_WAITALL))) { break; } } } - } else -#endif - { - total_bytes = ::recv(fd, __buf, __n, __flags); + } else { + total_bytes = ::recv(fd, _buf, _n, _flags); } - } while (total_bytes < 0 && errno == EINTR); + } while (total_bytes < 0 && (errno == EINTR && !dont_restart)); if (total_bytes > 0) { total_recv_bytes += total_bytes; @@ -722,24 +905,21 @@ ssize_t Socket::recv(void *__buf, size_t __n, int __flags) { total_bytes = 0; } - swoole_trace_log(SW_TRACE_SOCKET, "recv %ld/%ld bytes, errno=%d", total_bytes, __n, errno); + swoole_trace_log(SW_TRACE_SOCKET, "recv %ld/%ld bytes, errno=%d", total_bytes, _n, errno); return total_bytes; } -ssize_t Socket::send(const void *__buf, size_t __n, int __flags) { +ssize_t Socket::send(const void *_buf, size_t _n, int _flags) { ssize_t retval; do { -#ifdef SW_USE_OPENSSL if (ssl) { - retval = ssl_send(__buf, __n); - } else -#endif - { - retval = ::send(fd, __buf, __n, __flags); + retval = ssl_send(_buf, _n); + } else { + retval = ::send(fd, _buf, _n, _flags); } - } while (retval < 0 && errno == EINTR); + } while (retval < 0 && (errno == EINTR && !dont_restart)); if (retval > 0) { total_send_bytes += retval; @@ -748,29 +928,58 @@ ssize_t Socket::send(const void *__buf, size_t __n, int __flags) { } } - swoole_trace_log(SW_TRACE_SOCKET, "send %ld/%ld bytes, errno=%d", retval, __n, errno); + swoole_trace_log(SW_TRACE_SOCKET, "send %ld/%ld bytes, errno=%d", retval, _n, errno); return retval; } -ssize_t Socket::send_async(const void *__buf, size_t __n) { +ssize_t Socket::send_async(const void *_buf, size_t _n) { if (!swoole_event_is_available()) { - return send_blocking(__buf, __n); + return send_sync(_buf, _n, 0); } else { - return swoole_event_write(this, __buf, __n); + return swoole_event_write(this, _buf, _n); } } +ssize_t Socket::read_sync(void *_buf, size_t _len) { + ssize_t bytes = 0; + + auto rv = wait_for( + [this, _buf, _len, &bytes]() { + ssize_t n = read((char *) _buf + bytes, _len - bytes); + CHECK_RETURN_VALUE(n, SW_READY); + bytes += n; + return SW_READY; + }, + SW_EVENT_READ, + sec2msec(read_timeout)); + + return rv ? bytes : -1; +} + +ssize_t Socket::write_sync(const void *_buf, size_t _len) { + ssize_t bytes = 0; + + auto rv = wait_for( + [this, _buf, _len, &bytes]() { + ssize_t n = write((char *) _buf + bytes, _len - bytes); + CHECK_RETURN_VALUE(n, SW_READY); + bytes += n; + return SW_READY; + }, + SW_EVENT_WRITE, + sec2msec(write_timeout)); + + return rv ? bytes : -1; +} + ssize_t Socket::readv(IOVector *io_vector) { ssize_t retval; do { -#ifdef SW_USE_OPENSSL if (ssl) { retval = ssl_readv(io_vector); - } else -#endif - { + } else { retval = ::readv(fd, io_vector->get_iterator(), io_vector->get_remain_count()); io_vector->update_iterator(retval); } @@ -783,12 +992,9 @@ ssize_t Socket::writev(IOVector *io_vector) { ssize_t retval; do { -#ifdef SW_USE_OPENSSL if (ssl) { retval = ssl_writev(io_vector); - } else -#endif - { + } else { retval = ::writev(fd, io_vector->get_iterator(), io_vector->get_remain_count()); io_vector->update_iterator(retval); } @@ -797,26 +1003,131 @@ ssize_t Socket::writev(IOVector *io_vector) { return retval; } -ssize_t Socket::peek(void *__buf, size_t __n, int __flags) { +ssize_t Socket::peek(void *_buf, size_t _n, int _flags) const { ssize_t retval; - __flags |= MSG_PEEK; + _flags |= MSG_PEEK; do { -#ifdef SW_USE_OPENSSL if (ssl) { - retval = SSL_peek(ssl, __buf, __n); - } else -#endif - { - retval = ::recv(fd, __buf, __n, __flags); + retval = SSL_peek(ssl, _buf, _n); + } else { + retval = ::recv(fd, _buf, _n, _flags); } } while (retval < 0 && errno == EINTR); - swoole_trace_log(SW_TRACE_SOCKET, "peek %ld/%ld bytes, errno=%d", retval, __n, errno); + swoole_trace_log(SW_TRACE_SOCKET, "peek %ld/%ld bytes, errno=%d", retval, _n, errno); return retval; } -#ifdef SW_USE_OPENSSL +int Socket::catch_error(const int err) { + switch (err) { + case EFAULT: + abort(); + return SW_ERROR; + case EBADF: + case ENOENT: + return SW_INVALID; + case ECONNRESET: + case ECONNABORTED: + case EPIPE: + case ENOTCONN: + case ETIMEDOUT: + case ECONNREFUSED: + case ENETDOWN: + case ENETUNREACH: + case EHOSTDOWN: + case EHOSTUNREACH: + case SW_ERROR_SSL_BAD_CLIENT: + case SW_ERROR_SSL_RESET: + return SW_CLOSE; + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif + case 0: + return SW_WAIT; + case ENOBUFS: + kernel_nobufs = true; + return SW_WAIT; + default: + return SW_ERROR; + } +} + +SocketType Socket::convert_to_type(const int domain, const int type) { + if (domain == AF_INET && type == SOCK_STREAM) { + return SW_SOCK_TCP; + } else if (domain == AF_INET6 && type == SOCK_STREAM) { + return SW_SOCK_TCP6; + } else if (domain == AF_UNIX && type == SOCK_STREAM) { + return SW_SOCK_UNIX_STREAM; + } else if (domain == AF_INET && type == SOCK_DGRAM) { + return SW_SOCK_UDP; + } else if (domain == AF_INET6 && type == SOCK_DGRAM) { + return SW_SOCK_UDP6; + } else if (domain == AF_UNIX && type == SOCK_DGRAM) { + return SW_SOCK_UNIX_DGRAM; + } else if (domain == AF_INET && type == SOCK_RAW) { + return SW_SOCK_RAW; + } else if (domain == AF_INET6 && type == SOCK_RAW) { + return SW_SOCK_RAW6; + } else { + return SW_SOCK_RAW; + } +} + +SocketType Socket::convert_to_type(std::string &host) { + if (host.compare(0, 6, "unix:/", 0, 6) == 0) { + host = host.substr(sizeof("unix:") - 1); + host.erase(0, host.find_first_not_of('/') - 1); + return SW_SOCK_UNIX_STREAM; + } + if (host.find(':') != std::string::npos) { + return SW_SOCK_TCP6; + } + return SW_SOCK_TCP; +} + +int Socket::get_domain_and_type(SocketType type, int *sock_domain, int *sock_type) { + switch (type) { + case SW_SOCK_TCP6: + *sock_domain = AF_INET6; + *sock_type = SOCK_STREAM; + break; + case SW_SOCK_UNIX_STREAM: + *sock_domain = AF_UNIX; + *sock_type = SOCK_STREAM; + break; + case SW_SOCK_UDP: + *sock_domain = AF_INET; + *sock_type = SOCK_DGRAM; + break; + case SW_SOCK_UDP6: + *sock_domain = AF_INET6; + *sock_type = SOCK_DGRAM; + break; + case SW_SOCK_UNIX_DGRAM: + *sock_domain = AF_UNIX; + *sock_type = SOCK_DGRAM; + break; + case SW_SOCK_TCP: + *sock_domain = AF_INET; + *sock_type = SOCK_STREAM; + break; + case SW_SOCK_RAW: + *sock_domain = AF_INET; + *sock_type = SOCK_RAW; + break; + case SW_SOCK_RAW6: + *sock_domain = AF_INET6; + *sock_type = SOCK_RAW; + break; + default: + return SW_ERR; + } + + return SW_OK; +} #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT static int ssl_check_name(const char *name, ASN1_STRING *pattern) { @@ -854,7 +1165,7 @@ static int ssl_check_name(const char *name, ASN1_STRING *pattern) { } #endif -bool Socket::ssl_check_host(const char *tls_host_name) { +bool Socket::ssl_check_host(const char *tls_host_name) const { X509 *cert = ssl_get_peer_certificate(); if (cert == nullptr) { return false; @@ -946,7 +1257,7 @@ bool Socket::ssl_check_host(const char *tls_host_name) { return true; } -bool Socket::ssl_verify(bool allow_self_signed) { +bool Socket::ssl_verify(bool allow_self_signed) const { long err = SSL_get_verify_result(ssl); switch (err) { case X509_V_OK: @@ -972,22 +1283,21 @@ bool Socket::ssl_verify(bool allow_self_signed) { return true; } -X509 *Socket::ssl_get_peer_certificate() { +X509 *Socket::ssl_get_peer_certificate() const { if (!ssl) { - return NULL; + return nullptr; } return SSL_get_peer_certificate(ssl); } -STACK_OF(X509) * Socket::ssl_get_peer_cert_chain() { +STACK_OF(X509) * Socket::ssl_get_peer_cert_chain() const { if (!ssl) { - return NULL; + return nullptr; } return SSL_get_peer_cert_chain(ssl); } static int _ssl_read_x509_file(X509 *cert, char *buffer, size_t length) { - long len; BIO *bio = BIO_new(BIO_s_mem()); ON_SCOPE_EXIT { BIO_free(bio); @@ -1003,15 +1313,15 @@ static int _ssl_read_x509_file(X509 *cert, char *buffer, size_t length) { return -1; } - len = BIO_pending(bio); - if (len < 0 && len > (long) length) { - swoole_warning("certificate length[%ld] is too big", len); + int len = BIO_pending(bio); + if (len < 0 && len > static_cast(length)) { + swoole_warning("certificate length[%d] is too big", len); return -1; } return BIO_read(bio, buffer, len); } -std::vector Socket::ssl_get_peer_cert_chain(int limit) { +std::vector Socket::ssl_get_peer_cert_chain(int limit) const { std::vector list; STACK_OF(X509) *chain = ssl_get_peer_cert_chain(); if (chain == nullptr) { @@ -1027,15 +1337,15 @@ std::vector Socket::ssl_get_peer_cert_chain(int limit) { SW_LOOP_N(n) { X509 *cert = sk_X509_value(chain, i); - auto n = _ssl_read_x509_file(cert, sw_tg_buffer()->str, sw_tg_buffer()->size); - if (n > 0) { - list.emplace_back(sw_tg_buffer()->str, n); + auto rv = _ssl_read_x509_file(cert, sw_tg_buffer()->str, sw_tg_buffer()->size); + if (rv > 0) { + list.emplace_back(sw_tg_buffer()->str, rv); } } return list; } -bool Socket::ssl_get_peer_certificate(String *buf) { +bool Socket::ssl_get_peer_certificate(String *buf) const { int n = ssl_get_peer_certificate(buf->str, buf->size); if (n < 0) { return false; @@ -1045,7 +1355,7 @@ bool Socket::ssl_get_peer_certificate(String *buf) { } } -int Socket::ssl_get_peer_certificate(char *buffer, size_t length) { +int Socket::ssl_get_peer_certificate(char *buffer, size_t length) const { X509 *cert = ssl_get_peer_certificate(); if (cert == nullptr) { return SW_ERR; @@ -1059,8 +1369,10 @@ int Socket::ssl_get_peer_certificate(char *buffer, size_t length) { } const char *Socket::ssl_get_error_reason(int *reason) { - int error = ERR_get_error(); - *reason = ERR_GET_REASON(error); + ulong_t error = ERR_get_error(); + if (reason) { + *reason = ERR_GET_REASON(error); + } return ERR_reason_error_string(error); } @@ -1101,8 +1413,11 @@ ReturnCode Socket::ssl_accept() { } else if (err == SSL_ERROR_SSL) { int reason; const char *error_string = ssl_get_error_reason(&reason); - swoole_warning( - "bad SSL client[%s:%d], reason=%d, error_string=%s", info.get_ip(), info.get_port(), reason, error_string); + swoole_warning("bad SSL client[%s:%d], reason=%d, error_string=%s", + info.get_addr(), + info.get_port(), + reason, + error_string); return SW_ERROR; } else if (err == SSL_ERROR_SYSCALL) { #ifdef SW_SUPPORT_DTLS @@ -1146,28 +1461,32 @@ int Socket::ssl_connect() { return SW_OK; } else if (err == SSL_ERROR_ZERO_RETURN) { swoole_debug("SSL_connect(fd=%d) closed", fd); + swoole_set_last_error(SW_ERROR_SSL_RESET); return SW_ERR; } else if (err == SSL_ERROR_SYSCALL) { if (n) { swoole_set_last_error(errno); return SW_ERR; } + } else { + swoole_set_last_error(SW_ERROR_SSL_HANDSHAKE_FAILED); } - long err_code = ERR_get_error(); - char *msg = ERR_error_string(err_code, sw_tg_buffer()->str); - swoole_notice("Socket::ssl_connect(fd=%d) to server[%s:%d] failed. Error: %s[%ld|%d]", + ulong_t err_code = ERR_get_error(); + char error_buf[512]; + ERR_error_string_n(err_code, error_buf, sizeof(error_buf)); + swoole_notice("ssl_connect(fd=%d) to server[%s:%d] failed. Error: %s[%ld|%d]", fd, - info.get_ip(), + info.get_addr(), info.get_port(), - msg, + error_buf, err, ERR_GET_REASON(err_code)); return SW_ERR; } -int Socket::ssl_sendfile(const File &fp, off_t *_offset, size_t _size) { +ssize_t Socket::ssl_sendfile(const File &fp, off_t *_offset, size_t _size) { char buf[SW_BUFFER_SIZE_BIG]; ssize_t readn = _size > sizeof(buf) ? sizeof(buf) : _size; @@ -1241,7 +1560,7 @@ void Socket::ssl_close() { ssl = nullptr; } -void Socket::ssl_catch_error() { +void Socket::ssl_catch_error() const { int level = SW_LOG_NOTICE; int reason = ERR_GET_REASON(ERR_peek_error()); @@ -1315,15 +1634,15 @@ void Socket::ssl_catch_error() { SW_ERROR_SSL_BAD_PROTOCOL, "SSL connection#%d[%s:%d] protocol error[%d]", fd, - info.get_ip(), + info.get_addr(), info.get_port(), reason); } -ssize_t Socket::ssl_recv(void *__buf, size_t __n) { +ssize_t Socket::ssl_recv(void *_buf, size_t _n) { ssl_clear_error(); - int n = SSL_read(ssl, __buf, __n); + int n = SSL_read(ssl, _buf, _n); if (n < 0) { int _errno = SSL_get_error(ssl, n); switch (_errno) { @@ -1338,8 +1657,7 @@ ssize_t Socket::ssl_recv(void *__buf, size_t __n) { return SW_ERR; case SSL_ERROR_SYSCALL: - errno = SW_ERROR_SSL_RESET; - return SW_ERR; + return errno == 0 ? 0 : SW_ERR; case SSL_ERROR_SSL: ssl_catch_error(); @@ -1353,16 +1671,16 @@ ssize_t Socket::ssl_recv(void *__buf, size_t __n) { return n; } -ssize_t Socket::ssl_send(const void *__buf, size_t __n) { +ssize_t Socket::ssl_send(const void *_buf, size_t _n) { ssl_clear_error(); #ifdef SW_SUPPORT_DTLS - if (dtls && chunk_size && __n > chunk_size) { - __n = chunk_size; + if (dtls && chunk_size && _n > chunk_size) { + _n = chunk_size; } #endif - int n = SSL_write(ssl, __buf, __n); + int n = SSL_write(ssl, _buf, _n); if (n < 0) { int _errno = SSL_get_error(ssl, n); switch (_errno) { @@ -1425,8 +1743,8 @@ int Socket::ssl_create(SSLContext *ssl_context, int _flags) { return SW_ERR; } if (!SSL_set_fd(ssl, fd)) { - long err = ERR_get_error(); - swoole_warning("SSL_set_fd() failed. Error: %s[%ld]", ERR_reason_error_string(err), err); + ulong_t err = ERR_peek_error(); + swoole_warning("SSL_set_fd() failed. Error: %s[%lu]", ERR_reason_error_string(err), err); return SW_ERR; } if (_flags & SW_SSL_CLIENT) { @@ -1446,9 +1764,6 @@ int Socket::ssl_create(SSLContext *ssl_context, int _flags) { ssl_state = 0; return SW_OK; } - -#endif - } // namespace network using network::Socket; @@ -1460,9 +1775,28 @@ Socket *make_socket(SocketType type, FdType fd_type, int flags) { if (Socket::get_domain_and_type(type, &sock_domain, &sock_type) < 0) { swoole_warning("unknown socket type [%d]", type); errno = ESOCKTNOSUPPORT; + swoole_set_last_error(errno); + return nullptr; + } + + return make_socket(type, fd_type, sock_domain, sock_type, 0, flags); +} + +Socket *make_socket(SocketType type, FdType fd_type, int sock_domain, int sock_type, int socket_protocol, int flags) { + int sockfd = swoole::socket(sock_domain, sock_type, socket_protocol, flags); + if (sockfd < 0) { + swoole_set_last_error(errno); return nullptr; } + auto _socket = make_socket(sockfd, fd_type); + _socket->nonblock = !!(flags & SW_SOCK_NONBLOCK); + _socket->cloexec = !!(flags & SW_SOCK_CLOEXEC); + _socket->socket_type = type; + return _socket; +} + +int socket(int sock_domain, int sock_type, int socket_protocol, int flags) { bool nonblock = flags & SW_SOCK_NONBLOCK; bool cloexec = flags & SW_SOCK_CLOEXEC; @@ -1474,27 +1808,23 @@ Socket *make_socket(SocketType type, FdType fd_type, int flags) { if (cloexec) { sock_flags |= SOCK_CLOEXEC; } - int sockfd = socket(sock_domain, sock_type | sock_flags, 0); + int sockfd = ::socket(sock_domain, sock_type | sock_flags, socket_protocol); if (sockfd < 0) { - return nullptr; + return sockfd; } #else - int sockfd = socket(sock_domain, sock_type, 0); + int sockfd = ::socket(sock_domain, sock_type, socket_protocol); if (sockfd < 0) { - return nullptr; + return sockfd; } if (nonblock || cloexec) { if (!network::_fcntl_set_option(sockfd, nonblock ? 1 : -1, cloexec ? 1 : -1)) { close(sockfd); - return nullptr; + return sockfd; } } #endif - auto _socket = swoole::make_socket(sockfd, fd_type); - _socket->nonblock = nonblock; - _socket->cloexec = cloexec; - _socket->socket_type = type; - return _socket; + return sockfd; } Socket *make_server_socket(SocketType type, const char *address, int port, int backlog) { @@ -1503,12 +1833,17 @@ Socket *make_server_socket(SocketType type, const char *address, int port, int b swoole_sys_warning("socket() failed"); return nullptr; } - if (sock->bind(address, &port) < 0) { - sock->free(); - return nullptr; + if (sock->bind(address, port) < 0) { + swoole_sys_warning("bind(%d, %s:%d, %d) failed", sock->get_fd(), address, port, backlog); + goto __cleanup; } if (sock->is_stream() && sock->listen(backlog) < 0) { - swoole_sys_warning("listen(%s:%d, %d) failed", address, port, backlog); + swoole_sys_warning("listen(%d, %s:%d, %d) failed", sock->get_fd(), address, port, backlog); + goto __cleanup; + } + if (sock->get_name() < 0) { + swoole_sys_warning("getsockname(%d) failed", sock->get_fd()); + __cleanup: sock->free(); return nullptr; } @@ -1516,7 +1851,7 @@ Socket *make_server_socket(SocketType type, const char *address, int port, int b } Socket *make_socket(int fd, FdType fd_type) { - Socket *socket = new Socket(); + auto *socket = new Socket(); socket->fd = fd; socket->fd_type = fd_type; socket->removed = 1; diff --git a/src/network/stream.cc b/src/network/stream.cc index 189ee317ec..717785a6e3 100644 --- a/src/network/stream.cc +++ b/src/network/stream.cc @@ -14,8 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" -#include "swoole_api.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" @@ -25,12 +23,12 @@ namespace swoole { namespace network { static void Stream_onConnect(Client *cli) { - Stream *stream = (Stream *) cli->object; + auto *stream = static_cast(cli->object); if (stream->cancel) { cli->close(); } - *((uint32_t *) stream->buffer->str) = ntohl(stream->buffer->length - 4); - if (cli->send(cli, stream->buffer->str, stream->buffer->length, 0) < 0) { + *reinterpret_cast(stream->buffer->str) = ntohl(stream->buffer->length - 4); + if (cli->send(stream->buffer->str, stream->buffer->length, 0) < 0) { cli->close(); } else { delete stream->buffer; @@ -38,8 +36,8 @@ static void Stream_onConnect(Client *cli) { } } -static void Stream_onError(Client *cli) { - Stream *stream = (Stream *) cli->object; +static void Stream_onError(const Client *cli) { + auto *stream = static_cast(cli->object); stream->errCode = swoole_get_last_error(); swoole_error_log(SW_LOG_WARNING, @@ -56,8 +54,8 @@ static void Stream_onError(Client *cli) { delete stream; } -static void Stream_onReceive(Client *cli, const char *data, uint32_t length) { - Stream *stream = (Stream *) cli->object; +static void Stream_onReceive(const Client *cli, const char *data, uint32_t length) { + auto *stream = static_cast(cli->object); if (length == 4) { cli->socket->close_wait = 1; } else { @@ -68,8 +66,8 @@ static void Stream_onReceive(Client *cli, const char *data, uint32_t length) { static void Stream_onClose(Client *cli) { swoole_event_defer( [](void *data) { - Client *cli = (Client *) data; - delete (Stream *) cli->object; + const auto *cli = static_cast(data); + delete static_cast(cli->object); }, cli); } @@ -85,22 +83,30 @@ Stream::Stream(const char *dst_host, int dst_port, SocketType type) : client(typ client.onClose = Stream_onClose; client.object = this; - client.open_length_check = 1; + client.open_length_check = true; set_protocol(&client.protocol); - if (client.connect(&client, dst_host, dst_port, -1, 0) < 0) { + if (client.connect(dst_host, dst_port, -1, 0) < 0) { swoole_sys_warning("failed to connect to [%s:%d]", dst_host, dst_port); return; } connected = true; } -Stream::~Stream() { - if (buffer) { - delete buffer; +Stream *Stream::create(const char *dst_host, int dst_port, SocketType type) { + auto *stream = new Stream(dst_host, dst_port, type); + if (!stream->connected) { + delete stream; + return nullptr; + } else { + return stream; } } +Stream::~Stream() { + delete buffer; +} + /** * Stream Protocol: Length(32bit/Network Byte Order) + Body */ @@ -123,23 +129,21 @@ int Stream::send(const char *data, size_t length) { buffer = new String(swoole_size_align(length + 4, swoole_pagesize())); buffer->length = 4; } - if (buffer->append(data, length) < 0) { - return SW_ERR; - } + buffer->append(data, length); return SW_OK; } -ssize_t Stream::recv_blocking(Socket *sock, void *__buf, size_t __len) { +ssize_t Stream::recv_sync(Socket *sock, void *_buf, size_t _len) { int tmp = 0; - ssize_t ret = sock->recv_blocking(&tmp, sizeof(tmp), MSG_WAITALL); + ssize_t ret = sock->recv_sync(&tmp, sizeof(tmp), MSG_WAITALL); if (ret <= 0) { return SW_ERR; } - int length = (int) ntohl(tmp); - if (length <= 0 || length > (int) __len) { + const int length = static_cast(ntohl(tmp)); + if (length <= 0 || length > static_cast(_len)) { return SW_ERR; } - return sock->recv_blocking(__buf, length, MSG_WAITALL); + return sock->recv_sync(_buf, length, MSG_WAITALL); } } // namespace network diff --git a/src/os/async_thread.cc b/src/os/async_thread.cc index c2faae11f7..c657160978 100644 --- a/src/os/async_thread.cc +++ b/src/os/async_thread.cc @@ -14,14 +14,12 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_socket.h" #include "swoole_reactor.h" -#include "swoole_string.h" -#include "swoole_signal.h" #include "swoole_pipe.h" #include "swoole_async.h" #include "swoole_util.h" +#include "swoole_thread.h" #include #include @@ -30,41 +28,51 @@ #include #include #include -#include +#include + +static std::mutex async_thread_lock; +static std::shared_ptr async_thread_pool; + +swoole::AsyncThreads *sw_async_threads() { + return SwooleTG.async_threads; +} namespace swoole { namespace async { //------------------------------------------------------------------------------- class EventQueue { public: - inline void push(AsyncEvent *event) { - _queue.push(event); + void push(AsyncEvent *event) { + queue_.push(event); } - inline AsyncEvent *pop() { - if (_queue.empty()) { + AsyncEvent *pop() { + if (queue_.empty()) { return nullptr; } - AsyncEvent *retval = _queue.front(); - _queue.pop(); + AsyncEvent *retval = queue_.front(); + queue_.pop(); return retval; } - inline double get_max_wait_time() { - if (_queue.empty()) { + double get_max_wait_time() const { + if (queue_.empty()) { return 0; - } else { - AsyncEvent *event = _queue.front(); - return microtime() - event->timestamp; } + const AsyncEvent *event = queue_.front(); + return microtime() - event->timestamp; + } + + size_t count() const { + return queue_.size(); } - inline size_t count() { - return _queue.size(); + bool empty() const { + return queue_.empty(); } private: - std::queue _queue; + std::queue queue_; }; class ThreadPool { @@ -82,11 +90,13 @@ class ThreadPool { shutdown(); } + bool is_running() const { + return running; + } + bool start() { running = true; current_task_id = 0; - n_waiting = 0; - n_closing = 0; for (size_t i = 0; i < core_worker_num; i++) { create_thread(true); } @@ -100,8 +110,8 @@ class ThreadPool { event_mutex.lock(); running = false; - _cv.notify_all(); event_mutex.unlock(); + _cv.notify_all(); for (auto &i : threads) { std::thread *_thread = i.second; @@ -110,16 +120,14 @@ class ThreadPool { } delete _thread; } + threads.clear(); return true; } void schedule() { if (n_waiting == 0 && threads.size() < worker_num && max_wait_time > 0) { - event_mutex.lock(); - double _max_wait_time = _queue.get_max_wait_time(); - event_mutex.unlock(); - + double _max_wait_time = queue_.get_max_wait_time(); if (_max_wait_time > max_wait_time) { size_t n = 1; /** @@ -129,10 +137,10 @@ class ThreadPool { n = worker_num - threads.size(); } swoole_trace_log(SW_TRACE_AIO, - "Create %zu thread due to wait %fs, we will have %zu threads", - n, - _max_wait_time, - threads.size() + n); + "Create %zu thread due to wait %fs, we will have %zu threads", + n, + _max_wait_time, + threads.size() + n); while (n--) { create_thread(); } @@ -141,57 +149,48 @@ class ThreadPool { } AsyncEvent *dispatch(const AsyncEvent *request) { - if (SwooleTG.async_threads->schedule) { - schedule(); - } auto _event_copy = new AsyncEvent(*request); + event_mutex.lock(); + schedule(); _event_copy->task_id = current_task_id++; _event_copy->timestamp = microtime(); _event_copy->pipe_socket = SwooleTG.async_threads->write_socket; - event_mutex.lock(); - _queue.push(_event_copy); - _cv.notify_one(); + queue_.push(_event_copy); event_mutex.unlock(); + _cv.notify_one(); swoole_debug("push and notify one: %f", microtime()); return _event_copy; } - inline size_t get_worker_num() { + size_t get_worker_num() const { return threads.size(); } - inline size_t get_queue_size() { + size_t get_queue_size() { std::unique_lock lock(event_mutex); - return _queue.count(); - } - - static std::string get_thread_id(std::thread::id id) { - std::stringstream ss; - ss << id; - return ss.str(); + return queue_.count(); } void release_thread(std::thread::id tid) { auto i = threads.find(tid); if (i == threads.end()) { - swoole_warning("AIO thread#%s is missing", get_thread_id(tid).c_str()); + swoole_warning("AIO thread#%s is missing", swoole_thread_id_to_str(tid).c_str()); return; - } else { - std::thread *_thread = i->second; - swoole_trace_log(SW_TRACE_AIO, - "release idle thread#%s, we have %zu now", - get_thread_id(tid).c_str(), - threads.size() - 1); - if (_thread->joinable()) { - _thread->join(); - } - threads.erase(i); - delete _thread; } + std::thread *_thread = i->second; + swoole_trace_log(SW_TRACE_AIO, + "release idle thread#%s, we have %zu now", + swoole_thread_id_to_str(tid).c_str(), + threads.size() - 1); + if (_thread->joinable()) { + _thread->join(); + } + threads.erase(i); + delete _thread; } static void release_callback(AsyncEvent *event) { - std::thread::id *tid = reinterpret_cast(event->object); + auto *tid = static_cast(event->object); SwooleTG.async_threads->pool->release_thread(*tid); delete tid; // balance @@ -203,7 +202,8 @@ class ThreadPool { } private: - void create_thread(const bool is_core_worker = false); + void create_thread(bool is_core_worker = false); + void main_func(bool is_core_worker); size_t core_worker_num; size_t worker_num; @@ -212,115 +212,88 @@ class ThreadPool { bool running; - std::atomic n_waiting; - std::atomic n_closing; + std::atomic n_waiting{0}; + std::atomic n_closing{0}; size_t current_task_id = 0; std::unordered_map threads; - EventQueue _queue; + EventQueue queue_; std::mutex event_mutex; std::condition_variable _cv; }; +void ThreadPool::main_func(const bool is_core_worker) { + bool exit_flag = false; + swoole_thread_init(false); + + while (running) { + bool timeout = false; + std::unique_lock lock(event_mutex); + ++n_waiting; + if (is_core_worker || max_idle_time <= 0) { + _cv.wait(lock, [this] { return !queue_.empty() || !running; }); + } else { + timeout = !_cv.wait_for(lock, + std::chrono::microseconds(static_cast(max_idle_time) * 1000 * 1000), + [this] { return !queue_.empty() || !running; }); + } + --n_waiting; + + AsyncEvent *event = queue_.pop(); + lock.unlock(); + swoole_debug("%s: %f", event ? "pop 1 event" : "no event", microtime()); + if (event) { + if (sw_unlikely(event->handler == nullptr)) { + event->error = SW_ERROR_AIO_BAD_REQUEST; + event->retval = -1; + } else if (sw_unlikely(event->canceled)) { + event->error = SW_ERROR_AIO_CANCELED; + event->retval = -1; + } else { + event->handler(event); + } + + swoole_trace_log(SW_TRACE_AIO, + "aio_thread %s. ret=%ld, error=%d", + event->retval > 0 ? "ok" : "failed", + event->retval, + event->error); + + _send_event: + if (event->pipe_socket->write_sync(&event, sizeof(event)) <= 0) { + swoole_sys_warning("sendto swoole_aio_pipe_write failed"); + delete event; + } + // exit + if (exit_flag) { + --n_closing; + break; + } + } else if (timeout) { + if (n_closing != 0) { + // wait for the next round + continue; + } + /* notifies the main thread to release this thread */ + event = new AsyncEvent; + event->object = new std::thread::id(std::this_thread::get_id()); + event->callback = release_callback; + event->pipe_socket = SwooleG.aio_default_socket; + event->canceled = false; + + ++n_closing; + exit_flag = true; + goto _send_event; + } + } + swoole_thread_clean(false); +} + void ThreadPool::create_thread(const bool is_core_worker) { try { - std::thread *_thread = new std::thread([this, is_core_worker]() { - bool exit_flag = false; - SwooleTG.buffer_stack = new String(SW_STACK_BUFFER_SIZE); - ON_SCOPE_EXIT { - delete SwooleTG.buffer_stack; - SwooleTG.buffer_stack = nullptr; - }; - - swoole_signal_block_all(); - - while (running) { - event_mutex.lock(); - AsyncEvent *event = _queue.pop(); - event_mutex.unlock(); - - swoole_debug("%s: %f", event ? "pop 1 event" : "no event", microtime()); - - if (event) { - if (sw_unlikely(event->handler == nullptr)) { - event->error = SW_ERROR_AIO_BAD_REQUEST; - event->retval = -1; - } else if (sw_unlikely(event->canceled)) { - event->error = SW_ERROR_AIO_CANCELED; - event->retval = -1; - } else { - event->handler(event); - } - - swoole_trace_log(SW_TRACE_AIO, - "aio_thread %s. ret=%ld, error=%d", - event->retval > 0 ? "ok" : "failed", - event->retval, - event->error); - - _send_event: - while (true) { - ssize_t ret = event->pipe_socket->write(&event, sizeof(event)); - if (ret < 0) { - if (errno == EAGAIN) { - event->pipe_socket->wait_event(1000, SW_EVENT_WRITE); - continue; - } else if (errno == EINTR) { - continue; - } else { - delete event; - swoole_sys_warning("sendto swoole_aio_pipe_write failed"); - } - } - break; - } - - // exit - if (exit_flag) { - n_closing--; - break; - } - } else { - std::unique_lock lock(event_mutex); - if (_queue.count() > 0) { - continue; - } - if (!running) { - break; - } - ++n_waiting; - if (is_core_worker || max_idle_time <= 0) { - _cv.wait(lock); - } else { - while (true) { - if (_cv.wait_for(lock, std::chrono::microseconds((size_t)(max_idle_time * 1000 * 1000))) == - std::cv_status::timeout) { - if (running && n_closing != 0) { - // wait for the next round - continue; - } - /* notifies the main thread to release this thread */ - event = new AsyncEvent; - event->object = new std::thread::id(std::this_thread::get_id()); - event->callback = release_callback; - event->pipe_socket = SwooleG.aio_default_socket; - event->canceled = false; - - --n_waiting; - ++n_closing; - exit_flag = true; - goto _send_event; - } - break; - } - } - --n_waiting; - } - } - }); + auto *_thread = new std::thread([this, is_core_worker]() { main_func(is_core_worker); }); threads[_thread->get_id()] = _thread; } catch (const std::system_error &e) { swoole_sys_notice("create aio thread failed, please check your system configuration or adjust aio_worker_num"); - return; } } @@ -339,10 +312,6 @@ AsyncEvent *dispatch(const AsyncEvent *request) { } // namespace async int AsyncThreads::callback(Reactor *reactor, Event *event) { - if (SwooleTG.async_threads->schedule) { - SwooleTG.async_threads->pool->schedule(); - } - AsyncEvent *events[SW_AIO_EVENT_NUM]; ssize_t n = event->socket->read(events, sizeof(AsyncEvent *) * SW_AIO_EVENT_NUM); if (n < 0) { @@ -350,26 +319,26 @@ int AsyncThreads::callback(Reactor *reactor, Event *event) { return SW_ERR; } for (size_t i = 0; i < n / sizeof(AsyncEvent *); i++) { - AsyncEvent *event = events[i]; - if (!event->canceled) { - event->callback(event); + AsyncEvent *_event = events[i]; + if (!_event->canceled) { + _event->callback(_event); } SwooleTG.async_threads->task_num--; - delete event; + delete _event; } return SW_OK; } -size_t AsyncThreads::get_worker_num() { +size_t AsyncThreads::get_worker_num() const { return pool ? pool->get_worker_num() : 0; } -size_t AsyncThreads::get_queue_size() { +size_t AsyncThreads::get_queue_size() const { return pool ? pool->get_queue_size() : 0; } -void AsyncThreads::notify_one() { +void AsyncThreads::notify_one() const { if (pool) { pool->notify_one(); } @@ -378,7 +347,7 @@ void AsyncThreads::notify_one() { AsyncThreads::AsyncThreads() { if (!SwooleTG.reactor) { swoole_warning("no event loop, cannot initialized"); - throw swoole::Exception(SW_ERROR_WRONG_OPERATION); + throw Exception(SW_ERROR_WRONG_OPERATION); } pipe = new Pipe(false); @@ -386,6 +355,7 @@ AsyncThreads::AsyncThreads() { delete pipe; pipe = nullptr; swoole_throw_error(SW_ERROR_SYSTEM_CALL_FAIL); + return; } read_socket = pipe->get_socket(false); @@ -411,20 +381,32 @@ AsyncThreads::AsyncThreads() { return true; }); - init_lock.lock(); - pool = new async::ThreadPool( - SwooleG.aio_core_worker_num, SwooleG.aio_worker_num, SwooleG.aio_max_wait_time, SwooleG.aio_max_idle_time); - pool->start(); - schedule = true; - init_lock.unlock(); + async_thread_lock.lock(); + if (!async_thread_pool) { + async_thread_pool = std::make_shared( + SwooleG.aio_core_worker_num, SwooleG.aio_worker_num, SwooleG.aio_max_wait_time, SwooleG.aio_max_idle_time); + } + if (!async_thread_pool->is_running()) { + async_thread_pool->start(); + } + pool = async_thread_pool; + async_thread_lock.unlock(); SwooleG.aio_default_socket = write_socket; SwooleTG.async_threads = this; } AsyncThreads::~AsyncThreads() { - delete pool; - pool = nullptr; + pool.reset(); + async_thread_lock.lock(); + /** + * When the reference count is 1, it means that all reactor threads have ended + * and all aio threads can be terminated. + */ + if (async_thread_pool.use_count() == 1) { + async_thread_pool->shutdown(); + } + async_thread_lock.unlock(); pipe->close(); read_socket = nullptr; write_socket = nullptr; diff --git a/src/os/base.cc b/src/os/base.cc index 69689c8886..d9907a05e3 100644 --- a/src/os/base.cc +++ b/src/os/base.cc @@ -16,11 +16,32 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_socket.h" #include "swoole_async.h" +#include "swoole_signal.h" +#include "swoole_api.h" -#include +#include +#include + +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#if defined(__APPLE__) && defined(HAVE_CCRANDOMGENERATEBYTES) +#include +#if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000) || \ + (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) +#define OPENSSL_APPLE_CRYPTO_RANDOM 1 +#include +#include +#endif +#endif + +#include +#include #if __APPLE__ int swoole_daemon(int nochdir, int noclose) { @@ -66,10 +87,148 @@ int swoole_daemon(int nochdir, int noclose) { if (swoole_fork(SW_FORK_PRECHECK) < 0) { return -1; } - return daemon(nochdir, noclose); + auto rv = daemon(nochdir, noclose); + if (rv == 0) { + /** + * The daemon function forks the process multiple times, and the pid changes, + * which can lead to PHP assertion failures. + * After PHP 8.5, it is required that the process must call `refresh_memory_manager()` after forking, + * but this does not seem to take into account the invocation of the daemon function. + * If not modified, it will crash during shutdown, and users will think it is a bug in Swoole. + */ + if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_FORK)) { + swoole_call_hook(SW_GLOBAL_HOOK_AFTER_FORK, nullptr); + } + } + return rv; +} +#endif + +#ifdef HAVE_GETRANDOM +#include +#else +static ssize_t getrandom(void *buffer, size_t size, unsigned int __flags) { +#if defined(HAVE_CCRANDOMGENERATEBYTES) + /* + * arc4random_buf on macOS uses ccrng_generate internally from which + * the potential error is silented to respect the portable arc4random_buf interface contract + */ + if (CCRandomGenerateBytes(buffer, size) == kCCSuccess) { + return size; + } + return -1; +#elif defined(HAVE_ARC4RANDOM) + arc4random_buf(buffer, size); + return size; +#else + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return -1; + } + + size_t read_bytes; + ssize_t n; + for (read_bytes = 0; read_bytes < size; read_bytes += (size_t) n) { + n = read(fd, (char *) buffer + read_bytes, size - read_bytes); + if (n <= 0) { + break; + } + } + + close(fd); + + return read_bytes; +#endif +} +#endif + +#ifdef __ANDROID__ +static ssize_t getrandom(char *buf, size_t buflen, uint flags) { + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return -1; + } + ssize_t n = read(fd, buf, buflen); + close(fd); + return n; +} + +int pthread_getname_np(pthread_t thread, char *buf, size_t len) { + sw_snprintf(buf, len, "thread-%lu", (unsigned long) thread); + return 0; } #endif +size_t swoole_random_bytes(char *buf, size_t size) { + size_t read_bytes = 0; + + while (read_bytes < size) { + size_t amount_to_read = size - read_bytes; + ssize_t n = getrandom(buf + read_bytes, amount_to_read, 0); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } else { + break; + } + } + read_bytes += (size_t) n; + } + + return read_bytes; +} + +bool swoole_is_root_user() { + return geteuid() == 0; +} + +void swoole_set_isolation(const std::string &group_, const std::string &user_, const std::string &chroot_) { + group *_group = nullptr; + passwd *_passwd = nullptr; + // get group info + if (!group_.empty()) { + _group = getgrnam(group_.c_str()); + if (!_group) { + swoole_warning("get group [%s] info failed", group_.c_str()); + } + } + // get user info + if (!user_.empty()) { + _passwd = getpwnam(user_.c_str()); + if (!_passwd) { + swoole_warning("get user [%s] info failed", user_.c_str()); + } + } + // set process group + if (_group && setgid(_group->gr_gid) < 0) { + swoole_sys_warning("setgid to [%s] failed", group_.c_str()); + } + // set process user + if (_passwd && setuid(_passwd->pw_uid) < 0) { + swoole_sys_warning("setuid to [%s] failed", user_.c_str()); + } + // chroot + if (!chroot_.empty()) { + if (::chroot(chroot_.c_str()) == 0) { + if (chdir("/") < 0) { + swoole_sys_warning("chdir('/') failed"); + } + } else { + swoole_sys_warning("chroot('%s') failed", chroot_.c_str()); + } + } +} + +void swoole_set_process_death_signal(int signal) { +#if defined(__linux__) + prctl(PR_SET_PDEATHSIG, signal); +#elif defined(__FreeBSD__) + procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signal); +#else +#warning "no `PDEATHSIG` supports" +#endif +} + #ifdef HAVE_CPU_AFFINITY int swoole_set_cpu_affinity(cpu_set_t *set) { #ifdef __FreeBSD__ @@ -78,35 +237,105 @@ int swoole_set_cpu_affinity(cpu_set_t *set) { return sched_setaffinity(getpid(), sizeof(*set), set); #endif } + +int swoole_get_cpu_affinity(cpu_set_t *set) { +#ifdef __FreeBSD__ + return cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(*set), set); +#else + return sched_getaffinity(getpid(), sizeof(*set), set); +#endif +} #endif +#if defined(__linux__) +#include /* syscall(SYS_gettid) */ +#elif defined(__FreeBSD__) +#include /* pthread_getthreadid_np() */ +#elif defined(__OpenBSD__) +#include /* getthrid() */ +#elif defined(_AIX) +#include /* thread_self() */ +#elif defined(__NetBSD__) +#include /* _lwp_self() */ +#elif defined(__CYGWIN__) || defined(WIN32) +#include /* GetCurrentThreadId() */ +#endif + +long swoole_thread_get_native_id() { +#ifdef __APPLE__ + uint64_t native_id; + (void) pthread_threadid_np(NULL, &native_id); +#elif defined(__linux__) + pid_t native_id = syscall(SYS_gettid); +#elif defined(__FreeBSD__) + int native_id = pthread_getthreadid_np(); +#elif defined(__OpenBSD__) + pid_t native_id = getthrid(); +#elif defined(_AIX) + tid_t native_id = thread_self(); +#elif defined(__NetBSD__) + lwpid_t native_id = _lwp_self(); +#elif defined(__CYGWIN__) || defined(WIN32) + DWORD native_id = GetCurrentThreadId(); +#endif + return native_id; +} + +static bool check_pthread_return_value(int rc) { + if (rc == 0) { + return true; + } else { + swoole_set_last_error(rc); + return false; + } +} + +bool swoole_thread_set_name(const char *name) { +#if defined(__APPLE__) + return check_pthread_return_value(pthread_setname_np(name)); +#else + return check_pthread_return_value(pthread_setname_np(pthread_self(), name)); +#endif +} + +bool swoole_thread_get_name(char *buf, size_t len) { + return check_pthread_return_value(pthread_getname_np(pthread_self(), buf, len)); +} + +std::string swoole_thread_id_to_str(std::thread::id id) { + std::stringstream ss; + ss << id; + return ss.str(); +} + namespace swoole { -namespace async { +GethostbynameRequest::GethostbynameRequest(std::string _name, int _family) : name(std::move(_name)), family(_family) {} -void handler_gethostbyname(AsyncEvent *event) { - char addr[SW_IP_MAX_LENGTH]; - int ret = network::gethostbyname(event->flags, (char *) event->buf, addr); - sw_memset_zero(event->buf, event->nbytes); +GetaddrinfoRequest::GetaddrinfoRequest( + std::string _hostname, int _family, int _socktype, int _protocol, std::string _service) + : hostname(std::move(_hostname)), service(std::move(_service)) { + family = _family; + socktype = _socktype; + protocol = _protocol; + count = 0; + error = 0; +} - if (ret < 0) { - event->error = SW_ERROR_DNSLOOKUP_RESOLVE_FAILED; +namespace async { +void handler_gethostbyname(AsyncEvent *event) { + auto req = dynamic_cast(event->data.get()); + event->retval = network::gethostbyname(req); + if (event->retval < 0) { + event->error = swoole_get_last_error(); } else { - if (inet_ntop(event->flags, addr, (char *) event->buf, event->nbytes) == nullptr) { - ret = -1; - event->error = SW_ERROR_BAD_IPV6_ADDRESS; - } else { - event->error = 0; - ret = 0; - } + event->error = 0; } - event->retval = ret; } void handler_getaddrinfo(AsyncEvent *event) { - network::GetaddrinfoRequest *req = (network::GetaddrinfoRequest *) event->req; + auto req = dynamic_cast(event->data.get()); event->retval = network::getaddrinfo(req); event->error = req->error; } - } // namespace async } // namespace swoole diff --git a/src/os/file.cc b/src/os/file.cc index 59d7fe2588..f01434462b 100644 --- a/src/os/file.cc +++ b/src/os/file.cc @@ -24,7 +24,7 @@ int swoole_tmpfile(char *filename) { #endif if (tmp_fd < 0) { - swoole_sys_warning("mkstemp(%s) failed", filename); + swoole_sys_warning("mkstemp('%s') failed", filename); return SW_ERR; } else { return tmp_fd; @@ -63,7 +63,7 @@ ssize_t file_get_size(int fd) { std::shared_ptr file_get_contents(const std::string &filename) { File fp(filename, O_RDONLY); if (!fp.ready()) { - swoole_sys_warning("open(%s) failed", filename.c_str()); + swoole_sys_warning("open('%s') failed", filename.c_str()); return nullptr; } @@ -73,7 +73,7 @@ std::shared_ptr file_get_contents(const std::string &filename) { } else if (filesize == 0) { swoole_error_log(SW_LOG_TRACE, SW_ERROR_FILE_EMPTY, "file[%s] is empty", filename.c_str()); return nullptr; - } else if (filesize > SW_MAX_FILE_CONTENT) { + } else if (filesize > SwooleG.max_file_content) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_TOO_LARGE, "file[%s] is too large", filename.c_str()); return nullptr; } @@ -92,22 +92,22 @@ File make_tmpfile() { if (tmp_fd < 0) { return File(-1); } else { - return File(tmp_fd, std::string(tmpfile, l)); + return {tmp_fd, std::string(tmpfile, l)}; } } bool file_put_contents(const std::string &filename, const char *content, size_t length) { - if (length <= 0) { - swoole_error_log(SW_LOG_TRACE, SW_ERROR_FILE_EMPTY, "content is empty"); + if (length == 0) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_EMPTY, "content is empty"); return false; } - if (length > SW_MAX_FILE_CONTENT) { + if (length > SwooleG.max_file_content) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_FILE_TOO_LARGE, "content is too large"); return false; } File file(filename, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (!file.ready()) { - swoole_sys_warning("open(%s) failed", filename.c_str()); + swoole_sys_warning("open('%s') failed", filename.c_str()); return false; } return file.write_all(content, length); @@ -117,7 +117,64 @@ bool file_exists(const std::string &filename) { return access(filename.c_str(), F_OK) == 0; } -size_t File::write_all(const void *data, size_t len) { +File::File(const std::string &path, int oflags) { + fd_ = -1; + open(path, oflags); +} + +File::File(const std::string &path, int oflags, int mode) { + fd_ = -1; + open(path, oflags, mode); +} + +bool File::open(const std::string &path, int oflags, int mode) { + if (fd_ != -1) { + ::close(fd_); + } + if (oflags & CREATE) { + fd_ = ::open(path.c_str(), oflags, mode == 0 ? 0644 : mode); + } else { + fd_ = ::open(path.c_str(), oflags); + } + path_ = path; + flags_ = oflags; + return ready(); +} + +bool File::close() { + if (fd_ == -1) { + return false; + } + int tmp_fd = fd_; + fd_ = -1; + return ::close(tmp_fd) == 0; +} + +bool File::stat(FileStatus *_stat) const { + if (::fstat(fd_, _stat) < 0) { + swoole_sys_warning("fstat() failed"); + return false; + } + return true; +} + +File::~File() { + if (fd_ >= 0) { + ::close(fd_); + } +} + +static swReturnCode catch_fs_error(const ssize_t rv, const int error) { + if (rv == 0) { + return SW_CLOSE; + } + if (error == EINTR || error == EAGAIN || error == EWOULDBLOCK) { + return SW_CONTINUE; + } + return SW_ERROR; +} + +size_t File::write_all(const void *data, size_t len) const { size_t written_bytes = 0; while (written_bytes < len) { ssize_t n; @@ -128,13 +185,12 @@ size_t File::write_all(const void *data, size_t len) { } if (n > 0) { written_bytes += n; - } else if (n == 0) { - break; } else { - if (errno == EINTR) { - continue; - } else if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { + const auto rc = catch_fs_error(n, errno); + if (rc == SW_ERROR) { swoole_sys_warning("pwrite(%d, %p, %lu, %lu) failed", fd_, data, len - written_bytes, written_bytes); + } else if (rc == SW_CONTINUE) { + continue; } break; } @@ -142,19 +198,18 @@ size_t File::write_all(const void *data, size_t len) { return written_bytes; } -size_t File::read_all(void *buf, size_t len) { +size_t File::read_all(void *buf, size_t len) const { size_t read_bytes = 0; while (read_bytes < len) { ssize_t n = pread((char *) buf + read_bytes, len - read_bytes, read_bytes); if (n > 0) { read_bytes += n; - } else if (n == 0) { - break; } else { - if (errno == EINTR) { - continue; - } else if (!(errno == EAGAIN || errno == EWOULDBLOCK)) { + const auto rc = catch_fs_error(n, errno); + if (rc == SW_ERROR) { swoole_sys_warning("pread(%d, %p, %lu, %lu) failed", fd_, buf, len - read_bytes, read_bytes); + } else if (rc == SW_CONTINUE) { + continue; } break; } @@ -162,18 +217,34 @@ size_t File::read_all(void *buf, size_t len) { return read_bytes; } -std::shared_ptr File::read_content() { +ssize_t File::read_line(void *_buf, size_t _n) const { + char *buf = (char *) _buf; + auto offset = get_offset(); + ssize_t read_bytes = read(buf, _n - 1); + if (read_bytes <= 0) { + return read_bytes; + } + for (ssize_t i = 0; i < read_bytes; ++i) { + if (buf[i] == '\0' || buf[i] == '\n') { + buf[i + 1] = '\0'; + set_offset(offset + i + 1); + return i + 1; + } + } + buf[read_bytes] = '\0'; + set_offset(offset + read_bytes + 1); + return read_bytes; +} + +std::shared_ptr File::read_content() const { ssize_t n = 0; - std::shared_ptr data = std::make_shared(SW_BUFFER_SIZE_STD); - while (1) { + auto data = std::make_shared(SW_BUFFER_SIZE_STD); + while (true) { n = read(data->str + data->length, data->size - data->length); if (n <= 0) { - return data; - } else { - if (!data->grow((size_t) n)) { - return data; - } + break; } + data->grow((size_t) n); } return data; } diff --git a/src/os/msg_queue.cc b/src/os/msg_queue.cc index f40e2c1a15..2ed0f01134 100644 --- a/src/os/msg_queue.cc +++ b/src/os/msg_queue.cc @@ -14,16 +14,15 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_msg_queue.h" +namespace swoole { +#ifdef HAVE_MSGQUEUE #include #include -namespace swoole { - bool MsgQueue::destroy() { - if (msgctl(msg_id_, IPC_RMID, 0) < 0) { + if (msgctl(msg_id_, IPC_RMID, nullptr) < 0) { swoole_sys_warning("msgctl(%d, IPC_RMID) failed", msg_id_); return false; } @@ -31,7 +30,7 @@ bool MsgQueue::destroy() { return true; } -void MsgQueue::set_blocking(bool blocking) { +void MsgQueue::set_blocking(const bool blocking) { if (blocking == 0) { flags_ = flags_ | IPC_NOWAIT; } else { @@ -62,7 +61,7 @@ MsgQueue::~MsgQueue() { } } -ssize_t MsgQueue::pop(QueueNode *data, size_t mdata_size) { +ssize_t MsgQueue::pop(QueueNode *data, size_t mdata_size) const { ssize_t ret = msgrcv(msg_id_, data, mdata_size, data->mtype, flags_); if (ret < 0) { swoole_set_last_error(errno); @@ -73,8 +72,8 @@ ssize_t MsgQueue::pop(QueueNode *data, size_t mdata_size) { return ret; } -bool MsgQueue::push(QueueNode *in, size_t mdata_length) { - while (1) { +bool MsgQueue::push(const QueueNode *in, size_t mdata_length) const { + while (true) { if (msgsnd(msg_id_, in, mdata_length, flags_) == 0) { return true; } @@ -86,36 +85,64 @@ bool MsgQueue::push(QueueNode *in, size_t mdata_length) { } swoole_set_last_error(errno); break; - } return false; } -bool MsgQueue::stat(size_t *queue_num, size_t *queue_bytes) { - struct msqid_ds __stat; - if (msgctl(msg_id_, IPC_STAT, &__stat) == 0) { - *queue_num = __stat.msg_qnum; +bool MsgQueue::stat(size_t *queue_num, size_t *queue_bytes) const { + msqid_ds _stat; + if (msgctl(msg_id_, IPC_STAT, &_stat) == 0) { + *queue_num = _stat.msg_qnum; #ifndef __NetBSD__ - *queue_bytes = __stat.msg_cbytes; + *queue_bytes = _stat.msg_cbytes; #else *queue_bytes = __stat._msg_cbytes; #endif return true; - } else { - return false; } + return false; } -bool MsgQueue::set_capacity(size_t queue_bytes) { - struct msqid_ds __stat; - if (msgctl(msg_id_, IPC_STAT, &__stat) != 0) { +bool MsgQueue::set_capacity(size_t queue_bytes) const { + msqid_ds _stat; + if (msgctl(msg_id_, IPC_STAT, &_stat) != 0) { return false; } - __stat.msg_qbytes = queue_bytes; - if (msgctl(msg_id_, IPC_SET, &__stat)) { + _stat.msg_qbytes = queue_bytes; + if (msgctl(msg_id_, IPC_SET, &_stat)) { swoole_sys_warning("msgctl(msqid=%d, IPC_SET, msg_qbytes=%lu) failed", msg_id_, queue_bytes); return false; } return true; } +#else +MsgQueue::MsgQueue(key_t msg_key, bool blocking, int perms) { + swoole_error("current platform does not support `sysvmsg`"); +} + +void MsgQueue::set_blocking(bool blocking) {} + +bool MsgQueue::set_capacity(size_t queue_bytes) const { + return false; +} + +bool MsgQueue::push(const QueueNode *in, size_t mdata_length) const { + return false; +} + +ssize_t MsgQueue::pop(QueueNode *out, size_t mdata_size) const { + return -1; +} + +bool MsgQueue::stat(size_t *queue_num, size_t *queue_bytes) const { + return false; +} + +bool MsgQueue::destroy() { + return false; +} + +MsgQueue::~MsgQueue() { +} +#endif } // namespace swoole diff --git a/src/os/pipe.cc b/src/os/pipe.cc index 4a7dbf5349..59e6cd16b6 100644 --- a/src/os/pipe.cc +++ b/src/os/pipe.cc @@ -20,22 +20,10 @@ namespace swoole { using network::Socket; -bool SocketPair::init_socket(int master_fd, int worker_fd) { +void SocketPair::init_socket(int master_fd, int worker_fd) { master_socket = make_socket(master_fd, SW_FD_PIPE); - if (master_socket == nullptr) { - _error: - ::close(master_fd); - ::close(worker_fd); - return false; - } worker_socket = make_socket(worker_fd, SW_FD_PIPE); - if (worker_socket == nullptr) { - master_socket->free(); - ::close(worker_fd); - goto _error; - } set_blocking(blocking); - return true; } Pipe::Pipe(bool _blocking) : SocketPair(_blocking) { @@ -43,29 +31,45 @@ Pipe::Pipe(bool _blocking) : SocketPair(_blocking) { swoole_sys_warning("pipe() failed"); return; } - if (!init_socket(socks[1], socks[0])) { - return; + // socks[0]: (read end) + // socks[1]: (write end) + init_socket(socks[1], socks[0]); +} + +void SocketPair::set_blocking(bool _blocking) { + if (_blocking) { + worker_socket->set_block(); + master_socket->set_block(); + } else { + worker_socket->set_nonblock(); + master_socket->set_nonblock(); } + blocking = _blocking; } -ssize_t SocketPair::read(void *data, size_t length) { - if (blocking && timeout > 0) { - if (worker_socket->wait_event(timeout * 1000, SW_EVENT_READ) < 0) { - return SW_ERR; - } +ssize_t SocketPair::read(void *data, size_t length) const { + if (blocking) { + return worker_socket->read_sync(data, length); + } else { + return worker_socket->read(data, length); + } +} + +ssize_t SocketPair::write(const void *data, size_t length) const { + if (blocking) { + return master_socket->write_sync(data, length); + } else { + return master_socket->write(data, length); } - return worker_socket->read(data, length); } -ssize_t SocketPair::write(const void *data, size_t length) { - ssize_t n = master_socket->write(data, length); - if (blocking && n < 0 && timeout > 0 && master_socket->catch_write_error(errno) == SW_WAIT) { - if (master_socket->wait_event(timeout * 1000, SW_EVENT_READ) < 0) { - return SW_ERR; +void SocketPair::clean() const { + char buf[1024]; + while (worker_socket->wait_event(0, SW_EVENT_READ) == SW_OK) { + if (worker_socket->read(buf, sizeof(buf)) <= 0) { + break; } - n = master_socket->write(data, length); } - return n; } bool SocketPair::close(int which) { diff --git a/src/os/process_pool.cc b/src/os/process_pool.cc index e8d263833f..3c55f9cc02 100644 --- a/src/os/process_pool.cc +++ b/src/os/process_pool.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_memory.h" #include "swoole_socket.h" #include "swoole_string.h" @@ -25,60 +24,50 @@ #include "swoole_process_pool.h" #include "swoole_client.h" +SW_THREAD_LOCAL swoole::WorkerGlobal SwooleWG = {}; + namespace swoole { using network::Socket; using network::Stream; -/** - * call onTask - */ -static int ProcessPool_worker_loop(ProcessPool *pool, Worker *worker); -/** - * call onMessage - */ -static int ProcessPool_worker_loop_ex(ProcessPool *pool, Worker *worker); - -void ProcessPool::kill_timeout_worker(Timer *timer, TimerNode *tnode) { - uint32_t i; - pid_t reload_worker_pid = 0; - ProcessPool *pool = (ProcessPool *) tnode->data; - pool->reloading = false; - - for (i = 0; i < pool->worker_num; i++) { - if (i >= pool->reload_worker_i) { - reload_worker_pid = pool->reload_workers[i].pid; - if (swoole_kill(reload_worker_pid, 0) == -1) { - continue; - } - if (swoole_kill(reload_worker_pid, SIGKILL) < 0) { - swoole_sys_warning( - "failed to force kill worker process(pid=%d, id=%d)", pool->reload_workers[i].pid, i); - } else { - swoole_warning("force kill worker process(pid=%d, id=%d)", pool->reload_workers[i].pid, i); - } - } +static inline swReturnCode catch_system_error(int error) { + switch (error) { + case SW_SUCCESS: + case EAGAIN: + case EINTR: + return SW_CONTINUE; + default: + return SW_ERROR; } - errno = 0; - pool->reload_worker_i = 0; - pool->reload_init = false; +} + +static inline void worker_end_callback() { + swoole_timer_select(); + swoole_signal_dispatch(); } /** * Process manager */ int ProcessPool::create(uint32_t _worker_num, key_t _msgqueue_key, swIPCMode _ipc_mode) { +#ifndef HAVE_MSGQUEUE + if (_ipc_mode == SW_IPC_MSGQUEUE) { + swoole_warning("current platform does not support `sysvmsg`"); + return SW_ERR; + } +#endif worker_num = _worker_num; /** * Shared memory is used here */ - workers = (Worker *) sw_mem_pool()->alloc(_worker_num * sizeof(Worker)); + workers = static_cast(sw_mem_pool()->alloc(_worker_num * sizeof(Worker))); if (workers == nullptr) { swoole_sys_warning("malloc[1] failed"); return SW_ERR; } - if (create_message_box(65536) < 0) { + if (create_message_box(SW_MESSAGE_BOX_SIZE) < 0) { return SW_ERR; } @@ -114,11 +103,11 @@ int ProcessPool::create(uint32_t _worker_num, key_t _msgqueue_key, swIPCMode _ip } map_ = new std::unordered_map; - ipc_mode = _ipc_mode; - if (_ipc_mode > SW_IPC_NONE) { - main_loop = ProcessPool_worker_loop; - } + main_loop = run_with_task_protocol; + protocol_type_ = SW_PROTOCOL_TASK; + max_packet_size_ = SW_INPUT_BUFFER_SIZE; + max_wait_time = SW_WORKER_MAX_WAIT_TIME; SW_LOOP_N(_worker_num) { workers[i].pool = this; @@ -135,9 +124,47 @@ int ProcessPool::create_message_box(size_t memory_size) { return SW_OK; } -int ProcessPool::listen(const char *socket_file, int blacklog) { +int ProcessPool::create_message_bus() { + if (ipc_mode != SW_IPC_UNIXSOCK) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_UNIXSOCK"); + return SW_ERR; + } + if (message_bus) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "the message bus has been created"); + return SW_ERR; + } + auto *msg_id = static_cast(sw_mem_pool()->alloc(sizeof(sw_atomic_long_t))); + if (msg_id == nullptr) { + swoole_sys_warning("malloc[1] failed"); + return SW_ERR; + } + *msg_id = 1; + message_bus = new MessageBus(); + message_bus->set_id_generator([msg_id]() { return sw_atomic_fetch_add(msg_id, 1); }); + size_t ipc_max_size; +#ifndef __linux__ + ipc_max_size = SW_IPC_MAX_SIZE; +#else + int bufsize; + /** + * Get the maximum ipc[unix socket with dgram] transmission length + */ + if (workers[0].pipe_master->get_option(SOL_SOCKET, SO_SNDBUF, &bufsize) != 0) { + bufsize = SW_IPC_MAX_SIZE; + } + ipc_max_size = SW_MIN(bufsize, SW_IPC_BUFFER_MAX_SIZE); +#endif + message_bus->set_buffer_size(ipc_max_size); + if (!message_bus->alloc_buffer()) { + return SW_ERR; + } + return SW_OK; +} + +int ProcessPool::listen(const char *socket_file, int backlog) const { if (ipc_mode != SW_IPC_SOCKET) { - swoole_warning("ipc_mode is not SW_IPC_SOCKET"); + swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_SOCKET"); return SW_ERR; } stream_info_->socket_file = sw_strdup(socket_file); @@ -145,16 +172,16 @@ int ProcessPool::listen(const char *socket_file, int blacklog) { return SW_ERR; } stream_info_->socket_port = 0; - stream_info_->socket = make_server_socket(SW_SOCK_UNIX_STREAM, stream_info_->socket_file, 0, blacklog); + stream_info_->socket = make_server_socket(SW_SOCK_UNIX_STREAM, stream_info_->socket_file, 0, backlog); if (!stream_info_->socket) { return SW_ERR; } return SW_OK; } -int ProcessPool::listen(const char *host, int port, int blacklog) { +int ProcessPool::listen(const char *host, int port, int backlog) const { if (ipc_mode != SW_IPC_SOCKET) { - swoole_warning("ipc_mode is not SW_IPC_SOCKET"); + swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_SOCKET"); return SW_ERR; } stream_info_->socket_file = sw_strdup(host); @@ -162,33 +189,73 @@ int ProcessPool::listen(const char *host, int port, int blacklog) { return SW_ERR; } stream_info_->socket_port = port; - stream_info_->socket = make_server_socket(SW_SOCK_TCP, host, port, blacklog); + stream_info_->socket = make_server_socket(SW_SOCK_TCP, host, port, backlog); if (!stream_info_->socket) { return SW_ERR; } return SW_OK; } -/** - * start workers - */ -int ProcessPool::start() { - if (ipc_mode == SW_IPC_SOCKET && (stream_info_ == nullptr || stream_info_->socket == 0)) { - swoole_warning("must first listen to an tcp port"); +void ProcessPool::set_protocol(ProtocolType _protocol_type) { + switch (_protocol_type) { + case SW_PROTOCOL_TASK: + main_loop = run_with_task_protocol; + break; + case SW_PROTOCOL_STREAM: + main_loop = run_with_stream_protocol; + break; + case SW_PROTOCOL_MESSAGE: + main_loop = run_with_message_protocol; + break; + default: + abort(); + break; + } + protocol_type_ = _protocol_type; +} + +int ProcessPool::start_check() { + if (ipc_mode == SW_IPC_SOCKET && (stream_info_ == nullptr || stream_info_->socket == nullptr)) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "must first listen to an tcp port"); return SW_ERR; } - uint32_t i; running = started = true; master_pid = getpid(); + swoole_set_worker_type(SW_MASTER); - for (i = 0; i < worker_num; i++) { + if (async) { + main_loop = run_async; + } + + SW_LOOP_N(worker_num) { workers[i].pool = this; workers[i].id = start_id + i; workers[i].type = type; + if (workers[i].pipe_worker) { + workers[i].pipe_worker->buffer_size = UINT_MAX; + } + if (workers[i].pipe_master) { + workers[i].pipe_master->buffer_size = UINT_MAX; + } } - for (i = 0; i < worker_num; i++) { + return SW_OK; +} + +/** + * start workers + */ +int ProcessPool::start() { + if (start_check() < 0) { + return SW_ERR; + } + + if (onStart) { + onStart(this); + } + + SW_LOOP_N(worker_num) { if (spawn(&(workers[i])) < 0) { return SW_ERR; } @@ -203,12 +270,12 @@ int ProcessPool::schedule() { return 0; } - uint32_t i, target_worker_id = 0; + uint32_t target_worker_id = 0; uint8_t found = 0; - for (i = 0; i < worker_num + 1; i++) { + for (uint32_t i = 0; i < worker_num + 1; i++) { target_worker_id = sw_atomic_fetch_add(&round_id, 1) % worker_num; - if (workers[target_worker_id].status == SW_WORKER_IDLE) { + if (workers[target_worker_id].is_idle()) { found = 1; break; } @@ -219,23 +286,37 @@ int ProcessPool::schedule() { return target_worker_id; } -int ProcessPool::response(const char *data, int length) { - if (stream_info_ == nullptr || stream_info_->last_connection == nullptr || +int ProcessPool::response(const char *data, uint32_t length) const { + if (data == nullptr || length == 0 || stream_info_ == nullptr || stream_info_->last_connection == nullptr || stream_info_->response_buffer == nullptr) { swoole_set_last_error(SW_ERROR_INVALID_PARAMS); return SW_ERR; } - return stream_info_->response_buffer->append(data, length); + stream_info_->response_buffer->append(data, length); + return SW_OK; } -int ProcessPool::push_message(EventData *msg) { - if (message_box->push(msg, sizeof(msg->info) + msg->info.len) < 0) { +bool ProcessPool::send_message(WorkerId worker_id, const char *message, size_t l_message) const { + Worker *worker = get_worker(worker_id); + if (message_bus) { + SendData _task{}; + _task.info.reactor_id = swoole_get_worker_id(); + _task.info.len = l_message; + _task.data = message; + return message_bus->write(worker->pipe_master, &_task); + } else { + return worker->pipe_master->send_async(message, l_message); + } +} + +int ProcessPool::push_message(const EventData *msg) const { + if (message_box->push(msg, msg->size()) < 0) { return SW_ERR; } return swoole_kill(master_pid, SIGIO); } -int ProcessPool::push_message(uint8_t type, const void *data, size_t length) { +int ProcessPool::push_message(uint8_t _type, const void *data, size_t length) const { if (!message_box) { return SW_ERR; } @@ -244,35 +325,29 @@ int ProcessPool::push_message(uint8_t type, const void *data, size_t length) { assert(length < sizeof(msg.data)); msg.info = {}; - msg.info.type = type; + msg.info.type = _type; msg.info.len = length; memcpy(msg.data, data, length); return push_message(&msg); } -int ProcessPool::pop_message(void *data, size_t size) { +int ProcessPool::pop_message(void *data, size_t size) const { if (!message_box) { return SW_ERR; } return message_box->pop(data, size); } -/** - * dispatch data to worker - */ -int ProcessPool::dispatch(EventData *data, int *dst_worker_id) { - int ret = 0; - Worker *worker; - +swResultCode ProcessPool::dispatch(EventData *data, int *dst_worker_id) { if (use_socket) { Stream *stream = Stream::create(stream_info_->socket_file, 0, SW_SOCK_UNIX_STREAM); if (!stream) { return SW_ERR; } stream->response = nullptr; - if (stream->send((char *) data, sizeof(data->info) + data->info.len) < 0) { - stream->cancel = 1; + if (stream->send(reinterpret_cast(data), data->size()) < 0) { + stream->cancel = true; delete stream; return SW_ERR; } @@ -284,51 +359,40 @@ int ProcessPool::dispatch(EventData *data, int *dst_worker_id) { } *dst_worker_id += start_id; - worker = get_worker(*dst_worker_id); - - int sendn = sizeof(data->info) + data->info.len; - ret = worker->send_pipe_message(data, sendn, SW_PIPE_MASTER | SW_PIPE_NONBLOCK); + Worker *worker = get_worker(*dst_worker_id); - if (ret >= 0) { - sw_atomic_fetch_add(&worker->tasking_num, 1); - } else { - swoole_warning("send %d bytes to worker#%d failed", sendn, *dst_worker_id); + if (worker->send_pipe_message(data, data->size(), SW_PIPE_MASTER | SW_PIPE_NONBLOCK) < 0) { + swoole_warning("send %d bytes to worker#%d failed", data->size(), *dst_worker_id); + return SW_ERR; } - return ret; + return SW_OK; } -int ProcessPool::dispatch_blocking(const char *data, uint32_t len) { +swResultCode ProcessPool::dispatch_sync(const char *data, uint32_t len) const { assert(use_socket); - network::Client _socket(stream_info_->socket->socket_type, false); - if (!_socket.socket) { + network::Client client(stream_info_->socket->socket_type, false); + if (!client.ready()) { return SW_ERR; } - if (_socket.connect(&_socket, stream_info_->socket_file, stream_info_->socket_port, -1, 0) < 0) { + if (client.connect(stream_info_->socket_file, stream_info_->socket_port, -1, 0) < 0) { return SW_ERR; } uint32_t packed_len = htonl(len); - if (_socket.send(&_socket, (char *) &packed_len, 4, 0) < 0) { + if (client.send((char *) &packed_len, 4, 0) < 0) { return SW_ERR; } - if (_socket.send(&_socket, (char *) data, len, 0) < 0) { + if (client.send(data, len, 0) < 0) { return SW_ERR; } - _socket.close(); + client.close(); return SW_OK; } -/** - * dispatch data to worker - * @return SW_OK/SW_ERR - */ -int ProcessPool::dispatch_blocking(EventData *data, int *dst_worker_id) { - int ret = 0; - int sendn = sizeof(data->info) + data->info.len; - +swResultCode ProcessPool::dispatch_sync(EventData *data, int *dst_worker_id) { if (use_socket) { - return dispatch_blocking((char *) data, sendn); + return dispatch_sync(reinterpret_cast(data), data->size()); } if (*dst_worker_id < 0) { @@ -338,47 +402,73 @@ int ProcessPool::dispatch_blocking(EventData *data, int *dst_worker_id) { *dst_worker_id += start_id; Worker *worker = get_worker(*dst_worker_id); - ret = worker->send_pipe_message(data, sendn, SW_PIPE_MASTER); - if (ret < 0) { - swoole_warning("send %d bytes to worker#%d failed", sendn, *dst_worker_id); - } else { - sw_atomic_fetch_add(&worker->tasking_num, 1); + if (worker->send_pipe_message(data, data->size(), SW_PIPE_MASTER) < 0) { + swoole_warning("send %d bytes to worker#%d failed", data->size(), *dst_worker_id); + return SW_ERR; } - - return ret > 0 ? SW_OK : SW_ERR; + return SW_OK; } bool ProcessPool::reload() { - if (reloading) { + if (reload_task) { return false; } - reloading = true; + if (onBeforeReload) { + onBeforeReload(this); + } + reload_task = new ReloadTask(); + if (max_wait_time) { + reload_task->add_timeout_killer(max_wait_time); + } reload_count++; - reload_last_time = ::time(NULL); + reload_init = true; + reload_last_time = ::time(nullptr); return true; } -void ProcessPool::shutdown() { - uint32_t i; - int status; - Worker *worker; - running = 0; +void ProcessPool::stop(Worker *worker) { + worker->shutdown(); - // concurrent kill - for (i = 0; i < worker_num; i++) { - worker = &workers[i]; - if (swoole_kill(worker->pid, SIGTERM) < 0) { - swoole_sys_warning("kill(%d, SIGTERM) failed", worker->pid); - continue; - } + if (!swoole_event_is_available()) { + return; } - for (i = 0; i < worker_num; i++) { - worker = &workers[i]; - if (swoole_waitpid(worker->pid, &status, 0) < 0) { - swoole_sys_warning("waitpid(%d) failed", worker->pid); - } + + auto reactor = sw_reactor(); + if (worker->pipe_worker && !worker->pipe_worker->removed) { + swoole_event_del(worker->pipe_worker); + } + + if (onWorkerExit) { + reactor->set_end_callback(Reactor::PRIORITY_TRY_EXIT, [this, worker](Reactor *reactor) { + onWorkerExit(this, worker); + if (reactor->if_exit()) { + reactor->running = false; + } + }); + } +} + +void ProcessPool::reopen_logger() { + sw_logger()->reopen(); + + if (is_master()) { + kill_all_workers(SIGWINCH); + } +} + +void ProcessPool::kill_all_workers(int signo) { + SW_LOOP_N(worker_num) { + swoole_kill(workers[i].pid, signo); + } +} + +bool ProcessPool::shutdown() { + if (is_master()) { + running = false; + return true; + } else { + return swoole_kill(master_pid, SIGTERM) == 0; } - started = false; } pid_t ProcessPool::spawn(Worker *worker) { @@ -388,23 +478,26 @@ pid_t ProcessPool::spawn(Worker *worker) { switch (pid) { // child case 0: - /** - * Process start - */ + worker->init(); + worker->pid = getpid(); + swoole_set_worker_type(SW_WORKER); + swoole_set_worker_id(worker->id); + swoole_set_worker_pid(worker->pid); + SwooleWG.worker = worker; + if (async) { + if (swoole_event_init(SW_EVENTLOOP_WAIT_EXIT) < 0) { + exit(254); + } + sw_reactor()->ptr = this; + } if (onWorkerStart != nullptr) { - onWorkerStart(this, worker->id); + onWorkerStart(this, worker); } - /** - * Process main loop - */ if (main_loop) { ret_code = main_loop(this, worker); } - /** - * Process stop - */ if (onWorkerStop != nullptr) { - onWorkerStop(this, worker->id); + onWorkerStop(this, worker); } exit(ret_code); break; @@ -425,40 +518,42 @@ pid_t ProcessPool::spawn(Worker *worker) { return pid; } -int ProcessPool::get_max_request() { - int task_n; - if (max_request < 1) { - return -1; - } else { - task_n = max_request; - if (max_request_grace > 0) { - task_n += swoole_system_random(1, max_request_grace); +void ProcessPool::set_max_request(uint32_t _max_request, uint32_t _max_request_grace) { + max_request = _max_request; + max_request_grace = _max_request_grace; +} + +bool ProcessPool::is_worker_running(Worker *worker) const { + return running && !worker->is_shutdown() && !worker->has_exceeded_max_request(); +} + +void ProcessPool::at_worker_enter(Worker *worker) const { + if (worker->pipe_worker) { + worker->pipe_worker->dont_restart = 1; + } + if (ipc_mode == SW_IPC_UNIXSOCK) { + if (swoole_timer_is_available()) { + sw_timer()->reinit(true); + } else { + swoole_timer_create(true); } } - return task_n; } -void ProcessPool::set_max_request(uint32_t _max_request, uint32_t _max_request_grace) { - max_request = _max_request; - max_request_grace = _max_request_grace; +void ProcessPool::at_worker_exit(Worker *worker) { + if (swoole_timer_is_available()) { + swoole_timer_free(); + } } -static int ProcessPool_worker_loop(ProcessPool *pool, Worker *worker) { +int ProcessPool::run_with_task_protocol(ProcessPool *pool, Worker *worker) { struct { long mtype; EventData buf; } out{}; - ssize_t n = 0, ret, worker_task_always = 0; - int task_n = pool->get_max_request(); - if (task_n <= 0) { - worker_task_always = 1; - task_n = 1; - } + ssize_t n = 0; - /** - * Use from_fd save the task_worker->id - */ out.buf.info.server_fd = worker->id; if (pool->schedule_by_sysvmsg) { @@ -467,231 +562,346 @@ static int ProcessPool_worker_loop(ProcessPool *pool, Worker *worker) { out.mtype = worker->id + 1; } - while (pool->running && !SwooleWG.shutdown && task_n > 0) { + pool->at_worker_enter(worker); + while (pool->is_worker_running(worker)) { /** * fetch task */ if (pool->use_msgqueue) { n = pool->queue->pop((QueueNode *) &out, sizeof(out.buf)); - if (n < 0 && errno != EINTR) { - swoole_sys_warning("[Worker#%d] msgrcv() failed", worker->id); + if (n < 0 && catch_system_error(errno) == SW_ERROR) { + swoole_sys_warning("[Worker#%d] msgrcv(%d) failed", worker->id, pool->queue->get_id()); break; } } else if (pool->use_socket) { Socket *conn = pool->stream_info_->socket->accept(); if (conn == nullptr) { - if (errno == EAGAIN || errno == EINTR) { - continue; - } else { - swoole_sys_warning("accept(%d) failed", pool->stream_info_->socket->get_fd()); + if (catch_system_error(errno) == SW_ERROR) { + swoole_sys_warning( + "[Worker#%d] accept(%d) failed", worker->id, pool->stream_info_->socket->get_fd()); break; + } else { + goto _end; } } - n = Stream::recv_blocking(conn, (void *) &out.buf, sizeof(out.buf)); - if (n < 0) { + n = Stream::recv_sync(conn, &out.buf, sizeof(out.buf)); + if (n <= 0) { conn->free(); - continue; + goto _end; } pool->stream_info_->last_connection = conn; } else { - n = worker->pipe_worker->read(&out.buf, sizeof(out.buf)); - if (n < 0 && errno != EINTR) { + worker->pipe_worker->set_timeout(msec2sec(swoole_timer_get_next_msec()), SW_TIMEOUT_READ); + n = worker->pipe_worker->read_sync(&out.buf, sizeof(out.buf)); + if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] read(%d) failed", worker->id, worker->pipe_worker->fd); + break; } } - /** - * timer - */ if (n < 0) { - if (errno == EINTR && SwooleG.signal_alarm && SwooleTG.timer) { - _alarm_handler: - SwooleG.signal_alarm = false; - SwooleTG.timer->select(); - } - continue; + goto _end; } - - if (n != (ssize_t)(out.buf.info.len + sizeof(out.buf.info))) { - swoole_warning("bad task packet, The received data-length[%ld] is inconsistent with the packet-length[%ld]", + if (n != (ssize_t) out.buf.size()) { + swoole_warning("[Worker#%d] bad task packet, The received data-length[%ld] is inconsistent with the " + "packet-length[%ld]", + worker->id, n, out.buf.info.len + sizeof(out.buf.info)); - continue; } - - /** - * do task - */ - worker->status = SW_WORKER_BUSY; - ret = pool->onTask(pool, &out.buf); - worker->status = SW_WORKER_IDLE; - + if (pool->onTask(pool, worker, &out.buf) < 0) { + swoole_warning("[Worker#%d] the execution of task#%ld has failed", worker->id, pool->get_task_id(&out.buf)); + } if (pool->use_socket && pool->stream_info_->last_connection) { int _end = 0; - pool->stream_info_->last_connection->send_blocking((void *) &_end, sizeof(_end)); + pool->stream_info_->last_connection->send_sync((void *) &_end, sizeof(_end)); pool->stream_info_->last_connection->free(); pool->stream_info_->last_connection = nullptr; } - /** - * timer - */ - if (SwooleG.signal_alarm) { - goto _alarm_handler; - } + _end: + worker_end_callback(); + } + pool->at_worker_exit(worker); - if (ret >= 0 && !worker_task_always) { - task_n--; - } + return SW_OK; +} + +int ProcessPool::recv_packet(Reactor *reactor, Event *event) { + auto *pool = static_cast(reactor->ptr); + ssize_t n = event->socket->read(pool->packet_buffer, pool->max_packet_size_); + if (n < 0 && errno != EINTR) { + swoole_sys_warning("failed to read(%d) pipe", event->fd); } + RecvData msg{}; + msg.info.reactor_id = -1; + msg.info.len = n; + msg.data = pool->packet_buffer; + pool->onMessage(pool, &msg); return SW_OK; } -void ProcessPool::set_protocol(int task_protocol, uint32_t max_packet_size) { - if (task_protocol) { - main_loop = ProcessPool_worker_loop; - } else { - packet_buffer = new char[max_packet_size]; - if (stream_info_) { - stream_info_->response_buffer = new String(SW_BUFFER_SIZE_STD); +int ProcessPool::recv_message(Reactor *reactor, Event *event) { + auto *pool = static_cast(reactor->ptr); + if (pool->message_bus->read(event->socket) <= 0) { + return SW_OK; + } + auto pipe_buffer = pool->message_bus->get_buffer(); + auto packet = pool->message_bus->get_packet(); + RecvData msg; + msg.info = pipe_buffer->info; + msg.info.len = packet.length; + msg.data = packet.data; + pool->onMessage(pool, &msg); + pool->message_bus->pop(); + return SW_OK; +} + +int ProcessPool::run_async(ProcessPool *pool, Worker *worker) { + if (pool->ipc_mode == SW_IPC_UNIXSOCK && pool->onMessage) { + swoole_event_add(worker->pipe_worker, SW_EVENT_READ); + if (pool->message_bus) { + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, recv_message); + } else { + pool->packet_buffer = new char[pool->max_packet_size_]; + if (pool->stream_info_) { + pool->stream_info_->response_buffer = new String(SW_BUFFER_SIZE_STD); + } + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, recv_packet); } - max_packet_size_ = max_packet_size; - main_loop = ProcessPool_worker_loop_ex; } + return swoole_event_wait(); } -static int ProcessPool_worker_loop_ex(ProcessPool *pool, Worker *worker) { +int ProcessPool::run_with_stream_protocol(ProcessPool *pool, Worker *worker) { ssize_t n; - char *data; + RecvData msg{}; + msg.info.reactor_id = -1; + + pool->packet_buffer = new char[pool->max_packet_size_]; + if (pool->stream_info_) { + pool->stream_info_->response_buffer = new String(SW_BUFFER_SIZE_STD); + } - QueueNode *outbuf = (QueueNode *) pool->packet_buffer; + auto *outbuf = reinterpret_cast(pool->packet_buffer); outbuf->mtype = 0; - while (pool->running) { + pool->at_worker_enter(worker); + while (pool->is_worker_running(worker)) { /** * fetch task */ if (pool->use_msgqueue) { n = pool->queue->pop(outbuf, SW_MSGMAX); - if (n < 0 && errno != EINTR) { - swoole_sys_warning("[Worker#%d] msgrcv() failed", worker->id); + /** + * A fatal error has occurred; the message queue is no longer available, and the loop must be exited. + */ + if (n < 0 && catch_system_error(errno) == SW_ERROR) { + swoole_sys_warning("[Worker#%d] msgrcv(%d) failed", worker->id, pool->queue->get_id()); break; } - data = outbuf->mdata; + swoole_trace_log(SW_TRACE_WORKER, "pop from MsgQ#%d %lu bytes", pool->queue->get_id(), (ulong_t) n); + msg.info.len = n - sizeof(msg.info); + msg.data = outbuf->mdata; outbuf->mtype = 0; } else if (pool->use_socket) { Socket *conn = pool->stream_info_->socket->accept(); if (conn == nullptr) { - if (errno == EAGAIN || errno == EINTR) { - continue; - } else { - swoole_sys_warning("accept(%d) failed", pool->stream_info_->socket->get_fd()); + if (catch_system_error(errno) == SW_ERROR) { + swoole_sys_warning( + "[Worker#%d] accept(%d) failed", worker->id, pool->stream_info_->socket->get_fd()); break; + } else { + goto _end; } } - int tmp = 0; - if (conn->recv_blocking(&tmp, sizeof(tmp), MSG_WAITALL) <= 0) { + uint32_t packet_len = 0; + if (conn->recv_sync(&packet_len, sizeof(packet_len), MSG_WAITALL) <= 0) { goto _close; } - n = ntohl(tmp); + n = ntohl(packet_len); + /** + * Errors occurring during client connections do not affect subsequent requests, + * they continue after closure. + */ if (n <= 0) { goto _close; } else if (n > pool->max_packet_size_) { goto _close; } - if (conn->recv_blocking(pool->packet_buffer, n, MSG_WAITALL) <= 0) { + if (conn->recv_sync(pool->packet_buffer, n, MSG_WAITALL) <= 0) { _close: conn->free(); - continue; + goto _end; } - data = pool->packet_buffer; + msg.data = pool->packet_buffer; pool->stream_info_->last_connection = conn; } else { - n = worker->pipe_worker->read(pool->packet_buffer, pool->max_packet_size_); - if (n < 0 && errno != EINTR) { + worker->pipe_worker->set_timeout(msec2sec(swoole_timer_get_next_msec()), SW_TIMEOUT_READ); + n = worker->pipe_worker->read_sync(pool->packet_buffer, pool->max_packet_size_); + if (n < 0 && catch_system_error(errno) == SW_ERROR) { swoole_sys_warning("[Worker#%d] read(%d) failed", worker->id, worker->pipe_worker->fd); + break; } - data = pool->packet_buffer; + msg.data = pool->packet_buffer; } - /** - * timer - */ if (n < 0) { - if (errno == EINTR && SwooleG.signal_alarm && SwooleTG.timer) { - _alarm_handler: - SwooleG.signal_alarm = false; - SwooleTG.timer->select(); - } - continue; + goto _end; } - pool->onMessage(pool, data, n); + msg.info.len = n; + pool->onMessage(pool, &msg); if (pool->use_socket && pool->stream_info_->last_connection) { String *resp_buf = pool->stream_info_->response_buffer; if (resp_buf && resp_buf->length > 0) { int _l = htonl(resp_buf->length); - pool->stream_info_->last_connection->send_blocking(&_l, sizeof(_l)); - pool->stream_info_->last_connection->send_blocking(resp_buf->str, resp_buf->length); + pool->stream_info_->last_connection->send_sync(&_l, sizeof(_l)); + pool->stream_info_->last_connection->send_sync(resp_buf->str, resp_buf->length); resp_buf->clear(); } pool->stream_info_->last_connection->free(); pool->stream_info_->last_connection = nullptr; } - /** - * timer - */ - if (SwooleG.signal_alarm) { - goto _alarm_handler; - } + _end: + worker_end_callback(); } + pool->at_worker_exit(worker); + return SW_OK; } -/** - * add a worker to pool - */ -int ProcessPool_add_worker(ProcessPool *pool, Worker *worker) { - pool->map_->emplace(std::make_pair(worker->pid, worker)); +int ProcessPool::run_with_message_protocol(ProcessPool *pool, Worker *worker) { + if (pool->ipc_mode != SW_IPC_UNIXSOCK) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not support, ipc_mode must be SW_IPC_UNIXSOCK"); + return SW_ERR; + } + + auto fn = [&]() -> ReturnCode { + while (true) { + if (worker->pipe_worker->wait_event(swoole_timer_get_next_msec(), SW_EVENT_READ) < 0) { + return errno == EINTR ? SW_CONTINUE : SW_ERROR; + } + auto rv = pool->message_bus->read(worker->pipe_worker); + if (rv < 0) { + return errno == EINTR ? SW_CONTINUE : SW_ERROR; + } else if (rv > 0) { + auto pipe_buffer = pool->message_bus->get_buffer(); + auto packet = pool->message_bus->get_packet(); + RecvData msg; + msg.info = pipe_buffer->info; + msg.info.len = packet.length; + msg.data = packet.data; + pool->onMessage(pool, &msg); + pool->message_bus->pop(); + return SW_READY; + } + } + }; + + if (pool->message_bus == nullptr) { + pool->create_message_bus(); + } + + pool->at_worker_enter(worker); + while (pool->is_worker_running(worker)) { + switch (fn()) { + case SW_CONTINUE: + worker_end_callback(); + break; + case SW_READY: + break; + case SW_ERROR: + default: + swoole_sys_warning("[Worker #%d]failed to read data from pipe", worker->id); + worker->shutdown(); + break; + } + } + pool->at_worker_exit(worker); + return SW_OK; } +void ProcessPool::add_worker(Worker *worker) const { + map_->emplace(worker->pid, worker); +} + +bool ProcessPool::del_worker(const Worker *worker) const { + return map_->erase(worker->pid) > 0; +} + +Worker *ProcessPool::get_worker_by_pid(pid_t pid) const { + const auto iter = map_->find(pid); + if (iter == map_->end()) { + return nullptr; + } + return iter->second; +} + +void ProcessPool::set_type(int _type) { + type = _type; + for (uint32_t i = 0; i < worker_num; i++) { + workers[i].type = type; + } +} + +void ProcessPool::set_start_id(int _start_id) { + start_id = _start_id; + for (uint32_t i = 0; i < worker_num; i++) { + workers[i].id = start_id + i; + } +} + +bool ProcessPool::wait_detached_worker(std::unordered_set &detached_workers, pid_t pid) { + auto iter = detached_workers.find(pid); + if (iter == detached_workers.end()) { + swoole_warning("received an exit signal from an unknown child process[pid=%d]", pid); + return false; + } + detached_workers.erase(iter); + return true; +} + bool ProcessPool::detach() { if (!running || !message_box) { return false; } - WorkerStopMessage msg; - msg.pid = getpid(); - msg.worker_id = SwooleG.process_id; + auto worker = SwooleWG.worker; + worker->shutdown(); + if (async) { + swoole_event_del(worker->pipe_worker); + } + WorkerStopMessage msg; + msg.pid = worker->pid; + msg.worker_id = worker->id; if (push_message(SW_WORKER_MESSAGE_STOP, &msg, sizeof(msg)) < 0) { return false; } + running = false; return true; } int ProcessPool::wait() { - pid_t new_pid, reload_worker_pid = 0; - int ret; - - reload_workers = new Worker[worker_num](); - ON_SCOPE_EXIT { - delete[] reload_workers; - reload_workers = nullptr; - }; + std::unordered_set detached_workers; while (running) { ExitStatus exit_status = wait_process(); + const auto wait_error = errno; + + swoole_signal_dispatch(); - if (SwooleG.signal_alarm && SwooleTG.timer) { - SwooleG.signal_alarm = false; - SwooleTG.timer->select(); + if (sw_timer()) { + sw_timer()->select(); } + if (read_message) { EventData msg; while (pop_message(&msg, sizeof(msg)) > 0) { @@ -713,82 +923,105 @@ int ProcessPool::wait() { swoole_sys_warning("fork worker process failed"); return SW_ERR; } + detached_workers.insert(worker_stop_msg.pid); map_->erase(worker_stop_msg.pid); } read_message = false; } + if (exit_status.get_pid() < 0) { if (!running) { break; } - if (!reloading) { - if (errno > 0 && errno != EINTR) { - swoole_sys_warning("[Manager] wait failed"); + if (!reload_task) { + if (wait_error > 0 && wait_error != EINTR) { + swoole_sys_warning("wait() failed"); } continue; - } else { - if (!reload_init) { - swoole_info("reload workers"); - reload_init = true; - memcpy(reload_workers, workers, sizeof(Worker) * worker_num); - if (max_wait_time) { - swoole_timer_add((long) (max_wait_time * 1000), false, kill_timeout_worker, this); - } - } - goto _kill_worker; } } if (running) { + if (reload_init) { + reload_init = false; + reload_task->add_workers(workers, worker_num); + goto _kill_worker; + } else if (exit_status.get_pid() < 0) { + continue; + } + Worker *exit_worker = get_worker_by_pid(exit_status.get_pid()); if (exit_worker == nullptr) { if (onWorkerNotFound) { onWorkerNotFound(this, exit_status); } else { - swoole_warning("[Manager]unknown worker[pid=%d]", exit_status.get_pid()); + wait_detached_worker(detached_workers, exit_status.get_pid()); } continue; } if (!exit_status.is_normal_exit()) { - swoole_warning("worker#%d abnormal exit, status=%d, signal=%d" - "%s", - exit_worker->id, - exit_status.get_code(), - exit_status.get_signal(), - exit_status.get_signal() == SIGSEGV ? SwooleG.bug_report_message.c_str() : ""); + exit_worker->report_error(exit_status); + if (onWorkerError) { + onWorkerError(this, exit_worker, exit_status); + } } - new_pid = spawn(exit_worker); + pid_t new_pid = spawn(exit_worker); if (new_pid < 0) { swoole_sys_warning("Fork worker process failed"); return SW_ERR; } map_->erase(exit_status.get_pid()); - if (exit_status.get_pid() == reload_worker_pid) { - reload_worker_i++; + if (reload_task) { + reload_task->remove(exit_status.get_pid()); } } - // reload worker - _kill_worker: - if (reloading) { - // reload finish - if (reload_worker_i >= worker_num) { - reloading = reload_init = false; - reload_worker_pid = reload_worker_i = 0; - continue; - } - reload_worker_pid = reload_workers[reload_worker_i].pid; - ret = swoole_kill(reload_worker_pid, SIGTERM); - if (ret < 0) { - if (errno == ECHILD) { - reload_worker_i++; - goto _kill_worker; + + if (reload_task) { + if (reload_task->is_completed()) { + delete reload_task; + reload_task = nullptr; + if (onAfterReload) { + onAfterReload(this); } - swoole_sys_warning("[Manager]swKill(%d) failed", reload_workers[reload_worker_i].pid); - continue; + } else { + _kill_worker: + reload_task->kill_one(); } } } + + uint32_t i; + int status; + Worker *worker; + running = false; + + delete reload_task; + + if (onShutdown) { + onShutdown(this); + } + + // concurrent kill + for (i = 0; i < worker_num; i++) { + worker = &workers[i]; + if (swoole_kill(worker->pid, SIGTERM) < 0) { + swoole_sys_warning("kill(%d, SIGTERM) failed", worker->pid); + continue; + } + } + if (max_wait_time) { + swoole_timer_add((long) max_wait_time * 1000, false, [this](Timer *, TimerNode *) { kill_all_workers(); }); + } + for (i = 0; i < worker_num; i++) { + worker = &workers[i]; + if (swoole_waitpid(worker->pid, &status, 0) < 0) { + swoole_sys_warning("waitpid(%d) failed", worker->pid); + } + break; + } + started = false; + return SW_OK; } @@ -806,31 +1039,222 @@ void ProcessPool::destroy() { if (stream_info_) { if (stream_info_->socket) { unlink(stream_info_->socket_file); - sw_free((void *) stream_info_->socket_file); + sw_free(stream_info_->socket_file); } if (stream_info_->socket) { stream_info_->socket->free(); stream_info_->socket = nullptr; } - if (stream_info_->response_buffer) { - delete stream_info_->response_buffer; - } + delete stream_info_->response_buffer; delete stream_info_; + stream_info_ = nullptr; } if (packet_buffer) { delete[] packet_buffer; + packet_buffer = nullptr; } if (map_) { delete map_; + map_ = nullptr; } if (message_box) { message_box->destroy(); + message_box = nullptr; + } + + if (message_bus) { + delete message_bus; + message_bus = nullptr; } sw_mem_pool()->free(workers); } +void Worker::init() { + start_time = ::time(nullptr); + request_count = 0; + set_status_to_idle(); + SwooleWG.running = true; + SwooleWG.shutdown = false; +} + +void Worker::set_max_request(uint32_t max_request, uint32_t max_request_grace) { + if (max_request > 0 && max_request_grace > 0) { + max_request += swoole_system_random(1, max_request_grace); + } + SwooleWG.max_request = max_request; +} + +bool Worker::has_exceeded_max_request() const { + return SwooleWG.max_request > 0 && request_count >= SwooleWG.max_request; +} + +void Worker::shutdown() { + status = SW_WORKER_EXIT; + SwooleWG.shutdown = true; +} + +bool Worker::is_shutdown() { + return SwooleWG.shutdown; +} + +bool Worker::is_running() { + return SwooleWG.running; +} + +ssize_t Worker::send_pipe_message(const void *buf, size_t n, int flags) const { + Socket *pipe_sock; + + if (flags & SW_PIPE_MASTER) { + pipe_sock = pipe_master; + } else { + pipe_sock = pipe_worker; + } + + // message-queue + if (pool->use_msgqueue) { + struct { + long mtype; + EventData buf; + } msg; + + msg.mtype = id + 1; + memcpy(&msg.buf, buf, n); + + swoole_trace_log(SW_TRACE_WORKER, "push to MsgQ#%d %lu bytes", pool->queue->get_id(), (ulong_t) n); + + return pool->queue->push((QueueNode *) &msg, n) ? n : -1; + } + + if ((flags & SW_PIPE_NONBLOCK) && swoole_event_is_available()) { + return swoole_event_write(pipe_sock, buf, n); + } else { + return pipe_sock->send_sync(buf, n); + } +} + +void Worker::report_error(const ExitStatus &exit_status) const { + swoole_warning("worker(pid=%d, id=%d) abnormal exit, status=%d, signal=%d" + "%s", + exit_status.get_pid(), + id, + exit_status.get_code(), + exit_status.get_signal(), + exit_status.get_signal() == SIGSEGV ? SwooleG.bug_report_message.c_str() : ""); +} + +void ReloadTask::add_workers(Worker *list, size_t n) { + SW_LOOP_N(n) { + workers[list[i].pid] = &list[i]; + kill_queue.push(list[i].pid); + } +} + +void ReloadTask::add_timeout_killer(int timeout) { + timer = swoole_timer_add(sec2msec(timeout), false, [this](Timer *, TimerNode *) { + kill_all(); + timer = nullptr; + }); +} + +bool ReloadTask::remove(pid_t pid) { + auto iter = workers.find(pid); + if (iter != workers.end()) { + workers.erase(iter); + return true; + } else { + return false; + } +} + +ReloadTask::~ReloadTask() { + if (timer) { + swoole_timer_del(timer); + timer = nullptr; + } +} + +void ReloadTask::kill_all(int signal_number) { + for (auto &kv : workers) { + if (swoole_kill(kv.first, signal_number) < 0) { + if (errno == ECHILD || errno == ESRCH) { + continue; + } + swoole_sys_warning("failed to kill(%d, SIGTERM) worker#[%d]", kv.first, kv.second->id); + } else if (signal_number == SIGKILL) { + swoole_warning("force kill worker process(pid=%d, id=%d)", kv.first, kv.second->id); + } + } + + clear_queue(); +} + +void ReloadTask::kill_one(int signal_number) { + while (!kill_queue.empty()) { + auto pid = kill_queue.front(); + kill_queue.pop(); + auto iter = workers.find(pid); + if (iter == workers.end()) { + continue; + } + if (swoole_kill(pid, signal_number) < 0) { + if (errno == ECHILD || errno == ESRCH) { + workers.erase(iter); + continue; + } + swoole_sys_warning("kill(%d, SIGTERM) [%d] failed", pid, iter->second->id); + } + break; + } +} + +void ReloadTask::clear_queue() { + while (!kill_queue.empty()) { + kill_queue.pop(); + } +} } // namespace swoole + +swoole::WorkerId swoole_get_worker_id() { + return SwooleWG.id; +} + +pid_t swoole_get_worker_pid() { + return SwooleWG.pid; +} + +int swoole_get_worker_type() { + return SwooleWG.type; +} + +void swoole_set_worker_id(swoole::WorkerId worker_id) { + SwooleWG.id = worker_id; +} + +void swoole_set_worker_pid(pid_t pid) { + SwooleWG.pid = pid; +} + +void swoole_set_worker_type(int type) { + SwooleWG.type = type; +} + +char swoole_get_worker_symbol() { + switch (swoole_get_worker_type()) { + case SW_MASTER: + return '#'; + case SW_MANAGER: + return '$'; + case SW_WORKER: + return '*'; + case SW_TASK_WORKER: + return '^'; + case SW_USER_WORKER: + return '@'; + default: + return '%'; + } +} diff --git a/src/os/sendfile.cc b/src/os/sendfile.cc index abed9deb44..97be4e66d9 100644 --- a/src/os/sendfile.cc +++ b/src/os/sendfile.cc @@ -14,12 +14,19 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" -#if defined(HAVE_KQUEUE) && defined(HAVE_SENDFILE) +#include "swoole_socket.h" + +#if defined(__linux__) +#include + +ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { + return sendfile(out_fd, in_fd, offset, size); +} +#elif defined(HAVE_SENDFILE) && defined(HAVE_KQUEUE) #include #include -int swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { +ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { ssize_t ret; #ifdef __MACH__ @@ -64,7 +71,7 @@ int swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { return SW_OK; } #elif !defined(HAVE_SENDFILE) -int swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { +ssize_t swoole_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) { char buf[SW_BUFFER_SIZE_BIG]; size_t readn = size > sizeof(buf) ? sizeof(buf) : size; ssize_t n = pread(in_fd, buf, readn, *offset); diff --git a/src/os/signal.cc b/src/os/signal.cc index 11372be5d3..b6be26312c 100644 --- a/src/os/signal.cc +++ b/src/os/signal.cc @@ -14,8 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" -#include "swoole_api.h" #include "swoole_signal.h" #include "swoole_socket.h" #include "swoole_reactor.h" @@ -24,6 +22,8 @@ #include #endif +#include + #ifdef HAVE_KQUEUE #ifdef USE_KQUEUE_IDE_HELPER #include "helper/kqueue.h" @@ -31,6 +31,9 @@ #include #endif #endif +#ifdef __NetBSD__ +#include +#endif using swoole::Event; using swoole::Reactor; @@ -48,9 +51,11 @@ static void swoole_signalfd_clear(); #ifdef HAVE_KQUEUE static SignalHandler swoole_signal_kqueue_set(int signo, SignalHandler handler); +static void swoole_signal_kqueue_clear(); #endif -static void swoole_signal_async_handler(int signo); +static void signal_handler_safety(int signo); +static void signal_handler_simple(int signo); #ifdef HAVE_SIGNALFD static sigset_t signalfd_mask; @@ -62,27 +67,43 @@ static inline bool swoole_signalfd_is_available() { } #endif static Signal signals[SW_SIGNO_MAX]; -static int _lock = 0; +static bool triggered_signals[SW_SIGNO_MAX]; char *swoole_signal_to_str(int sig) { static char buf[64]; snprintf(buf, sizeof(buf), "%s", strsignal(sig)); - if (strchr(buf, ':') == 0) { + if (strchr(buf, ':') == nullptr) { size_t len = strlen(buf); snprintf(buf + len, sizeof(buf) - len, ": %d", sig); } return buf; } -/** - * block all singal - */ -void swoole_signal_block_all(void) { +void swoole_signal_block_all() { + if (SwooleTG.signal_blocking_all) { + return; + } sigset_t mask; sigfillset(&mask); int ret = pthread_sigmask(SIG_BLOCK, &mask, nullptr); if (ret < 0) { - swoole_sys_warning("pthread_sigmask() failed"); + swoole_sys_warning("pthread_sigmask(SIG_BLOCK) failed"); + } else { + SwooleTG.signal_blocking_all = true; + } +} + +void swoole_signal_unblock_all() { + if (!SwooleTG.signal_blocking_all) { + return; + } + sigset_t mask; + sigfillset(&mask); + int ret = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); + if (ret < 0) { + swoole_sys_warning("pthread_sigmask(SIG_UNBLOCK) failed"); + } else { + SwooleTG.signal_blocking_all = false; } } @@ -104,8 +125,7 @@ SignalHandler swoole_signal_set(int signo, SignalHandler func, int restart, int signals[signo].activated = false; } - struct sigaction act { - }, oact{}; + struct sigaction act{}, oact{}; act.sa_handler = func; if (mask) { sigfillset(&act.sa_mask); @@ -113,45 +133,55 @@ SignalHandler swoole_signal_set(int signo, SignalHandler func, int restart, int sigemptyset(&act.sa_mask); } act.sa_flags = 0; + if (restart) { + act.sa_flags |= SA_RESTART; + } if (sigaction(signo, &act, &oact) < 0) { return nullptr; } return oact.sa_handler; } +SW_API bool swoole_signal_isset(int signo) { + return signals[signo].handler && signals[signo].activated; +} + /** * set new signal handler and return origin signal handler */ -SignalHandler swoole_signal_set(int signo, SignalHandler handler) { +SignalHandler swoole_signal_set(int signo, SignalHandler handler, bool safety) { #ifdef HAVE_SIGNALFD if (SwooleG.enable_signalfd && swoole_event_is_available()) { return swoole_signalfd_set(signo, handler); - } else + } #endif - { #ifdef HAVE_KQUEUE - // SIGCHLD can not be monitored by kqueue, if blocked by SIG_IGN - // see https://www.freebsd.org/cgi/man.cgi?kqueue - // if there's no main reactor, signals cannot be monitored either - if (signo != SIGCHLD && sw_reactor()) { - return swoole_signal_kqueue_set(signo, handler); - } else -#endif - { - signals[signo].handler = handler; - signals[signo].activated = true; - signals[signo].signo = signo; - return swoole_signal_set(signo, swoole_signal_async_handler, 1, 0); - } + // SIGCHLD can not be monitored by kqueue, if blocked by SIG_IGN + // see https://www.freebsd.org/cgi/man.cgi?kqueue + // if there's no main reactor, signals cannot be monitored either + if (SwooleG.enable_kqueue && swoole_event_is_available() && signo != SIGCHLD) { + return swoole_signal_kqueue_set(signo, handler); } +#endif + + signals[signo].handler = handler; + signals[signo].activated = true; + signals[signo].signo = signo; + return swoole_signal_set(signo, safety ? signal_handler_safety : signal_handler_simple, 0, 0); } -static void swoole_signal_async_handler(int signo) { +static void signal_handler_safety(int signo) { + triggered_signals[signo] = true; + SwooleG.signal_dispatch = true; +} + +static void signal_handler_simple(int signo) { if (sw_reactor()) { - sw_reactor()->singal_no = signo; + signal_handler_safety(signo); } else { + static int _lock = 0; // discard signal - if (_lock || !SwooleG.init) { + if (_lock) { return; } _lock = 1; @@ -160,6 +190,19 @@ static void swoole_signal_async_handler(int signo) { } } +void swoole_signal_dispatch() { + if (!SwooleG.signal_dispatch) { + return; + } + SW_LOOP_N(SW_SIGNO_MAX) { + if (triggered_signals[i]) { + swoole_signal_callback(i); + triggered_signals[i] = false; + } + } + SwooleG.signal_dispatch = false; +} + void swoole_signal_callback(int signo) { if (signo >= SW_SIGNO_MAX) { swoole_warning("signal[%d] numberis invalid", signo); @@ -171,6 +214,9 @@ void swoole_signal_callback(int signo) { SW_LOG_WARNING, SW_ERROR_UNREGISTERED_SIGNAL, SW_UNREGISTERED_SIGNAL_FMT, swoole_signal_to_str(signo)); return; } + if (callback == SIG_IGN || callback == SIG_DFL) { + return; + } callback(signo); } @@ -183,33 +229,37 @@ SignalHandler swoole_signal_get_handler(int signo) { } } -void swoole_signal_clear(void) { +uint32_t swoole_signal_get_listener_num() { + return SwooleG.signal_listener_num + SwooleG.signal_async_listener_num; +} + +void swoole_signal_clear() { #ifdef HAVE_SIGNALFD if (SwooleG.enable_signalfd && swoole_signalfd_is_available()) { swoole_signalfd_clear(); - } else + return; + } #endif - { - SW_LOOP_N(SW_SIGNO_MAX) { - if (signals[i].activated) { + #ifdef HAVE_KQUEUE - if (signals[i].signo != SIGCHLD && sw_reactor()) { - swoole_signal_kqueue_set(signals[i].signo, nullptr); - } else + if (SwooleG.enable_kqueue) { + swoole_signal_kqueue_clear(); + return; + } #endif - { - swoole_signal_set(signals[i].signo, (SignalHandler) -1, 1, 0); - } - } + + SW_LOOP_N(SW_SIGNO_MAX) { + if (signals[i].activated) { + swoole_signal_set(signals[i].signo, reinterpret_cast(-1), 1, 0); } } - sw_memset_zero(&signals, sizeof(signals)); + sw_memset_zero(signals, sizeof(signals)); } #ifdef HAVE_SIGNALFD void swoole_signalfd_init() { sigemptyset(&signalfd_mask); - sw_memset_zero(&signals, sizeof(signals)); + sw_memset_zero(signals, sizeof(signals)); } /** @@ -229,13 +279,6 @@ static SignalHandler swoole_signalfd_set(int signo, SignalHandler handler) { signals[signo].activated = true; } - if (!swoole_event_is_available()) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_OPERATION_NOT_SUPPORT, - "The signalfd must only be used after event loop is initialized"); - return nullptr; - } - if (swoole_signalfd_is_available()) { sigprocmask(SIG_SETMASK, &signalfd_mask, nullptr); signalfd(signal_fd, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC); @@ -245,6 +288,17 @@ static SignalHandler swoole_signalfd_set(int signo, SignalHandler handler) { return origin_handler; } +static void swoole_signalfd_close() { + if (!swoole_signalfd_is_available()) { + return; + } + signal_socket->fd = -1; + signal_socket->free(); + close(signal_fd); + signal_socket = nullptr; + signal_fd = -1; +} + static bool swoole_signalfd_create() { if (swoole_signalfd_is_available()) { return false; @@ -258,11 +312,7 @@ static bool swoole_signalfd_create() { signal_socket = swoole::make_socket(signal_fd, SW_FD_SIGNAL); if (sigprocmask(SIG_BLOCK, &signalfd_mask, nullptr) == -1) { swoole_sys_warning("sigprocmask() failed"); - signal_socket->fd = -1; - signal_socket->free(); - close(signal_fd); - signal_socket = nullptr; - signal_fd = -1; + swoole_signalfd_close(); return false; } signalfd_create_pid = getpid(); @@ -275,8 +325,8 @@ bool swoole_signalfd_setup(Reactor *reactor) { if (!swoole_signalfd_is_available() && !swoole_signalfd_create()) { return false; } - if (!swoole_event_isset_handler(SW_FD_SIGNAL)) { - swoole_event_set_handler(SW_FD_SIGNAL, swoole_signalfd_event_callback); + if (!swoole_event_isset_handler(SW_FD_SIGNAL, SW_EVENT_READ)) { + swoole_event_set_handler(SW_FD_SIGNAL, SW_EVENT_READ, swoole_signalfd_event_callback); reactor->set_exit_condition(Reactor::EXIT_CONDITION_SIGNALFD, [](Reactor *reactor, size_t &event_num) -> bool { event_num--; return true; @@ -291,27 +341,26 @@ bool swoole_signalfd_setup(Reactor *reactor) { if (!(signal_socket->events & SW_EVENT_READ) && swoole_event_add(signal_socket, SW_EVENT_READ) < 0) { return false; } + reactor->erase_end_callback(Reactor::PRIORITY_SIGNAL_CALLBACK); return true; } static void swoole_signalfd_clear() { - if (swoole_signalfd_is_available()) { - if (sigprocmask(SIG_UNBLOCK, &signalfd_mask, nullptr) < 0) { - swoole_sys_warning("sigprocmask(SIG_UNBLOCK) failed"); - } - if (signal_socket) { - signal_socket->free(); - signal_socket = nullptr; - } - sw_memset_zero(&signals, sizeof(signals)); - sw_memset_zero(&signalfd_mask, sizeof(signalfd_mask)); + if (!swoole_signalfd_is_available()) { + return; } - SwooleG.signal_fd = 0; - signal_fd = -1; + + if (sigprocmask(SIG_UNBLOCK, &signalfd_mask, nullptr) < 0) { + swoole_sys_warning("sigprocmask(SIG_UNBLOCK) failed"); + } + sw_memset_zero(signals, sizeof(signals)); + sw_memset_zero(&signalfd_mask, sizeof(signalfd_mask)); + + swoole_signalfd_close(); } static int swoole_signalfd_event_callback(Reactor *reactor, Event *event) { - struct signalfd_siginfo siginfo; + signalfd_siginfo siginfo; ssize_t n = read(event->fd, &siginfo, sizeof(siginfo)); if (n < 0) { swoole_sys_warning("read from signalfd failed"); @@ -361,7 +410,7 @@ static SignalHandler swoole_signal_kqueue_set(int signo, SignalHandler handler) signals[signo].handler = handler; signals[signo].signo = signo; signals[signo].activated = true; -#ifndef __NetBSD__ +#if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sigptr = &signals[signo]; #else auto sigptr = reinterpret_cast(&signals[signo]); @@ -376,4 +425,15 @@ static SignalHandler swoole_signal_kqueue_set(int signo, SignalHandler handler) return origin_handler; } + +static void swoole_signal_kqueue_clear() { + SW_LOOP_N(SW_SIGNO_MAX) { + if (signals[i].activated && swoole_event_is_available()) { + signals[i].activated = false; + signals[i].handler = nullptr; + swoole_signal_kqueue_set(signals[i].signo, nullptr); + } + } + sw_memset_zero(signals, sizeof(signals)); +} #endif diff --git a/src/os/timer.cc b/src/os/timer.cc index 6ef90fd04d..2ed39bce14 100644 --- a/src/os/timer.cc +++ b/src/os/timer.cc @@ -17,51 +17,41 @@ #include "swoole_timer.h" #include "swoole_signal.h" -#include +#include +#include namespace swoole { - static int SystemTimer_set(Timer *timer, long next_msec); -bool Timer::init_system_timer() { +void Timer::init_with_system_timer() { set = SystemTimer_set; close = [](Timer *timer) { SystemTimer_set(timer, -1); }; swoole_signal_set(SIGALRM, [](int sig) { SwooleG.signal_alarm = true; }); - return true; } -/** - * setitimer - */ static int SystemTimer_set(Timer *timer, long next_msec) { - struct itimerval timer_set; - struct timeval now; - if (gettimeofday(&now, nullptr) < 0) { - swoole_sys_warning("gettimeofday() failed"); - return SW_ERR; - } - + itimerval timer_set{}; if (next_msec > 0) { - int sec = next_msec / 1000; - int msec = next_msec % 1000; - timer_set.it_interval.tv_sec = sec; - timer_set.it_interval.tv_usec = msec * 1000; - timer_set.it_value.tv_sec = sec; - timer_set.it_value.tv_usec = timer_set.it_interval.tv_usec; - - if (timer_set.it_value.tv_usec > 1e6) { - timer_set.it_value.tv_usec = timer_set.it_value.tv_usec - 1e6; - timer_set.it_value.tv_sec += 1; - } - } else { - timer_set = {}; + timer_set.it_interval = {next_msec / 1000, static_cast((next_msec % 1000) * 1000)}; + timer_set.it_value = timer_set.it_interval; } + return setitimer(ITIMER_REAL, &timer_set, nullptr) < 0 ? SW_ERR : SW_OK; +} - if (setitimer(ITIMER_REAL, &timer_set, nullptr) < 0) { - swoole_sys_warning("setitimer() failed"); - return SW_ERR; - } - return SW_OK; +void realtime_get(timespec *time) { + auto now = std::chrono::system_clock::now(); + auto ns = std::chrono::duration_cast(now.time_since_epoch()); + time->tv_sec = ns.count() / SW_NUM_BILLION; + time->tv_nsec = ns.count() % SW_NUM_BILLION; } +void realtime_add(timespec *time, const int64_t add_msec) { + time->tv_sec += add_msec / 1000; + time->tv_nsec += add_msec % 1000 * SW_NUM_MILLION; + if (time->tv_nsec >= SW_NUM_BILLION) { + int secs = time->tv_nsec / SW_NUM_BILLION; + time->tv_sec += secs; + time->tv_nsec -= secs * SW_NUM_BILLION; + } +} } // namespace swoole diff --git a/src/os/unix_socket.cc b/src/os/unix_socket.cc index 08b34422df..55d6bb3c9e 100644 --- a/src/os/unix_socket.cc +++ b/src/os/unix_socket.cc @@ -18,19 +18,16 @@ #include "swoole_socket.h" namespace swoole { -UnixSocket::UnixSocket(bool blocking, int _protocol) : - SocketPair(blocking), protocol_(_protocol) { +UnixSocket::UnixSocket(bool blocking, int _protocol) : SocketPair(blocking), protocol_(_protocol) { if (socketpair(AF_UNIX, protocol_, 0, socks) < 0) { swoole_sys_warning("socketpair() failed"); return; } - if (!init_socket(socks[1], socks[0])) { - return; - } + init_socket(socks[1], socks[0]); set_buffer_size(network::Socket::default_buffer_size); } -bool UnixSocket::set_buffer_size(size_t _size) { +bool UnixSocket::set_buffer_size(size_t _size) const { if (!master_socket->set_buffer_size(_size)) { return false; } @@ -39,4 +36,4 @@ bool UnixSocket::set_buffer_size(size_t _size) { } return true; } -} +} // namespace swoole diff --git a/src/os/wait.cc b/src/os/wait.cc index e90b11f5f3..a4a523ef6c 100644 --- a/src/os/wait.cc +++ b/src/os/wait.cc @@ -13,11 +13,12 @@ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ -#include "swoole.h" -#include "swoole_api.h" + #include "swoole_process_pool.h" #include "swoole_coroutine.h" +#include "swoole_coroutine_api.h" #include "swoole_coroutine_system.h" +#include "swoole_iouring.h" #include "swoole_signal.h" #include @@ -32,9 +33,13 @@ struct WaitTask { int status; }; +/** + * Wait, waitpid, and signal cannot be used in a multithreaded environment; + * they are only applicable to the main thread. There is no need to treat them as thread-local variables. + */ static std::list wait_list; -static std::unordered_map waitpid_map; -static std::unordered_map child_processes; +static std::unordered_map waitpid_map; +static std::unordered_map child_processes; bool signal_ready = false; @@ -84,41 +89,72 @@ static void signal_init() { } } -pid_t System::wait(int *__stat_loc, double timeout) { - return System::waitpid(-1, __stat_loc, 0, timeout); +#if SW_USE_IOURING +static pid_t iouring_waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout = -1) { + auto rs = Iouring::waitpid(_pid, _stat_loc, _options, timeout); + if (rs < 0) { + swoole_set_last_error(errno); + } + return rs; +} +#endif + +pid_t System::wait(int *_stat_loc, double timeout) { + return System::waitpid(-1, _stat_loc, 0, timeout); +} + +pid_t System::waitpid_safe(pid_t _pid, int *_stat_loc, int _options) { + if (sw_unlikely(SwooleTG.reactor == nullptr || !Coroutine::get_current() || (_options & WNOHANG))) { + return ::waitpid(_pid, _stat_loc, _options); + } + +#if SW_USE_IOURING + return iouring_waitpid(_pid, _stat_loc, _options); +#endif + + pid_t retval = -1; + wait_for([_pid, &retval, _stat_loc]() -> bool { + retval = ::waitpid(_pid, _stat_loc, WNOHANG); + return retval != 0; + }); + + return retval; } /** * @error: errno & swoole_get_last_error() */ -pid_t System::waitpid(pid_t __pid, int *__stat_loc, int __options, double timeout) { - if (__pid < 0) { +pid_t System::waitpid(pid_t _pid, int *_stat_loc, int _options, double timeout) { +#if SW_USE_IOURING + return iouring_waitpid(_pid, _stat_loc, _options, timeout); +#endif + if (_pid < 0) { if (!child_processes.empty()) { auto i = child_processes.begin(); - pid_t __pid = i->first; - *__stat_loc = i->second; + pid_t pid = i->first; + *_stat_loc = i->second; child_processes.erase(i); - return __pid; + return pid; } } else { - auto i = child_processes.find(__pid); + auto i = child_processes.find(_pid); if (i != child_processes.end()) { - *__stat_loc = i->second; + *_stat_loc = i->second; child_processes.erase(i); - return __pid; + return _pid; } } - if (sw_unlikely(SwooleTG.reactor == nullptr || !Coroutine::get_current() || (__options & WNOHANG))) { - return ::waitpid(__pid, __stat_loc, __options); + if (sw_unlikely(SwooleTG.reactor == nullptr || !Coroutine::get_current() || (_options & WNOHANG))) { + return ::waitpid(_pid, _stat_loc, _options); } - /* try once if failed we init the task, and we must register SIGCHLD before try waitpid, or we may lose the SIGCHLD + /* try once if failed to init the task, and must register SIGCHLD before try waitpid, or may lose the SIGCHLD */ WaitTask task; signal_init(); - task.pid = ::waitpid(__pid, __stat_loc, __options | WNOHANG); - if (task.pid > 0) { + task.pid = ::waitpid(_pid, _stat_loc, _options | WNOHANG); + if (task.pid != 0) { return task.pid; } @@ -127,36 +163,16 @@ pid_t System::waitpid(pid_t __pid, int *__stat_loc, int __options, double timeou task.co = Coroutine::get_current(); /* enqueue */ - if (__pid < 0) { + if (_pid < 0) { wait_list.push_back(&task); } else { - waitpid_map[__pid] = &task; + waitpid_map[_pid] = &task; } - /* timeout controller */ - TimerNode *timer = nullptr; - if (timeout > 0) { - timer = swoole_timer_add( - timeout * 1000, - false, - [](Timer *timer, TimerNode *tnode) { - Coroutine *co = (Coroutine *) tnode->data; - co->resume(); - }, - task.co); - } - - Coroutine::CancelFunc cancel_fn = [timer](Coroutine *co) { - if (timer) { - swoole_timer_del(timer); - } - co->resume(); - return true; - }; - task.co->yield(&cancel_fn); + task.co->yield_ex(timeout); /* dequeue */ - if (__pid < 0) { + if (_pid < 0) { if (task.pid > 0) { wait_list.pop_front(); } else { @@ -164,34 +180,45 @@ pid_t System::waitpid(pid_t __pid, int *__stat_loc, int __options, double timeou wait_list.remove(&task); } } else { - waitpid_map.erase(__pid); + waitpid_map.erase(_pid); } /* clear and assign result */ if (task.pid > 0) { - if (timer) { - swoole_timer_del(timer); - } - *__stat_loc = task.status; - } else { - swoole_set_last_error(task.co->is_canceled() ? SW_ERROR_CO_CANCELED : ETIMEDOUT); - errno = swoole_get_last_error(); + *_stat_loc = task.status; + } else if (task.co->is_timedout()) { + errno = ETIMEDOUT; + swoole_set_last_error(ETIMEDOUT); } return task.pid; } extern "C" { - size_t swoole_coroutine_wait_count() { return wait_list.size() + waitpid_map.size(); } -pid_t swoole_coroutine_wait(int *__stat_loc) { - return System::wait(__stat_loc); +pid_t swoole_coroutine_wait(int *_stat_loc) { + return System::wait(_stat_loc); } -pid_t swoole_coroutine_waitpid(pid_t __pid, int *__stat_loc, int __options) { - return System::waitpid(__pid, __stat_loc, __options); +pid_t swoole_coroutine_waitpid(pid_t _pid, int *_stat_loc, int _options) { + return System::waitpid(_pid, _stat_loc, _options); } } + +pid_t swoole_waitpid(pid_t _pid, int *_stat_loc, int _options) { + pid_t retval; + SW_LOOP { + retval = waitpid(_pid, _stat_loc, _options); + if (!(retval < 0 && errno == EINTR)) { + break; + } + swoole_signal_dispatch(); + if (sw_timer()) { + sw_timer()->select(); + } + } + return retval; +} diff --git a/src/protocol/base.cc b/src/protocol/base.cc index b8e4daed80..cc8a1e5fd8 100644 --- a/src/protocol/base.cc +++ b/src/protocol/base.cc @@ -16,12 +16,19 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" namespace swoole { +#define proto_error(_sock, _error, _fmt, ...) \ + network::Address _remote_addr; \ + if (_sock->get_peer_name(&_remote_addr) < 0) { \ + _remote_addr = _sock->info; \ + } \ + swoole_error_log( \ + SW_LOG_WARNING, _error, _fmt "<%s:%d>", ##__VA_ARGS__, _remote_addr.get_addr(), _remote_addr.get_port()) + /** * return the package total length */ @@ -29,7 +36,6 @@ ssize_t Protocol::default_length_func(const Protocol *protocol, network::Socket uint16_t length_offset = protocol->package_length_offset; uint8_t package_length_size = protocol->get_package_length_size ? protocol->get_package_length_size(socket) : protocol->package_length_size; - int32_t body_length; if (package_length_size == 0) { // protocol error @@ -42,27 +48,21 @@ ssize_t Protocol::default_length_func(const Protocol *protocol, network::Socket pl->header_len = length_offset + package_length_size; return 0; } - body_length = swoole_unpack(protocol->package_length_type, pl->buf + length_offset); + int64_t body_length = swoole_unpack(protocol->package_length_type, pl->buf + length_offset); // Length error // Protocol length is not legitimate, out of bounds or exceed the allocated length if (body_length < 0) { - swoole_warning("invalid package (size=%d) from socket#%u<%s:%d>", - pl->buf_size, - socket->fd, - socket->info.get_ip(), - socket->info.get_port()); return SW_ERR; } - swoole_debug("length=%d", protocol->package_body_offset + body_length); + swoole_debug("length=%ld", protocol->package_body_offset + body_length); pl->header_len = protocol->package_length_size; // total package length return protocol->package_body_offset + body_length; } -int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) { +int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) const { RecvData rdata{}; - int retval; if (buffer->length < package_eof_len) { return SW_CONTINUE; @@ -72,7 +72,6 @@ int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) { rdata.info.len = length; rdata.data = data; if (onPackage(this, socket, &rdata) < 0) { - retval = SW_CLOSE; return false; } if (socket->removed) { @@ -81,12 +80,8 @@ int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) { return true; }); - if (socket->removed) { + if (socket->removed || n < 0) { return SW_CLOSE; - } - - if (n < 0) { - return retval; } else if (n == 0) { return SW_CONTINUE; } else if (n < (ssize_t) buffer->length) { @@ -98,11 +93,9 @@ int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) { buffer->clear(); } -#ifdef SW_USE_OPENSSL if (socket->ssl) { return SW_CONTINUE; } -#endif return SW_OK; } @@ -111,12 +104,12 @@ int Protocol::recv_split_by_eof(network::Socket *socket, String *buffer) { * @return SW_ERR: close the connection * @return SW_OK: continue */ -int Protocol::recv_with_length_protocol(network::Socket *socket, String *buffer) { +int Protocol::recv_with_length_protocol(network::Socket *socket, String *buffer) const { RecvData rdata{}; PacketLength pl{}; ssize_t package_length; uint8_t _package_length_size = get_package_length_size ? get_package_length_size(socket) : package_length_size; - uint32_t recv_size; + uint32_t recv_size = 0; ssize_t recv_n = 0; // protocol error @@ -175,53 +168,47 @@ int Protocol::recv_with_length_protocol(network::Socket *socket, String *buffer) buffer->clear(); } } -#ifdef SW_USE_OPENSSL if (socket->ssl) { goto _do_recv; } -#endif return SW_OK; } else { _do_get_length: pl.buf = buffer->str; pl.buf_size = buffer->length; + // TODO: support dynamic calculation of header buffer length (recv_size) + pl.header_len = 0; package_length = get_package_length(this, socket, &pl); // invalid package, close connection. if (package_length < 0) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_PACKAGE_MALFORMED_DATA, - "received %zu bytes of malformed data from the client[%s:%d]", - buffer->length, - socket->info.get_ip(), - socket->info.get_port()); + proto_error(socket, + SW_ERROR_PACKAGE_MALFORMED_DATA, + "received %zu bytes of malformed data, remote_addr=", + buffer->length); return SW_ERR; } // no length else if (package_length == 0) { - if (buffer->length == package_length_offset + package_length_size) { + if (buffer->length == recv_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_NOT_FOUND, - "bad request, no length found in %ld bytes", + "bad request, no length found in %zu bytes, remote_addr=", buffer->length); return SW_ERR; } else { return SW_OK; } } else if (package_length > package_max_length) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, - "package is too big, remote_addr=%s:%d, length=%zu", - socket->info.get_ip(), - socket->info.get_port(), - package_length); + proto_error(socket, + SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, + "the received packet length %ld is too large, remote_addr=", + package_length); return SW_ERR; } // get length success else { if (buffer->size < (size_t) package_length) { - if (!buffer->extend(package_length)) { - return SW_ERR; - } + buffer->extend(package_length); } socket->recv_wait = 1; buffer->offset = package_length; @@ -241,9 +228,9 @@ int Protocol::recv_with_length_protocol(network::Socket *socket, String *buffer) * @return SW_ERR: close the connection * @return SW_OK: continue */ -int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) { +int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) const { bool recv_again = false; - int buf_size; + size_t buf_size; RecvData rdata{}; _recv_data: @@ -254,7 +241,7 @@ int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) { buf_size = SW_BUFFER_SIZE_STD; } - int n = socket->recv(buf_ptr, buf_size, 0); + ssize_t n = socket->recv(buf_ptr, buf_size, 0); if (n < 0) { switch (socket->catch_read_error(errno)) { case SW_ERROR: @@ -294,17 +281,18 @@ int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) { return SW_OK; } buffer->clear(); -#ifdef SW_USE_OPENSSL if (socket->ssl && SSL_pending(socket->ssl) > 0) { goto _recv_data; } -#endif return SW_OK; } // over max length, will discard if (buffer->length == package_max_length) { - swoole_warning("Package is too big. package_length=%d", (int) buffer->length); + proto_error(socket, + SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, + "The received data packet is too large, length=%lu", + (ulong_t) buffer->length); return SW_ERR; } @@ -316,9 +304,7 @@ int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) { if (extend_size > package_max_length) { extend_size = package_max_length; } - if (!buffer->extend(extend_size)) { - return SW_ERR; - } + buffer->extend(extend_size); } } // no eof @@ -330,3 +316,123 @@ int Protocol::recv_with_eof_protocol(network::Socket *socket, String *buffer) { } } // namespace swoole + +int64_t swoole_unpack(char type, const void *data) { + switch (type) { + /*-------------------------8bit-----------------------------*/ + case 'c': // signed char + return *(const int8_t *) data; + case 'C': // unsigned char + return *(const uint8_t *) data; + + /*-------------------------16bit-----------------------------*/ + case 's': { // signed short (16 bit, host byte order) + int16_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'S': { // unsigned short (16 bit, host byte order) + uint16_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'n': { // unsigned short (16 bit, big endian byte order) + uint16_t val; + memcpy(&val, data, sizeof(val)); + return ntohs(val); + } + case 'v': { // unsigned short (16 bit, little endian byte order) + uint16_t val; + memcpy(&val, data, sizeof(val)); +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + return val; +#else + return swoole_swap_endian16(val); +#endif + } + + /*-------------------------Machine dependent size-----------------------------*/ + case 'i': { // signed integer (machine dependent size and byte order) + int val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'I': { // unsigned integer (machine dependent size and byte order) + unsigned int val; + memcpy(&val, data, sizeof(val)); + return val; + } + + /*-------------------------32bit-----------------------------*/ + case 'l': { // signed long (32 bit, host byte order) + int32_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'L': { // unsigned long (32 bit, host byte order) + uint32_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'N': { // unsigned long (32 bit, big endian byte order) + uint32_t val; + memcpy(&val, data, sizeof(val)); + return ntohl(val); + } + case 'V': { // unsigned long (32 bit, little endian byte order) + uint32_t val; + memcpy(&val, data, sizeof(val)); +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + return val; +#else + return swoole_swap_endian32(val); +#endif + } + + /*-------------------------64bit-----------------------------*/ + case 'q': { // signed long long (64 bit, host byte order) + int64_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'Q': { // unsigned long long (64 bit, host byte order) + uint64_t val; + memcpy(&val, data, sizeof(val)); + return val; + } + case 'J': { // unsigned long long (64 bit, big endian byte order) + uint64_t val; + memcpy(&val, data, sizeof(val)); + return swoole_ntoh64(val); // Network byte order (big endian) to host byte order + } + case 'P': { // unsigned long long (64 bit, little endian byte order) + uint64_t val; + memcpy(&val, data, sizeof(val)); +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + return val; +#else + return swoole_swap_endian64(val); +#endif + } + default: { + swoole_error_log(SW_LOG_ERROR, SW_ERROR_INVALID_PARAMS, "Invalid format specifier '%c'", type); + return -1; + } + } +} + +uint64_t swoole_hton64(uint64_t value) { +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + return swoole_swap_endian64(value); +#else + return value; +#endif +} + +uint64_t swoole_ntoh64(uint64_t value) { +#if SW_BYTE_ORDER == SW_LITTLE_ENDIAN + return swoole_swap_endian64(value); +#else + return value; +#endif +} diff --git a/src/protocol/dtls.cc b/src/protocol/dtls.cc index 641dc759e5..3b8b4afb2f 100644 --- a/src/protocol/dtls.cc +++ b/src/protocol/dtls.cc @@ -1,6 +1,22 @@ +/* + +----------------------------------------------------------------------+ + | 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 "swoole_server.h" -#ifdef SW_SUPPORT_DTLS +#ifdef SW_SUPPORT_DTLS namespace swoole { namespace dtls { //------------------------------------------------------------------------------- @@ -8,17 +24,16 @@ namespace dtls { int BIO_write(BIO *b, const char *data, int dlen) { swoole_trace_log(SW_TRACE_SSL, "BIO_write(%d)", dlen); - Session *session = (Session *) BIO_get_data(b); + auto *session = (Session *) BIO_get_data(b); return session->socket->write(data, dlen); } int BIO_read(BIO *b, char *data, int len) { - Session *session = (Session *) BIO_get_data(b); - Buffer *buffer; + auto *session = (Session *) BIO_get_data(b); BIO_clear_retry_flags(b); if (!session->rxqueue.empty()) { - buffer = session->rxqueue.front(); + Buffer *buffer = session->rxqueue.front(); swoole_trace("BIO_read(%d, peek=%d)=%d", len, session->peek_mode, buffer->length); @@ -39,7 +54,7 @@ int BIO_read(BIO *b, char *data, int len) { long BIO_ctrl(BIO *b, int cmd, long lval, void *ptrval) { long retval = 0; - Session *session = (Session *) BIO_get_data(b); + auto *session = (Session *) BIO_get_data(b); swoole_trace_log(SW_TRACE_SSL, "BIO_ctrl(BIO[%p], cmd[%d], lval[%ld], ptrval[%p])", b, cmd, lval, ptrval); @@ -87,6 +102,12 @@ long BIO_ctrl(BIO *b, int cmd, long lval, void *ptrval) { case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: retval = 0; break; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + case BIO_CTRL_GET_KTLS_SEND: + case BIO_CTRL_GET_KTLS_RECV: + retval = 0; + break; +#endif default: swoole_warning("unknown cmd: %d", cmd); retval = 0; @@ -108,7 +129,7 @@ int BIO_destroy(BIO *b) { static BIO_METHOD *_bio_methods = nullptr; static int dtls_session_index = 0; -BIO_METHOD *BIO_get_methods(void) { +BIO_METHOD *BIO_get_methods() { if (_bio_methods) { return _bio_methods; } @@ -123,15 +144,19 @@ BIO_METHOD *BIO_get_methods(void) { BIO_meth_set_destroy(_bio_methods, BIO_destroy); #ifdef OPENSSL_IS_BORINGSSL - BIO_meth_set_ctrl(_bio_methods, (long (*)(BIO *, int, long, void *)) BIO_callback_ctrl); + BIO_meth_set_ctrl(_bio_methods, (long (*)(BIO *, int, long, void *)) BIO_ctrl); #else +#if OPENSSL_VERSION_NUMBER > 0x1010007fL BIO_meth_set_callback_ctrl(_bio_methods, (long (*)(BIO *, int, BIO_info_cb *)) BIO_callback_ctrl); +#else + BIO_meth_set_callback_ctrl(_bio_methods, (long (*)(BIO *, int, bio_info_cb *)) BIO_callback_ctrl); +#endif #endif return _bio_methods; } -void BIO_meth_free(void) { +void BIO_meth_free() { if (_bio_methods) { BIO_meth_free(_bio_methods); } @@ -140,7 +165,7 @@ void BIO_meth_free(void) { } void Session::append(const char *data, ssize_t len) { - Buffer *buffer = (Buffer *) sw_malloc(sizeof(*buffer) + len); + auto *buffer = static_cast(sw_malloc(sizeof(Buffer) + len)); buffer->length = len; memcpy(buffer->data, data, buffer->length); rxqueue.push_back(buffer); @@ -150,9 +175,13 @@ bool Session::init() { if (socket->ssl) { return false; } - if (socket->ssl_create(ctx, SW_SSL_SERVER) < 0) { + + if (socket->ssl_create(ctx.get(), SW_SSL_SERVER) < 0) { + swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } + + socket->set_buffer_size(Socket::default_buffer_size); socket->dtls = 1; BIO *bio = BIO_new(BIO_get_methods()); @@ -180,10 +209,10 @@ bool Session::listen() { } else if (retval < 0) { int reason = ERR_GET_REASON(ERR_peek_error()); swoole_warning("DTLSv1_listen() failed, client[%s:%d], reason=%d, error_string=%s", - socket->info.get_ip(), - socket->info.get_port(), - reason, - swoole_ssl_get_error()); + socket->get_addr(), + socket->get_port(), + reason, + swoole_ssl_get_error()); return false; } else { listened = true; @@ -195,5 +224,4 @@ bool Session::listen() { //------------------------------------------------------------------------------- } // namespace dtls } // namespace swoole - #endif diff --git a/src/protocol/http.cc b/src/protocol/http.cc index 88de2fb123..900e29abe0 100644 --- a/src/protocol/http.cc +++ b/src/protocol/http.cc @@ -15,9 +15,8 @@ */ #include "swoole_http.h" -#include "swoole_server.h" - -#include +#include "swoole_proxy.h" +#include "swoole_base64.h" #include "swoole_util.h" #include "swoole_http2.h" @@ -26,7 +25,6 @@ #include "thirdparty/multipart_parser.h" -using std::string; using swoole::http_server::Request; using swoole::http_server::StaticHandler; using swoole::network::SendfileTask; @@ -41,163 +39,118 @@ static const char *method_strings[] = { // 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_date_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; +HttpProxy *HttpProxy::create(const std::string &host, int port, const std::string &user, const std::string &pwd) { + auto http_proxy = new HttpProxy(); + http_proxy->host = host; + http_proxy->port = port; + if (!user.empty() && !pwd.empty()) { + http_proxy->username = user; + http_proxy->password = pwd; } + return http_proxy; +} - /** - * 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; - } +std::string HttpProxy::get_auth_str() const { + char auth_buf[256]; + char encode_buf[512]; + size_t n = sw_snprintf(auth_buf, + sizeof(auth_buf), + "%.*s:%.*s", + (int) username.length(), + username.c_str(), + (int) password.length(), + password.c_str()); + base64_encode((unsigned char *) auth_buf, n, encode_buf); + return {encode_buf}; +} - /** - * 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; +size_t HttpProxy::pack(const String *send_buffer, const std::string &host_name) const { + if (!password.empty()) { + auto auth_str = get_auth_str(); + return sw_snprintf(send_buffer->str, + send_buffer->size, + SW_HTTP_PROXY_FMT "Proxy-Authorization: Basic %.*s\r\n\r\n", + (int) target_host.length(), + target_host.c_str(), + target_port, + (int) host_name.length(), + host_name.c_str(), + target_port, + (int) auth_str.length(), + auth_str.c_str()); + } else { + return sw_snprintf(send_buffer->str, + send_buffer->size, + SW_HTTP_PROXY_FMT "\r\n", + (int) target_host.length(), + target_host.c_str(), + target_port, + (int) host_name.length(), + host_name.c_str(), + target_port); } +} - auto task = handler.get_task(); - 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: %s\r\n" - "Date: %s\r\n" - "Last-Modified: %s\r\n" - "Server: %s\r\n\r\n", - request->keep_alive ? "keep-alive" : "close", - (long) task->length, - handler.get_mimetype(), - 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 (task->length != 0) { - response.info.type = SW_SERVER_EVENT_SEND_FILE; - response.info.len = sizeof(*task) + task->length + 1; - response.data = (char *) task; - send_to_connection(&response); - } +bool HttpProxy::handshake(const String *recv_buffer) { + bool ret = false; + char *buf = recv_buffer->str; + size_t len = recv_buffer->length; + int state = 0; + char *p = buf; + char *pe = buf + len; - // 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); + if (recv_buffer->length < sizeof(SW_HTTP_PROXY_HANDSHAKE_RESPONSE) - 1) { + return false; } - return true; -} - -void Server::destroy_http_request(Connection *conn) { - auto request = reinterpret_cast(conn->object); - if (!request) { - return; + for (; p < buf + len; p++) { + if (state == 0) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "HTTP/1.1") || SW_STR_ISTARTS_WITH(p, pe - p, "HTTP/1.0")) { + state = 1; + p += sizeof("HTTP/1.x") - 1; + } else { + break; + } + } else if (state == 1) { + if (isspace(*p)) { + continue; + } else { + if (SW_STR_ISTARTS_WITH(p, pe - p, "200")) { + state = 2; + p += sizeof("200") - 1; + } else { + swoole_set_last_error(SW_ERROR_HTTP_PROXY_HANDSHAKE_FAILED); + break; + } + } + } else if (state == 2) { + ret = true; + break; + /** + * The response message is generally "Connection established," + * although it is not specified in the RFC documents, and thus will not be checked for now. + */ +#if SW_HTTP_PROXY_CHECK_MESSAGE + if (isspace(*p)) { + continue; + } else { + if (SW_STR_ISTARTS_WITH(p, pe - p, "Connection established")) { + ret = true; + } + break; + } +#endif + } } - delete request; - conn->object = nullptr; -} -void Server::add_http_compression_type(const std::string &type) { - if (http_compression_types == nullptr) { - http_compression_types = std::make_shared>(); - } - http_compression_types->emplace(type); + return ret; } namespace http_server { //----------------------------------------------------------------- static int multipart_on_header_field(multipart_parser *p, const char *at, size_t length) { - Request *request = (Request *) p->data; + auto *request = static_cast(p->data); request->form_data_->current_header_name = at; request->form_data_->current_header_name_len = length; @@ -208,8 +161,8 @@ static int multipart_on_header_field(multipart_parser *p, const char *at, size_t static int multipart_on_header_value(multipart_parser *p, const char *at, size_t length) { swoole_trace("header_value: at=%.*s, length=%lu", (int) length, at, length); - Request *request = (Request *) p->data; - FormData *form_data = request->form_data_; + auto *request = static_cast(p->data); + auto *form_data = request->form_data_; form_data->multipart_buffer_->append(form_data->current_header_name, form_data->current_header_name_len); form_data->multipart_buffer_->append(SW_STRL(": ")); @@ -248,25 +201,34 @@ static int multipart_on_header_value(multipart_parser *p, const char *at, size_t } static int multipart_on_data(multipart_parser *p, const char *at, size_t length) { - Request *request = (Request *) p->data; + auto request = (Request *) p->data; + auto form_data = request->form_data_; swoole_trace("on_data: length=%lu", length); if (!p->fp) { - request->form_data_->multipart_buffer_->append(at, length); + if (form_data->multipart_buffer_->length + length > request->max_length_) { + request->excepted = 1; + request->unavailable = 1; + return 1; + } + form_data->multipart_buffer_->append(at, length); return 0; } - request->form_data_->upload_filesize += length; - if (request->form_data_->upload_filesize > request->form_data_->upload_max_filesize) { + form_data->upload_filesize += length; + if (form_data->upload_filesize > form_data->upload_max_filesize) { + request->excepted = 1; request->too_large = 1; return 1; } + ssize_t n = fwrite(at, sizeof(char), length, p->fp); if (n != (off_t) length) { fclose(p->fp); p->fp = nullptr; request->excepted = 1; - swoole_sys_warning("write upload file failed"); + request->unavailable = 1; + swoole_sys_warning("failed to write upload file"); return 1; } @@ -275,8 +237,8 @@ static int multipart_on_data(multipart_parser *p, const char *at, size_t length) static int multipart_on_header_complete(multipart_parser *p) { swoole_trace("on_header_complete"); - Request *request = (Request *) p->data; - FormData *form_data = request->form_data_; + auto *request = static_cast(p->data); + auto *form_data = request->form_data_; if (p->fp) { form_data->multipart_buffer_->append(SW_STRL(SW_HTTP_UPLOAD_FILE ": ")); form_data->multipart_buffer_->append(form_data->upload_tmpfile->str, strlen(form_data->upload_tmpfile->str)); @@ -288,8 +250,8 @@ static int multipart_on_header_complete(multipart_parser *p) { static int multipart_on_data_end(multipart_parser *p) { swoole_trace("on_data_end\n"); - Request *request = (Request *) p->data; - FormData *form_data = request->form_data_; + auto *request = static_cast(p->data); + auto *form_data = request->form_data_; request->multipart_header_parsed = 0; if (p->fp) { form_data->multipart_buffer_->append(SW_STRL("\r\n" SW_HTTP_UPLOAD_FILE)); @@ -302,18 +264,18 @@ static int multipart_on_data_end(multipart_parser *p) { } static int multipart_on_part_begin(multipart_parser *p) { - swoole_trace("on_part_begi\n"); - Request *request = (Request *) p->data; - FormData *form_data = request->form_data_; - form_data->multipart_buffer_->append(p->multipart_boundary, p->boundary_length); + swoole_trace("on_part_begin"); + auto *request = static_cast(p->data); + auto *form_data = request->form_data_; + form_data->multipart_buffer_->append(p->boundary, p->boundary_length); form_data->multipart_buffer_->append(SW_STRL("\r\n")); return 0; } static int multipart_on_body_end(multipart_parser *p) { - Request *request = (Request *) p->data; - FormData *form_data = request->form_data_; - form_data->multipart_buffer_->append(p->multipart_boundary, p->boundary_length); + auto *request = static_cast(p->data); + auto *form_data = request->form_data_; + form_data->multipart_buffer_->append(p->boundary, p->boundary_length); form_data->multipart_buffer_->append(SW_STRL("--")); request->content_length_ = form_data->multipart_buffer_->length - request->header_length_; @@ -327,7 +289,7 @@ static int multipart_on_body_end(multipart_parser *p) { char *ptr_end = request->multipart_buffer_->str + (request->multipart_buffer_->length - (sizeof("\r\n\r\n") - 1)); for (; ptr < ptr_end; ptr++) { - if (SW_STRCASECT(ptr, ptr_end - ptr, "Content-Length:")) { + if (SW_STR_ISTARTS_WITH(ptr, ptr_end - ptr, "Content-Length:")) { ptr += (sizeof("Content-Length:") - 1); // skip spaces while (*ptr == ' ') { @@ -356,7 +318,7 @@ static int multipart_on_body_end(multipart_parser *p) { return 0; } -static const multipart_parser_settings mt_parser_settings = { +static constexpr multipart_parser_settings mt_parser_settings = { multipart_on_header_field, multipart_on_header_value, multipart_on_data, @@ -366,12 +328,31 @@ static const multipart_parser_settings mt_parser_settings = { multipart_on_body_end, }; +static thread_local char http_status_message[128]; + +// clang-format off +int list_of_status_code[128] = { + 100, 101, 102, 103, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, + 300, 301, 302, 303, 304, 305, 306, 307, 308, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, + 410, 411, 412, 413, 414, 415, 416, 417, 418, 421, + 422, 423, 424, 425, 426, 428, 429, 431, 451, + 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511, + -1, +}; +// clang-format on + const char *get_status_message(int code) { switch (code) { case 100: return "100 Continue"; case 101: return "101 Switching Protocols"; + case 102: + return "102 Processing"; + case 103: + return "103 Early Hints"; case 201: return "201 Created"; case 202: @@ -402,8 +383,12 @@ const char *get_status_message(int code) { return "304 Not Modified"; case 305: return "305 Use Proxy"; + case 306: + return "306 (Unused)"; case 307: return "307 Temporary Redirect"; + case 308: + return "308 Permanent Redirect"; case 400: return "400 Bad Request"; case 401: @@ -431,13 +416,13 @@ const char *get_status_message(int code) { case 412: return "412 Precondition Failed"; case 413: - return "413 Request Entity Too Large"; + return "413 Payload Too Large"; case 414: - return "414 Request URI Too Long"; + return "414 URI Too Long"; case 415: return "415 Unsupported Media Type"; case 416: - return "416 Requested Range Not Satisfiable"; + return "416 Range Not Satisfiable"; case 417: return "417 Expectation Failed"; case 418: @@ -450,6 +435,8 @@ const char *get_status_message(int code) { return "423 Locked"; case 424: return "424 Failed Dependency"; + case 425: + return "425 Too Early"; case 426: return "426 Upgrade Required"; case 428: @@ -458,10 +445,12 @@ const char *get_status_message(int code) { return "429 Too Many Requests"; case 431: return "431 Request Header Fields Too Large"; + case 451: + return "451 Unavailable For Legal Reasons"; case 500: return "500 Internal Server Error"; case 501: - return "501 Method Not Implemented"; + return "501 Not Implemented"; case 502: return "502 Bad Gateway"; case 503: @@ -481,13 +470,16 @@ const char *get_status_message(int code) { case 511: return "511 Network Authentication Required"; case 200: - default: + case 0: return "200 OK"; + default: + sw_snprintf(http_status_message, sizeof(http_status_message), "%d Unknown Status", code); + return http_status_message; } } void parse_cookie(const char *at, size_t length, const ParseCookieCallback &cb) { - char *key, *value; + char *value; const char *separator = ";\0"; size_t key_len = 0; char *strtok_buf = nullptr; @@ -496,7 +488,7 @@ void parse_cookie(const char *at, size_t length, const ParseCookieCallback &cb) memcpy(_c, at, length); _c[length] = '\0'; - key = strtok_r(_c, separator, &strtok_buf); + char *key = strtok_r(_c, separator, &strtok_buf); while (key) { size_t value_len; value = strchr(key, '='); @@ -522,7 +514,7 @@ void parse_cookie(const char *at, size_t length, const ParseCookieCallback &cb) break; } next_cookie: - key = strtok_r(NULL, separator, &strtok_buf); + key = strtok_r(nullptr, separator, &strtok_buf); } } @@ -533,10 +525,13 @@ bool parse_multipart_boundary( offset++; continue; } - if (SW_STRCASECT(at + offset, length - offset, "boundary=")) { + + if (offset + sizeof("boundary=") - 1 <= length && + SW_STR_ISTARTS_WITH(at + offset, length - offset, "boundary=")) { offset += sizeof("boundary=") - 1; break; } + void *delimiter = memchr((void *) (at + offset), ';', length - offset); if (delimiter == nullptr) { return false; @@ -545,23 +540,30 @@ bool parse_multipart_boundary( } } + if (offset >= length) { + return false; + } + int boundary_len = length - offset; char *boundary_str = (char *) at + offset; // find eof of boundary if (boundary_len > 0) { // find ';' - char *tmp = (char *) memchr(boundary_str, ';', boundary_len); - if (tmp) { - boundary_len = tmp - boundary_str; + char *semicolon = (char *) memchr(boundary_str, ';', boundary_len); + if (semicolon) { + boundary_len = semicolon - boundary_str; } } if (boundary_len <= 0) { return false; } // trim '"' - if (boundary_len >= 2 && boundary_str[0] == '"' && *(boundary_str + boundary_len - 1) == '"') { + if (boundary_len >= 2 && boundary_str[0] == '"' && boundary_str[boundary_len - 1] == '"') { boundary_str++; boundary_len -= 2; + if (boundary_len <= 0) { + return false; + } } *out_boundary_str = boundary_str; *out_boundary_len = boundary_len; @@ -569,15 +571,12 @@ bool parse_multipart_boundary( return true; } -static int url_htoi(char *s) { - int value; - int c; - - c = ((unsigned char *) s)[0]; +static int url_htoi(const char *s) { + int c = ((unsigned char *) s)[0]; if (isupper(c)) { c = tolower(c); } - value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; + int value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; c = ((unsigned char *) s)[1]; if (isupper(c)) { @@ -636,74 +635,32 @@ char *url_encode(char const *str, size_t len) { memcpy(tmp, ret, size); sw_free(ret); ret = tmp; - } while (0); + } while (false); return ret; } -/** - * only GET/POST - */ int Request::get_protocol() { char *p = buffer_->str; char *pe = p + buffer_->length; + char state = 0; 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; + // HTTP Method + SW_LOOP_N(sizeof(method_strings) / sizeof(char *) - 1) { + auto method_str = method_strings[i]; + auto n = strlen(method_str); + if (memcmp(p, method_str, n) == 0) { + method = i + 1; + p += n; + goto _found_method; + } } // HTTP2 Connection Preface - else if (memcmp(p, SW_STRL("PRI")) == 0) { + 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; @@ -717,29 +674,31 @@ int Request::get_protocol() { return SW_ERR; } - // http version - char state = 0; + // HTTP Version +_found_method: for (; p < pe; p++) { switch (state) { case 0: - if (isspace(*p)) { + if (*p == ' ') { continue; } state = 1; url_offset_ = p - buffer_->str; break; case 1: - if (isspace(*p)) { + if (*p == ' ') { state = 2; url_length_ = p - buffer_->str - url_offset_; continue; + } else if (*p == '\r' || *p == '\n') { + goto _excepted; } break; case 2: - if (isspace(*p)) { + if (*p == ' ') { continue; } - if ((size_t)(pe - p) < (sizeof("HTTP/1.x") - 1)) { + if ((size_t) (pe - p) < (sizeof("HTTP/1.x") - 1)) { return SW_ERR; } if (memcmp(p, SW_STRL("HTTP/1.1")) == 0) { @@ -772,7 +731,7 @@ void Request::parse_header_info() { for (; p < pe; p++) { if (*(p - 1) == '\n' && *(p - 2) == '\r') { - if (SW_STRCASECT(p, pe - p, "Content-Length:")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "Content-Length:")) { // strlen("Content-Length:") p += (sizeof("Content-Length:") - 1); // skip spaces @@ -781,32 +740,32 @@ void Request::parse_header_info() { } content_length_ = strtoull(p, nullptr, 10); known_length = 1; - } else if (SW_STRCASECT(p, pe - p, "Connection:")) { + } else if (SW_STR_ISTARTS_WITH(p, pe - p, "Connection:")) { // strlen("Connection:") p += (sizeof("Connection:") - 1); // skip spaces while (*p == ' ') { p++; } - if (SW_STRCASECT(p, pe - p, "keep-alive")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "keep-alive")) { keep_alive = 1; } - } else if (SW_STRCASECT(p, pe - p, "Transfer-Encoding:")) { + } else if (SW_STR_ISTARTS_WITH(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")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "chunked")) { chunked = 1; } - } else if (SW_STRCASECT(p, pe - p, "Content-Type:")) { + } else if (SW_STR_ISTARTS_WITH(p, pe - p, "Content-Type:")) { p += (sizeof("Content-Type:") - 1); while (*p == ' ') { p++; } - if (SW_STRCASECT(p, pe - p, "multipart/form-data")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "multipart/form-data")) { form_data_ = new FormData(); form_data_->multipart_boundary_buf = p + (sizeof("multipart/form-data") - 1); form_data_->multipart_boundary_len = strchr(p, '\r') - form_data_->multipart_boundary_buf; @@ -821,7 +780,7 @@ void Request::parse_header_info() { } } -bool Request::init_multipart_parser(Server *server) { +bool Request::init_multipart_parser(const Server *server) { char *boundary_str; int boundary_len; if (!parse_multipart_boundary( @@ -866,15 +825,25 @@ void Request::destroy_multipart_parser() { } bool Request::parse_multipart_data(String *buffer) { - size_t n = multipart_parser_execute(form_data_->multipart_parser_, buffer->str, buffer->length); + excepted = 0; + ssize_t n = multipart_parser_execute(form_data_->multipart_parser_, buffer->str, buffer->length); swoole_trace("multipart_parser_execute: buffer->length=%lu, n=%lu\n", buffer->length, n); - if (n != buffer->length) { - swoole_error_log(SW_LOG_WARNING, + if (n < 0) { + int l_error = + multipart_parser_error_msg(form_data_->multipart_parser_, sw_tg_buffer()->str, sw_tg_buffer()->size); + swoole_error_log(SW_LOG_NOTICE, + SW_ERROR_SERVER_INVALID_REQUEST, + "parse multipart body failed, reason: %.*s", + l_error, + sw_tg_buffer()->str); + return false; + } else if ((size_t) n != buffer->length) { + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %zu/%zu bytes processed", n, buffer->length); - return false; + return excepted; } buffer->clear(); return true; @@ -886,21 +855,18 @@ Request::~Request() { } } -bool Request::has_expect_header() { - // char *buf = buffer->str + buffer->offset; +bool Request::has_expect_header() const { 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")) { + for (char *p = buf; p < pe; p++) { + if (*p == '\r' && (size_t) (pe - p) > sizeof("\r\nExpect")) { p += 2; - if (SW_STRCASECT(p, pe - p, "Expect: ")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "Expect: ")) { p += sizeof("Expect: ") - 1; - if (SW_STRCASECT(p, pe - p, "100-continue")) { + if (SW_STR_ISTARTS_WITH(p, pe - p, "100-continue")) { return true; } else { return false; @@ -930,67 +896,98 @@ int Request::get_header_length() { } int Request::get_chunked_body_length() { - char *p = buffer_->str + buffer_->offset; - char *pe = buffer_->str + buffer_->length; + const char *p = buffer_->str + buffer_->offset; + const 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; + /** + * Ending with SW_HTTP_CHUNK_EOF indicates that the HTTP request may have been fully received, + * but this is not certain. It is still necessary to skip over the data sections based on + * the length of each chunk of the HTTP data until SW_HTTP_CHUNK_EOF (\0\r\n\r\n) is found. + */ + if (static_cast(pe - p) < sizeof(SW_HTTP_CHUNK_EOF) - 1 || + memcmp(pe - sizeof(SW_HTTP_CHUNK_EOF) + 1, SW_STRL(SW_HTTP_CHUNK_EOF)) != 0) { + return SW_ERR; + } + + while (true) { + char *endptr; + size_t chunk_length = strtoul(p, &endptr, 16); + if (endptr == nullptr || *endptr != '\r') { + break; } - buffer_->offset = p - buffer_->str; + + swoole_trace_log(SW_TRACE_HTTP, "chunk_length=%zu, chunk_len_str=%.*s\n", chunk_length, (int) (endptr - p), p); + + // Found the HTTP Chunk EOF if (chunk_length == 0) { - break; + known_length = 1; + content_length_ = endptr - (buffer_->str + header_length_) + 4; + return SW_OK; + } else { + // chunk length [hex str] + CRLF + data + CRLF + p = endptr + 2 + chunk_length + 2; + // Continue to parse the next segment of HTTP CHUNK data. + if (p < pe) { + continue; + } + /** + * If the calculated data length exceeds the length of the currently received data, + * then SW_HTTP_CHUNK_EOF may be part of the data, + * necessitating the reception of additional data and recalculating the HTTP Chunk length. + */ + return SW_ERR; } + break; } - known_length = 1; - return SW_OK; + excepted = 1; + return SW_ERR; } -string Request::get_date_if_modified_since() { +std::string Request::get_header(const char *name) const { + size_t name_len = strlen(name); char *p = buffer_->str + url_offset_ + url_length_ + 10; char *pe = buffer_->str + header_length_; - string result; - - char *date_if_modified_since = nullptr; - size_t length_if_modified_since = 0; + 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, "If-Modified-Since")) { - p += sizeof("If-Modified-Since"); + if (SW_STR_ISTARTS_WITH(p, pe - p, "\r\n")) { + i = 0; + is_error_header_name = false; + break; + } + + if (!is_error_header_name && swoole_str_istarts_with(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)) { - date_if_modified_since = p; + buffer = p; state = 2; } break; case 2: - if (SW_STRCASECT(p, pe - p, "\r\n")) { - length_if_modified_since = p - date_if_modified_since; - return string(date_if_modified_since, length_if_modified_since); + if (SW_STR_ISTARTS_WITH(p, pe - p, "\r\n")) { + return {buffer, static_cast(p - buffer)}; } break; default: @@ -998,7 +995,7 @@ string Request::get_date_if_modified_since() { } } - return string(""); + return {}; } int get_method(const char *method_str, size_t method_len) { @@ -1012,7 +1009,7 @@ int get_method(const char *method_str, size_t method_len) { } const char *get_method_string(int method) { - if (method < 0 || method > SW_HTTP_PRI) { + if (method <= 0 || method > SW_HTTP_PRI) { return nullptr; } return method_strings[method - 1]; @@ -1027,17 +1024,17 @@ int dispatch_request(Server *serv, const Protocol *proto, Socket *_socket, const } //----------------------------------------------------------------- -static void protocol_status_error(Socket *socket, Connection *conn) { +static void protocol_status_error(Socket *socket, const 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_addr(), conn->info.get_port()); } ssize_t get_package_length(const Protocol *protocol, Socket *socket, PacketLength *pl) { - Connection *conn = (Connection *) socket->object; + auto *conn = static_cast(socket->object); if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { return websocket::get_package_length(protocol, socket, pl); } else if (conn->http2_stream) { @@ -1049,9 +1046,9 @@ ssize_t get_package_length(const Protocol *protocol, Socket *socket, PacketLengt } uint8_t get_package_length_size(Socket *socket) { - Connection *conn = (Connection *) socket->object; + auto *conn = (Connection *) socket->object; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { - return SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_MASK_LEN + sizeof(uint64_t); + return SW_WEBSOCKET_FRAME_HEADER_SIZE; } else if (conn->http2_stream) { return SW_HTTP2_FRAME_HEADER_SIZE; } else { @@ -1061,7 +1058,7 @@ uint8_t get_package_length_size(Socket *socket) { } int dispatch_frame(const Protocol *proto, Socket *socket, const RecvData *rdata) { - Connection *conn = (Connection *) socket->object; + auto *conn = (Connection *) socket->object; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { return websocket::dispatch_frame(proto, socket, rdata); } else if (conn->http2_stream) { diff --git a/src/protocol/http2.cc b/src/protocol/http2.cc index dd906dc818..4e56ef9a3a 100644 --- a/src/protocol/http2.cc +++ b/src/protocol/http2.cc @@ -16,9 +16,9 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_socket.h" #include "swoole_http2.h" +#include "swoole_protocol.h" using swoole::PacketLength; using swoole::Protocol; @@ -27,33 +27,125 @@ using swoole::network::Socket; namespace swoole { namespace http2 { -int send_setting_frame(Protocol *protocol, Socket *_socket) { - char setting_frame[SW_HTTP2_FRAME_HEADER_SIZE + SW_HTTP2_SETTING_OPTION_SIZE * 3]; - char *p = setting_frame; +static Settings default_settings = { + SW_HTTP2_DEFAULT_HEADER_TABLE_SIZE, + SW_HTTP2_DEFAULT_ENABLE_PUSH, + SW_HTTP2_DEFAULT_MAX_CONCURRENT_STREAMS, + SW_HTTP2_DEFAULT_INIT_WINDOW_SIZE, + SW_HTTP2_DEFAULT_MAX_FRAME_SIZE, + SW_HTTP2_DEFAULT_MAX_HEADER_LIST_SIZE, +}; + +void put_default_setting(enum swHttp2SettingId id, uint32_t value) { + switch (id) { + case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: + default_settings.header_table_size = value; + break; + case SW_HTTP2_SETTINGS_ENABLE_PUSH: + default_settings.enable_push = value; + break; + case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + default_settings.max_concurrent_streams = value; + break; + case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: + default_settings.init_window_size = value; + break; + case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: + default_settings.max_frame_size = value; + break; + case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + default_settings.max_header_list_size = value; + break; + default: + assert(0); + break; + } +} + +uint32_t get_default_setting(enum swHttp2SettingId id) { + switch (id) { + case SW_HTTP2_SETTING_HEADER_TABLE_SIZE: + return default_settings.header_table_size; + case SW_HTTP2_SETTINGS_ENABLE_PUSH: + return default_settings.enable_push; + case SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + return default_settings.max_concurrent_streams; + case SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE: + return default_settings.init_window_size; + case SW_HTTP2_SETTINGS_MAX_FRAME_SIZE: + return default_settings.max_frame_size; + case SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + return default_settings.max_header_list_size; + default: + assert(0); + return 0; + } +} + +static inline void pack_setting_item(char *_buf, enum swHttp2SettingId _id, uint32_t _value) { uint16_t id; uint32_t value; + id = htons(_id); + memcpy(_buf, &id, sizeof(id)); + value = htonl(_value); + memcpy(_buf + 2, &value, sizeof(value)); +} - set_frame_header(p, SW_HTTP2_TYPE_SETTINGS, SW_HTTP2_SETTING_OPTION_SIZE * 3, 0, 0); +size_t pack_setting_frame(char *buf, const Settings &settings, bool server_side) { + char *p = buf; + size_t size = SW_HTTP2_SETTING_OPTION_SIZE * (server_side ? 5 : 6); + set_frame_header(p, SW_HTTP2_TYPE_SETTINGS, size, 0, 0); p += SW_HTTP2_FRAME_HEADER_SIZE; - id = htons(SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - memcpy(p, &id, sizeof(id)); - value = htonl(SW_HTTP2_MAX_MAX_CONCURRENT_STREAMS); - memcpy(p + 2, &value, sizeof(value)); + pack_setting_item(p, SW_HTTP2_SETTING_HEADER_TABLE_SIZE, settings.header_table_size); p += SW_HTTP2_SETTING_OPTION_SIZE; - id = htons(SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE); - memcpy(p, &id, sizeof(id)); - value = htonl(SW_HTTP2_DEFAULT_WINDOW_SIZE); - memcpy(p + 2, &value, sizeof(value)); + if (!server_side) { + pack_setting_item(p, SW_HTTP2_SETTINGS_ENABLE_PUSH, settings.enable_push); + p += SW_HTTP2_SETTING_OPTION_SIZE; + } + + pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, settings.max_concurrent_streams); p += SW_HTTP2_SETTING_OPTION_SIZE; - id = htons(SW_HTTP2_SETTINGS_MAX_FRAME_SIZE); - memcpy(p, &id, sizeof(id)); - value = htonl(SW_HTTP2_MAX_MAX_FRAME_SIZE); - memcpy(p + 2, &value, sizeof(value)); + pack_setting_item(p, SW_HTTP2_SETTINGS_INIT_WINDOW_SIZE, settings.init_window_size); + p += SW_HTTP2_SETTING_OPTION_SIZE; - return _socket->send(setting_frame, sizeof(setting_frame), 0); + pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_FRAME_SIZE, settings.max_frame_size); + p += SW_HTTP2_SETTING_OPTION_SIZE; + + pack_setting_item(p, SW_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, settings.max_header_list_size); + p += SW_HTTP2_SETTING_OPTION_SIZE; + + return p - buf; +} + +ReturnCode unpack_setting_data(const char *buf, + ssize_t length, + const std::function &cb) { + uint16_t id = 0; + uint32_t value = 0; + + while (length > 0) { + id = ntohs(*(uint16_t *) (buf)); + value = ntohl(*(uint32_t *) (buf + sizeof(uint16_t))); + + auto rc = cb(id, value); + if (rc != SW_SUCCESS) { + return rc; + } + + buf += sizeof(id) + sizeof(value); + length -= sizeof(id) + sizeof(value); + } + + return SW_SUCCESS; +} + +int send_setting_frame(Protocol *protocol, Socket *_socket) { + char setting_frame[SW_HTTP2_SETTING_FRAME_SIZE]; + size_t n = pack_setting_frame(setting_frame, default_settings, true); + return _socket->send(setting_frame, n, 0); } /** @@ -97,7 +189,7 @@ const char *get_type(int type) { case SW_HTTP2_TYPE_CONTINUATION: return "CONTINUATION"; default: - return "UNKOWN"; + return "UNKNOWN"; } } diff --git a/src/protocol/message_bus.cc b/src/protocol/message_bus.cc new file mode 100644 index 0000000000..47e0e842ab --- /dev/null +++ b/src/protocol/message_bus.cc @@ -0,0 +1,367 @@ +/* + +----------------------------------------------------------------------+ + | Swoole | + +----------------------------------------------------------------------+ + | Copyright (c) 2012-2015 The Swoole Group | + +----------------------------------------------------------------------+ + | 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 "swoole_message_bus.h" +#include "swoole_process_pool.h" + +#include + +using swoole::network::Address; +using swoole::network::Socket; + +namespace swoole { + +PacketPtr MessageBus::get_packet() const { + PacketPtr pkt; + if (buffer_->info.flags & SW_EVENT_DATA_PTR) { + memcpy(&pkt, buffer_->data, sizeof(pkt)); + } else if (buffer_->info.flags & SW_EVENT_DATA_OBJ_PTR) { + String *object; + memcpy(&object, buffer_->data, sizeof(object)); + pkt.data = object->str; + pkt.length = object->length; + } else { + pkt.data = buffer_->data; + pkt.length = buffer_->info.len; + } + + return pkt; +} + +bool MessageBus::alloc_buffer() { + void *_ptr = allocator_->malloc(buffer_size_); + if (_ptr) { + buffer_ = (PipeBuffer *) _ptr; + sw_memset_zero(&buffer_->info, sizeof(buffer_->info)); + return true; + } else { + return false; + } +} + +void MessageBus::pass(const SendData *task) const { + memcpy(&buffer_->info, &task->info, sizeof(buffer_->info)); + if (task->info.len > 0) { + buffer_->info.flags = SW_EVENT_DATA_PTR; + PacketPtr pkt{task->info.len, (char *) task->data}; + buffer_->info.len = sizeof(pkt); + memcpy(buffer_->data, &pkt, sizeof(pkt)); + } +} + +char *MessageBus::move_packet() { + uint64_t msg_id = buffer_->info.msg_id; + auto iter = packet_pool_.find(msg_id); + if (iter != packet_pool_.end()) { + auto str = iter->second.get(); + char *val = str->str; + str->str = nullptr; + return val; + } else { + return nullptr; + } +} + +String *MessageBus::get_packet_buffer() { + String *packet_buffer = nullptr; + + auto iter = packet_pool_.find(buffer_->info.msg_id); + if (iter == packet_pool_.end()) { + if (!buffer_->is_begin()) { + return nullptr; + } + packet_buffer = make_string(buffer_->info.len, allocator_); + packet_pool_.emplace(buffer_->info.msg_id, std::shared_ptr(packet_buffer)); + } else { + packet_buffer = iter->second.get(); + } + + return packet_buffer; +} + +ReturnCode MessageBus::prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer) { + recv_chunk_count++; + if (!buffer_->is_end()) { + /** + * if the reactor thread sends too many chunks to the worker process, + * the worker process may receive chunks all the time, + * resulting in the worker process being unable to handle other tasks. + * in order to make the worker process handle tasks fairly, + * the maximum number of consecutive chunks received by the worker is limited. + */ + if (recv_chunk_count >= SW_WORKER_MAX_RECV_CHUNK_COUNT) { + swoole_trace_log(SW_TRACE_WORKER, + "worker#%d receives the chunk data to the maximum[%d], return to event loop", + swoole_get_worker_id(), + recv_chunk_count); + return SW_WAIT; + } + return SW_CONTINUE; + } else { + /** + * Because we don't want to split the EventData parameters into DataHead and data, + * we store the value of the worker_buffer pointer in EventData.data. + * The value of this pointer will be fetched in the Server::get_pipe_packet() function. + */ + buffer_->info.flags |= SW_EVENT_DATA_OBJ_PTR; + memcpy(buffer_->data, &packet_buffer, sizeof(packet_buffer)); + swoole_trace("msg_id=%" PRIu64 ", len=%u", buffer_->info.msg_id, buffer_->info.len); + + return SW_READY; + } +} + +/** + * @return -1: a fatal error has occurred and needs to be terminated + * @return 0: continue + * @return >0: success + */ +ssize_t MessageBus::read(Socket *sock) { + ssize_t recv_n = 0; + uint16_t recv_chunk_count = 0; + DataHead *info = &buffer_->info; + struct iovec buffers[2]; + +_read_from_pipe: + recv_n = recv(sock->get_fd(), info, sizeof(buffer_->info), MSG_PEEK); + if (recv_n < 0) { + if (sock->catch_read_error(errno) == SW_WAIT) { + return SW_OK; + } + return SW_ERR; + } else if (recv_n == 0) { + swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); + return SW_ERR; + } + + if (!buffer_->is_chunked()) { + return sock->read(buffer_, sizeof(buffer_->info) + buffer_->info.len); + } + + auto packet_buffer = get_packet_buffer(); + if (packet_buffer == nullptr) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, + "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", + info->msg_id, + sock->get_fd(), + info->reactor_id); + // Read data from the socket buffer and discard it. + recv(sock->get_fd(), info, sizeof(buffer_->info), 0); + return SW_OK; + } + + size_t remain_len = buffer_->info.len - packet_buffer->length; + buffers[0].iov_base = info; + buffers[0].iov_len = sizeof(buffer_->info); + buffers[1].iov_base = packet_buffer->str + packet_buffer->length; + buffers[1].iov_len = SW_MIN(buffer_size_ - sizeof(buffer_->info), remain_len); + + recv_n = readv(sock->get_fd(), buffers, 2); + if (recv_n == 0) { + swoole_warning("receive pipeline data error, pipe_fd=%d, reactor_id=%d", sock->get_fd(), info->reactor_id); + return SW_ERR; + } + if (recv_n < 0 && sock->catch_read_error(errno) == SW_WAIT) { + return SW_OK; + } + if (recv_n > 0) { + packet_buffer->length += (recv_n - sizeof(buffer_->info)); + swoole_trace("append msgid=%" PRIu64 ", buffer=%p, n=%ld", buffer_->info.msg_id, packet_buffer, recv_n); + } + + switch (prepare_packet(recv_chunk_count, packet_buffer)) { + case SW_READY: + return recv_n; + case SW_CONTINUE: + goto _read_from_pipe; + case SW_WAIT: + return SW_OK; + default: + assert(0); + return SW_ERR; + } +} + +/** + * Notice: only supports dgram type socket + */ +ssize_t MessageBus::read_with_buffer(Socket *sock) { + ssize_t recv_n; + uint16_t recv_chunk_count = 0; + +_read_from_pipe: + recv_n = sock->read(buffer_, buffer_size_); + if (recv_n < 0) { + if (sock->catch_read_error(errno) == SW_WAIT) { + return SW_OK; + } + return SW_ERR; + } else if (recv_n == 0) { + swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); + return SW_ERR; + } + + recv_chunk_count++; + + if (!buffer_->is_chunked()) { + return recv_n; + } + + String *packet_buffer = get_packet_buffer(); + if (packet_buffer == nullptr) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, + "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", + buffer_->info.msg_id, + sock->get_fd(), + buffer_->info.reactor_id); + return SW_ERR; + } + packet_buffer->append(buffer_->data, recv_n - sizeof(buffer_->info)); + + switch (prepare_packet(recv_chunk_count, packet_buffer)) { + case SW_READY: + return recv_n; + case SW_CONTINUE: + goto _read_from_pipe; + case SW_WAIT: + return SW_OK; + default: + assert(0); + return SW_ERR; + } +} + +bool MessageBus::write(Socket *sock, SendData *resp) const { + const char *payload = resp->data; + uint32_t l_payload = resp->info.len; + off_t offset = 0; + uint32_t copy_n; + + struct iovec iov[2]; + + uint64_t msg_id = id_generator_(); + uint32_t max_length = buffer_size_ - sizeof(resp->info); + resp->info.msg_id = msg_id; + + auto send_fn = [](Socket *sock, const iovec *iov, size_t iovcnt) { + if (swoole_event_is_available()) { + return swoole_event_writev(sock, iov, iovcnt); + } else { + return sock->writev_sync(iov, iovcnt); + } + }; + + if (l_payload == 0 || payload == nullptr) { + resp->info.flags = 0; + resp->info.len = 0; + iov[0].iov_base = &resp->info; + iov[0].iov_len = sizeof(resp->info); + return send_fn(sock, iov, 1) == (ssize_t) iov[0].iov_len; + } + + if (!always_chunked_transfer_ && l_payload <= max_length) { + resp->info.flags = 0; + resp->info.len = l_payload; + iov[0].iov_base = &resp->info; + iov[0].iov_len = sizeof(resp->info); + iov[1].iov_base = (void *) payload; + iov[1].iov_len = l_payload; + + if (send_fn(sock, iov, 2) == (ssize_t) (sizeof(resp->info) + l_payload)) { + return true; + } + if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_IPC_MSG_MIN) { + max_length = SW_IPC_MSG_MIN; + } else { + return false; + } + } + + resp->info.flags = SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN; + resp->info.len = l_payload; + + while (l_payload > 0) { + if (l_payload > max_length) { + copy_n = max_length; + } else { + resp->info.flags |= SW_EVENT_DATA_END; + copy_n = l_payload; + } + + iov[0].iov_base = &resp->info; + iov[0].iov_len = sizeof(resp->info); + iov[1].iov_base = (void *) (payload + offset); + iov[1].iov_len = copy_n; + + swoole_trace("finish, type=%d|len=%u", resp->info.type, copy_n); + + if (send_fn(sock, iov, 2) < 0) { + if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_IPC_MSG_MIN) { + max_length = SW_IPC_MSG_MIN; + if (resp->info.flags & SW_EVENT_DATA_END) { + resp->info.flags &= ~SW_EVENT_DATA_END; + } + continue; + } + return false; + } + + if (resp->info.flags & SW_EVENT_DATA_BEGIN) { + resp->info.flags &= ~SW_EVENT_DATA_BEGIN; + } + + l_payload -= copy_n; + offset += copy_n; + } + + return true; +} + +size_t MessageBus::get_memory_size() const { + size_t size = buffer_size_; + for (auto &p : packet_pool_) { + size += p.second->size; + } + return size; +} + +void MessageBus::init_pipe_socket(const Socket *sock) { + int pipe_fd = sock->get_fd(); + if ((size_t) pipe_fd >= pipe_sockets_.size()) { + pipe_sockets_.resize(pipe_fd + 1); + } + auto _socket = make_socket(pipe_fd, SW_FD_PIPE); + _socket->buffer_size = UINT_MAX; + if (!_socket->nonblock) { + _socket->set_nonblock(); + } + pipe_sockets_[pipe_fd] = _socket; +} + +MessageBus::~MessageBus() { + for (auto _socket : pipe_sockets_) { + if (_socket) { + _socket->fd = -1; + _socket->free(); + } + } +} + +} // namespace swoole diff --git a/src/protocol/mime_type.cc b/src/protocol/mime_type.cc index 44d043c942..177606b285 100644 --- a/src/protocol/mime_type.cc +++ b/src/protocol/mime_type.cc @@ -18,6 +18,8 @@ #include "swoole_mime_type.h" +#include + namespace swoole { namespace mime_type { @@ -389,7 +391,9 @@ static std::unordered_map map_( static const std::string octet_stream("application/octet-stream"); static std::string get_suffix(const std::string &filename) { - return std::string(filename).substr(filename.find_last_of('.') + 1); + std::string suffix = std::string(filename).substr(filename.find_last_of('.') + 1); + std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); + return suffix; } const std::unordered_map &list() { diff --git a/src/protocol/mqtt.cc b/src/protocol/mqtt.cc index f2ce9de04f..cfc1f96583 100644 --- a/src/protocol/mqtt.cc +++ b/src/protocol/mqtt.cc @@ -51,7 +51,7 @@ ssize_t get_package_length(const Protocol *protocol, Socket *conn, PacketLength int mul = 1; ssize_t length = 0; ssize_t variable_header_byte_count = 0; - while (1) { + while (true) { variable_header_byte_count++; byte = pl->buf[variable_header_byte_count]; length += (byte & 127) * mul; diff --git a/src/protocol/redis.cc b/src/protocol/redis.cc index c625f0c717..9c3e296010 100644 --- a/src/protocol/redis.cc +++ b/src/protocol/redis.cc @@ -16,7 +16,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_protocol.h" @@ -38,6 +37,19 @@ struct Request { int offset; }; +const char *get_number(const char *p, int *_ret) { + char *endptr; + p++; + int ret = strtol(p, &endptr, 10); + if (strncmp(SW_CRLF, endptr, SW_CRLF_LEN) == 0) { + p += (endptr - p) + SW_CRLF_LEN; + *_ret = ret; + return p; + } else { + return nullptr; + } +} + int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { const char *p, *pe; int ret; @@ -48,7 +60,7 @@ int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { network::Socket *socket = conn->socket; if (conn->object == nullptr) { - request = (Request *) sw_malloc(sizeof(Request)); + request = static_cast(sw_malloc(sizeof(Request))); if (!request) { swoole_warning("malloc(%ld) failed", sizeof(Request)); return SW_ERR; @@ -56,7 +68,7 @@ int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { sw_memset_zero(request, sizeof(Request)); conn->object = request; } else { - request = (Request *) conn->object; + request = static_cast(conn->object); } _recv_data: @@ -85,9 +97,7 @@ int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { if (extend_size > protocol->package_max_length) { extend_size = protocol->package_max_length; } - if (!buffer->extend(extend_size)) { - return SW_ERR; - } + buffer->extend(extend_size); } else if (buffer->length == buffer->size) { _package_too_big: swoole_warning("Package is too big. package_length=%ld", buffer->length); @@ -173,40 +183,34 @@ int recv_packet(Protocol *protocol, Connection *conn, String *buffer) { return SW_ERR; } -bool format(String *buf) { - return buf->append(SW_STRL(SW_REDIS_RETURN_NIL)) == SW_OK; +void format_nil(String *buf) { + buf->append(SW_STRL(SW_REDIS_RETURN_NIL)); } -bool format(String *buf, enum ReplyType type, const std::string &value) { +void format(String *buf, ReplyType type, const std::string &value) { if (type == REPLY_STATUS) { if (value.empty()) { - return buf->append(SW_STRL("+OK\r\n")) == SW_OK; + buf->append(SW_STRL("+OK\r\n")); } else { - return buf->format("+%.*s\r\n", value.length(), value.c_str()) > 0; + buf->format("+%.*s\r\n", value.length(), value.c_str()); } } else if (type == REPLY_ERROR) { if (value.empty()) { - return buf->append(SW_STRL("-ERR\r\n")) == SW_OK; + buf->append(SW_STRL("-ERR\r\n")); } else { - return buf->format("-%.*s\r\n", value.length(), value.c_str()) > 0; + buf->format("-%.*s\r\n", value.length(), value.c_str()); } } else if (type == REPLY_STRING) { - if (value.empty() or value.length() > SW_REDIS_MAX_STRING_SIZE) { - return false; - } else { - if (buf->format("$%zu\r\n", value.length()) == 0) { - return false; - } + if (!value.empty()) { + buf->format("$%zu\r\n", value.length()); buf->append(value); buf->append(SW_CRLF, SW_CRLF_LEN); - return true; } } - return false; } -bool format(String *buf, enum ReplyType type, long value) { - return buf->format(":%" PRId64 "\r\n", value) > 0; +void format(String *buf, ReplyType type, long value) { + buf->format(":%" PRId64 "\r\n", value); } std::vector parse(const char *data, size_t len) { @@ -221,14 +225,14 @@ std::vector parse(const char *data, size_t len) { do { switch (state) { case STATE_RECEIVE_TOTAL_LINE: - if (*p == '*' && (p = get_number(p, &ret))) { + if (*p == '*' && ((p = get_number(p, &ret)))) { state = STATE_RECEIVE_LENGTH; break; } /* no break */ case STATE_RECEIVE_LENGTH: - if (*p == '$' && (p = get_number(p, &ret))) { + if (*p == '$' && ((p = get_number(p, &ret)))) { if (ret == -1) { break; } @@ -237,14 +241,14 @@ std::vector parse(const char *data, size_t len) { break; } // integer - else if (*p == ':' && (p = get_number(p, &ret))) { + else if (*p == ':' && ((p = get_number(p, &ret)))) { result.push_back(std::to_string(ret)); break; } /* no break */ case STATE_RECEIVE_STRING: - result.push_back(std::string(p, length)); + result.emplace_back(p, length); p += length + SW_CRLF_LEN; state = STATE_RECEIVE_LENGTH; break; diff --git a/src/protocol/socks5.cc b/src/protocol/socks5.cc index f58939666c..ba8691a572 100644 --- a/src/protocol/socks5.cc +++ b/src/protocol/socks5.cc @@ -17,6 +17,7 @@ */ #include "swoole_proxy.h" +#include "swoole_socket.h" namespace swoole { const char *Socks5Proxy::strerror(int code) { @@ -41,4 +42,175 @@ const char *Socks5Proxy::strerror(int code) { return "Unknown error"; } } + +Socks5Proxy *Socks5Proxy::create( + int socket_type, const std::string &host, int port, const std::string &user, const std::string &pwd) { + if (user.length() > 250 || pwd.length() > 250) { + swoole_error_log(SW_LOG_NOTICE, + SW_ERROR_SOCKS5_AUTH_FAILED, + "SOCKS5 username or password is too long, max length is 250 bytes"); + return nullptr; + } + auto socks5_proxy = new Socks5Proxy(); + socks5_proxy->host = host; + socks5_proxy->port = port; + socks5_proxy->dns_tunnel = 1; + socks5_proxy->socket_type = socket_type; + if (!user.empty() && !pwd.empty()) { + socks5_proxy->username = user; + socks5_proxy->password = pwd; + } + return socks5_proxy; +} + +ssize_t Socks5Proxy::pack_negotiate_request() { + char *p = buf; + p[0] = SW_SOCKS5_VERSION_CODE; // Version + p[1] = 0x01; + method = username.empty() ? SW_SOCKS5_METHOD_NO_AUTH : SW_SOCKS5_METHOD_AUTH; + p[2] = method; + return 3; +} + +ssize_t Socks5Proxy::pack_auth_request() { + char *p = buf; + // username + p[0] = 0x01; + p[1] = username.length(); + p += 2; + if (!username.empty()) { + memcpy(p, username.c_str(), username.length()); + p += username.length(); + } + // password + p[0] = password.length(); + p += 1; + if (!password.empty()) { + memcpy(p, password.c_str(), password.length()); + p += password.length(); + } + return p - buf; +} + +bool Socks5Proxy::handshake(const char *rbuf, + size_t rlen, + const std::function &send_fn) { + if (rlen < 2) { + swoole_error_log( + SW_LOG_NOTICE, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, "SOCKS5 handshake failed, data length is too short"); + return false; + } + + const uchar resp_version = rbuf[0]; + const uchar resp_result = rbuf[1]; + + if (state == SW_SOCKS5_STATE_HANDSHAKE) { + if (resp_version != SW_SOCKS5_VERSION_CODE) { + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); + return false; + } + if (method != resp_result) { + swoole_error_log( + SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_METHOD, "SOCKS authentication method is not supported"); + return false; + } + // authenticate request + if (method == SW_SOCKS5_METHOD_AUTH) { + const auto len = pack_auth_request(); + state = SW_SOCKS5_STATE_AUTH; + return send_fn(buf, len) == len; + } + // send connect request + else { + _send_connect_request: + state = SW_SOCKS5_STATE_CONNECT; + const auto len = pack_connect_request(); + if (len < 0) { + return false; + } + return send_fn(buf, len) == len; + } + } else if (state == SW_SOCKS5_STATE_AUTH) { + if (resp_version != 0x01) { + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); + return false; + } + if (resp_result != 0) { + swoole_error_log( + SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS username/password authentication failed"); + return false; + } + goto _send_connect_request; + } else if (state == SW_SOCKS5_STATE_CONNECT) { + if (resp_version != SW_SOCKS5_VERSION_CODE) { + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_UNSUPPORT_VERSION, "SOCKS version is not supported"); + return false; + } +#if 0 + uchar reg = recv_data[2]; + uchar type = recv_data[3]; + uint32_t ip = *(uint32_t *) (recv_data + 4); + uint16_t port = *(uint16_t *) (recv_data + 8); +#endif + if (resp_result == 0) { + state = SW_SOCKS5_STATE_READY; + return true; + } else { + swoole_error_log(SW_LOG_NOTICE, + SW_ERROR_SOCKS5_SERVER_ERROR, + "Socks5 server error, reason :%s", + Socks5Proxy::strerror(resp_result)); + return false; + } + } + return true; +} + +ssize_t Socks5Proxy::pack_connect_request() { + char *p = buf; + p[0] = SW_SOCKS5_VERSION_CODE; + p[1] = 0x01; // CONNECT command + p[2] = 0x00; // Reserved byte + p += 3; + + if (dns_tunnel) { + if (host.length() > 480) { + swoole_error_log( + SW_LOG_NOTICE, SW_ERROR_SOCKS5_AUTH_FAILED, "SOCKS5 host is too long, max length is 480 bytes"); + return -1; + } + p[0] = 0x03; + p[1] = target_host.length(); + p += 2; + memcpy(p, target_host.c_str(), target_host.length()); + p += target_host.length(); + } else { + network::Address target_addr; + if (!target_addr.assign(static_cast(socket_type), target_host, target_port, false)) { + swoole_error_log( + SW_LOG_NOTICE, + SW_ERROR_SOCKS5_HANDSHAKE_FAILED, + "When disable SOCKS5 proxy DNS tunnel connection, the destination host must be an IP address."); + return SW_ERR; + } + if (network::Socket::is_inet4(static_cast(socket_type))) { + p[0] = 0x01; // IPv4 address type + p += 1; + memcpy(p, &target_addr.addr.inet_v4.sin_addr, sizeof(target_addr.addr.inet_v4.sin_addr)); + p += sizeof(target_addr.addr.inet_v4.sin_addr); + } else if (network::Socket::is_inet6(static_cast(socket_type))) { + p[0] = 0x04; // IPv6 address type + p += 1; + memcpy(p, &target_addr.addr.inet_v6.sin6_addr, sizeof(target_addr.addr.inet_v6.sin6_addr)); + p += sizeof(target_addr.addr.inet_v6.sin6_addr); + } else { + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SOCKS5_HANDSHAKE_FAILED, "Unsupported socket type for SOCKS5"); + return SW_ERR; + } + } + const auto _target_port = htons(target_port); + memcpy(p, &_target_port, sizeof(_target_port)); + p += 2; + return p - buf; +} } // namespace swoole diff --git a/src/protocol/ssl.cc b/src/protocol/ssl.cc index 72cd5592d2..1af3c1ed61 100644 --- a/src/protocol/ssl.cc +++ b/src/protocol/ssl.cc @@ -14,32 +14,24 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_string.h" #include "swoole_socket.h" #include "swoole_ssl.h" #include "swoole_util.h" -#ifdef SW_USE_OPENSSL - using swoole::SSLContext; using swoole::network::Address; using swoole::network::Socket; -#if OPENSSL_VERSION_NUMBER < 0x10000000L -#error "require openssl version 1.0 or later" +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#error "OpenSSL 1.1.0 or later is required" #endif static bool openssl_init = false; -static bool openssl_thread_safety_init = false; static int ssl_connection_index = 0; static int ssl_port_index = 0; -static pthread_mutex_t *lock_array; static int swoole_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); -#ifndef OPENSSL_NO_RSA -static RSA *swoole_ssl_rsa_key_callback(SSL *ssl, int is_export, int key_length); -#endif #if OPENSSL_VERSION_NUMBER < 0x10100000L static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context); #endif @@ -49,31 +41,20 @@ static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len); #endif -#ifdef __GNUC__ -#define MAYBE_UNUSED __attribute__((used)) -#else -#define MAYBE_UNUSED -#endif - std::string swoole_ssl_get_version_message() { return swoole::std_string::format("OPENSSL_VERSION: %s\n", OPENSSL_VERSION_TEXT); } -static void MAYBE_UNUSED swoole_ssl_lock_callback(int mode, int type, const char *file, int line); - -void swoole_ssl_init(void) { +void swoole_ssl_init() { if (openssl_init) { return; } -#if OPENSSL_VERSION_NUMBER >= 0x10100003L && !defined(LIBRESSL_VERSION_NUMBER) - OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, - nullptr); -#else - OPENSSL_config(nullptr); - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); -#endif + + if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, + nullptr)) { + swoole_error("OPENSSL_init_ssl() failed"); + return; + } ssl_connection_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); if (ssl_connection_index < 0) { @@ -102,87 +83,29 @@ void swoole_ssl_destroy() { if (!openssl_init) { return; } - - SW_LOOP_N(CRYPTO_num_locks()) { - pthread_mutex_destroy(&(lock_array[i])); - } - - OPENSSL_free(lock_array); - -#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0 - (void) CRYPTO_THREADID_set_callback(nullptr); -#else - CRYPTO_set_id_callback(nullptr); -#endif - CRYPTO_set_locking_callback(nullptr); openssl_init = false; - openssl_thread_safety_init = false; -} - -static void MAYBE_UNUSED swoole_ssl_lock_callback(int mode, int type, const char *file, int line) { - if (mode & CRYPTO_LOCK) { - pthread_mutex_lock(&(lock_array[type])); - } else { - pthread_mutex_unlock(&(lock_array[type])); - } } static int ssl_error_cb(const char *str, size_t len, void *buf) { - memcpy(buf, str, len); - + auto s = static_cast(buf); + memcpy(s->str, str, len); + s->length = len; + s->set_null_terminated(); return 0; } const char *swoole_ssl_get_error() { - ERR_print_errors_cb(ssl_error_cb, sw_tg_buffer()->str); - + sw_tg_buffer()->clear(); + sw_tg_buffer()->set_null_terminated(); + ERR_print_errors_cb(ssl_error_cb, sw_tg_buffer()); return sw_tg_buffer()->str; } -#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0 -static void MAYBE_UNUSED swoole_ssl_id_callback(CRYPTO_THREADID *id) { - CRYPTO_THREADID_set_numeric(id, (ulong_t) pthread_self()); -} -#else -static ulong_t swoole_ssl_id_callback(void) { - return (ulong_t) pthread_self(); -} -#endif - -void swoole_ssl_init_thread_safety() { - if (!openssl_init) { - return; - } - - if (openssl_thread_safety_init) { - return; - } - - lock_array = (pthread_mutex_t *) OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t)); - SW_LOOP_N(CRYPTO_num_locks()) { - pthread_mutex_init(&(lock_array[i]), nullptr); - } - -#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0 - (void) CRYPTO_THREADID_set_callback(swoole_ssl_id_callback); -#else - CRYPTO_set_id_callback(swoole_ssl_id_callback); -#endif - - CRYPTO_set_locking_callback(swoole_ssl_lock_callback); - openssl_thread_safety_init = true; -} - -bool swoole_ssl_is_thread_safety() { - return openssl_thread_safety_init; -} - static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) { - BIO *rbio, *wbio; - swSocket *sock; + Socket *sock; if (where & SSL_CB_HANDSHAKE_START) { - sock = (swSocket *) SSL_get_ex_data(ssl, ssl_connection_index); + sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index); if (sock->ssl_state == SW_SSL_STATE_READY) { sock->ssl_renegotiation = 1; @@ -191,11 +114,11 @@ static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) { } if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) { - sock = (swSocket *) SSL_get_ex_data(ssl, ssl_connection_index); + sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index); if (!sock->ssl_handshake_buffer_set) { /* - * By default OpenSSL uses 4k buffer during a handshake, + * By default, OpenSSL uses 4k buffer during a handshake, * which is too low for long certificate chains and might * result in extra round-trips. * @@ -204,9 +127,8 @@ static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) { * If they are different, we assume that it's due to buffering * added to wbio, and set buffer size. */ - - rbio = SSL_get_rbio(ssl); - wbio = SSL_get_wbio(ssl); + BIO *rbio = SSL_get_rbio(ssl); + BIO *wbio = SSL_get_wbio(ssl); if (rbio != wbio) { (void) BIO_set_write_buffer_size(wbio, SW_SSL_BUFFER_SIZE); @@ -218,36 +140,21 @@ static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) { namespace swoole { -#ifndef OPENSSL_NO_NEXTPROTONEG - #define HTTP2_H2_ALPN "\x02h2" #define HTTP2_H2_16_ALPN "\x05h2-16" #define HTTP2_H2_14_ALPN "\x05h2-14" #define HTTP1_NPN "\x08http/1.1" -static bool ssl_select_proto(const uchar **out, uchar *outlen, const uchar *in, uint inlen, const std::string &key) { - for (auto p = in, end = in + inlen; p + key.size() <= end; p += *p + 1) { - if (std::equal(std::begin(key), std::end(key), p)) { - *out = p + 1; - *outlen = *p; - return true; - } - } - return false; -} - -static bool ssl_select_h2(const uchar **out, uchar *outlen, const uchar *in, uint inlen) { - return ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_ALPN) || - ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_16_ALPN) || - ssl_select_proto(out, outlen, in, inlen, HTTP2_H2_14_ALPN); -} +#define ssl_error(_fmt, ...) \ + long _ssl_error = ERR_get_error(); \ + swoole_warning(_fmt ", Error: %s[%ld]", ##__VA_ARGS__, ERR_reason_error_string(_ssl_error), _ssl_error); #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation static int ssl_alpn_advertised(SSL *ssl, const uchar **out, uchar *outlen, const uchar *in, uint32_t inlen, void *arg) { unsigned int protos_len; const char *protos; - SSLContext *cfg = (SSLContext *) arg; + auto *cfg = (SSLContext *) arg; if (cfg->http_v2) { protos = HTTP2_H2_ALPN HTTP1_NPN; protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1; @@ -256,7 +163,7 @@ static int ssl_alpn_advertised(SSL *ssl, const uchar **out, uchar *outlen, const protos_len = sizeof(HTTP1_NPN) - 1; } - if (SSL_select_next_proto((unsigned char **) out, outlen, (const uchar *) protos, protos_len, in, inlen) != + if (SSL_select_next_proto((uchar **) out, outlen, (const uchar *) protos, protos_len, in, inlen) != OPENSSL_NPN_NEGOTIATED) { return SSL_TLSEXT_ERR_NOACK; } @@ -264,34 +171,14 @@ static int ssl_alpn_advertised(SSL *ssl, const uchar **out, uchar *outlen, const } #endif -static int ssl_select_next_proto_cb(SSL *ssl, uchar **out, uchar *outlen, const uchar *in, uint inlen, void *arg) { -#ifdef SW_LOG_TRACE_OPEN - std::string info("[NPN] server offers:\n"); - for (unsigned int i = 0; i < inlen; i += in[i] + 1) { - info += " * " + std::string(reinterpret_cast(&in[i + 1]), in[i]); - } - swoole_trace_log(SW_TRACE_HTTP2, "[NPN] server offers: %s", info.c_str()); -#endif - SSLContext *ctx = (SSLContext *) arg; - if (ctx->http_v2 && !ssl_select_h2(const_cast(out), outlen, in, inlen)) { - swoole_warning("HTTP/2 protocol was not selected, expects [h2]"); - return SSL_TLSEXT_ERR_NOACK; - } else if (ctx->http) { - *out = (uchar *) HTTP1_NPN; - *outlen = sizeof(HTTP1_NPN) - 1; - } - return SSL_TLSEXT_ERR_OK; -} -#endif - static int ssl_passwd_callback(char *buf, int num, int verify, void *data) { - SSLContext *ctx = (SSLContext *) data; + const auto *ctx = static_cast(data); if (!ctx->passphrase.empty()) { - int len = ctx->passphrase.length(); + const int len = static_cast(ctx->passphrase.length()); if (len < num - 1) { memcpy(buf, ctx->passphrase.c_str(), len); buf[len] = '\0'; - return (int) len; + return len; } } return 0; @@ -316,8 +203,7 @@ bool SSLContext::create() { } context = SSL_CTX_new(method); if (context == nullptr) { - int error = ERR_get_error(); - swoole_warning("SSL_CTX_new() failed, Error: %s[%d]", ERR_reason_error_string(error), error); + ssl_error("SSL_CTX_new() failed"); return false; } @@ -425,11 +311,7 @@ bool SSLContext::create() { * set the local certificate from CertFile */ if (SSL_CTX_use_certificate_file(context, cert_file.c_str(), SSL_FILETYPE_PEM) <= 0) { - int error = ERR_get_error(); - swoole_warning("SSL_CTX_use_certificate_file(%s) failed, Error: %s[%d]", - cert_file.c_str(), - ERR_reason_error_string(error), - error); + ssl_error("SSL_CTX_use_certificate_file(%s) failed", cert_file.c_str()); return true; } /* @@ -437,31 +319,23 @@ bool SSLContext::create() { * we need call this function */ if (SSL_CTX_use_certificate_chain_file(context, cert_file.c_str()) <= 0) { - int error = ERR_get_error(); - swoole_warning("SSL_CTX_use_certificate_chain_file(%s) failed, Error: %s[%d]", - cert_file.c_str(), - ERR_reason_error_string(error), - error); + ssl_error("SSL_CTX_use_certificate_chain_file(%s) failed", cert_file.c_str()); return false; } } if (!key_file.empty()) { /* - * set the private key from KeyFile (may be the same as CertFile) + * set the private key from KeyFile (maybe the same as CertFile) */ if (SSL_CTX_use_PrivateKey_file(context, key_file.c_str(), SSL_FILETYPE_PEM) <= 0) { - int error = ERR_get_error(); - swoole_warning("SSL_CTX_use_PrivateKey_file(%s) failed, Error: %s[%d]", - key_file.c_str(), - ERR_reason_error_string(error), - error); + ssl_error("SSL_CTX_use_PrivateKey_file(%s) failed", key_file.c_str()); return false; } /* * verify private key */ if (!SSL_CTX_check_private_key(context)) { - swoole_warning("Private key does not match the public certificate"); + ssl_error("SSL_CTX_check_private_key() failed"); return false; } } @@ -475,13 +349,15 @@ bool SSLContext::create() { } #endif - if (verify_peer && !set_capath()) { - return false; + auto rs = set_capath(); + if (verify_peer) { + if (!rs) { + return false; + } } else { SSL_CTX_set_verify(context, SSL_VERIFY_NONE, nullptr); } -#if OPENSSL_VERSION_NUMBER >= 0x10002000L if (http || http_v2) { unsigned int protos_len; const char *protos; @@ -490,31 +366,24 @@ bool SSLContext::create() { protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1; } else { protos = HTTP1_NPN; - protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1; + protos_len = sizeof(HTTP1_NPN) - 1; } -#ifndef OPENSSL_NO_NEXTPROTONEG - SSL_CTX_set_next_proto_select_cb(context, ssl_select_next_proto_cb, nullptr); -#endif if (SSL_CTX_set_alpn_protos(context, (const uchar *) protos, protos_len) < 0) { + ssl_error("SSL_CTX_set_alpn_protos(%s) failed", protos); return false; } - #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation SSL_CTX_set_alpn_select_cb(context, ssl_alpn_advertised, (void *) this); #endif - - SSL_CTX_set_session_id_context(context, (const unsigned char *) "HTTP", sizeof("HTTP") - 1); + SSL_CTX_set_session_id_context(context, (const uchar *) "HTTP", sizeof("HTTP") - 1); SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_SERVER); - SSL_CTX_sess_set_cache_size(context, 1); } -#endif #ifdef OPENSSL_IS_BORINGSSL SSL_CTX_set_grease_enabled(context, grease); #endif if (!client_cert_file.empty() && !set_client_certificate()) { - swoole_warning("set_client_certificate() error"); return false; } @@ -526,7 +395,7 @@ bool SSLContext::create() { return true; } -bool SSLContext::set_capath() { +bool SSLContext::set_capath() const { if (!cafile.empty() || !capath.empty()) { const char *_cafile = cafile.empty() ? nullptr : cafile.c_str(); const char *_capath = capath.empty() ? nullptr : capath.c_str(); @@ -535,7 +404,7 @@ bool SSLContext::set_capath() { } } else { if (!SSL_CTX_set_default_verify_paths(context)) { - swoole_warning("Unable to set default verify locations and no CA settings specified"); + ssl_error("SSL_CTX_set_default_verify_paths() failed"); return false; } } @@ -547,25 +416,22 @@ bool SSLContext::set_capath() { return true; } -bool SSLContext::set_ciphers() { +bool SSLContext::set_ciphers() const { #ifndef TLS1_2_VERSION return true; #endif if (!ciphers.empty()) { if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) == 0) { - swoole_warning("SSL_CTX_set_cipher_list(\"%s\") failed", ciphers.c_str()); + ssl_error("SSL_CTX_set_cipher_list(\"%s\") failed", ciphers.c_str()); return false; } - if (prefer_server_ciphers) { - SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE); + if (prefer_server_ciphers && !SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE)) { + ssl_error("SSL_CTX_set_options(SSL_OP_CIPHER_SERVER_PREFERENCE) failed"); + return false; } } -#ifndef OPENSSL_NO_RSA - SSL_CTX_set_tmp_rsa_callback(context, swoole_ssl_rsa_key_callback); -#endif - if (!dhparam.empty() && !set_dhparam()) { return false; } @@ -580,24 +446,22 @@ bool SSLContext::set_ciphers() { return true; } -bool SSLContext::set_client_certificate() { - STACK_OF(X509_NAME) * list; - - const char *cert_file = client_cert_file.c_str(); +bool SSLContext::set_client_certificate() const { + const char *_cert_file = client_cert_file.c_str(); int depth = verify_depth; SSL_CTX_set_verify(context, SSL_VERIFY_PEER, swoole_ssl_verify_callback); SSL_CTX_set_verify_depth(context, depth); - if (SSL_CTX_load_verify_locations(context, cert_file, nullptr) == 0) { - swoole_warning("SSL_CTX_load_verify_locations(\"%s\") failed", cert_file); + if (SSL_CTX_load_verify_locations(context, _cert_file, nullptr) == 0) { + ssl_error("SSL_CTX_load_verify_locations(\"%s\") failed", _cert_file); return false; } ERR_clear_error(); - list = SSL_load_client_CA_file(cert_file); + STACK_OF(X509_NAME) *list = SSL_load_client_CA_file(_cert_file); if (list == nullptr) { - swoole_warning("SSL_load_client_CA_file(\"%s\") failed", cert_file); + ssl_error("SSL_load_client_CA_file(\"%s\") failed", _cert_file); return false; } @@ -607,7 +471,7 @@ bool SSLContext::set_client_certificate() { return true; } -bool SSLContext::set_ecdh_curve() { +bool SSLContext::set_ecdh_curve() const { #ifndef OPENSSL_NO_ECDH /* * Elliptic-Curve Diffie-Hellman parameters are either "named curves" @@ -618,11 +482,11 @@ bool SSLContext::set_ecdh_curve() { #if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST) /* * OpenSSL 1.0.2+ allows configuring a curve list instead of a single - * curve previously supported. By default an internal list is used, + * curve previously supported. By default, an internal list is used, * with prime256v1 being preferred by server in OpenSSL 1.0.2b+ * and X25519 in OpenSSL 1.1.0+. * - * By default a curve preferred by the client will be used for + * By default, a curve preferred by the client will be used for * key exchange. The SSL_OP_CIPHER_SERVER_PREFERENCE option can * be used to prefer server curves instead, similar to what it * does for ciphers. @@ -669,21 +533,33 @@ bool SSLContext::set_ecdh_curve() { return true; } -bool SSLContext::set_dhparam() { - DH *dh; - BIO *bio; - +bool SSLContext::set_dhparam() const { const char *file = dhparam.c_str(); - bio = BIO_new_file(file, "r"); + BIO *bio = BIO_new_file(file, "r"); if (bio == nullptr) { - swoole_warning("BIO_new_file(%s) failed", file); + ssl_error("BIO_new_file(%s) failed", file); + return false; + } + +#if OPENSSL_VERSION_MAJOR >= 3 + EVP_PKEY *pkey = PEM_read_bio_Parameters(bio, nullptr); + if (pkey == nullptr) { + ssl_error("PEM_read_bio_Parameters('%s') failed", file); + BIO_free(bio); return false; } - dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); + if (SSL_CTX_set0_tmp_dh_pkey(context, pkey) != 1) { + ssl_error("SSL_CTX_set0_tmp_dh_pkey('%s') failed", file); + EVP_PKEY_free(pkey); + BIO_free(bio); + return false; + } +#else + DH *dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr); if (dh == nullptr) { - swoole_warning("PEM_read_bio_DHparams(%s) failed", file); + ssl_error("PEM_read_bio_DHparams(%s) failed", file); BIO_free(bio); return false; } @@ -691,6 +567,8 @@ bool SSLContext::set_dhparam() { SSL_CTX_set_tmp_dh(context, dh); DH_free(dh); +#endif + BIO_free(bio); return true; @@ -748,7 +626,7 @@ static void calculate_cookie(SSL *ssl, uchar *cookie_secret, uint cookie_length) } static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) { - uchar *buffer, result[EVP_MAX_MD_SIZE]; + uchar result[EVP_MAX_MD_SIZE]; uint length = 0, result_len; Address sa{}; @@ -772,7 +650,7 @@ static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) } length += sizeof(in_port_t); - buffer = (uchar *) OPENSSL_malloc(length); + auto *buffer = (uchar *) OPENSSL_malloc(length); if (buffer == nullptr) { swoole_sys_warning("out of memory"); @@ -812,31 +690,6 @@ static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_l } #endif -#ifndef OPENSSL_NO_RSA -static RSA *swoole_ssl_rsa_key_callback(SSL *ssl, int is_export, int key_length) { - static RSA *rsa_tmp = nullptr; - if (rsa_tmp) { - return rsa_tmp; - } - - BIGNUM *bn = BN_new(); - if (bn == nullptr) { - swoole_warning("allocation error generating RSA key"); - return nullptr; - } - - if (!BN_set_word(bn, RSA_F4) || ((rsa_tmp = RSA_new()) == nullptr) || - !RSA_generate_key_ex(rsa_tmp, key_length, bn, nullptr)) { - if (rsa_tmp) { - RSA_free(rsa_tmp); - } - rsa_tmp = nullptr; - } - BN_free(bn); - return rsa_tmp; -} -#endif - #if OPENSSL_VERSION_NUMBER < 0x10100000L static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context) { DH *dh; @@ -868,5 +721,3 @@ static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context) { return SW_OK; } #endif - -#endif diff --git a/src/protocol/websocket.cc b/src/protocol/websocket.cc index 52f40d0eb8..a4580b0ade 100644 --- a/src/protocol/websocket.cc +++ b/src/protocol/websocket.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_server.h" #include "swoole_websocket.h" @@ -26,13 +25,6 @@ using swoole::network::Socket; namespace swoole { namespace websocket { -static inline uint16_t get_ext_flags(uchar opcode, uchar flags) { - uint16_t ext_flags = opcode; - ext_flags = ext_flags << 8; - ext_flags += flags; - return ext_flags; -} - /* The following is websocket data frame: +-+-+-+-+-------+-+-------------+-------------------------------+ 0 1 2 3 | @@ -101,9 +93,9 @@ ssize_t get_package_length(const Protocol *protocol, Socket *conn, PacketLength return get_package_length_impl(pl); } -static sw_inline void mask(char *data, size_t len, const char *mask_key) { +void mask(char *data, size_t len, const char *mask_key) { size_t n = len / 8; - uint64_t mask_key64 = ((uint64_t)(*((uint32_t *) mask_key)) << 32) | *((uint32_t *) mask_key); + uint64_t mask_key64 = ((uint64_t) (*((uint32_t *) mask_key)) << 32) | *((uint32_t *) mask_key); size_t i; for (i = 0; i < n; i++) { @@ -115,10 +107,10 @@ static sw_inline void mask(char *data, size_t len, const char *mask_key) { } } -bool encode(String *buffer, const char *data, size_t length, char opcode, uint8_t _flags) { +bool encode(String *buffer, const char *data, size_t length, uint8_t opcode, uint8_t _flags) { int pos = 0; char frame_header[16]; - Header *header = (Header *) frame_header; + auto *header = (Header *) frame_header; header->FIN = !!(_flags & FLAG_FIN); header->OPCODE = opcode; header->RSV1 = !!(_flags & FLAG_RSV1); @@ -131,12 +123,12 @@ bool encode(String *buffer, const char *data, size_t length, char opcode, uint8_ header->LENGTH = length; } else if (length <= SW_WEBSOCKET_EXT16_MAX_LEN) { header->LENGTH = SW_WEBSOCKET_EXT16_LENGTH; - uint16_t *length_ptr = (uint16_t *) (frame_header + pos); + auto *length_ptr = (uint16_t *) (frame_header + pos); *length_ptr = htons(length); pos += sizeof(*length_ptr); } else { header->LENGTH = SW_WEBSOCKET_EXT64_LENGTH; - uint64_t *length_ptr = (uint64_t *) (frame_header + pos); + auto *length_ptr = (uint64_t *) (frame_header + pos); *length_ptr = swoole_hton64(length); pos += sizeof(*length_ptr); } @@ -165,7 +157,13 @@ bool encode(String *buffer, const char *data, size_t length, char opcode, uint8_ } bool decode(Frame *frame, char *data, size_t length) { - memcpy(frame, data, SW_WEBSOCKET_HEADER_LEN); + frame->header.OPCODE = data[0] & 0xf; + frame->header.RSV1 = (data[0] >> 6) & 0x1; + frame->header.RSV2 = (data[0] >> 5) & 0x1; + frame->header.RSV3 = (data[0] >> 4) & 0x1; + frame->header.FIN = (data[0] >> 7) & 0x1; + frame->header.MASK = (data[1] >> 7) & 0x1; + frame->header.LENGTH = data[1] & 0x7f; PacketLength pl{data, (uint32_t) length, 0}; ssize_t total_length = get_package_length_impl(&pl); @@ -200,10 +198,10 @@ bool decode(Frame *frame, char *data, size_t length) { return true; } -int pack_close_frame(String *buffer, int code, char *reason, size_t length, uint8_t flags) { +bool pack_close_frame(String *buffer, int code, const char *reason, size_t length, uint8_t flags) { if (sw_unlikely(length > SW_WEBSOCKET_CLOSE_REASON_MAX_LEN)) { swoole_warning("the max length of close reason is %d", SW_WEBSOCKET_CLOSE_REASON_MAX_LEN); - return SW_ERR; + return false; } char payload[SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_CLOSE_CODE_LEN + SW_WEBSOCKET_CLOSE_REASON_MAX_LEN]; @@ -214,29 +212,29 @@ int pack_close_frame(String *buffer, int code, char *reason, size_t length, uint } flags |= FLAG_FIN; if (!encode(buffer, payload, SW_WEBSOCKET_CLOSE_CODE_LEN + length, OPCODE_CLOSE, flags)) { - return SW_ERR; + return false; } - return SW_OK; + return true; } -void print_frame(Frame *frame) { - printf("FIN: %x, RSV1: %d, RSV2: %d, RSV3: %d, opcode: %d, MASK: %d, length: %ld\n", - frame->header.FIN, - frame->header.RSV1, - frame->header.RSV2, - frame->header.RSV3, - frame->header.OPCODE, - frame->header.MASK, - frame->payload_length); +void print_frame(const Frame *frame) { + sw_printf("FIN: %x, RSV1: %d, RSV2: %d, RSV3: %d, opcode: %d, MASK: %d, length: %ld\n", + frame->header.FIN, + frame->header.RSV1, + frame->header.RSV2, + frame->header.RSV3, + frame->header.OPCODE, + frame->header.MASK, + frame->payload_length); if (frame->payload_length) { - printf("payload: %.*s\n", (int) frame->payload_length, frame->payload); + sw_printf("payload: %.*s\n", (int) frame->payload_length, frame->payload); } } int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata) { - Server *serv = (Server *) proto->private_data_2; - Connection *conn = (Connection *) _socket->object; + auto *serv = (Server *) proto->private_data_2; + auto *conn = (Connection *) _socket->object; RecvData dispatch_data{}; String send_frame{}; const char *data = rdata->data; @@ -259,7 +257,7 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata case OPCODE_CONTINUATION: frame_buffer = conn->websocket_buffer; if (frame_buffer == nullptr) { - swoole_warning("bad frame[opcode=0]. remote_addr=%s:%d", conn->info.get_ip(), conn->info.get_port()); + swoole_warning("bad frame[opcode=0]. remote_addr=%s:%d", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } offset = length - ws.payload_length; @@ -267,7 +265,8 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata port = serv->get_port_by_fd(conn->fd); // frame data overflow if (frame_buffer->length + frame_length > port->protocol.package_max_length) { - swoole_warning("websocket frame is too big, remote_addr=%s:%d", conn->info.get_ip(), conn->info.get_port()); + swoole_warning( + "websocket frame is too big, remote_addr=%s:%d", conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } // merge incomplete data @@ -286,15 +285,15 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata case OPCODE_TEXT: case OPCODE_BINARY: { offset = length - ws.payload_length; - int ext_flags = get_ext_flags(ws.header.OPCODE, get_flags(&ws)); + uint16_t ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); if (!ws.header.FIN) { if (conn->websocket_buffer) { swoole_warning("merging incomplete frame, bad request. remote_addr=%s:%d", - conn->info.get_ip(), + conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } - conn->websocket_buffer = new swoole::String(data + offset, length - offset); + conn->websocket_buffer = new String(data + offset, length - offset); conn->websocket_buffer->offset = ext_flags; } else { dispatch_data.info.ext_flags = ext_flags; @@ -309,7 +308,7 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata if (length >= (sizeof(buf) - SW_WEBSOCKET_HEADER_LEN)) { swoole_warning("%s frame application data is too big. remote_addr=%s:%d", ws.header.OPCODE == OPCODE_PING ? "ping" : "pong", - conn->info.get_ip(), + conn->info.get_addr(), conn->info.get_port()); return SW_ERR; } else if (length == SW_WEBSOCKET_HEADER_LEN) { @@ -320,7 +319,7 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata dispatch_data.info.len = length - offset; dispatch_data.data = dispatch_data.info.len == 0 ? nullptr : data + offset; } - dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, get_flags(&ws)); + dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); Server::dispatch_task(proto, _socket, &dispatch_data); break; @@ -332,7 +331,7 @@ int dispatch_frame(const Protocol *proto, Socket *_socket, const RecvData *rdata if (conn->websocket_status != STATUS_CLOSING) { // Dispatch the frame with the same format of message frame offset = length - ws.payload_length; - dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, get_flags(&ws)); + dispatch_data.info.ext_flags = get_ext_flags(ws.header.OPCODE, ws.get_flags()); dispatch_data.info.len = length - offset; dispatch_data.data = data + offset; Server::dispatch_task(proto, _socket, &dispatch_data); diff --git a/src/reactor/base.cc b/src/reactor/base.cc index 0f59306201..505aaf7ce9 100644 --- a/src/reactor/base.cc +++ b/src/reactor/base.cc @@ -14,12 +14,12 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_socket.h" #include "swoole_signal.h" #include "swoole_reactor.h" +#include "swoole_timer.h" + #include "swoole_api.h" -#include "swoole_c_api.h" namespace swoole { using network::Socket; @@ -32,29 +32,27 @@ using network::Socket; #endif #endif -static void reactor_begin(Reactor *reactor); - #ifdef HAVE_EPOLL ReactorImpl *make_reactor_epoll(Reactor *_reactor, int max_events); #endif -#ifdef HAVE_POLL -ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events); -#endif - #ifdef HAVE_KQUEUE ReactorImpl *make_reactor_kqueue(Reactor *_reactor, int max_events); #endif -ReactorImpl *make_reactor_select(Reactor *_reactor); +ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events); -void ReactorImpl::after_removal_failure(network::Socket *_socket) { +void ReactorImpl::after_removal_failure(const Socket *_socket) const { if (!_socket->silent_remove) { - swoole_sys_warning("failed to delete events[fd=%d#%d, type=%d, events=%d]", - _socket->fd, - reactor_->id, - _socket->fd_type, - _socket->events); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_EVENT_REMOVE_FAILED, + "failed to delete events[fd=%d#%d, type=%d, events=%d, errno=%d]", + _socket->fd, + reactor_->id, + _socket->fd_type, + _socket->events, + errno); + swoole_print_backtrace_on_error(); } } @@ -62,12 +60,17 @@ Reactor::Reactor(int max_event, Type _type) { if (_type == TYPE_AUTO) { #ifdef HAVE_EPOLL type_ = TYPE_EPOLL; -#elif defined(HAVE_KQUEUE) - type_ = TYPE_KQUEUE; -#elif defined(HAVE_POLL) - type_ = TYPE_POLL; #else - type_ = TYPE_SELECT; + type_ = TYPE_POLL; +#ifdef HAVE_KQUEUE + /** + * When kqueue is enabled, the Process mode of the Server module and functionalities such as Task operations, + * pipe messaging, and inter-process message forwarding that rely on pipe communication will be unavailable. + */ + if (SwooleG.enable_kqueue) { + type_ = TYPE_KQUEUE; + } +#endif #endif } else { type_ = _type; @@ -84,23 +87,17 @@ Reactor::Reactor(int max_event, Type _type) { impl = make_reactor_kqueue(this, max_event); break; #endif -#ifdef HAVE_POLL - case TYPE_POLL: - impl = make_reactor_poll(this, max_event); - break; -#endif - case TYPE_SELECT: default: - impl = make_reactor_select(this); + impl = make_reactor_poll(this, max_event); break; } - if (!impl->ready()) { - running = false; + ready_ = impl->ready(); + if (!ready_) { return; } - running = true; + timeout_msec = -1; idle_task = {}; future_task = {}; @@ -132,12 +129,9 @@ Reactor::Reactor(int max_event, Type _type) { } }); - set_end_callback(PRIORITY_SIGNAL_CALLBACK, [](Reactor *reactor) { - if (sw_unlikely(reactor->singal_no)) { - swoole_signal_callback(reactor->singal_no); - reactor->singal_no = 0; - } - }); + if (swoole_is_main_thread()) { + set_end_callback(PRIORITY_SIGNAL_CALLBACK, [](Reactor *) { swoole_signal_dispatch(); }); + } set_end_callback(PRIORITY_TRY_EXIT, [](Reactor *reactor) { if (reactor->wait_exit && reactor->if_exit()) { @@ -146,39 +140,42 @@ Reactor::Reactor(int max_event, Type _type) { }); #ifdef SW_USE_MALLOC_TRIM - set_end_callback(PRIORITY_MALLOC_TRIM, [](Reactor *reactor) { - time_t now = ::time(nullptr); - if (reactor->last_malloc_trim_time < now - SW_MALLOC_TRIM_INTERVAL) { - malloc_trim(SW_MALLOC_TRIM_PAD); - reactor->last_malloc_trim_time = now; - } - }); + if (swoole_is_main_thread()) { + set_end_callback(PRIORITY_MALLOC_TRIM, [](Reactor *reactor) { + time_t now = ::time(nullptr); + if (reactor->last_malloc_trim_time < now - SW_MALLOC_TRIM_INTERVAL) { + malloc_trim(SW_MALLOC_TRIM_PAD); + reactor->last_malloc_trim_time = now; + } + }); + } #endif - set_exit_condition(EXIT_CONDITION_DEFAULT, - [](Reactor *reactor, size_t &event_num) -> bool { return event_num == 0; }); + set_exit_condition(EXIT_CONDITION_DEFAULT, [](Reactor *, size_t &event_num) -> bool { return event_num == 0; }); } -bool Reactor::set_handler(int _fdtype, ReactorHandler handler) { - int fdtype = get_fd_type(_fdtype); - - if (fdtype >= SW_MAX_FDTYPE) { - swoole_warning("fdtype > SW_MAX_FDTYPE[%d]", SW_MAX_FDTYPE); - return false; +void Reactor::set_handler(const int fd_type, const int event, const ReactorHandler handler) { + if (isset_read_event(event)) { + read_handler[fd_type] = handler; + } else if (isset_write_event(event)) { + write_handler[fd_type] = handler; + } else if (isset_error_event(event)) { + error_handler[fd_type] = handler; + } else { + assert(0); } +} - if (isset_read_event(_fdtype)) { - read_handler[fdtype] = handler; - } else if (isset_write_event(_fdtype)) { - write_handler[fdtype] = handler; - } else if (isset_error_event(_fdtype)) { - error_handler[fdtype] = handler; +bool Reactor::isset_handler(const int fd_type, const int event) const { + if (isset_read_event(event)) { + return read_handler[fd_type] != nullptr; + } else if (isset_write_event(event)) { + return write_handler[fd_type] != nullptr; + } else if (isset_error_event(event)) { + return error_handler[fd_type] != nullptr; } else { - swoole_warning("unknown fdtype"); return false; } - - return true; } bool Reactor::if_exit() { @@ -191,30 +188,18 @@ bool Reactor::if_exit() { return true; } -void Reactor::activate_future_task() { - onBegin = reactor_begin; -} - -static void reactor_begin(Reactor *reactor) { - if (reactor->future_task.callback) { - reactor->future_task.callback(reactor->future_task.data); - } -} - int Reactor::_close(Reactor *reactor, Socket *socket) { swoole_trace_log(SW_TRACE_CLOSE, "fd=%d", socket->fd); socket->free(); return SW_OK; } -using SendFunc = std::function; -using AppendFunc = std::function; - -static ssize_t write_func( - Reactor *reactor, Socket *socket, const size_t __len, const SendFunc &send_fn, const AppendFunc &append_fn) { - ssize_t retval; +ssize_t Reactor::write_func(const Reactor *reactor, + Socket *socket, + const size_t _len, + const std::function &send_fn, + const std::function &append_fn) { Buffer *buffer = socket->out_buffer; - int fd = socket->fd; if (socket->buffer_size == 0) { socket->set_memory_buffer_size(Socket::default_buffer_size); @@ -224,24 +209,23 @@ static ssize_t write_func( socket->set_fd_option(1, -1); } - if ((uint32_t) __len > socket->buffer_size) { + if ((uint32_t) _len > socket->buffer_size) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_PACKAGE_LENGTH_TOO_LARGE, - "data packet is too large, cannot exceed the buffer size"); + "data packet is too large, cannot exceed the socket buffer size"); return SW_ERR; } if (Buffer::empty(buffer)) { -#ifdef SW_USE_OPENSSL + ssize_t retval; if (socket->ssl_send_) { goto _alloc_buffer; } -#endif _do_send: retval = send_fn(); if (retval > 0) { - if ((ssize_t) __len == retval) { + if ((ssize_t) _len == retval) { return retval; } else { goto _alloc_buffer; @@ -250,15 +234,16 @@ static ssize_t write_func( _alloc_buffer: if (!socket->out_buffer) { buffer = new Buffer(socket->chunk_size); - if (!buffer) { - swoole_warning("create worker buffer failed"); - return SW_ERR; - } socket->out_buffer = buffer; } if (!socket->isset_writable_event()) { reactor->add_write_event(socket); } + /** + * Part of the data has been successfully written to the kernel's socket buffer, + * and at this point, writing to the memory queue is permitted under any circumstances. + * Ensure that the async write operation either succeeds completely or fails entirely. + */ goto _append_buffer; } else if (errno == EINTR) { goto _do_send; @@ -267,21 +252,19 @@ static ssize_t write_func( return SW_ERR; } } else { - _append_buffer: - if (buffer->length() > socket->buffer_size) { - if (socket->dontwait) { - swoole_set_last_error(SW_ERROR_OUTPUT_BUFFER_OVERFLOW); - return SW_ERR; - } else { - swoole_error_log( - SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "socket#%d output buffer overflow", fd); - sw_yield(); - socket->wait_event(SW_SOCKET_OVERFLOW_WAIT, SW_EVENT_WRITE); - } + if (buffer->length() + _len > socket->buffer_size) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_OUTPUT_BUFFER_OVERFLOW, + "socket#%d output buffer overflow: (%u/%u)", + socket->get_fd(), + buffer->length(), + socket->buffer_size); + return SW_ERR; } + _append_buffer: append_fn(buffer); } - return __len; + return _len; } ssize_t Reactor::_write(Reactor *reactor, Socket *socket, const void *buf, size_t n) { @@ -292,18 +275,16 @@ ssize_t Reactor::_write(Reactor *reactor, Socket *socket, const void *buf, size_ }; auto append_fn = [&send_bytes, buf, n](Buffer *buffer) { ssize_t offset = send_bytes > 0 ? send_bytes : 0; - buffer->append((const char *) buf + offset, n - offset); + buffer->append(static_cast(buf) + offset, n - offset); }; return write_func(reactor, socket, n, send_fn, append_fn); } -ssize_t Reactor::_writev(Reactor *reactor, network::Socket *socket, const iovec *iov, size_t iovcnt) { -#ifdef SW_USE_OPENSSL +ssize_t Reactor::_writev(Reactor *reactor, Socket *socket, const iovec *iov, size_t iovcnt) { if (socket->ssl) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "does not support SSL"); return SW_ERR; } -#endif ssize_t send_bytes = 0; size_t n = 0; @@ -359,13 +340,13 @@ int Reactor::_writable_callback(Reactor *reactor, Event *ev) { return SW_OK; } -void Reactor::drain_write_buffer(swSocket *socket) { +void Reactor::drain_write_buffer(Socket *socket) { Event event = {}; event.socket = socket; event.fd = socket->fd; while (!Buffer::empty(socket->out_buffer)) { - if (socket->wait_event(network::Socket::default_write_timeout, SW_EVENT_WRITE) == SW_ERR) { + if (socket->wait_event(static_cast(sec2msec(Socket::default_write_timeout)), SW_EVENT_WRITE) == SW_ERR) { break; } _writable_callback(this, &event); @@ -375,35 +356,46 @@ void Reactor::drain_write_buffer(swSocket *socket) { } } -void Reactor::add_destroy_callback(Callback cb, void *data) { +void Reactor::add_destroy_callback(const Callback &cb, void *data) { destroy_callbacks.append(cb, data); } -void Reactor::set_end_callback(enum EndCallback id, const std::function &fn) { - end_callbacks[id] = fn; +void Reactor::set_end_callback(const EndCallback _id, const std::function &fn) { + end_callbacks[_id] = fn; +} + +void Reactor::erase_end_callback(const EndCallback _id) { + end_callbacks.erase(_id); } /** * Returns false, the reactor cannot be exited, the next condition is skipped * Returns true, the reactor can exit and will continue to execute the next conditional function */ -void Reactor::set_exit_condition(enum ExitCondition id, const std::function &fn) { - exit_conditions[id] = fn; +void Reactor::set_exit_condition(const ExitCondition _id, const std::function &fn) { + exit_conditions[_id] = fn; } -void Reactor::defer(Callback cb, void *data) { +void Reactor::defer(const Callback &cb, void *data) { if (defer_tasks == nullptr) { defer_tasks = new CallbackManager; } defer_tasks->append(cb, data); } -void Reactor::execute_end_callbacks(bool timedout) { +void Reactor::execute_end_callbacks(bool _timed_out) { + timed_out = _timed_out; for (auto &kv : end_callbacks) { kv.second(this); } } +void Reactor::execute_begin_callback() const { + if (future_task.callback) { + future_task.callback(future_task.data); + } +} + Reactor::~Reactor() { destroyed = true; destroy_callbacks.execute(); @@ -412,5 +404,4 @@ Reactor::~Reactor() { swoole_call_hook(SW_GLOBAL_HOOK_ON_REACTOR_DESTROY, this); } } - } // namespace swoole diff --git a/src/reactor/epoll.cc b/src/reactor/epoll.cc index f0b9114523..cc871e391d 100644 --- a/src/reactor/epoll.cc +++ b/src/reactor/epoll.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_socket.h" #include "swoole_reactor.h" @@ -32,32 +31,31 @@ namespace swoole { using network::Socket; -class ReactorEpoll : public ReactorImpl { - private: +class ReactorEpoll final : public ReactorImpl { int epfd_; - struct epoll_event *events_ = nullptr; + epoll_event *events_ = nullptr; public: ReactorEpoll(Reactor *_reactor, int max_events); - ~ReactorEpoll(); + ~ReactorEpoll() override; bool ready() override; int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; - int del(Socket *socket) override; - int wait(struct timeval *) override; + int del(Socket *_socket) override; + int wait() override; - static inline int get_events(int fdtype) { + static int get_events(const int fd_type) { int events = 0; - if (Reactor::isset_read_event(fdtype)) { + if (Reactor::isset_read_event(fd_type)) { events |= EPOLLIN; } - if (Reactor::isset_write_event(fdtype)) { + if (Reactor::isset_write_event(fd_type)) { events |= EPOLLOUT; } - if (fdtype & SW_EVENT_ONCE) { + if (fd_type & SW_EVENT_ONCE) { events |= EPOLLONESHOT; } - if (Reactor::isset_error_event(fdtype)) { + if (Reactor::isset_error_event(fd_type)) { events |= (EPOLLRDHUP | EPOLLHUP | EPOLLERR); } return events; @@ -70,12 +68,12 @@ ReactorImpl *make_reactor_epoll(Reactor *_reactor, int max_events) { ReactorEpoll::ReactorEpoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) { epfd_ = epoll_create(512); - if (!ready()) { - swoole_sys_warning("epoll_create failed"); + if (!ReactorEpoll::ready()) { + swoole_sys_warning("epoll_create() failed"); return; } - events_ = new struct epoll_event[max_events]; + events_ = new epoll_event[max_events]; reactor_->max_event_num = max_events; reactor_->native_handle = epfd_; } @@ -92,14 +90,19 @@ ReactorEpoll::~ReactorEpoll() { } int ReactorEpoll::add(Socket *socket, int events) { - struct epoll_event e; + epoll_event e; e.events = get_events(events); e.data.ptr = socket; if (epoll_ctl(epfd_, EPOLL_CTL_ADD, socket->fd, &e) < 0) { - swoole_sys_warning( - "failed to add events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events); + swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_ADD, fd=%d, fd_type=%d, events=%d) failed", + reactor_->id, + epfd_, + socket->fd, + socket->fd_type, + events); + swoole_print_backtrace_on_error(); return SW_ERR; } @@ -112,35 +115,56 @@ int ReactorEpoll::add(Socket *socket, int events) { int ReactorEpoll::del(Socket *_socket) { if (_socket->removed) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_EVENT_SOCKET_REMOVED, - "failed to delete events[fd=%d, fd_type=%d], it has already been removed", - _socket->fd, _socket->fd_type); + swoole_error_log( + SW_LOG_WARNING, + SW_ERROR_EVENT_REMOVE_FAILED, + "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], this socket has already been removed", + reactor_->id, + _socket->fd, + _socket->fd_type); + swoole_print_backtrace_on_error(); return SW_ERR; } + if (epoll_ctl(epfd_, EPOLL_CTL_DEL, _socket->fd, nullptr) < 0) { after_removal_failure(_socket); + /** + * Before removing it from the epoll event loop, the close operation has be executed, + * must cleanup related resources with this socket. + */ if (errno != EBADF && errno != ENOENT) { + swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_DEL, fd=%d, fd_type=%d) failed", + reactor_->id, + epfd_, + _socket->fd, + _socket->fd_type); + swoole_print_backtrace_on_error(); return SW_ERR; } } - swoole_trace_log(SW_TRACE_REACTOR, "remove event[reactor_id=%d|fd=%d]", reactor_->id, _socket->fd); + swoole_trace_log( + SW_TRACE_REACTOR, "remove event[reactor_id=%d|fd=%d|type=%d]", reactor_->id, _socket->fd, _socket->fd_type); reactor_->_del(_socket); return SW_OK; } int ReactorEpoll::set(Socket *socket, int events) { - struct epoll_event e; + epoll_event e; e.events = get_events(events); e.data.ptr = socket; int ret = epoll_ctl(epfd_, EPOLL_CTL_MOD, socket->fd, &e); if (ret < 0) { - swoole_sys_warning( - "failed to set events[fd=%d#%d, type=%d, events=%d]", socket->fd, reactor_->id, socket->fd_type, events); + swoole_sys_warning("[Reactor#%d] epoll_ctl(epfd=%d, EPOLL_CTL_MOD, fd=%d, fd_type=%d, events=%d) failed", + reactor_->id, + epfd_, + socket->fd, + socket->fd_type, + events); + swoole_print_backtrace_on_error(); return SW_ERR; } @@ -150,43 +174,40 @@ int ReactorEpoll::set(Socket *socket, int events) { return SW_OK; } -int ReactorEpoll::wait(struct timeval *timeo) { +int ReactorEpoll::wait() { Event event; ReactorHandler handler; - int i, n, ret; - - int reactor_id = reactor_->id; - int max_event_num = reactor_->max_event_num; - - if (reactor_->timeout_msec == 0) { - if (timeo == nullptr) { - reactor_->timeout_msec = -1; - } else { - reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000; - } - } + int n, ret; reactor_->before_wait(); while (reactor_->running) { - if (reactor_->onBegin != nullptr) { - reactor_->onBegin(reactor_); - } - n = epoll_wait(epfd_, events_, max_event_num, reactor_->get_timeout_msec()); + reactor_->execute_begin_callback(); + + n = epoll_wait(epfd_, events_, reactor_->max_event_num, reactor_->get_timeout_msec()); if (n < 0) { if (!reactor_->catch_error()) { - swoole_sys_warning("[Reactor#%d] epoll_wait failed", reactor_id); + swoole_sys_warning("[Reactor#%d] epoll_wait(epfd=%d, max_events=%d, timeout=%d) failed", + reactor_->id, + epfd_, + reactor_->max_event_num, + reactor_->get_timeout_msec()); return SW_ERR; } else { +#ifdef SW_USE_IOURING + if (sw_likely(errno == EINTR && reactor_->iouring_interrupt_handler)) { + reactor_->iouring_interrupt_handler(reactor_); + } +#endif goto _continue; } } else if (n == 0) { reactor_->execute_end_callbacks(true); SW_REACTOR_CONTINUE; } - for (i = 0; i < n; i++) { - event.reactor_id = reactor_id; - event.socket = (Socket *) events_[i].data.ptr; + for (int i = 0; i < n; i++) { + event.reactor_id = reactor_->id; + event.socket = static_cast(events_[i].data.ptr); event.type = event.socket->fd_type; event.fd = event.socket->fd; @@ -195,18 +216,18 @@ int ReactorEpoll::wait(struct timeval *timeo) { } // read if ((events_[i].events & EPOLLIN) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_READ, event.type); + handler = reactor_->get_handler(event.type, SW_EVENT_READ); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("EPOLLIN handle failed. fd=%d", event.fd); + swoole_sys_warning("EPOLLIN handle failed [fd=%d, type=%d]", event.fd, event.type); } } // write if ((events_[i].events & EPOLLOUT) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_WRITE, event.type); + handler = reactor_->get_handler(event.type, SW_EVENT_WRITE); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("EPOLLOUT handle failed. fd=%d", event.fd); + swoole_sys_warning("EPOLLOUT handle failed [fd=%d, type=%d]", event.fd, event.type); } } // error @@ -218,7 +239,7 @@ int ReactorEpoll::wait(struct timeval *timeo) { handler = reactor_->get_error_handler(event.type); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("EPOLLERR handle failed. fd=%d", event.fd); + swoole_sys_warning("EPOLLERR handle failed [fd=%d, type=%d]", event.fd, event.type); } } if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) { diff --git a/src/reactor/kqueue.cc b/src/reactor/kqueue.cc index ffb4f4222b..6e171a2845 100644 --- a/src/reactor/kqueue.cc +++ b/src/reactor/kqueue.cc @@ -14,7 +14,6 @@ +----------------------------------------------------------------------+ */ -#include "swoole.h" #include "swoole_socket.h" #include "swoole_reactor.h" #include "swoole_signal.h" @@ -26,9 +25,11 @@ #else #include #endif +#ifdef __NetBSD__ +#include +#endif namespace swoole { - using network::Socket; class ReactorKqueue : public ReactorImpl { @@ -61,7 +62,7 @@ class ReactorKqueue : public ReactorImpl { int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; int del(Socket *socket) override; - int wait(struct timeval *) override; + int wait() override; }; ReactorImpl *make_reactor_kqueue(Reactor *_reactor, int max_events) { @@ -71,7 +72,7 @@ ReactorImpl *make_reactor_kqueue(Reactor *_reactor, int max_events) { ReactorKqueue::ReactorKqueue(Reactor *reactor, int max_events) : ReactorImpl(reactor) { epfd_ = kqueue(); if (epfd_ < 0) { - swoole_warning("[swReactorKqueueCreate] kqueue_create[0] fail"); + swoole_sys_warning("kqueue() failed"); return; } @@ -99,7 +100,7 @@ int ReactorKqueue::add(Socket *socket, int events) { int fd = socket->fd; int fflags = 0; -#ifndef __NetBSD__ +#if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); @@ -112,8 +113,13 @@ int ReactorKqueue::add(Socket *socket, int events) { EV_SET(&e, fd, EVFILT_READ, EV_ADD, fflags, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning( - "add events_[fd=%d, reactor_id=%d, type=%d, events=read] failed", fd, reactor_->id, socket->fd_type); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_EVENT_ADD_FAILED, + "add events[fd=%d, reactor_id=%d, type=%d, events=read] failed", + fd, + reactor_->id, + socket->fd_type); + swoole_print_backtrace_on_error(); return SW_ERR; } } @@ -122,8 +128,13 @@ int ReactorKqueue::add(Socket *socket, int events) { EV_SET(&e, fd, EVFILT_WRITE, EV_ADD, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning( - "add events_[fd=%d, reactor_id=%d, type=%d, events=write] failed", fd, reactor_->id, socket->fd_type); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_EVENT_ADD_FAILED, + "add events[fd=%d, reactor_id=%d, type=%d, events=write] failed", + fd, + reactor_->id, + socket->fd_type); + swoole_print_backtrace_on_error(); return SW_ERR; } } @@ -141,7 +152,7 @@ int ReactorKqueue::set(Socket *socket, int events) { int fd = socket->fd; int fflags = 0; -#ifndef __NetBSD__ +#if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); @@ -154,14 +165,16 @@ int ReactorKqueue::set(Socket *socket, int events) { EV_SET(&e, fd, EVFILT_READ, EV_ADD, fflags, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning("kqueue->set(%d, SW_EVENT_READ) failed", fd); + swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_UPDATE_FAILED, "kqueue->set(%d, SW_EVENT_READ) failed", fd); + swoole_print_backtrace_on_error(); return SW_ERR; } } else { EV_SET(&e, fd, EVFILT_READ, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning("kqueue->del(%d, SW_EVENT_READ) failed", fd); + swoole_error_log(SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "kqueue->del(%d, SW_EVENT_READ) failed", fd); + swoole_print_backtrace_on_error(); return SW_ERR; } } @@ -170,14 +183,18 @@ int ReactorKqueue::set(Socket *socket, int events) { EV_SET(&e, fd, EVFILT_WRITE, EV_ADD, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning("kqueue->set(%d, SW_EVENT_WRITE) failed", fd); + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_EVENT_UPDATE_FAILED, "kqueue->set(%d, SW_EVENT_WRITE) failed", fd); + swoole_print_backtrace_on_error(); return SW_ERR; } } else { EV_SET(&e, fd, EVFILT_WRITE, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning("kqueue->del(%d, SW_EVENT_WRITE) failed", fd); + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "kqueue->del(%d, SW_EVENT_WRITE) failed", fd); + swoole_print_backtrace_on_error(); return SW_ERR; } } @@ -193,7 +210,7 @@ int ReactorKqueue::del(Socket *socket) { int ret; int fd = socket->fd; -#ifndef __NetBSD__ +#if !defined(__NetBSD__) || (defined(__NetBSD__) && __NetBSD_Version__ >= 1000000000) auto sobj = socket; #else auto sobj = reinterpret_cast(socket); @@ -201,7 +218,8 @@ int ReactorKqueue::del(Socket *socket) { if (socket->removed) { swoole_error_log( - SW_LOG_WARNING, SW_ERROR_EVENT_SOCKET_REMOVED, "failed to delete event[%d], has been removed", socket->fd); + SW_LOG_WARNING, SW_ERROR_EVENT_REMOVE_FAILED, "failed to delete event[%d], has been removed", socket->fd); + swoole_print_backtrace_on_error(); return SW_ERR; } @@ -209,7 +227,7 @@ int ReactorKqueue::del(Socket *socket) { EV_SET(&e, fd, EVFILT_READ, EV_DELETE, 0, 0, sobj); ret = ::kevent(epfd_, &e, 1, nullptr, 0, nullptr); if (ret < 0) { - swoole_sys_warning("kqueue->del(%d, SW_EVENT_READ) failed", fd); + after_removal_failure(socket); if (errno != EBADF && errno != ENOENT) { return SW_ERR; } @@ -233,7 +251,7 @@ int ReactorKqueue::del(Socket *socket) { return SW_OK; } -int ReactorKqueue::wait(struct timeval *timeo) { +int ReactorKqueue::wait() { Event event; ReactorHandler handler; @@ -241,20 +259,11 @@ int ReactorKqueue::wait(struct timeval *timeo) { struct timespec t = {}; struct timespec *t_ptr; - if (reactor_->timeout_msec == 0) { - if (timeo == nullptr) { - reactor_->timeout_msec = -1; - } else { - reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000; - } - } - reactor_->before_wait(); while (reactor_->running) { - if (reactor_->onBegin != nullptr) { - reactor_->onBegin(reactor_); - } + reactor_->execute_begin_callback(); + if (reactor_->timeout_msec > 0) { t.tv_sec = reactor_->timeout_msec / 1000; t.tv_nsec = (reactor_->timeout_msec - t.tv_sec * 1000) * 1000 * 1000; @@ -270,7 +279,7 @@ int ReactorKqueue::wait(struct timeval *timeo) { n = ::kevent(epfd_, nullptr, 0, events_, event_max_, t_ptr); if (n < 0) { if (!reactor_->catch_error()) { - swoole_warning("kqueue[#%d], epfd=%d", reactor_->id, epfd_); + swoole_sys_warning("kevent(epfd=%d) failed ", epfd_); return SW_ERR; } else { goto _continue; @@ -292,12 +301,13 @@ int ReactorKqueue::wait(struct timeval *timeo) { case EVFILT_READ: case EVFILT_WRITE: { if (fetch_event(&event, udata)) { - handler = reactor_->get_handler(kevent->filter == EVFILT_READ ? SW_EVENT_READ : SW_EVENT_WRITE, - event.type); + handler = reactor_->get_handler(event.type, + kevent->filter == EVFILT_READ ? SW_EVENT_READ : SW_EVENT_WRITE); if (sw_unlikely(handler(reactor_, &event) < 0)) { swoole_sys_warning("kqueue event %s socket#%d handler failed", - kevent->filter == EVFILT_READ ? "read" : "write", - event.fd); + kevent->filter == EVFILT_READ ? "read" : "write", + event.fd); + swoole_print_backtrace_on_error(); } del_once_socket(event.socket); } @@ -307,7 +317,9 @@ int ReactorKqueue::wait(struct timeval *timeo) { Signal *signal_data = (Signal *) udata; if (signal_data->activated) { if (signal_data->handler) { - signal_data->handler(signal_data->signo); + if (sw_likely(signal_data->handler != SIG_IGN)) { + signal_data->handler(signal_data->signo); + } } else { swoole_error_log(SW_LOG_WARNING, SW_ERROR_UNREGISTERED_SIGNAL, @@ -318,7 +330,8 @@ int ReactorKqueue::wait(struct timeval *timeo) { break; } default: - swoole_warning("unknown event filter[%d]", kevent->filter); + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_EVENT_UNKNOWN_DATA, "unknown event filter[%d]", kevent->filter); break; } } diff --git a/src/reactor/poll.cc b/src/reactor/poll.cc index 1fc15e98a9..ce344bc65f 100644 --- a/src/reactor/poll.cc +++ b/src/reactor/poll.cc @@ -14,32 +14,27 @@ +----------------------------------------------------------------------+ */ -#include - -#include "swoole.h" #include "swoole_socket.h" #include "swoole_reactor.h" namespace swoole { - using network::Socket; -class ReactorPoll : public ReactorImpl { - uint32_t max_fd_num; - Socket **fds_; - struct pollfd *events_; - bool exists(int fd); +class ReactorPoll final : public ReactorImpl { + pollfd *events_; + int max_events_; + int set_events() const; public: ReactorPoll(Reactor *_reactor, int max_events); - ~ReactorPoll(); + ~ReactorPoll() override; bool ready() override { return true; }; int add(Socket *socket, int events) override; int set(Socket *socket, int events) override; int del(Socket *socket) override; - int wait(struct timeval *) override; + int wait() override; }; ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events) { @@ -47,139 +42,143 @@ ReactorImpl *make_reactor_poll(Reactor *_reactor, int max_events) { } ReactorPoll::ReactorPoll(Reactor *_reactor, int max_events) : ReactorImpl(_reactor) { - fds_ = new Socket *[max_events]; - events_ = new struct pollfd[max_events]; - - max_fd_num = max_events; + events_ = new pollfd[max_events]; + max_events_ = max_events; reactor_->max_event_num = max_events; } ReactorPoll::~ReactorPoll() { - delete[] fds_; delete[] events_; } -int ReactorPoll::add(Socket *socket, int events) { - int fd = socket->fd; - if (exists(fd)) { - swoole_warning("fd#%d is already exists", fd); +int ReactorPoll::set_events() const { + const auto sockets = reactor_->get_sockets(); + int count = 0; + for (const auto pair : sockets) { + const auto _socket = pair.second; + events_[count].fd = _socket->fd; + events_[count].events = translate_events_to_poll(_socket->events); + events_[count].revents = 0; + count++; + } + return count; +} + +int ReactorPoll::add(Socket *socket, const int events) { + if (reactor_->_exists(socket)) { + swoole_error_log( + SW_LOG_WARNING, + SW_ERROR_EVENT_ADD_FAILED, + "[Reactor#%d] failed to add events[fd=%d, fd_type=%d, events=%d], the socket#%d is already exists", + reactor_->id, + socket->fd, + socket->fd_type, + events, + socket->fd); + swoole_print_backtrace_on_error(); return SW_ERR; } - int cur = reactor_->get_event_num(); - if (reactor_->get_event_num() == max_fd_num) { - swoole_warning("too many connection, more than %d", max_fd_num); + if (reactor_->get_event_num() == static_cast(max_events_)) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_EVENT_ADD_FAILED, "too many sockets, the max events is %d", max_events_); + swoole_print_backtrace_on_error(); return SW_ERR; } + swoole_trace("fd=%d, events=%d", socket->fd, events); reactor_->_add(socket, events); - swoole_trace("fd=%d, events=%d", fd, events); - - fds_[cur] = socket; - events_[cur].fd = fd; - events_[cur].events = 0; - - if (Reactor::isset_read_event(events)) { - events_[cur].events |= POLLIN; - } - if (Reactor::isset_write_event(events)) { - events_[cur].events |= POLLOUT; - } - if (Reactor::isset_error_event(events)) { - events_[cur].events |= POLLHUP; - } - return SW_OK; } int ReactorPoll::set(Socket *socket, int events) { - uint32_t i; + if (!reactor_->_exists(socket)) { + swoole_error_log( + SW_LOG_WARNING, + SW_ERROR_SOCKET_NOT_EXISTS, + "[Reactor#%d] failed to set events[fd=%d, fd_type=%d, events=%d], the socket#%d has already been removed", + reactor_->id, + socket->fd, + socket->fd_type, + events, + socket->fd); + swoole_print_backtrace_on_error(); + return SW_ERR; + } swoole_trace("fd=%d, events=%d", socket->fd, events); + reactor_->_set(socket, events); - for (i = 0; i < reactor_->get_event_num(); i++) { - // found - if (events_[i].fd == socket->fd) { - events_[i].events = 0; - if (Reactor::isset_read_event(events)) { - events_[i].events |= POLLIN; - } - if (Reactor::isset_write_event(events)) { - events_[i].events |= POLLOUT; - } - // execute parent method - reactor_->_set(socket, events); - return SW_OK; - } - } - - return SW_ERR; + return SW_OK; } int ReactorPoll::del(Socket *socket) { if (socket->removed) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_EVENT_SOCKET_REMOVED, - "failed to delete event[%d], it has already been removed", - socket->fd); + swoole_error_log( + SW_LOG_WARNING, + SW_ERROR_SOCKET_NOT_EXISTS, + "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], the socket#%d has already been removed", + reactor_->id, + socket->fd, + socket->fd_type, + socket->fd); + swoole_print_backtrace_on_error(); return SW_ERR; } - for (uint32_t i = 0; i < reactor_->get_event_num(); i++) { - if (events_[i].fd == socket->fd) { - for (; i < reactor_->get_event_num(); i++) { - if (i == reactor_->get_event_num()) { - fds_[i] = nullptr; - events_[i].fd = 0; - events_[i].events = 0; - } else { - fds_[i] = fds_[i + 1]; - events_[i] = events_[i + 1]; - } - } - reactor_->_del(socket); - return SW_OK; - } + if (!reactor_->_exists(socket)) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SOCKET_NOT_EXISTS, + "[Reactor#%d] failed to delete events[fd=%d, fd_type=%d], the socket#%d is not exists", + reactor_->id, + socket->fd, + socket->fd_type, + socket->fd); + swoole_print_backtrace_on_error(); + return SW_ERR; } - - return SW_ERR; + swoole_trace("fd=%d", socket->fd); + reactor_->_del(socket); + return SW_OK; } -int ReactorPoll::wait(struct timeval *timeo) { +int ReactorPoll::wait() { Event event; ReactorHandler handler; - int ret; - - if (reactor_->timeout_msec == 0) { - if (timeo == nullptr) { - reactor_->timeout_msec = -1; - } else { - reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000; - } - } - reactor_->before_wait(); while (reactor_->running) { - if (reactor_->onBegin != nullptr) { - reactor_->onBegin(reactor_); - } - ret = poll(events_, reactor_->get_event_num(), reactor_->get_timeout_msec()); + reactor_->execute_begin_callback(); + const int event_num = set_events(); + int ret = poll(events_, event_num, reactor_->get_timeout_msec()); if (ret < 0) { if (!reactor_->catch_error()) { - swoole_sys_warning("poll error"); + swoole_sys_warning("[Reactor#%d] poll(nfds=%d, timeout=%d) failed", + reactor_->id, + event_num, + reactor_->get_timeout_msec()); break; - } else { - goto _continue; } } else if (ret == 0) { reactor_->execute_end_callbacks(true); SW_REACTOR_CONTINUE; } else { - for (uint32_t i = 0; i < reactor_->get_event_num(); i++) { - event.socket = fds_[i]; + for (int i = 0; i < event_num; i++) { + /** + * A revents value of 0 indicates that no events have occurred on this socket, + * so the next file descriptor should be checked; + * likewise, if the file descriptor has already been removed, it should be skipped. + * It is essential to check whether this file descriptor exists in the reactor, + * as the event handler may remove a file descriptor with pending but already triggered readable or + * writable events that have not yet been processed. + */ + if (events_[i].revents == 0 || !reactor_->exists(events_[i].fd)) { + continue; + } + + event.socket = reactor_->get_socket(events_[i].fd); event.fd = events_[i].fd; event.reactor_id = reactor_->id; event.type = event.socket->fd_type; @@ -187,22 +186,23 @@ int ReactorPoll::wait(struct timeval *timeo) { if (events_[i].revents & (POLLHUP | POLLERR)) { event.socket->event_hup = 1; } - swoole_trace("Event: fd=%d|reactor_id=%d|type=%d", event.fd, reactor_->id, event.type); // in if ((events_[i].revents & POLLIN) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_READ, event.type); + handler = reactor_->get_handler(event.type, SW_EVENT_READ); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("poll[POLLIN] handler failed. fd=%d", event.fd); + swoole_sys_warning("POLLIN handle failed. fd=%d", event.fd); + swoole_print_backtrace_on_error(); } } // out if ((events_[i].revents & POLLOUT) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_WRITE, event.type); + handler = reactor_->get_handler(event.type, SW_EVENT_WRITE); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("poll[POLLOUT] handler failed. fd=%d", event.fd); + swoole_sys_warning("POLLOUT handle failed. fd=%d", event.fd); + swoole_print_backtrace_on_error(); } } // error @@ -214,7 +214,8 @@ int ReactorPoll::wait(struct timeval *timeo) { handler = reactor_->get_error_handler(event.type); ret = handler(reactor_, &event); if (ret < 0) { - swoole_sys_warning("poll[POLLERR] handler failed. fd=%d", event.fd); + swoole_sys_warning("POLLERR handle failed. fd=%d", event.fd); + swoole_print_backtrace_on_error(); } } if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) { @@ -222,20 +223,39 @@ int ReactorPoll::wait(struct timeval *timeo) { } } } - _continue: reactor_->execute_end_callbacks(false); SW_REACTOR_CONTINUE; } return SW_OK; } -bool ReactorPoll::exists(int fd) { - for (uint32_t i = 0; i < reactor_->get_event_num(); i++) { - if (events_[i].fd == fd) { - return true; - } +int16_t translate_events_to_poll(int events) { + int16_t poll_events = 0; + + if (events & SW_EVENT_READ) { + poll_events |= POLLIN; + } + if (events & SW_EVENT_WRITE) { + poll_events |= POLLOUT; } - return false; + + return poll_events; } +int translate_events_from_poll(int16_t events) { + int sw_events = 0; + + if (events & POLLIN) { + sw_events |= SW_EVENT_READ; + } + if (events & POLLOUT) { + sw_events |= SW_EVENT_WRITE; + } + // ignore ERR and HUP, because event is already processed at IN and OUT handler. + if ((events & POLLERR || events & POLLHUP) && !(events & POLLIN || events & POLLOUT)) { + sw_events |= SW_EVENT_ERROR; + } + + return sw_events; +} } // namespace swoole diff --git a/src/reactor/select.cc b/src/reactor/select.cc deleted file mode 100644 index 71ec4d146c..0000000000 --- a/src/reactor/select.cc +++ /dev/null @@ -1,221 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | 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 "swoole.h" -#include "swoole_socket.h" -#include "swoole_reactor.h" -#include - -#include - -namespace swoole { - -using network::Socket; - -class ReactorSelect : public ReactorImpl { - fd_set rfds; - fd_set wfds; - fd_set efds; - std::unordered_map fds; - int maxfd; - - public: - ReactorSelect(Reactor *reactor); - ~ReactorSelect() {} - bool ready() override { - return true; - } - int add(Socket *socket, int events) override; - int set(Socket *socket, int events) override; - int del(Socket *socket) override; - int wait(struct timeval *) override; -}; - -#define SW_FD_SET(fd, set) \ - do { \ - if (fd < FD_SETSIZE) FD_SET(fd, set); \ - } while (0) - -#define SW_FD_CLR(fd, set) \ - do { \ - if (fd < FD_SETSIZE) FD_CLR(fd, set); \ - } while (0) - -#define SW_FD_ISSET(fd, set) ((fd < FD_SETSIZE) && FD_ISSET(fd, set)) - -ReactorImpl *make_reactor_select(Reactor *_reactor) { - return new ReactorSelect(_reactor); -} - -ReactorSelect::ReactorSelect(Reactor *reactor) : ReactorImpl(reactor) { - maxfd = 0; -} - -int ReactorSelect::add(Socket *socket, int events) { - int fd = socket->fd; - if (fd > FD_SETSIZE) { - swoole_warning("max fd value is FD_SETSIZE(%d).\n", FD_SETSIZE); - return SW_ERR; - } - - reactor_->_add(socket, events); - fds.emplace(fd, socket); - if (fd > maxfd) { - maxfd = fd; - } - - return SW_OK; -} - -int ReactorSelect::del(Socket *socket) { - if (socket->removed) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_EVENT_SOCKET_REMOVED, - "failed to delete event[%d], it has already been removed", - socket->fd); - return SW_ERR; - } - int fd = socket->fd; - if (fds.erase(fd) == 0) { - swoole_warning("swReactorSelect: fd[%d] not found", fd); - return SW_ERR; - } - SW_FD_CLR(fd, &rfds); - SW_FD_CLR(fd, &wfds); - SW_FD_CLR(fd, &efds); - reactor_->_del(socket); - return SW_OK; -} - -int ReactorSelect::set(Socket *socket, int events) { - auto i = fds.find(socket->fd); - if (i == fds.end()) { - swoole_warning("swReactorSelect: sock[%d] not found", socket->fd); - return SW_ERR; - } - reactor_->_set(socket, events); - return SW_OK; -} - -int ReactorSelect::wait(struct timeval *timeo) { - Event event; - ReactorHandler handler; - struct timeval timeout; - int ret; - - if (reactor_->timeout_msec == 0) { - if (timeo == nullptr) { - reactor_->timeout_msec = -1; - } else { - reactor_->timeout_msec = timeo->tv_sec * 1000 + timeo->tv_usec / 1000; - } - } - - reactor_->before_wait(); - - while (reactor_->running) { - FD_ZERO(&(rfds)); - FD_ZERO(&(wfds)); - FD_ZERO(&(efds)); - - if (reactor_->onBegin != nullptr) { - reactor_->onBegin(reactor_); - } - - for (auto i = fds.begin(); i != fds.end(); i++) { - int fd = i->first; - int events = i->second->events; - if (Reactor::isset_read_event(events)) { - SW_FD_SET(fd, &(rfds)); - } - if (Reactor::isset_write_event(events)) { - SW_FD_SET(fd, &(wfds)); - } - if (Reactor::isset_error_event(events)) { - SW_FD_SET(fd, &(efds)); - } - } - - if (reactor_->timeout_msec < 0) { - timeout.tv_sec = UINT_MAX; - timeout.tv_usec = 0; - } else if (reactor_->defer_tasks) { - timeout.tv_sec = 0; - timeout.tv_usec = 0; - } else { - timeout.tv_sec = reactor_->timeout_msec / 1000; - timeout.tv_usec = reactor_->timeout_msec - timeout.tv_sec * 1000; - } - - ret = select(maxfd + 1, &(rfds), &(wfds), &(efds), &timeout); - if (ret < 0) { - if (!reactor_->catch_error()) { - swoole_sys_warning("select error"); - break; - } else { - goto _continue; - } - } else if (ret == 0) { - reactor_->execute_end_callbacks(true); - SW_REACTOR_CONTINUE; - } else { - for (int fd = 0; fd <= maxfd; fd++) { - auto i = fds.find(fd); - if (i == fds.end()) { - continue; - } - event.socket = i->second; - event.fd = event.socket->fd; - event.reactor_id = reactor_->id; - event.type = event.socket->fd_type; - - // read - if (SW_FD_ISSET(event.fd, &(rfds)) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_READ, event.type); - ret = handler(reactor_, &event); - if (ret < 0) { - swoole_sys_warning("[Reactor#%d] select event[type=READ, fd=%d] handler fail", reactor_->id, event.fd); - } - } - // write - if (SW_FD_ISSET(event.fd, &(wfds)) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_WRITE, event.type); - ret = handler(reactor_, &event); - if (ret < 0) { - swoole_sys_warning("[Reactor#%d] select event[type=WRITE, fd=%d] handler fail", reactor_->id, event.fd); - } - } - // error - if (SW_FD_ISSET(event.fd, &(efds)) && !event.socket->removed) { - handler = reactor_->get_handler(SW_EVENT_ERROR, event.type); - ret = handler(reactor_, &event); - if (ret < 0) { - swoole_sys_warning("[Reactor#%d] select event[type=ERROR, fd=%d] handler fail", reactor_->id, event.fd); - } - } - if (!event.socket->removed && (event.socket->events & SW_EVENT_ONCE)) { - del(event.socket); - } - } - } - _continue: - reactor_->execute_end_callbacks(false); - SW_REACTOR_CONTINUE; - } - return SW_OK; -} - -} // namespace swoole diff --git a/src/server/base.cc b/src/server/base.cc index c726709966..cf0058ddda 100644 --- a/src/server/base.cc +++ b/src/server/base.cc @@ -18,8 +18,44 @@ namespace swoole { +Factory *Server::create_base_factory() { + reactor_num = worker_num; + connection_list = static_cast(sw_calloc(max_connection, sizeof(Connection))); + if (connection_list == nullptr) { + swoole_sys_warning("calloc[2](%zu) failed", max_connection * sizeof(Connection)); + return nullptr; + } + gs->connection_nums = static_cast(sw_shm_calloc(worker_num, sizeof(sw_atomic_t))); + if (gs->connection_nums == nullptr) { + swoole_sys_warning("sw_shm_calloc(%ld) for gs->connection_nums failed", worker_num * sizeof(sw_atomic_t)); + return nullptr; + } + + for (const auto port : ports) { + port->gs->connection_nums = static_cast(sw_shm_calloc(worker_num, sizeof(sw_atomic_t))); + if (port->gs->connection_nums == nullptr) { + swoole_sys_warning("sw_shm_calloc(%ld) for port->connection_nums failed", worker_num * sizeof(sw_atomic_t)); + return nullptr; + } + } + + return new BaseFactory(this); +} + +void Server::destroy_base_factory() const { + sw_free(connection_list); + sw_shm_free((void *) gs->connection_nums); + for (auto port : ports) { + sw_shm_free((void *) port->gs->connection_nums); + } + gs->connection_nums = nullptr; +} + +BaseFactory::BaseFactory(Server *server) : Factory(server) {} + +BaseFactory::~BaseFactory() = default; + bool BaseFactory::start() { - SwooleWG.run_always = true; return true; } @@ -54,8 +90,9 @@ bool BaseFactory::dispatch(SendData *task) { } } - server_->message_bus.pass(task); - server_->worker_accept_event(&server_->message_bus.get_buffer()->info); + auto bus = server_->get_worker_message_bus(); + bus->pass(task); + server_->worker_accept_event(&bus->get_buffer()->info); return true; } @@ -64,7 +101,7 @@ bool BaseFactory::dispatch(SendData *task) { * only stream fd */ bool BaseFactory::notify(DataHead *info) { - Connection *conn = server_->get_connection(info->fd); + const auto conn = server_->get_connection(info->fd); if (conn == nullptr || conn->active == 0) { swoole_warning("dispatch[type=%d] failed, socket#%ld is not active", info->type, info->fd); return false; @@ -89,24 +126,25 @@ bool BaseFactory::end(SessionId session_id, int flags) { _send.info.fd = session_id; _send.info.len = 0; _send.info.type = SW_SERVER_EVENT_CLOSE; - _send.info.reactor_id = SwooleG.process_id; + _send.info.reactor_id = swoole_get_worker_id(); Session *session = server_->get_session(session_id); if (!session->fd) { - swoole_error_log(SW_LOG_NOTICE, + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "failed to close connection, session#%ld does not exist", session_id); return false; } - if (session->reactor_id != SwooleG.process_id) { - Worker *worker = server_->get_worker(session->reactor_id); - if (worker->pipe_master->send_async((const char *) &_send.info, sizeof(_send.info)) < 0) { - swoole_sys_warning("failed to send %lu bytes to pipe_master", sizeof(_send.info)); - return false; - } - return true; + if (server_->if_forward_message(session)) { + swoole_trace_log(SW_TRACE_SERVER, + "session_id=%ld, fd=%d, session->reactor_id=%d", + session_id, + session->fd, + session->reactor_id); + _send.info.type = SW_SERVER_EVENT_CLOSE_FORWARD; + return forward_message(session, &_send); } Connection *conn = server_->get_connection_verify_no_ssl(session_id); @@ -132,7 +170,7 @@ bool BaseFactory::end(SessionId session_id, int flags) { } conn->closing = 1; - if (server_->onClose != nullptr && !conn->closed) { + if (server_->if_do_close_callback(conn)) { DataHead info{}; info.fd = session_id; if (conn->close_actively) { @@ -144,20 +182,19 @@ bool BaseFactory::end(SessionId session_id, int flags) { server_->onClose(server_, &info); } conn->closing = 0; - conn->closed = 1; conn->close_errno = 0; + network::Socket *_socket = conn->socket; - if (conn->socket == nullptr) { + if (_socket == nullptr) { swoole_warning("session#%ld->socket is nullptr", session_id); return false; } - if (Buffer::empty(conn->socket->out_buffer) || (conn->close_reset || conn->peer_closed || conn->close_force)) { + if (Buffer::empty(_socket->out_buffer) || (conn->close_reset || conn->peer_closed || conn->close_force)) { Reactor *reactor = SwooleTG.reactor; - return Server::close_connection(reactor, conn->socket) == SW_OK; + return Server::close_connection(reactor, _socket) == SW_OK; } else { - BufferChunk *chunk = conn->socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); - chunk->value.data.val1 = _send.info.type; + _socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); conn->close_queued = 1; return true; } @@ -167,33 +204,40 @@ bool BaseFactory::finish(SendData *data) { SessionId session_id = data->info.fd; Session *session = server_->get_session(session_id); - if (session->reactor_id != SwooleG.process_id) { - swoole_trace("session->reactor_id=%d, SwooleG.process_id=%d", session->reactor_id, SwooleG.process_id); - Worker *worker = server_->gs->event_workers.get_worker(session->reactor_id); - EventData proxy_msg{}; - - if (data->info.type == SW_SERVER_EVENT_SEND_DATA) { - if (!server_->message_bus.write(worker->pipe_master, data)) { - swoole_sys_warning("failed to send %u bytes to pipe_master", data->info.len); - return false; - } - swoole_trace( - "proxy message, fd=%d, len=%ld", worker->pipe_master->fd, sizeof(proxy_msg.info) + proxy_msg.info.len); - } else if (data->info.type == SW_SERVER_EVENT_SEND_FILE) { - memcpy(&proxy_msg.info, &data->info, sizeof(proxy_msg.info)); - memcpy(proxy_msg.data, data->data, data->info.len); - size_t __len = sizeof(proxy_msg.info) + proxy_msg.info.len; - return worker->pipe_master->send_async((const char *) &proxy_msg, __len); + if (server_->if_forward_message(session)) { + swoole_trace_log(SW_TRACE_SERVER, + "session_id=%ld, fd=%d, session->reactor_id=%d", + session_id, + session->fd, + session->reactor_id); + + if (data->info.type == SW_SERVER_EVENT_SEND_DATA || data->info.type == SW_SERVER_EVENT_SEND_FILE) { + return forward_message(session, data); } else { - swoole_warning("unkown event type[%d]", data->info.type); + swoole_warning("unknown event type[%d]", data->info.type); return false; } - return true; } else { return server_->send_to_connection(data) == SW_OK; } } -BaseFactory::~BaseFactory() {} +bool BaseFactory::forward_message(const Session *session, SendData *data) const { + const Worker *worker = server_->get_event_worker_pool()->get_worker(session->reactor_id); + swoole_trace_log(SW_TRACE_SERVER, + "fd=%d, worker_id=%d, type=%d, len=%u", + worker->pipe_master->get_fd(), + session->reactor_id, + data->info.type, + data->info.len); + + auto mb = server_->get_worker_message_bus(); + auto sock = server_->is_thread_mode() ? mb->get_pipe_socket(worker->pipe_master) : worker->pipe_master; + if (!mb->write(sock, data)) { + swoole_sys_warning("failed to send %u bytes to pipe_master", data->info.len); + return false; + } + return true; +} } // namespace swoole diff --git a/src/server/manager.cc b/src/server/manager.cc index cda225668e..bf82d544ca 100644 --- a/src/server/manager.cc +++ b/src/server/manager.cc @@ -20,236 +20,130 @@ #include #include -#if defined(__linux__) -#include -#elif defined(__FreeBSD__) -#include -#endif - namespace swoole { - -using ReloadWorkerList = std::unordered_map; - +/** + * The functionality of the Manager class is similar to that of the ProcessPool, + * but due to the more complex management requirements for processes in the Server module, the ProcessPool falls short. + * Therefore, we add a new class, and the Manager class should reuse the ProcessPool code as much as possible. + */ struct Manager { bool reload_all_worker; bool reload_task_worker; bool force_kill; - uint32_t reload_worker_num; - pid_t reload_worker_pid; Server *server_; std::vector kill_workers; - void start(Server *_server); - void add_timeout_killer(Worker *workers, int n); + void wait(Server *_server); + void terminate_all_worker(); + void reopen_logger() const; static void signal_handler(int sig); static void timer_callback(Timer *timer, TimerNode *tnode); - static void kill_timeout_process(Timer *timer, TimerNode *tnode); }; void Manager::timer_callback(Timer *timer, TimerNode *tnode) { - Server *serv = (Server *) tnode->data; + auto *serv = static_cast(tnode->data); if (serv->isset_hook(Server::HOOK_MANAGER_TIMER)) { serv->call_hook(Server::HOOK_MANAGER_TIMER, serv); } } -void Manager::kill_timeout_process(Timer *timer, TimerNode *tnode) { - ReloadWorkerList *_list = (ReloadWorkerList *) tnode->data; - - for (auto i = _list->begin(); i != _list->end(); i++) { - pid_t pid = i->second; - uint32_t worker_id = i->first; - if (swoole_kill(pid, 0) == -1) { - continue; - } - if (swoole_kill(pid, SIGKILL) < 0) { - swoole_sys_warning("kill(%d, SIGKILL) [%u] failed", pid, worker_id); - } else { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, - "worker(pid=%d, id=%d) exit timeout, force kill the process", - pid, - worker_id); - } - } - errno = 0; - - delete (_list); -} - -void Manager::add_timeout_killer(Worker *workers, int n) { - if (!server_->max_wait_time) { - return; - } - /** - * separate old workers, free memory in the timer - */ - ReloadWorkerList *_list = new ReloadWorkerList(); - SW_LOOP_N(n) { - _list->emplace(workers[i].id, workers[i].pid); - } - /** - * Multiply max_wait_time by 2 to prevent conflict with worker - */ - swoole_timer_after((long) (server_->max_wait_time * 2 * 1000), kill_timeout_process, _list); -} - -// create worker child proccess int Server::start_manager_process() { - pid_t pid; - - if (task_worker_num > 0) { - if (create_task_workers() < 0) { - return SW_ERR; - } - - Worker *worker; - SW_LOOP_N(task_worker_num) { - worker = &gs->task_workers.workers[i]; - create_worker(worker); - if (task_ipc_mode == TASK_IPC_UNIXSOCK) { - store_pipe_fd(worker->pipe_object); - } - } + if (!create_event_workers()) { + return SW_ERR; } - // User Worker Process - if (get_user_worker_num() > 0) { - if (create_user_workers() < 0) { - return SW_ERR; - } - - int i = 0; - for (auto worker : user_worker_list) { - memcpy(&user_workers[i], worker, sizeof(user_workers[i])); - create_worker(worker); - i++; - } + if (get_event_worker_pool()->create_message_box(SW_MESSAGE_BOX_SIZE) == SW_ERR) { + return SW_ERR; } - if (gs->event_workers.create_message_box(65536) == SW_ERR) { + if (get_user_worker_num() > 0 && !create_user_workers()) { return SW_ERR; } - pid = swoole_fork(0); - switch (pid) { - // fork manager process - case 0: { - // wait master process - if (!is_started()) { - swoole_error("master process is not running"); - return SW_ERR; - } - - pid_t pid; + auto fn = [this]() { + gs->manager_pid = getpid(); if (task_worker_num > 0) { - if (gs->task_workers.start() == SW_ERR) { - swoole_error("failed to start task workers"); - return SW_ERR; + if (get_task_worker_pool()->start() == SW_ERR) { + swoole_sys_error("failed to start task worker"); + return; } } + /* + * Must be set after ProcessPool:start(), + * the default ProcessPool will set type of the main process as SW_MASTER, + * while in server mode it should be SW_MANAGER + */ + swoole_set_worker_type(SW_MANAGER); + SW_LOOP_N(worker_num) { Worker *worker = get_worker(i); - pid = spawn_event_worker(worker); - if (pid < 0) { - swoole_error("fork() failed"); - return SW_ERR; - } else { - worker->pid = pid; + if (factory_->spawn_event_worker(worker) < 0) { + swoole_sys_error("failed to fork event worker"); + return; } } if (!user_worker_list.empty()) { for (auto worker : user_worker_list) { - if (worker->pipe_object) { - store_pipe_fd(worker->pipe_object); - } - pid = spawn_user_worker(worker); - if (pid < 0) { - swoole_error("failed to start user workers"); - return SW_ERR; + if (factory_->spawn_user_worker(worker) < 0) { + swoole_sys_error("failed to fork user worker"); + return; } } } - SwooleG.process_type = SW_PROCESS_MANAGER; - SwooleG.pid = getpid(); Manager manager{}; - manager.start(this); - exit(0); - break; - } - // master process - default: - gs->event_workers.master_pid = gs->manager_pid = pid; - break; - case -1: - swoole_error("fork() failed"); - return SW_ERR; - } - return SW_OK; -} + manager.wait(this); + }; -void Server::check_worker_exit_status(int worker_id, const ExitStatus &exit_status) { - if (exit_status.get_status() != 0) { - swoole_warning("worker(pid=%d, id=%d) abnormal exit, status=%d, signal=%d" - "%s", - exit_status.get_pid(), - worker_id, - exit_status.get_code(), - exit_status.get_signal(), - exit_status.get_signal() == SIGSEGV ? SwooleG.bug_report_message.c_str() : ""); - - if (onWorkerError != nullptr) { - onWorkerError(this, worker_id, exit_status); + if (is_base_mode()) { + fn(); + } else { + if (swoole_fork_exec(fn) < 0) { + swoole_sys_warning("failed fork manager process"); + return SW_ERR; } } + return SW_OK; } -void Manager::start(Server *_server) { +void Manager::wait(Server *_server) { server_ = _server; - server_->manager = this; + server_->manager_ = this; - ProcessPool *pool = &server_->gs->event_workers; + ProcessPool *pool = server_->get_event_worker_pool(); + ProcessPool *task_pool = server_->get_task_worker_pool(); pool->onWorkerMessage = Server::read_worker_message; + pool->max_wait_time = server_->max_wait_time; + _server->gs->manager_pid = pool->master_pid = task_pool->master_pid = getpid(); - SwooleTG.reactor = nullptr; - - pool->reload_workers = new Worker[_server->worker_num + _server->task_worker_num]; - ON_SCOPE_EXIT { - delete[] pool->reload_workers; - pool->reload_workers = nullptr; - server_->manager = nullptr; - }; + swoole_event_free(); - // for reload swoole_signal_set(SIGHUP, nullptr); - swoole_signal_set(SIGCHLD, signal_handler); - swoole_signal_set(SIGTERM, signal_handler); - swoole_signal_set(SIGUSR1, signal_handler); - swoole_signal_set(SIGUSR2, signal_handler); - swoole_signal_set(SIGIO, signal_handler); - swoole_signal_set(SIGALRM, signal_handler); + swoole_signal_set(SIGCHLD, signal_handler); // child process exit + swoole_signal_set(SIGTERM, signal_handler); // shutdown + swoole_signal_set(SIGUSR1, signal_handler); // reload all workers + swoole_signal_set(SIGUSR2, signal_handler); // reload task workers + swoole_signal_set(SIGIO, signal_handler); // read message + swoole_signal_set(SIGALRM, signal_handler); // timer + swoole_signal_set(SIGWINCH, signal_handler); // reopen log file #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, signal_handler); #endif - // swSignal_set(SIGINT, signal_handler); -#if defined(__linux__) - prctl(PR_SET_PDEATHSIG, SIGTERM); -#elif defined(__FreeBSD__) - int sigid = SIGTERM; - procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sigid); -#endif -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_wait(&_server->gs->manager_barrier); -#else - SW_START_SLEEP; -#endif + if (_server->is_process_mode()) { + swoole_set_process_death_signal(SIGTERM); + + _server->gs->manager_barrier.wait(); + + if (_server->reload_async && swoole_is_root_user() && !_server->user_.empty()) { + swoole_set_isolation(_server->group_, _server->user_, _server->chroot_); + } + } if (_server->isset_hook(Server::HOOK_MANAGER_START)) { _server->call_hook(Server::HOOK_MANAGER_START, _server); @@ -260,11 +154,14 @@ void Manager::start(Server *_server) { } if (_server->manager_alarm > 0) { - swoole_timer_add((long) (_server->manager_alarm * 1000), true, timer_callback, _server); + swoole_timer_add(sec2msec(_server->manager_alarm), true, timer_callback, _server); } while (_server->running) { ExitStatus exit_status = wait_process(); + const auto wait_error = errno; + + swoole_signal_dispatch(); if (pool->read_message) { EventData msg; @@ -279,160 +176,132 @@ void Manager::start(Server *_server) { WorkerStopMessage worker_stop_msg; memcpy(&worker_stop_msg, msg.data, sizeof(worker_stop_msg)); if (worker_stop_msg.worker_id >= _server->worker_num) { - _server->spawn_task_worker(_server->get_worker(worker_stop_msg.worker_id)); + _server->factory_->spawn_task_worker(_server->get_worker(worker_stop_msg.worker_id)); } else { Worker *worker = _server->get_worker(worker_stop_msg.worker_id); - pid_t new_pid = _server->spawn_event_worker(worker); - if (new_pid > 0) { - worker->pid = new_pid; - } + _server->factory_->spawn_event_worker(worker); } } pool->read_message = false; } - if (SwooleG.signal_alarm && SwooleTG.timer) { - SwooleG.signal_alarm = 0; - swoole_timer_select(); + if (sw_timer()) { + sw_timer()->select(); } if (exit_status.get_pid() < 0) { - if (!pool->reloading) { - _error: - if (errno > 0 && errno != EINTR) { + if (!pool->reload_task) { + if (wait_error > 0 && wait_error != EINTR) { swoole_sys_warning("wait() failed"); } continue; } - // reload task & event workers - else if (reload_all_worker) { + } + + if (_server->running) { + if (reload_all_worker) { // reload task & event workers + pool->reload_init = reload_all_worker = false; swoole_info("Server is reloading all workers now"); if (_server->onBeforeReload != nullptr) { _server->onBeforeReload(_server); } - if (!pool->reload_init) { - pool->reload_init = true; - memcpy(pool->reload_workers, _server->workers, sizeof(Worker) * _server->worker_num); - - add_timeout_killer(_server->workers, _server->worker_num); - - reload_worker_num = _server->worker_num; - if (_server->task_worker_num > 0) { - memcpy(pool->reload_workers + _server->worker_num, - _server->gs->task_workers.workers, - sizeof(Worker) * _server->task_worker_num); - reload_worker_num += _server->task_worker_num; - - add_timeout_killer(_server->gs->task_workers.workers, _server->task_worker_num); - } - - reload_all_worker = false; - if (_server->reload_async) { - SW_LOOP_N(_server->worker_num) { - if (swoole_kill(pool->reload_workers[i].pid, SIGTERM) < 0) { - swoole_sys_warning("swKill(%d, SIGTERM) [%d] failed", pool->reload_workers[i].pid, i); - } - } - pool->reload_worker_i = _server->worker_num; - } else { - pool->reload_worker_i = 0; - } + auto reload_task = pool->reload_task; + reload_task->add_workers(_server->workers, _server->worker_num); + if (_server->task_worker_num > 0) { + reload_task->add_workers(_server->get_task_worker_pool()->workers, _server->task_worker_num); } - goto _kill_worker; - } - // only reload task workers - else if (reload_task_worker) { + /** + * When the `reload_async` option is enabled, the SIGTERM signal will be sent to all work processes + * immediately. When the work process receives the signal, it will remove the event listening of the + * work process pipeline, no longer receive data, and then send a process exit message to the management + * process. When the management process receives the message, it will immediately create a new worker + * process. At this time, there will be two processes at the same time. The old process will exit by + * itself after completing the legacy tasks, and the new client request data and tasks will be processed + * by the new worker process. + */ + if (_server->reload_async) { + reload_task->kill_all(SIGTERM); + } else { + goto _kill_worker; + } + } else if (reload_task_worker) { // only reload task workers + pool->reload_init = reload_task_worker = false; if (_server->task_worker_num == 0) { swoole_warning("cannot reload task workers, task workers is not started"); - pool->reloading = false; continue; } swoole_info("Server is reloading task workers now"); if (_server->onBeforeReload != nullptr) { _server->onBeforeReload(_server); } - if (!pool->reload_init) { - memcpy(pool->reload_workers, - _server->gs->task_workers.workers, - sizeof(Worker) * _server->task_worker_num); - add_timeout_killer(_server->gs->task_workers.workers, _server->task_worker_num); - reload_worker_num = _server->task_worker_num; - pool->reload_worker_i = 0; - pool->reload_init = true; - reload_task_worker = false; - } + auto reload_task = pool->reload_task; + reload_task->add_workers(_server->get_task_worker_pool()->workers, _server->task_worker_num); goto _kill_worker; - } else { - goto _error; + } else if (exit_status.get_pid() < 0) { + continue; } - } - if (_server->running) { + // event workers SW_LOOP_N(_server->worker_num) { + Worker *worker = _server->get_worker(i); // find worker - if (exit_status.get_pid() != _server->workers[i].pid) { + if (exit_status.get_pid() != worker->pid) { continue; } // check the process return code and signal - _server->check_worker_exit_status(i, exit_status); + _server->factory_->check_worker_exit_status(worker, exit_status); - while (1) { - Worker *worker = _server->get_worker(i); - pid_t new_pid = _server->spawn_event_worker(worker); - if (new_pid < 0) { + do { + if (_server->factory_->spawn_event_worker(worker) < 0) { SW_START_SLEEP; continue; - } else { - worker->pid = new_pid; - break; } - } + break; + } while (true); } // task worker - if (_server->gs->task_workers.map_) { - auto iter = _server->gs->task_workers.map_->find(exit_status.get_pid()); - if (iter != _server->gs->task_workers.map_->end()) { - _server->check_worker_exit_status(iter->second->id, exit_status); - _server->spawn_task_worker(iter->second); + if (task_pool->map_) { + auto iter = task_pool->map_->find(exit_status.get_pid()); + if (iter != task_pool->map_->end()) { + _server->factory_->check_worker_exit_status(iter->second, exit_status); + _server->factory_->spawn_task_worker(iter->second); } } // user process if (!_server->user_worker_map.empty()) { - Server::wait_other_worker(&_server->gs->event_workers, exit_status); + Server::wait_other_worker(pool, exit_status); } - if (exit_status.get_pid() == reload_worker_pid && pool->reloading) { - pool->reload_worker_i++; + if (pool->reload_task) { + pool->reload_task->remove(exit_status.get_pid()); } } - // reload worker - _kill_worker: - if (pool->reloading) { + + if (pool->reload_task) { // reload finish - if (pool->reload_worker_i >= reload_worker_num) { - reload_worker_pid = pool->reload_worker_i = 0; - pool->reload_init = pool->reloading = false; + if (pool->reload_task->is_completed()) { + delete pool->reload_task; + pool->reload_task = nullptr; if (_server->onAfterReload != nullptr) { _server->onAfterReload(_server); } - continue; - } - reload_worker_pid = pool->reload_workers[pool->reload_worker_i].pid; - if (swoole_kill(reload_worker_pid, SIGTERM) < 0) { - if (errno == ECHILD || errno == ESRCH) { - pool->reload_worker_i++; - goto _kill_worker; - } - swoole_sys_warning("kill(%d, SIGTERM) [%d] failed", - pool->reload_workers[pool->reload_worker_i].pid, - pool->reload_worker_i); + } else { + _kill_worker: + pool->reload_task->kill_one(SIGTERM); } } } - if (SwooleTG.timer) { + delete pool->reload_task; + + if (swoole_timer_is_available()) { swoole_timer_free(); + /** + * The timer will override the SIGALRM signal handler, + * which needs to be reset to the manager's signal handler. + */ + swoole_signal_set(SIGALRM, signal_handler); } // wait child process if (_server->max_wait_time) { @@ -441,12 +310,12 @@ void Manager::start(Server *_server) { kill_workers.push_back(_server->workers[i].pid); } if (_server->task_worker_num > 0) { - SW_LOOP_N(_server->gs->task_workers.worker_num) { - kill_workers.push_back(_server->gs->task_workers.workers[i].pid); + SW_LOOP_N(task_pool->worker_num) { + kill_workers.push_back(task_pool->workers[i].pid); } } if (!_server->user_worker_map.empty()) { - for (auto kv : _server->user_worker_map) { + for (auto &kv : _server->user_worker_map) { kill_workers.push_back(kv.second->pid); } } @@ -455,9 +324,9 @@ void Manager::start(Server *_server) { */ alarm(_server->max_wait_time * 2); } - _server->kill_event_workers(); - _server->kill_task_workers(); - _server->kill_user_workers(); + _server->factory_->kill_event_workers(); + _server->factory_->kill_task_workers(); + _server->factory_->kill_user_workers(); // force kill if (_server->max_wait_time) { alarm(0); @@ -467,52 +336,56 @@ void Manager::start(Server *_server) { } } -void Manager::signal_handler(int sig) { +void Manager::terminate_all_worker() { + alarm(0); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, + "wait timeout, all worker processes will be forcibly terminated"); + for (int &kill_worker : kill_workers) { + swoole_kill(kill_worker, SIGKILL); + } +} + +void Manager::reopen_logger() const { + sw_logger()->reopen(); + + SW_LOOP_N(server_->get_all_worker_num()) { + const auto worker = server_->get_worker(i); + swoole_kill(worker->pid, SIGWINCH); + } +} + +void Manager::signal_handler(int signo) { Server *_server = sw_server(); - if (!_server || !_server->manager) { + if (!_server || !_server->manager_) { return; } - Manager *manager = _server->manager; - ProcessPool *pool = &_server->gs->event_workers; + Manager *manager = _server->manager_; + ProcessPool *pool = _server->get_event_worker_pool(); - switch (sig) { + switch (signo) { case SIGTERM: _server->running = false; break; - /** - * reload all workers - */ case SIGUSR1: - if (pool->reload()) { - manager->reload_all_worker = true; - } - sw_logger()->reopen(); - break; - /** - * only reload task workers - */ case SIGUSR2: - if (pool->reload()) { - manager->reload_task_worker = true; - } - sw_logger()->reopen(); + _server->reload(signo == SIGUSR1); break; case SIGIO: pool->read_message = true; break; case SIGALRM: - SwooleG.signal_alarm = 1; if (manager->force_kill) { - alarm(0); - for (auto i = manager->kill_workers.begin(); i != manager->kill_workers.end(); i++) { - kill(*i, SIGKILL); - } + manager->terminate_all_worker(); } break; + case SIGWINCH: + manager->reopen_logger(); + break; default: #ifdef SIGRTMIN - if (sig == SIGRTMIN) { - sw_logger()->reopen(); + if (signo == SIGRTMIN) { + manager->reopen_logger(); } #endif break; @@ -523,40 +396,40 @@ void Manager::signal_handler(int sig) { * @return: success returns pid, failure returns SW_ERR. */ int Server::wait_other_worker(ProcessPool *pool, const ExitStatus &exit_status) { - Server *serv = (Server *) pool->ptr; + auto serv = static_cast(pool->ptr); Worker *exit_worker = nullptr; int worker_type; do { - if (serv->gs->task_workers.map_) { - auto iter = serv->gs->task_workers.map_->find(exit_status.get_pid()); - if (iter != serv->gs->task_workers.map_->end()) { - worker_type = SW_PROCESS_TASKWORKER; + if (serv->get_task_worker_pool()->map_) { + const auto iter = serv->get_task_worker_pool()->map_->find(exit_status.get_pid()); + if (iter != serv->get_task_worker_pool()->map_->end()) { + worker_type = SW_TASK_WORKER; exit_worker = iter->second; break; } } if (!serv->user_worker_map.empty()) { - auto iter = serv->user_worker_map.find(exit_status.get_pid()); + const auto iter = serv->user_worker_map.find(exit_status.get_pid()); if (iter != serv->user_worker_map.end()) { - worker_type = SW_PROCESS_USERWORKER; + worker_type = SW_USER_WORKER; exit_worker = iter->second; break; } } return SW_ERR; - } while (0); + } while (false); - serv->check_worker_exit_status(exit_worker->id, exit_status); + serv->factory_->check_worker_exit_status(exit_worker, exit_status); pid_t new_process_pid = -1; switch (worker_type) { - case SW_PROCESS_TASKWORKER: - new_process_pid = serv->spawn_task_worker(exit_worker); + case SW_TASK_WORKER: + new_process_pid = serv->factory_->spawn_task_worker(exit_worker); break; - case SW_PROCESS_USERWORKER: - new_process_pid = serv->spawn_user_worker(exit_worker); + case SW_USER_WORKER: + new_process_pid = serv->factory_->spawn_user_worker(exit_worker); break; default: /* never here */ @@ -575,16 +448,16 @@ void Server::read_worker_message(ProcessPool *pool, EventData *msg) { return; } - Server *serv = (Server *) pool->ptr; + const auto serv = static_cast(pool->ptr); int command_id = msg->info.server_fd; - auto iter = serv->command_handlers.find(command_id); + const auto iter = serv->command_handlers.find(command_id); if (iter == serv->command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[command_id=%d]", command_id); return; } - Server::Command::Handler handler = iter->second; - auto result = handler(serv, std::string(msg->data, msg->info.len)); + Command::Handler handler = iter->second; + const auto result = handler(serv, std::string(msg->data, msg->info.len)); SendData task{}; task.info.fd = msg->info.fd; @@ -597,110 +470,30 @@ void Server::read_worker_message(ProcessPool *pool, EventData *msg) { serv->message_bus.write(serv->get_command_reply_socket(), &task); } -/** - * kill and wait all user process - */ -void Server::kill_user_workers() { - if (user_worker_map.empty()) { - return; - } - - for (auto &kv : user_worker_map) { - swoole_kill(kv.second->pid, SIGTERM); - } - - for (auto &kv : user_worker_map) { - int __stat_loc; - if (swoole_waitpid(kv.second->pid, &__stat_loc, 0) < 0) { - swoole_sys_warning("waitpid(%d) failed", kv.second->pid); - } - } -} - -/** - * kill and wait all child process - */ -void Server::kill_event_workers() { - int status; - - if (worker_num == 0) { - return; +bool Server::reload(bool reload_all_workers) const { + if (is_thread_mode()) { + return reload_worker_threads(reload_all_workers); } - SW_LOOP_N(worker_num) { - swoole_trace("[Manager]kill worker processor"); - swoole_kill(workers[i].pid, SIGTERM); + if (gs->manager_pid == 0) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_OPERATION_NOT_SUPPORT, "not supported with single process mode"); + return false; } - SW_LOOP_N(worker_num) { - if (swoole_waitpid(workers[i].pid, &status, 0) < 0) { - swoole_sys_warning("waitpid(%d) failed", workers[i].pid); - } - } -} -/** - * kill and wait task process - */ -void Server::kill_task_workers() { - if (task_worker_num == 0) { - return; + if (getpid() != gs->manager_pid) { + return swoole_kill(get_manager_pid(), reload_all_workers ? SIGUSR1 : SIGUSR2) == 0; } - gs->task_workers.shutdown(); -} - -pid_t Server::spawn_event_worker(Worker *worker) { - pid_t pid; - pid = swoole_fork(0); - - // fork() failed - if (pid < 0) { - swoole_sys_warning("Fork Worker failed"); - return SW_ERR; + if (!get_event_worker_pool()->reload()) { + return false; } - // worker child processor - else if (pid == 0) { - exit(start_event_worker(worker)); - } - // parent,add to writer - else { - return pid; - } -} - -pid_t Server::spawn_user_worker(Worker *worker) { - pid_t pid = swoole_fork(0); - if (pid < 0) { - swoole_sys_warning("Fork Worker failed"); - return SW_ERR; - } - // child - else if (pid == 0) { - SwooleG.process_type = SW_PROCESS_USERWORKER; - SwooleG.process_id = worker->id; - SwooleWG.worker = worker; - worker->pid = getpid(); - onUserWorkerStart(this, worker); - exit(0); - } - // parent - else { - if (worker->pid) { - user_worker_map.erase(worker->pid); - } - /** - * worker: local memory - * user_workers: shared memory - */ - get_worker(worker->id)->pid = worker->pid = pid; - user_worker_map.emplace(std::make_pair(pid, worker)); - return pid; + if (reload_all_workers) { + manager_->reload_all_worker = true; + } else { + manager_->reload_task_worker = true; } -} - -pid_t Server::spawn_task_worker(Worker *worker) { - return gs->task_workers.spawn(worker); + return true; } } // namespace swoole diff --git a/src/server/master.cc b/src/server/master.cc index 4eee327bcf..b69f48d1dc 100644 --- a/src/server/master.cc +++ b/src/server/master.cc @@ -17,26 +17,32 @@ #include "swoole_server.h" #include "swoole_memory.h" #include "swoole_lock.h" +#include "swoole_thread.h" #include "swoole_util.h" +#include "swoole_hash.h" -#include +#include "swoole_api.h" + +#include +#include +#include using swoole::network::Address; using swoole::network::SendfileTask; using swoole::network::Socket; -swoole::Server *g_server_instance = nullptr; - namespace swoole { -static void Server_signal_handler(int sig); - -TimerCallback Server::get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn) { +TimerCallback Server::get_timeout_callback(ListenPort *port, Reactor *reactor, Connection *conn) const { return [this, port, conn, reactor](Timer *, TimerNode *) { if (conn->protect) { return; } long ms = time(true); + swoole_trace_log(SW_TRACE_SERVER, + "timeout_callback, last_received_time=%f, last_sent_time=%f", + conn->socket->last_received_time, + conn->socket->last_sent_time); if (ms - conn->socket->last_received_time < port->max_idle_time && ms - conn->socket->last_sent_time < port->max_idle_time) { return; @@ -55,11 +61,11 @@ TimerCallback Server::get_timeout_callback(ListenPort *port, Reactor *reactor, C void Server::disable_accept() { enable_accept_timer = swoole_timer_add( - SW_ACCEPT_RETRY_TIME * 1000, + SW_ACCEPT_RETRY_TIME, false, - [](Timer *timer, TimerNode *tnode) { - Server *serv = (Server *) tnode->data; - for (auto port : serv->ports) { + [](Timer *timer, const TimerNode *tnode) { + auto *serv = static_cast(tnode->data); + for (const auto port : serv->ports) { if (port->is_dgram()) { continue; } @@ -73,7 +79,7 @@ void Server::disable_accept() { return; } - for (auto port : ports) { + for (const auto port : ports) { if (port->is_dgram()) { continue; } @@ -81,20 +87,8 @@ void Server::disable_accept() { } } -void Server::close_port(bool only_stream_port) { - for (auto port : ports) { - if (only_stream_port && port->is_dgram()) { - continue; - } - if (port->socket) { - port->socket->free(); - port->socket = nullptr; - } - } -} - -void Server::call_command_callback(int64_t request_id, const std::string &result) { - auto iter = command_callbacks.find(request_id); +void Server::call_command_callback(const int64_t request_id, const std::string &result) { + const auto iter = command_callbacks.find(request_id); if (iter == command_callbacks.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, @@ -103,20 +97,21 @@ void Server::call_command_callback(int64_t request_id, const std::string &result return; } iter->second(this, result); + command_callbacks.erase(request_id); } -ResultCode Server::call_command_handler(MessageBus &mb, uint16_t worker_id, Socket *sock) { - PipeBuffer *buffer = mb.get_buffer(); - int command_id = buffer->info.server_fd; - auto iter = command_handlers.find(command_id); +void Server::call_command_handler(MessageBus &mb, uint16_t worker_id, Socket *sock) { + const PipeBuffer *buffer = mb.get_buffer(); + const int command_id = buffer->info.server_fd; + const auto iter = command_handlers.find(command_id); if (iter == command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[command_id=%d]", command_id); - return SW_OK; + return; } - Server::Command::Handler handler = iter->second; - auto packet = mb.get_packet(); - auto result = handler(this, std::string(packet.data, packet.length)); + const Command::Handler handler = iter->second; + const auto packet = mb.get_packet(); + const auto result = handler(this, std::string(packet.data, packet.length)); SendData task{}; task.info.fd = buffer->info.fd; @@ -126,7 +121,7 @@ ResultCode Server::call_command_handler(MessageBus &mb, uint16_t worker_id, Sock task.info.len = result.length(); task.data = result.c_str(); - return mb.write(sock, &task) ? SW_OK : SW_ERR; + mb.write(sock, &task); } std::string Server::call_command_handler_in_master(int command_id, const std::string &msg) { @@ -141,7 +136,7 @@ std::string Server::call_command_handler_in_master(int command_id, const std::st } int Server::accept_command_result(Reactor *reactor, Event *event) { - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); if (serv->message_bus.read(event->socket) <= 0) { return SW_OK; @@ -158,8 +153,8 @@ int Server::accept_command_result(Reactor *reactor, Event *event) { } int Server::accept_connection(Reactor *reactor, Event *event) { - Server *serv = (Server *) reactor->ptr; - ListenPort *listen_host = serv->get_port_by_server_fd(event->fd); + auto serv = static_cast(reactor->ptr); + auto listen_host = static_cast(event->socket->object); for (int i = 0; i < SW_ACCEPT_MAX_COUNT; i++) { Socket *sock = event->socket->accept(); @@ -193,6 +188,15 @@ int Server::accept_connection(Reactor *reactor, Event *event) { return SW_OK; } + if (listen_host->ssl) { + if (!listen_host->ssl_create(sock)) { + serv->abort_connection(reactor, listen_host, sock); + return SW_OK; + } + } else { + sock->ssl = nullptr; + } + // add to connection_list Connection *conn = serv->add_connection(listen_host, sock, event->fd); if (conn == nullptr) { @@ -201,16 +205,6 @@ int Server::accept_connection(Reactor *reactor, Event *event) { } sock->chunk_size = SW_SEND_BUFFER_SIZE; -#ifdef SW_USE_OPENSSL - if (listen_host->ssl) { - if (!listen_host->ssl_create(conn, sock)) { - serv->abort_connection(reactor, listen_host, sock); - return SW_OK; - } - } else { - sock->ssl = nullptr; - } -#endif if (serv->single_thread) { if (serv->connection_incoming(reactor, conn) < 0) { serv->abort_connection(reactor, listen_host, sock); @@ -222,7 +216,7 @@ int Server::accept_connection(Reactor *reactor, Event *event) { ev.fd = conn->session_id; ev.reactor_id = conn->reactor_id; ev.server_fd = event->fd; - if (serv->send_to_reactor_thread((EventData *) &ev, sizeof(ev), conn->session_id) < 0) { + if (serv->send_to_reactor_thread(reinterpret_cast(&ev), sizeof(ev), conn->session_id) < 0) { serv->abort_connection(reactor, listen_host, sock); return SW_OK; } @@ -232,18 +226,16 @@ int Server::accept_connection(Reactor *reactor, Event *event) { return SW_OK; } -int Server::connection_incoming(Reactor *reactor, Connection *conn) { +int Server::connection_incoming(Reactor *reactor, Connection *conn) const { ListenPort *port = get_port_by_server_fd(conn->server_fd); if (port->max_idle_time > 0) { - auto timeout_callback = get_timeout_callback(port, reactor, conn); - conn->socket->recv_timeout_ = port->max_idle_time; - conn->socket->recv_timer = swoole_timer_add(port->max_idle_time * 1000, true, timeout_callback); + const auto timeout_callback = get_timeout_callback(port, reactor, conn); + conn->socket->read_timeout = port->max_idle_time; + conn->socket->recv_timer = swoole_timer_add(sec2msec(port->max_idle_time), true, timeout_callback); } -#ifdef SW_USE_OPENSSL if (conn->socket->ssl) { return reactor->add(conn->socket, SW_EVENT_READ); } -#endif // delay receive, wait resume command if (!enable_delay_receive) { if (reactor->add(conn->socket, SW_EVENT_READ) < 0) { @@ -260,7 +252,7 @@ int Server::connection_incoming(Reactor *reactor, Connection *conn) { } #ifdef SW_SUPPORT_DTLS -dtls::Session *Server::accept_dtls_connection(ListenPort *port, Address *sa) { +dtls::Session *Server::accept_dtls_connection(const ListenPort *port, const Address *sa) { dtls::Session *session = nullptr; Connection *conn = nullptr; @@ -269,7 +261,6 @@ dtls::Session *Server::accept_dtls_connection(ListenPort *port, Address *sa) { return nullptr; } - int fd = sock->fd; sock->set_reuse_addr(); #ifdef HAVE_KQUEUE sock->set_reuse_port(); @@ -284,7 +275,7 @@ dtls::Session *Server::accept_dtls_connection(ListenPort *port, Address *sa) { break; } - if (sock->bind(port->socket->info) < 0) { + if (sock->bind(port->host, port->port) < 0) { swoole_sys_warning("bind() failed"); goto _cleanup; } @@ -304,22 +295,15 @@ dtls::Session *Server::accept_dtls_connection(ListenPort *port, Address *sa) { goto _cleanup; } - session = new dtls::Session(sock, port->ssl_context); - port->dtls_sessions->emplace(fd, session); - - if (!session->init()) { - goto _cleanup; + session = port->create_dtls_session(sock); + if (session) { + return session; } - return session; - _cleanup: if (conn) { *conn = {}; } - if (session) { - delete session; - } sock->free(); return nullptr; } @@ -343,37 +327,86 @@ void Server::set_max_connection(uint32_t _max_connection) { } } +bool Server::set_document_root(const std::string &path) { + if (path.length() > PATH_MAX) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_NAME_TOO_LONG, "The length of document_root must be less than %d", PATH_MAX); + return false; + } + + char _realpath[PATH_MAX]; + if (!realpath(path.c_str(), _realpath)) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_DIR_NOT_EXIST, "document_root[%s] does not exist", path.c_str()); + return false; + } + + document_root = std::string(_realpath); + return true; +} + +void Server::add_http_compression_type(const std::string &type) { + if (http_compression_types == nullptr) { + http_compression_types = std::make_shared>(); + } + http_compression_types->emplace(type); +} + +const char *Server::get_startup_error_message() const { + auto error_msg = swoole_get_last_error_msg(); + if (strlen(error_msg) == 0 && swoole_get_last_error() > 0) { + auto buf = sw_tg_buffer(); + buf->clear(); + buf->append(swoole_get_last_error()); + buf->str[buf->length] = '\0'; + error_msg = buf->str; + } + return error_msg; +} + int Server::start_check() { + assert(is_created()); // disable notice when use SW_DISPATCH_ROUND and SW_DISPATCH_QUEUE if (is_process_mode()) { if (!is_support_unsafe_events()) { if (onConnect) { - swoole_warning("cannot set 'onConnect' event when using dispatch_mode=1/3/7"); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_INVALID_CALLBACK, + "cannot set 'onConnect' event when using dispatch_mode=%d", + dispatch_mode); onConnect = nullptr; } if (onClose) { - swoole_warning("cannot set 'onClose' event when using dispatch_mode=1/3/7"); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_INVALID_CALLBACK, + "cannot set 'onClose' event when using dispatch_mode=%d", + dispatch_mode); onClose = nullptr; } if (onBufferFull) { - swoole_warning("cannot set 'onBufferFull' event when using dispatch_mode=1/3/7"); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_INVALID_CALLBACK, + "cannot set 'onBufferFull' event when using dispatch_mode=%d", + dispatch_mode); onBufferFull = nullptr; } if (onBufferEmpty) { - swoole_warning("cannot set 'onBufferEmpty' event when using dispatch_mode=1/3/7"); + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_INVALID_CALLBACK, + "cannot set 'onBufferEmpty' event when using dispatch_mode=%d", + dispatch_mode); onBufferEmpty = nullptr; } - disable_notify = 1; + disable_notify = true; } if (!is_support_send_yield()) { - send_yield = 0; + send_yield = false; } } else { max_queued_bytes = 0; } if (task_worker_num > 0) { if (onTask == nullptr) { - swoole_warning("onTask event callback must be set"); + swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onTask' callback"); return SW_ERR; } } @@ -381,81 +414,78 @@ int Server::start_check() { send_timeout = SW_TIMER_MIN_SEC; } if (heartbeat_check_interval > 0) { - for (auto ls : ports) { - if (ls->heartbeat_idle_time == 0) { - ls->heartbeat_idle_time = heartbeat_check_interval * 2; + for (auto port : ports) { + if (port->heartbeat_idle_time == 0) { + port->heartbeat_idle_time = heartbeat_check_interval * 2; } } } - for (auto ls : ports) { - if (ls->protocol.package_max_length < SW_BUFFER_MIN_SIZE) { - ls->protocol.package_max_length = SW_BUFFER_MIN_SIZE; + for (auto port : ports) { + if (port->protocol.package_max_length < SW_BUFFER_MIN_SIZE) { + port->protocol.package_max_length = SW_BUFFER_MIN_SIZE; } - if (if_require_receive_callback(ls, onReceive != nullptr)) { - swoole_warning("require onReceive callback"); + if (if_require_receive_callback(port, onReceive != nullptr)) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onReceive' callback"); return SW_ERR; } - if (if_require_packet_callback(ls, onPacket != nullptr)) { - swoole_warning("require onPacket callback"); + if (if_require_packet_callback(port, onPacket != nullptr)) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_CALLBACK, "require 'onPacket' callback"); return SW_ERR; } - if (ls->heartbeat_idle_time > 0) { - int expect_heartbeat_check_interval = ls->heartbeat_idle_time > 2 ? ls->heartbeat_idle_time / 2 : 1; + if (port->heartbeat_idle_time > 0) { + int expect_heartbeat_check_interval = port->heartbeat_idle_time > 2 ? port->heartbeat_idle_time / 2 : 1; if (heartbeat_check_interval == 0 || heartbeat_check_interval > expect_heartbeat_check_interval) { heartbeat_check_interval = expect_heartbeat_check_interval; } } + if (port->open_websocket_protocol) { + port->websocket_settings.compression = websocket_compression; + } } -#ifdef SW_USE_OPENSSL - /** - * OpenSSL thread-safe - */ - if (is_process_mode() && !single_thread) { - swoole_ssl_init_thread_safety(); - } -#endif return SW_OK; } -int Server::start_master_thread() { - SwooleTG.type = THREAD_MASTER; - SwooleTG.id = single_thread ? 0 : reactor_num; - - Reactor *reactor = sw_reactor(); +int Server::start_master_thread(Reactor *reactor) { + swoole_set_thread_type(THREAD_MASTER); + swoole_set_thread_id(single_thread ? 0 : reactor_num); if (SwooleTG.timer && SwooleTG.timer->get_reactor() == nullptr) { - SwooleTG.timer->reinit(reactor); + SwooleTG.timer->reinit(); } init_signal_handler(); - SwooleG.pid = getpid(); - SwooleG.process_type = SW_PROCESS_MASTER; + swoole_set_worker_type(SW_MASTER); + + if (is_thread_mode()) { + swoole_set_worker_pid(swoole_thread_get_native_id()); + } else if (is_process_mode()) { + swoole_set_worker_pid(getpid()); + } reactor->ptr = this; - reactor->set_handler(SW_FD_STREAM_SERVER, Server::accept_connection); + reactor->set_handler(SW_FD_STREAM_SERVER, SW_EVENT_READ, accept_connection); if (pipe_command) { if (!single_thread) { - reactor->set_handler(SW_FD_PIPE, Server::accept_command_result); + reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, accept_command_result); } reactor->add(pipe_command->get_socket(true), SW_EVENT_READ); } - if ((master_timer = swoole_timer_add(1000, true, Server::timer_callback, this)) == nullptr) { + if ((master_timer = swoole_timer_add(1000L, true, Server::timer_callback, this)) == nullptr) { swoole_event_free(); return SW_ERR; } -#ifdef HAVE_PTHREAD_BARRIER - if (!single_thread) { - pthread_barrier_wait(&reactor_thread_barrier); + if (!single_thread && !is_thread_mode()) { + reactor_thread_barrier.wait(); } - pthread_barrier_wait(&gs->manager_barrier); -#else - SW_START_SLEEP; -#endif + if (is_process_mode()) { + gs->manager_barrier.wait(); + } + gs->master_pid = getpid(); if (isset_hook(HOOK_MASTER_START)) { call_hook(HOOK_MASTER_START, this); @@ -477,6 +507,7 @@ void Server::store_listen_socket() { connection_list[sockfd].socket_type = ls->type; connection_list[sockfd].object = ls; connection_list[sockfd].info.assign(ls->type, ls->host, ls->port); + ls->socket->object = ls; if (sockfd >= 0) { set_minfd(sockfd); set_maxfd(sockfd); @@ -485,9 +516,9 @@ void Server::store_listen_socket() { } /** - * only the memory of the Worker structure is allocated, no process is fork + * only the memory of the Worker structure is allocated, no process is forked */ -int Server::create_task_workers() { +bool Server::create_task_workers() { key_t key = 0; swIPCMode ipc_mode; @@ -500,51 +531,95 @@ int Server::create_task_workers() { ipc_mode = SW_IPC_UNIXSOCK; } - ProcessPool *pool = &gs->task_workers; + ProcessPool *pool = get_task_worker_pool(); *pool = {}; if (pool->create(task_worker_num, key, ipc_mode) < 0) { swoole_warning("[Master] create task_workers failed"); - return SW_ERR; + return false; } pool->set_max_request(task_max_request, task_max_request_grace); pool->set_start_id(worker_num); - pool->set_type(SW_PROCESS_TASKWORKER); + pool->set_type(SW_TASK_WORKER); if (ipc_mode == SW_IPC_SOCKET) { char sockfile[sizeof(struct sockaddr_un)]; snprintf(sockfile, sizeof(sockfile), "/tmp/swoole.task.%d.sock", gs->master_pid); - if (gs->task_workers.listen(sockfile, 2048) < 0) { - return SW_ERR; + if (get_task_worker_pool()->listen(sockfile, 2048) < 0) { + return false; } } - init_task_workers(); + /* + * For Server::task_sync(), create notify pipe and result shared memory. + */ + task_results = static_cast(sw_shm_calloc(worker_num, sizeof(EventData))); + if (!task_results) { + swoole_warning("sw_shm_calloc(%d, %zu) for task_result failed", worker_num, sizeof(EventData)); + return false; + } + SW_LOOP_N(worker_num) { + auto _pipe = new Pipe(true); + if (!_pipe->ready()) { + sw_shm_free(task_results); + delete _pipe; + return false; + } + task_notify_pipes.emplace_back(_pipe); + } - return SW_OK; + if (!init_task_workers()) { + return false; + } + + return true; +} + +void Server::destroy_task_workers() const { + if (task_results) { + sw_shm_free(task_results); + } + get_task_worker_pool()->destroy(); +} + +bool Server::create_event_workers() { + SW_LOOP_N(worker_num) { + create_worker(get_worker(i)); + } + return true; } /** * @description: * only the memory of the Worker structure is allocated, no process is fork. * called when the manager process start. - * @param Server * @return: SW_OK|SW_ERR */ -int Server::create_user_workers() { - user_workers = (Worker *) sw_shm_calloc(get_user_worker_num(), sizeof(Worker)); +bool Server::create_user_workers() { + user_workers = static_cast(sw_shm_calloc(get_user_worker_num(), sizeof(Worker))); if (user_workers == nullptr) { - swoole_sys_warning("gmalloc[server->user_workers] failed"); - return SW_ERR; + swoole_sys_warning("sw_shm_calloc(%lu, %zu) for user_workers failed", get_user_worker_num(), sizeof(Worker)); + return false; } - return SW_OK; + + int i = 0; + for (const auto worker : user_worker_list) { + memcpy(&user_workers[i], worker, sizeof(user_workers[i])); + create_worker(worker); + i++; + } + + return true; } /** * [Master] */ void Server::create_worker(Worker *worker) { - worker->lock = new Mutex(Mutex::PROCESS_SHARED); + worker->lock = new Mutex(true); + if (worker->pipe_object) { + store_pipe_fd(worker->pipe_object); + } } void Server::destroy_worker(Worker *worker) { @@ -555,52 +630,14 @@ void Server::destroy_worker(Worker *worker) { /** * [Worker] */ -void Server::init_worker(Worker *worker) { -#ifdef HAVE_CPU_AFFINITY - if (open_cpu_affinity) { - cpu_set_t cpu_set; - CPU_ZERO(&cpu_set); - if (cpu_affinity_available_num) { - CPU_SET(cpu_affinity_available[SwooleG.process_id % cpu_affinity_available_num], &cpu_set); - } else { - CPU_SET(SwooleG.process_id % SW_CPU_NUM, &cpu_set); - } - if (swoole_set_cpu_affinity(&cpu_set) < 0) { - swoole_sys_warning("swoole_set_cpu_affinity() failed"); - } - } -#endif - - if (max_request < 1) { - SwooleWG.run_always = true; - } else { - SwooleWG.max_request = max_request; - if (max_request_grace > 0) { - SwooleWG.max_request += swoole_system_random(1, max_request_grace); - } - } - - worker->start_time = ::time(nullptr); - worker->request_count = 0; -} - -void Server::call_worker_start_callback(Worker *worker) { - void *hook_args[2]; - hook_args[0] = this; - hook_args[1] = (void *) (uintptr_t) worker->id; - - if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START)) { - swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START, hook_args); - } - if (isset_hook(HOOK_WORKER_START)) { - call_hook(Server::HOOK_WORKER_START, hook_args); - } - if (onWorkerStart) { - onWorkerStart(this, worker->id); - } +void Server::init_event_worker(Worker *worker) const { + worker->init(); + worker->set_max_request(max_request, max_request_grace); } int Server::start() { + swoole_clear_last_error(); + swoole_clear_last_error_msg(); if (start_check() < 0) { return SW_ERR; } @@ -614,22 +651,13 @@ int Server::start() { } // run as daemon if (daemonize > 0) { - /** - * redirect STDOUT to log file - */ + // redirect stdout/stderr to log file if (sw_logger()->is_opened()) { - sw_logger()->redirect_stdout_and_stderr(1); + sw_logger()->redirect_stdout_and_stderr(true); } - /** - * redirect STDOUT_FILENO/STDERR_FILENO to /dev/null - */ + // redirect stdout/stderr to /dev/null else { - null_fd = open("/dev/null", O_WRONLY); - if (null_fd > 0) { - swoole_redirect_stdout(null_fd); - } else { - swoole_sys_warning("open(/dev/null) failed"); - } + swoole_redirect_stdout("/dev/null"); } if (swoole_daemon(0, 1) < 0) { @@ -637,42 +665,21 @@ int Server::start() { } } - // master pid - gs->master_pid = getpid(); gs->start_time = ::time(nullptr); /** * store to ProcessPool object */ - gs->event_workers.ptr = this; - gs->event_workers.workers = workers; - gs->event_workers.worker_num = worker_num; - gs->event_workers.use_msgqueue = 0; + auto pool = get_event_worker_pool(); + pool->ptr = this; + pool->workers = workers; + pool->worker_num = worker_num; + pool->use_msgqueue = 0; SW_LOOP_N(worker_num) { - gs->event_workers.workers[i].pool = &gs->event_workers; - gs->event_workers.workers[i].id = i; - gs->event_workers.workers[i].type = SW_PROCESS_WORKER; - } - - /* - * For swoole_server->taskwait, create notify pipe and result shared memory. - */ - if (task_worker_num > 0 && worker_num > 0) { - task_result = (EventData *) sw_shm_calloc(worker_num, sizeof(EventData)); - if (!task_result) { - swoole_warning("malloc[task_result] failed"); - return SW_ERR; - } - SW_LOOP_N(worker_num) { - auto _pipe = new Pipe(true); - if (!_pipe->ready()) { - sw_shm_free(task_result); - delete _pipe; - return SW_ERR; - } - task_notify_pipes.emplace_back(_pipe); - } + pool->workers[i].pool = pool; + pool->workers[i].id = i; + pool->workers[i].type = SW_WORKER; } if (!user_worker_list.empty()) { @@ -685,7 +692,7 @@ int Server::start() { running = true; // factory start - if (!factory->start()) { + if (!factory_->start()) { return SW_ERR; } // write PID file @@ -696,8 +703,13 @@ int Server::start() { int ret; if (is_base_mode()) { ret = start_reactor_processes(); - } else { + } else if (is_process_mode()) { ret = start_reactor_threads(); + } else if (is_thread_mode()) { + ret = start_worker_threads(); + } else { + abort(); + return SW_ERR; } // failed to start if (ret < 0) { @@ -714,44 +726,35 @@ int Server::start() { /** * initializing server config, set default */ -Server::Server(enum Mode _mode) { - swoole_init(); - +Server::Server(Mode _mode) { reactor_num = SW_CPU_NUM > SW_REACTOR_MAX_THREAD ? SW_REACTOR_MAX_THREAD : SW_CPU_NUM; worker_num = SW_CPU_NUM; max_connection = SW_MIN(SW_MAX_CONNECTION, SwooleG.max_sockets); mode_ = _mode; // http server -#ifdef SW_HAVE_COMPRESSION - http_compression = 1; + http_compression = true; http_compression_level = SW_Z_BEST_SPEED; compression_min_length = SW_COMPRESSION_MIN_LENGTH_DEFAULT; -#endif -#ifdef __linux__ - timezone_ = timezone; -#else - struct timezone tz; - struct timeval tv; - gettimeofday(&tv, &tz); - timezone_ = tz.tz_minuteswest * 60; -#endif + timezone_ = get_timezone(); - /** - * alloc shared memory - */ - gs = (ServerGS *) sw_shm_malloc(sizeof(ServerGS)); + gs = static_cast(sw_shm_malloc(sizeof(ServerGS))); if (gs == nullptr) { - swoole_error("[Master] Fatal Error: failed to allocate memory for Server->gs"); + swoole_sys_warning("[Master] Fatal Error: failed to allocate memory for Server->gs"); + throw std::bad_alloc(); } - gs->pipe_packet_msg_id = 1; gs->max_concurrency = UINT_MAX; - message_bus.set_id_generator([this]() { return sw_atomic_fetch_add(&gs->pipe_packet_msg_id, 1); }); + msg_id_generator = [this]() { return sw_atomic_fetch_add(&gs->pipe_packet_msg_id, 1); }; + message_bus.set_id_generator(msg_id_generator); + +#ifdef SW_THREAD + worker_thread_start = [](std::shared_ptr, const WorkerFn &fn) { fn(); }; +#endif - g_server_instance = this; + SwooleG.server = this; } Server::~Server() { @@ -764,24 +767,52 @@ Server::~Server() { sw_shm_free(gs); } +Worker *Server::get_worker(uint16_t worker_id) const { + // Event Worker + if (worker_id < worker_num) { + return &(get_event_worker_pool()->workers[worker_id]); + } + + // Task Worker + uint32_t task_worker_max = task_worker_num + worker_num; + if (worker_id < task_worker_max) { + return &(get_task_worker_pool()->workers[worker_id - worker_num]); + } + + // User Worker + uint32_t user_worker_max = task_worker_max + user_worker_list.size(); + if (worker_id < user_worker_max) { + return &(user_workers[worker_id - task_worker_max]); + } + + return nullptr; +} + int Server::create() { if (is_created()) { return SW_ERR; } + assert(!ports.empty()); + if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_CREATE, this); } - session_list = (Session *) sw_shm_calloc(SW_SESSION_LIST_SIZE, sizeof(Session)); + if (!init_network_interface_addr_map()) { + return SW_ERR; + } + + session_list = static_cast(sw_shm_calloc(SW_SESSION_LIST_SIZE, sizeof(Session))); if (session_list == nullptr) { - swoole_error("sw_shm_calloc(%ld) for session_list failed", SW_SESSION_LIST_SIZE * sizeof(Session)); + swoole_sys_warning("sw_shm_calloc(%d, %zu) for session_list failed", SW_SESSION_LIST_SIZE, sizeof(Session)); return SW_ERR; } - port_gs_list = (ServerPortGS *) sw_shm_calloc(ports.size(), sizeof(ServerPortGS)); + port_gs_list = static_cast(sw_shm_calloc(ports.size(), sizeof(ServerPortGS))); if (port_gs_list == nullptr) { - swoole_error("sw_shm_calloc() for port_connnection_num_array failed"); + swoole_sys_warning( + "sw_shm_calloc(%zu, %zu) for port_connection_num_array failed", ports.size(), sizeof(ServerPortGS)); return SW_ERR; } @@ -794,25 +825,25 @@ int Server::create() { locations = std::make_shared>(); } - if (http_compression_types && http_compression_types->size() > 0) { - http_compression = 1; + if (http_compression_types && !http_compression_types->empty()) { + http_compression = true; } // Max Connections uint32_t minimum_connection = (worker_num + task_worker_num) * 2 + 32; - if (ports.size() > 0) { + if (!ports.empty()) { minimum_connection += ports.back()->get_fd(); } if (max_connection < minimum_connection) { - max_connection = SwooleG.max_sockets; + auto real_max_connection = SW_MAX(minimum_connection + 1, SwooleG.max_sockets); swoole_warning( - "max_connection must be bigger than %u, it's reset to %u", minimum_connection, SwooleG.max_sockets); + "max_connection must be bigger than %u, it's reset to %u", minimum_connection, real_max_connection); + max_connection = real_max_connection; } // Reactor Thread Num if (reactor_num > SW_CPU_NUM * SW_MAX_THREAD_NCPU) { - swoole_warning("serv->reactor_num == %d, Too many threads, reset to max value %d", - reactor_num, - SW_CPU_NUM * SW_MAX_THREAD_NCPU); + swoole_warning( + "reactor_num == %d, Too many threads, reset to max value %d", reactor_num, SW_CPU_NUM * SW_MAX_THREAD_NCPU); reactor_num = SW_CPU_NUM * SW_MAX_THREAD_NCPU; } else if (reactor_num == 0) { reactor_num = SW_CPU_NUM; @@ -832,40 +863,38 @@ int Server::create() { // TaskWorker Process Num if (task_worker_num > 0) { if (task_worker_num > SW_CPU_NUM * SW_MAX_WORKER_NCPU) { - swoole_warning("serv->task_worker_num == %d, Too many processes, reset to max value %d", + swoole_warning("task_worker_num == %d, Too many processes, reset to max value %d", task_worker_num, SW_CPU_NUM * SW_MAX_WORKER_NCPU); task_worker_num = SW_CPU_NUM * SW_MAX_WORKER_NCPU; } } - workers = (Worker *) sw_shm_calloc(worker_num, sizeof(Worker)); + workers = static_cast(sw_shm_calloc(worker_num, sizeof(Worker))); if (workers == nullptr) { - swoole_sys_warning("gmalloc[server->workers] failed"); + swoole_sys_warning("sw_shm_calloc(%d, %zu) for workers failed", worker_num, sizeof(Worker)); return SW_ERR; } - int retval; if (is_base_mode()) { - factory = new BaseFactory(this); - retval = create_reactor_processes(); + factory_ = create_base_factory(); + } else if (is_thread_mode()) { + factory_ = create_thread_factory(); } else { - factory = new ProcessFactory(this); - retval = create_reactor_threads(); + factory_ = create_process_factory(); + } + if (!factory_) { + return SW_ERR; } -#ifdef HAVE_PTHREAD_BARRIER - if (is_process_mode()) { - pthread_barrier_init(&reactor_thread_barrier, nullptr, reactor_num + 1); - pthread_barrierattr_setpshared(&gs->manager_barrier_attr, PTHREAD_PROCESS_SHARED); - pthread_barrier_init(&gs->manager_barrier, &gs->manager_barrier_attr, 2); + if (task_worker_num > 0 && !create_task_workers()) { + return SW_ERR; } -#endif if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_SERVER_CREATE, this); } - return retval; + return SW_OK; } void Server::clear_timer() { @@ -883,74 +912,160 @@ void Server::clear_timer() { } } -void Server::shutdown() { - swoole_trace_log(SW_TRACE_SERVER, "shutdown service"); - if (getpid() != gs->master_pid) { - kill(gs->master_pid, SIGTERM); - return; +bool Server::shutdown() { + if (sw_unlikely(!is_started())) { + swoole_set_last_error(SW_ERROR_WRONG_OPERATION); + return false; + } + + /** + * In thread mode, the worker thread masks all signals, and only a specific signal is processed. + * Sending a signal to its own process can inform the main thread to prepare for exit. + */ + if (is_thread_mode() && is_master_thread()) { + stop_master_thread(); + return true; } + if (is_process_mode()) { - if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN)) { - swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, this); + /** + * The working process may be using a non-root user, and it will not be possible to send a signal to the master + * process. A shutdown command must be sent via a pipe. + */ + SendData _send{}; + _send.info.type = SW_SERVER_EVENT_SHUTDOWN_SIGNAL; + if (is_process_mode()) { + return get_reactor_pipe_socket(0, 0)->send_async(&_send, sizeof(_send)) > 0; } - if (onBeforeShutdown) { - onBeforeShutdown(this); + } else { + pid_t pid; + if (is_base_mode()) { + pid = get_manager_pid() == 0 ? get_master_pid() : get_manager_pid(); + } else { + pid = get_master_pid(); + } + + if (swoole_kill(pid, SIGTERM) < 0) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_SYSTEM_CALL_FAIL, "failed to shutdown, kill(%d, SIGTERM) failed", pid); + return false; } } - running = false; - // stop all thread - if (SwooleTG.reactor) { - Reactor *reactor = SwooleTG.reactor; - reactor->set_wait_exit(true); - for (auto port : ports) { - if (port->is_dgram() and is_process_mode()) { - continue; - } - reactor->del(port->socket); + + return true; +} + +bool Server::signal_handler_reload(bool reload_all_workers) const { + const auto rv = reload(reload_all_workers); + sw_logger()->reopen(); + return rv; +} + +bool Server::signal_handler_read_message() const { + get_event_worker_pool()->read_message = true; + return true; +} + +bool Server::signal_handler_reopen_logger() const { + swoole_trace_log(SW_TRACE_SERVER, "reopen log file ['%s']", sw_logger()->get_file()); + sw_logger()->reopen(); + + if (is_process_mode()) { + swoole_kill(gs->manager_pid, SIGWINCH); + } + + return true; +} + +void Server::stop_master_thread() { + Reactor *reactor = SwooleTG.reactor; + reactor->set_wait_exit(true); + for (auto port : ports) { + if (port->is_dgram() && !is_base_mode()) { + continue; } - if (pipe_command) { - reactor->del(pipe_command->get_socket(true)); - } - clear_timer(); - if (max_wait_time > 0) { - time_t shutdown_time = std::time(nullptr); - auto fn = [shutdown_time, this](Reactor *reactor, size_t &) { - time_t now = std::time(nullptr); - if (now - shutdown_time > max_wait_time) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, - "graceful shutdown failed, forced termination"); - reactor->running = false; - } - return true; - }; - reactor->set_exit_condition(Reactor::EXIT_CONDITION_FORCED_TERMINATION, fn); + if (!port->socket->removed) { + reactor->del(port->socket); } } + if (pipe_command) { + reactor->del(pipe_command->get_socket(true)); + } + clear_timer(); + if (max_wait_time > 0) { + time_t shutdown_time = std::time(nullptr); + auto fn = [shutdown_time, this](Reactor *reactor, size_t &) { + time_t now = std::time(nullptr); + if (now - shutdown_time > max_wait_time) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, + "graceful shutdown failed, forced termination"); + reactor->running = false; + } + return true; + }; + reactor->set_exit_condition(Reactor::EXIT_CONDITION_FORCED_TERMINATION, fn); + } + if (is_thread_mode()) { + stop_worker_threads(); + } + if (is_process_mode() && single_thread) { + get_thread(0)->shutdown(reactor); + } +} +bool Server::signal_handler_shutdown() { + swoole_trace_log(SW_TRACE_SERVER, "shutdown begin"); if (is_base_mode()) { - gs->event_workers.running = 0; + if (gs->manager_pid > 0) { + running = false; + } else { + // single process worker, exit directly + get_event_worker_pool()->running = false; + stop_async_worker(sw_worker()); + } + return true; } + if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN)) { + swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, this); + } + if (onBeforeShutdown) { + onBeforeShutdown(this); + } + running = false; + stop_master_thread(); + swoole_trace_log(SW_TRACE_SERVER, "shutdown end"); + return true; +} - swoole_info("Server is shutdown now"); +bool Server::signal_handler_child_exit() const { + if (!running) { + return false; + } + if (is_base_mode()) { + return false; + } + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid > 0 && pid == gs->manager_pid) { + swoole_warning("Fatal Error: manager process exit. status=%d, signal=[%s]", + WEXITSTATUS(status), + swoole_signal_to_str(WTERMSIG(status))); + } + return true; } void Server::destroy() { + if (!factory_) { + return; + } + swoole_trace_log(SW_TRACE_SERVER, "release service"); if (swoole_isset_hook(SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN)) { swoole_call_hook(SW_GLOBAL_HOOK_AFTER_SERVER_SHUTDOWN, this); } - /** - * shutdown workers - */ - factory->shutdown(); - if (is_base_mode()) { - swoole_trace_log(SW_TRACE_SERVER, "terminate task workers"); - if (task_worker_num > 0) { - gs->task_workers.shutdown(); - gs->task_workers.destroy(); - } - } else { + + if (is_process_mode()) { swoole_trace_log(SW_TRACE_SERVER, "terminate reactor threads"); /** * Wait until all the end of the thread @@ -958,6 +1073,23 @@ void Server::destroy() { join_reactor_thread(); } + /** + * The position of the following code cannot be modified. + * We need to ensure that in SWOOLE_PROCESS mode, all SW_WRITE_EVENT events in the reactor thread are fully + * completed. Therefore, the worker process must wait for the reactor thread to exit first; otherwise, the main + * thread will keep waiting for the reactor thread to exit. + */ + if (is_started()) { + factory_->shutdown(); + } + + SW_LOOP_N(worker_num) { + Worker *worker = &workers[i]; + destroy_worker(worker); + } + + release_pipe_buffers(); + for (auto port : ports) { port->close(); } @@ -966,41 +1098,37 @@ void Server::destroy() { sw_shm_free(user_workers); user_workers = nullptr; } - if (null_fd > 0) { - ::close(null_fd); - null_fd = -1; - } + swoole_signal_clear(); - /** - * shutdown status - */ + gs->start = 0; gs->shutdown = 1; - /** - * callback - */ + if (onShutdown) { onShutdown(this); } - if (is_base_mode()) { - destroy_reactor_processes(); - } else { - destroy_reactor_threads(); - } + SW_LOOP_N(SW_MAX_HOOK_TYPE) { if (hooks[i]) { - std::list *l = reinterpret_cast *>(hooks[i]); + auto l = static_cast *>(hooks[i]); hooks[i] = nullptr; delete l; } } -#ifdef HAVE_PTHREAD_BARRIER - if (is_process_mode()) { - pthread_barrier_destroy(&reactor_thread_barrier); - pthread_barrier_destroy(&gs->manager_barrier); - pthread_barrierattr_destroy(&gs->manager_barrier_attr); + + if (is_base_mode()) { + destroy_base_factory(); + } else if (is_thread_mode()) { + destroy_thread_factory(); + } else { + destroy_process_factory(); } -#endif + + if (task_worker_num > 0) { + swoole_trace_log(SW_TRACE_SERVER, "terminate task workers"); + destroy_task_workers(); + } + sw_shm_free(session_list); sw_shm_free(port_gs_list); sw_shm_free(workers); @@ -1009,10 +1137,10 @@ void Server::destroy() { port_gs_list = nullptr; workers = nullptr; - delete factory; - factory = nullptr; + delete factory_; + factory_ = nullptr; - g_server_instance = nullptr; + SwooleG.server = nullptr; } /** @@ -1025,7 +1153,8 @@ bool Server::feedback(Connection *conn, enum ServerEventType event) { _send.info.reactor_id = conn->reactor_id; if (is_process_mode()) { - return send_to_reactor_thread((EventData *) &_send.info, sizeof(_send.info), conn->session_id) > 0; + return send_to_reactor_thread( + reinterpret_cast(&_send.info), sizeof(_send.info), conn->session_id) > 0; } else { return send_to_connection(&_send) == SW_OK; } @@ -1048,7 +1177,7 @@ bool Server::command(WorkerId process_id, if (is_process_mode() && !is_master()) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "command() can only be used in master process"); return false; - } else if (is_base_mode() && SwooleWG.worker->id != 0) { + } else if (is_base_mode() && sw_worker()->id != 0) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "command() can only be used in worker process 0"); return false; } @@ -1063,56 +1192,58 @@ bool Server::command(WorkerId process_id, } int command_id = iter->second.id; - int64_t requset_id = command_current_request_id++; + int64_t request_id = command_current_request_id++; Socket *pipe_sock; SendData task{}; - task.info.fd = requset_id; + task.info.fd = request_id; task.info.reactor_id = process_id; task.info.server_fd = command_id; task.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; task.info.len = msg.length(); task.data = msg.c_str(); + command_callbacks[request_id] = fn; + if (!(process_type & iter->second.accepted_process_types)) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [process_type]"); + _fail: + command_callbacks.erase(request_id); return false; } if (process_type == Command::REACTOR_THREAD) { if (!is_process_mode()) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [server_mode]"); - return false; + goto _fail; } if (process_id >= reactor_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid thread_id[%d]", process_id); - return false; + goto _fail; } pipe_sock = get_worker(process_id)->pipe_worker; } else if (process_type == Command::EVENT_WORKER) { if (process_id >= worker_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid worker_id[%d]", process_id); - return false; + goto _fail; } pipe_sock = get_worker(process_id)->pipe_master; } else if (process_type == Command::TASK_WORKER) { if (process_id >= task_worker_num) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_INVALID_PARAMS, "invalid task_worker_id[%d]", process_id); - return false; + goto _fail; } EventData buf; - memset(&buf.info, 0, sizeof(buf.info)); if (!task_pack(&buf, msg.c_str(), msg.length())) { - return false; + goto _fail; } buf.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; - buf.info.fd = requset_id; + buf.info.fd = request_id; buf.info.server_fd = command_id; int _dst_worker_id = process_id; - if (gs->task_workers.dispatch(&buf, &_dst_worker_id) <= 0) { - return false; + if (!this->task(&buf, &_dst_worker_id)) { + goto _fail; } - command_callbacks[requset_id] = fn; return true; } else if (process_type == Command::MANAGER) { EventData buf; @@ -1122,18 +1253,17 @@ bool Server::command(WorkerId process_id, "message is too large, maximum length is %lu, the given length is %lu", sizeof(buf.data), msg.length()); - return false; + goto _fail; } memset(&buf.info, 0, sizeof(buf.info)); buf.info.type = SW_SERVER_EVENT_COMMAND_REQUEST; - buf.info.fd = requset_id; + buf.info.fd = request_id; buf.info.server_fd = command_id; buf.info.len = msg.length(); memcpy(buf.data, msg.c_str(), msg.length()); - if (gs->event_workers.push_message(&buf) < 0) { - return false; + if (get_event_worker_pool()->push_message(&buf) < 0) { + goto _fail; } - command_callbacks[requset_id] = fn; return true; } else if (process_type == Command::MASTER) { auto result = call_command_handler_in_master(command_id, msg); @@ -1141,12 +1271,11 @@ bool Server::command(WorkerId process_id, return true; } else { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_OPERATION_NOT_SUPPORT, "unsupported [process_type]"); - return false; + goto _fail; } if (!message_bus.write(pipe_sock, &task)) { - return false; + goto _fail; } - command_callbacks[requset_id] = fn; return true; } @@ -1168,13 +1297,13 @@ void Server::store_pipe_fd(UnixSocket *p) { /** * @process Worker */ -bool Server::send(SessionId session_id, const void *data, uint32_t length) { +bool Server::send(SessionId session_id, const void *data, uint32_t length) const { SendData _send{}; _send.info.fd = session_id; _send.info.type = SW_SERVER_EVENT_SEND_DATA; _send.data = (char *) data; _send.info.len = length; - if (factory->finish(&_send)) { + if (factory_->finish(&_send)) { sw_atomic_fetch_add(&gs->response_count, 1); sw_atomic_fetch_add(&gs->total_send_bytes, length); ListenPort *port = get_port_by_session_id(session_id); @@ -1182,14 +1311,26 @@ bool Server::send(SessionId session_id, const void *data, uint32_t length) { sw_atomic_fetch_add(&port->gs->response_count, 1); sw_atomic_fetch_add(&port->gs->total_send_bytes, length); } - if (SwooleWG.worker) { - SwooleWG.worker->response_count++; + if (sw_worker()) { + sw_worker()->response_count++; } return true; } return false; } +bool Server::has_kernel_nobufs_error(SessionId session_id) const { + auto conn = get_connection(session_id); + if (!conn || !conn->socket) { + return false; + } + if (is_process_mode()) { + return get_reactor_pipe_socket(session_id, conn->reactor_id)->has_kernel_nobufs(); + } else { + return conn->socket->has_kernel_nobufs(); + } +} + int Server::schedule_worker(int fd, SendData *data) { uint32_t key = 0; @@ -1211,23 +1352,17 @@ int Server::schedule_worker(int fd, SendData *data) { // Using the IP touch access to hash else if (dispatch_mode == DISPATCH_IPMOD) { Connection *conn = get_connection(fd); - // UDP + Address *addr; if (conn == nullptr) { - key = fd; - } - // IPv4 - else if (conn->socket_type == SW_SOCK_TCP) { - key = conn->info.addr.inet_v4.sin_addr.s_addr; + auto *packet = (DgramPacket *) data->data; + addr = &packet->socket_addr; + } else { + addr = &conn->info; } - // IPv6 - else { -#ifdef HAVE_KQUEUE - key = *(((uint32_t *) &conn->info.addr.inet_v6.sin6_addr) + 3); -#elif defined(_WIN32) - key = conn->info.addr.inet_v6.sin6_addr.u.Word[3]; -#else - key = conn->info.addr.inet_v6.sin6_addr.s6_addr32[3]; -#endif + if (Socket::is_inet4(addr->type)) { + key = ntohl(addr->addr.inet_v4.sin_addr.s_addr); + } else { + key = swoole_hash_php((char *) &addr->addr.inet_v6, sizeof(addr->addr.inet_v6)); } } else if (dispatch_mode == DISPATCH_UIDMOD) { Connection *conn = get_connection(fd); @@ -1239,7 +1374,7 @@ int Server::schedule_worker(int fd, SendData *data) { } else if (dispatch_mode == DISPATCH_CO_CONN_LB) { Connection *conn = get_connection(fd); if (conn == nullptr) { - return key % worker_num; + return fd % worker_num; } if (conn->worker_id < 0) { conn->worker_id = get_lowest_load_worker_id(); @@ -1262,8 +1397,8 @@ int Server::schedule_worker(int fd, SendData *data) { * [Master] send to client or append to out_buffer * @return SW_OK or SW_ERR */ -int Server::send_to_connection(SendData *_send) { - SessionId session_id = _send->info.fd; +int Server::send_to_connection(const SendData *_send) const { + const SessionId session_id = _send->info.fd; const char *_send_data = _send->data; uint32_t _send_length = _send->info.len; @@ -1299,7 +1434,7 @@ int Server::send_to_connection(SendData *_send) { assert(fd % reactor_num == SwooleTG.id); } - if (is_base_mode() && conn->overflow) { + if (!is_process_mode() && conn->overflow) { if (send_yield) { swoole_set_last_error(SW_ERROR_OUTPUT_SEND_YIELD); } else { @@ -1358,10 +1493,8 @@ int Server::send_to_connection(SendData *_send) { goto _buffer_send; } - ssize_t n; - _direct_send: - n = _socket->send(_send_data, _send_length, 0); + ssize_t n = _socket->send(_send_data, _send_length, 0); if (n == _send_length) { conn->last_send_time = microtime(); return SW_OK; @@ -1384,17 +1517,15 @@ int Server::send_to_connection(SendData *_send) { } } - BufferChunk *chunk; // close connection if (_send->info.type == SW_SERVER_EVENT_CLOSE) { - chunk = _socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); - chunk->value.data.val1 = _send->info.type; + _socket->out_buffer->alloc(BufferChunk::TYPE_CLOSE, 0); conn->close_queued = 1; } // sendfile to client else if (_send->info.type == SW_SERVER_EVENT_SEND_FILE) { - SendfileTask *task = (SendfileTask *) _send_data; - if (conn->socket->sendfile(task->filename, task->offset, task->length) < 0) { + auto *task = (SendfileTask *) _send_data; + if (conn->socket->sendfile_async(task->filename, task->offset, task->length) < 0) { return false; } } @@ -1402,8 +1533,7 @@ int Server::send_to_connection(SendData *_send) { else { // connection is closed if (conn->peer_closed) { - swoole_error_log( - SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED_BY_CLIENT, "socket#%d is closed by client", fd); + swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSED_BY_CLIENT, "socket#%d is closed by client", fd); return false; } // connection output buffer overflow @@ -1411,10 +1541,8 @@ int Server::send_to_connection(SendData *_send) { if (send_yield) { swoole_set_last_error(SW_ERROR_OUTPUT_SEND_YIELD); } else { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_OUTPUT_BUFFER_OVERFLOW, - "connection#%d output buffer overflow", - fd); + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_OUTPUT_BUFFER_OVERFLOW, "connection#%d output buffer overflow", fd); } conn->overflow = 1; if (onBufferEmpty && onBufferFull == nullptr) { @@ -1425,7 +1553,6 @@ int Server::send_to_connection(SendData *_send) { _socket->out_buffer->append(_send_data, _send_length); conn->send_queued_bytes = _socket->out_buffer->length(); - ListenPort *port = get_port_by_fd(fd); if (onBufferFull && conn->high_watermark == 0 && _socket->out_buffer->length() >= port->buffer_high_watermark) { notify(conn, SW_SERVER_EVENT_BUFFER_FULL); conn->high_watermark = 1; @@ -1433,10 +1560,14 @@ int Server::send_to_connection(SendData *_send) { } if (port->max_idle_time > 0 && _socket->send_timer == nullptr) { - auto timeout_callback = get_timeout_callback(port, reactor, conn); - _socket->send_timeout_ = port->max_idle_time; + const auto timeout_callback = get_timeout_callback(port, reactor, conn); + _socket->read_timeout = port->max_idle_time; _socket->last_sent_time = time(true); - _socket->send_timer = swoole_timer_add(port->max_idle_time * 1000, true, timeout_callback); + _socket->send_timer = swoole_timer_add(sec2msec(port->max_idle_time), true, timeout_callback); + swoole_trace_log(SW_TRACE_SERVER, + "added send_timer[id=%ld], port->max_idle_time=%f", + _socket->send_timer->id, + port->max_idle_time); } if (!_socket->isset_writable_event()) { @@ -1449,19 +1580,19 @@ int Server::send_to_connection(SendData *_send) { /** * use in master process */ -bool Server::notify(Connection *conn, enum ServerEventType event) { +bool Server::notify(Connection *conn, ServerEventType event) const { DataHead notify_event{}; notify_event.type = event; notify_event.reactor_id = conn->reactor_id; notify_event.fd = conn->fd; notify_event.server_fd = conn->server_fd; - return factory->notify(¬ify_event); + return factory_->notify(¬ify_event); } /** * @process Worker */ -bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length) { +bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, off_t offset, size_t length) const { if (sw_unlikely(session_id <= 0)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SESSION_INVALID_ID, "invalid fd[%ld]", session_id); return false; @@ -1474,7 +1605,7 @@ bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, o } char _buffer[SW_IPC_BUFFER_SIZE]; - SendfileTask *req = reinterpret_cast(_buffer); + auto *req = reinterpret_cast(_buffer); // file name size if (sw_unlikely(l_file > sizeof(_buffer) - sizeof(*req) - 1)) { @@ -1487,7 +1618,7 @@ bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, o return false; } // string must be zero termination (for `state` system call) - swoole_strlcpy((char *) req->filename, file, sizeof(_buffer) - sizeof(*req)); + swoole_strlcpy(req->filename, file, sizeof(_buffer) - sizeof(*req)); // check state struct stat file_stat; @@ -1516,23 +1647,23 @@ bool Server::sendfile(SessionId session_id, const char *file, uint32_t l_file, o send_data.info.len = sizeof(SendfileTask) + l_file + 1; send_data.data = _buffer; - return factory->finish(&send_data); + return factory_->finish(&send_data); } /** * [Worker] Returns the number of bytes sent */ -bool Server::sendwait(SessionId session_id, const void *data, uint32_t length) { +bool Server::sendwait(SessionId session_id, const void *data, uint32_t length) const { Connection *conn = get_connection_verify(session_id); if (!conn) { swoole_error_log(SW_LOG_TRACE, - SW_ERROR_SESSION_CLOSED, - "send %d byte failed, because session#%ld is closed", + SW_ERROR_SESSION_NOT_EXIST, + "send %d byte failed, because session#%ld is not exists", length, session_id); return false; } - return conn->socket->send_blocking(data, length) == length; + return conn->socket->send_sync(data, length) == length; } void Server::call_hook(HookType type, void *arg) { @@ -1543,32 +1674,47 @@ void Server::call_hook(HookType type, void *arg) { /** * [Worker] */ -bool Server::close(SessionId session_id, bool reset) { - return factory->end(session_id, reset ? (CLOSE_ACTIVELY | CLOSE_RESET) : CLOSE_ACTIVELY); +bool Server::close(SessionId session_id, bool reset) const { + return factory_->end(session_id, reset ? (CLOSE_ACTIVELY | CLOSE_RESET) : CLOSE_ACTIVELY); +} + +bool Server::send_pipe_message(WorkerId worker_id, EventData *msg) { + msg->info.type = SW_SERVER_EVENT_PIPE_MESSAGE; + + return send_to_worker_from_worker(get_worker(worker_id), msg, msg->size(), SW_PIPE_MASTER | SW_PIPE_NONBLOCK) > 0; +} + +bool Server::send_pipe_message(WorkerId worker_id, const char *data, size_t len) { + EventData buf; + if (!task_pack(&buf, data, len)) { + return false; + } + return send_pipe_message(worker_id, &buf); } -void Server::init_signal_handler() { +void Server::init_signal_handler() const { swoole_signal_set(SIGPIPE, nullptr); swoole_signal_set(SIGHUP, nullptr); if (is_process_mode()) { - swoole_signal_set(SIGCHLD, Server_signal_handler); + swoole_signal_set(SIGCHLD, master_signal_handler); } else { - swoole_signal_set(SIGIO, Server_signal_handler); + swoole_signal_set(SIGIO, master_signal_handler); } - swoole_signal_set(SIGUSR1, Server_signal_handler); - swoole_signal_set(SIGUSR2, Server_signal_handler); - swoole_signal_set(SIGTERM, Server_signal_handler); + swoole_signal_set(SIGUSR1, master_signal_handler); + swoole_signal_set(SIGUSR2, master_signal_handler); + swoole_signal_set(SIGTERM, master_signal_handler); + swoole_signal_set(SIGWINCH, master_signal_handler); #ifdef SIGRTMIN - swoole_signal_set(SIGRTMIN, Server_signal_handler); + swoole_signal_set(SIGRTMIN, master_signal_handler); #endif - // for test - swoole_signal_set(SIGVTALRM, Server_signal_handler); - set_minfd(SwooleG.signal_fd); + if (SwooleG.signal_fd > 0) { + set_minfd(SwooleG.signal_fd); + } } void Server::timer_callback(Timer *timer, TimerNode *tnode) { - Server *serv = (Server *) tnode->data; + auto *serv = static_cast(tnode->data); time_t now = ::time(nullptr); if (serv->scheduler_warning && serv->warning_time < now) { serv->scheduler_warning = false; @@ -1576,24 +1722,35 @@ void Server::timer_callback(Timer *timer, TimerNode *tnode) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_NO_IDLE_WORKER, "No idle worker is available"); } - if (serv->gs->task_workers.scheduler_warning && serv->gs->task_workers.warning_time < now) { - serv->gs->task_workers.scheduler_warning = 0; - serv->gs->task_workers.warning_time = now; + auto task_pool = serv->get_task_worker_pool(); + if (task_pool->scheduler_warning && task_pool->warning_time < now) { + task_pool->scheduler_warning = 0; + task_pool->warning_time = now; swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_NO_IDLE_WORKER, "No idle task worker is available"); } if (serv->hooks[Server::HOOK_MASTER_TIMER]) { serv->call_hook(Server::HOOK_MASTER_TIMER, serv); } + + if (!serv->is_running()) { + sw_reactor()->running = false; + serv->stop_master_thread(); + } } int Server::add_worker(Worker *worker) { + if (is_created()) { + swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "must add worker before server is created"); + return SW_ERR; + } user_worker_list.push_back(worker); + worker->id = user_worker_list.size() - 1; return worker->id; } -int Server::add_hook(Server::HookType type, const Callback &func, int push_back) { - return swoole::hook_add(hooks, (int) type, func, push_back); +void Server::add_hook(Server::HookType type, const Callback &func, int push_back) { + swoole::hook_add(hooks, (int) type, func, push_back); } bool Server::add_command(const std::string &name, int accepted_process_types, const Command::Handler &func) { @@ -1603,7 +1760,7 @@ bool Server::add_command(const std::string &name, int accepted_process_types, co if (commands.find(name) != commands.end()) { return false; } - if (is_process_mode() && pipe_command == nullptr) { + if (!is_base_mode() && pipe_command == nullptr) { auto _pipe = new UnixSocket(false, SOCK_DGRAM); if (!_pipe->ready()) { delete _pipe; @@ -1622,11 +1779,11 @@ bool Server::add_command(const std::string &name, int accepted_process_types, co return true; } -void Server::check_port_type(ListenPort *ls) { +void Server::check_port_type(const ListenPort *ls) { if (ls->is_dgram()) { // dgram socket, setting socket buffer size ls->socket->set_buffer_size(ls->socket_buffer_size); - have_dgram_sock = 1; + have_dgram_sock = true; dgram_port_num++; if (ls->type == SW_SOCK_UDP) { udp_socket_ipv4 = ls->socket; @@ -1636,11 +1793,11 @@ void Server::check_port_type(ListenPort *ls) { dgram_socket = ls->socket; } } else { - have_stream_sock = 1; + have_stream_sock = true; } } -bool Server::is_healthy_connection(double now, Connection *conn) { +bool Server::is_healthy_connection(double now, const Connection *conn) const { if (conn->protect || conn->last_recv_time == 0) { return true; } @@ -1673,7 +1830,6 @@ int Server::add_systemd_socket() { } int count = 0; - int sock; int start_fd; if (!swoole_get_env("LISTEN_FDS_START", &start_fd)) { @@ -1683,8 +1839,8 @@ int Server::add_systemd_socket() { return 0; } - for (sock = start_fd; sock < start_fd + n; sock++) { - std::unique_ptr ptr(new ListenPort()); + for (int sock = start_fd; sock < start_fd + n; sock++) { + std::unique_ptr ptr(new ListenPort(this)); ListenPort *ls = ptr.get(); if (!ls->import(sock)) { @@ -1697,6 +1853,7 @@ int Server::add_systemd_socket() { ptr.release(); check_port_type(ls); ports.push_back(ls); + ls->object_id = ports.size(); count++; } @@ -1704,7 +1861,7 @@ int Server::add_systemd_socket() { } ListenPort *Server::add_port(SocketType type, const char *host, int port) { - if (session_list) { + if (is_created()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "must add port before server is created"); return nullptr; } @@ -1715,7 +1872,7 @@ ListenPort *Server::add_port(SocketType type, const char *host, int port) { SW_MAX_LISTEN_PORT); return nullptr; } - if (!(type == SW_SOCK_UNIX_DGRAM || type == SW_SOCK_UNIX_STREAM) && (port < 0 || port > 65535)) { + if (!Socket::is_local(type) && !Address::verify_port(port)) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_LISTEN_PORT, "invalid port [%d]", port); return nullptr; } @@ -1728,134 +1885,68 @@ ListenPort *Server::add_port(SocketType type, const char *host, int port) { return nullptr; } - std::unique_ptr ptr(new ListenPort); + std::unique_ptr ptr(new ListenPort(this)); ListenPort *ls = ptr.get(); ls->type = type; ls->port = port; ls->host = host; -#ifdef SW_USE_OPENSSL if (type & SW_SOCK_SSL) { - type = (SocketType)(type & (~SW_SOCK_SSL)); + type = static_cast(type & (~SW_SOCK_SSL)); ls->type = type; ls->ssl = 1; - ls->ssl_context = new SSLContext(); - ls->ssl_context->prefer_server_ciphers = 1; - ls->ssl_context->session_tickets = 0; - ls->ssl_context->stapling = 1; - ls->ssl_context->stapling_verify = 1; - ls->ssl_context->ciphers = sw_strdup(SW_SSL_CIPHER_LIST); - ls->ssl_context->ecdh_curve = sw_strdup(SW_SSL_ECDH_CURVE); - - if (ls->is_dgram()) { -#ifdef SW_SUPPORT_DTLS - ls->ssl_context->protocols = SW_SSL_DTLS; - ls->dtls_sessions = new std::unordered_map; - -#else - swoole_warning("DTLS support require openssl-1.1 or later"); - return nullptr; -#endif - } + ls->ssl_context_init(); } -#endif - ls->socket = make_socket( - ls->type, ls->is_dgram() ? SW_FD_DGRAM_SERVER : SW_FD_STREAM_SERVER, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); - if (ls->socket == nullptr) { + if (ls->create_socket() < 0) { + swoole_set_last_error(errno); return nullptr; } -#if defined(SW_SUPPORT_DTLS) && defined(HAVE_KQUEUE) - if (ls->is_dtls()) { - ls->socket->set_reuse_port(); - } -#endif - if (ls->socket->bind(ls->host, &ls->port) < 0) { - ls->socket->free(); - return nullptr; - } - ls->socket->info.assign(ls->type, ls->host, ls->port); check_port_type(ls); ptr.release(); ports.push_back(ls); + ls->object_id = ports.size(); return ls; } -static void Server_signal_handler(int sig) { - swoole_trace_log(SW_TRACE_SERVER, "signal[%d] %s triggered in %d", sig, swoole_signal_to_str(sig), getpid()); +void Server::master_signal_handler(int signo) { + swoole_trace_log(SW_TRACE_SERVER, "signal[%d] %s triggered in %d", signo, swoole_signal_to_str(signo), getpid()); Server *serv = sw_server(); - if (!SwooleG.running or !serv) { + if (!SwooleG.running || !serv || !serv->is_running()) { return; } - int status; - pid_t pid; - switch (sig) { + switch (signo) { case SIGTERM: - serv->shutdown(); + serv->signal_handler_shutdown(); break; case SIGCHLD: - if (!serv->running) { - break; - } - if (sw_server()->is_base_mode()) { - break; - } - pid = waitpid(-1, &status, WNOHANG); - if (pid > 0 && pid == serv->gs->manager_pid) { - swoole_warning("Fatal Error: manager process exit. status=%d, signal=[%s]", - WEXITSTATUS(status), - swoole_signal_to_str(WTERMSIG(status))); - } + serv->signal_handler_child_exit(); break; - /** - * for test - */ - case SIGVTALRM: - swoole_warning("SIGVTALRM coming"); - break; - /** - * proxy the restart signal - */ case SIGUSR1: case SIGUSR2: - if (serv->is_base_mode()) { - if (!serv->gs->event_workers.reload()) { - break; - } - serv->gs->event_workers.reload_init = false; - } else { - swoole_kill(serv->gs->manager_pid, sig); - } - sw_logger()->reopen(); + serv->signal_handler_reload(signo == SIGUSR1); break; case SIGIO: - serv->gs->event_workers.read_message = true; + serv->signal_handler_read_message(); + break; + case SIGWINCH: + serv->signal_handler_reopen_logger(); break; default: - #ifdef SIGRTMIN - if (sig == SIGRTMIN) { - uint32_t i; - Worker *worker; - for (i = 0; i < serv->worker_num + serv->task_worker_num + serv->get_user_worker_num(); i++) { - worker = serv->get_worker(i); - swoole_kill(worker->pid, SIGRTMIN); - } - if (serv->is_process_mode()) { - swoole_kill(serv->gs->manager_pid, SIGRTMIN); - } - sw_logger()->reopen(); + if (signo == SIGRTMIN) { + serv->signal_handler_reopen_logger(); } #endif break; } } -void Server::foreach_connection(const std::function &callback) { +void Server::foreach_connection(const std::function &callback) const { for (int fd = get_minfd(); fd <= get_maxfd(); fd++) { Connection *conn = get_connection(fd); if (is_valid_connection(conn)) { @@ -1864,7 +1955,7 @@ void Server::foreach_connection(const std::function &callbac } } -void Server::abort_connection(Reactor *reactor, ListenPort *ls, Socket *_socket) { +void Server::abort_connection(Reactor *reactor, const ListenPort *ls, Socket *_socket) const { sw_atomic_fetch_add(&gs->abort_count, 1); sw_atomic_fetch_add(&ls->gs->abort_count, 1); if (_socket->object) { @@ -1874,14 +1965,115 @@ void Server::abort_connection(Reactor *reactor, ListenPort *ls, Socket *_socket) } } +// see https://github.com/swoole/swoole-src/issues/5407 +// see https://github.com/swoole/swoole-src/issues/5432 +void Server::reset_worker_counter(Worker *worker) const { + auto value = worker->concurrency; + if (value > 0 && sw_atomic_value_cmp_set(&worker->concurrency, value, 0) == value) { + sw_atomic_sub_fetch(&gs->concurrency, value); + if ((int) gs->concurrency < 0) { + gs->concurrency = 0; + } + } + worker->request_count = 0; + worker->response_count = 0; + worker->dispatch_count = 0; +} + +void Server::abort_worker(Worker *worker) const { + reset_worker_counter(worker); + + if (is_base_mode()) { + SW_LOOP_N(SW_SESSION_LIST_SIZE) { + Session *session = get_session(i); + if (session->reactor_id == worker->id) { + session->fd = 0; + } + } + } +} + +bool Server::init_network_interface_addr_map() { + struct ifaddrs *ifaddr = nullptr; + if (getifaddrs(&ifaddr) != 0) { + swoole_sys_warning("getifaddrs() failed"); + return false; + } + + uint16_t index = 1; + for (struct ifaddrs *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr || !(ifa->ifa_flags & IFF_UP)) { + continue; + } + network::Address na{}; + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *) ifa->ifa_addr; + na.addr.inet_v4.sin_family = AF_INET; + na.addr.inet_v4.sin_addr = sin->sin_addr; + na.type = SW_SOCK_TCP; + local_addr_v4_map.emplace(index++, na); + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) ifa->ifa_addr; + na.addr.inet_v6.sin6_family = AF_INET6; + na.addr.inet_v6.sin6_addr = sin6->sin6_addr; + na.type = SW_SOCK_TCP6; + local_addr_v6_map.emplace(index++, na); + } + } + + freeifaddrs(ifaddr); + return true; +} + +uint16_t Server::get_local_addr_index(network::Address *addr) { + if (addr->type == SW_SOCK_TCP) { + for (auto kv : local_addr_v4_map) { + if (memcmp(addr->addr_v4(), kv.second.addr_v4(), sizeof(*addr->addr_v4())) == 0) { + return kv.first; + } + } + } else { + for (auto kv : local_addr_v6_map) { + if (memcmp(addr->addr_v6(), kv.second.addr_v6(), sizeof(*addr->addr_v6())) == 0) { + return kv.first; + } + } + } + return 0; +} + +const char *Server::get_local_addr(Connection *conn) { + if (conn->socket_type == SW_SOCK_TCP) { + auto iter = local_addr_v4_map.find(conn->local_addr_index); + if (iter != local_addr_v4_map.end()) { + return iter->second.get_addr(); + } else { + return "127.0.0.1"; + } + } else if (conn->socket_type == SW_SOCK_TCP6) { + auto iter = local_addr_v6_map.find(conn->local_addr_index); + if (iter != local_addr_v6_map.end()) { + return iter->second.get_addr(); + } else { + return "::1"; + } + } else { + return get_port_by_server_fd(conn->server_fd)->host.c_str(); + } +} + +const char *Server::get_remote_addr(Connection *conn) { + return conn->info.get_addr(); +} + /** * new connection */ -Connection *Server::add_connection(ListenPort *ls, Socket *_socket, int server_fd) { +Connection *Server::add_connection(const ListenPort *ls, Socket *_socket, int server_fd) { int fd = _socket->fd; Connection *connection = &(connection_list[fd]); - ReactorId reactor_id = is_base_mode() ? SwooleG.process_id : fd % reactor_num; + ReactorId reactor_id = is_base_mode() ? swoole_get_worker_id() : fd % reactor_num; *connection = {}; sw_spinlock(&gs->spinlock); @@ -1909,11 +2101,11 @@ Connection *Server::add_connection(ListenPort *ls, Socket *_socket, int server_f _socket->object = connection; _socket->removed = 1; _socket->buffer_size = ls->socket_buffer_size; - _socket->send_timeout_ = _socket->recv_timeout_ = 0; + _socket->write_timeout = _socket->read_timeout = 0; // TCP Nodelay - if (ls->open_tcp_nodelay && (ls->type == SW_SOCK_TCP || ls->type == SW_SOCK_TCP6)) { - if (ls->socket->set_tcp_nodelay() != 0) { + if (ls->open_tcp_nodelay && ls->socket->is_tcp()) { + if (!_socket->set_tcp_nodelay()) { swoole_sys_warning("setsockopt(TCP_NODELAY) failed"); } _socket->enable_tcp_nodelay = true; @@ -1935,17 +2127,22 @@ Connection *Server::add_connection(ListenPort *ls, Socket *_socket, int server_f connection->fd = fd; connection->reactor_id = reactor_id; - connection->server_fd = (sw_atomic_t) server_fd; + connection->server_fd = server_fd; connection->last_recv_time = connection->connect_time = microtime(); connection->active = 1; connection->worker_id = -1; connection->socket_type = ls->type; connection->socket = _socket; + connection->ssl = _socket->ssl != nullptr; memcpy(&connection->info.addr, &_socket->info.addr, _socket->info.len); connection->info.len = _socket->info.len; connection->info.type = connection->socket_type; + connection->socket->get_name(); + connection->local_port = connection->socket->info.get_port(); + connection->local_addr_index = get_local_addr_index(&connection->socket->info); + if (!ls->ssl) { _socket->direct_send = 1; } @@ -1960,25 +2157,33 @@ Connection *Server::add_connection(ListenPort *ls, Socket *_socket, int server_f gs->accept_count++; ls->gs->accept_count++; - sw_atomic_fetch_add(&gs->connection_num, 1); - sw_atomic_fetch_add(&ls->gs->connection_num, 1); + if (is_base_mode()) { + sw_atomic_fetch_add(&gs->connection_nums[reactor_id], 1); + sw_atomic_fetch_add(&ls->gs->connection_nums[reactor_id], 1); + } else { + sw_atomic_fetch_add(&gs->connection_num, 1); + sw_atomic_fetch_add(&ls->gs->connection_num, 1); + } return connection; } void Server::init_ipc_max_size() { -#ifndef __linux__ - ipc_max_size = SW_IPC_MAX_SIZE; -#else - int bufsize; - /** - * Get the maximum ipc[unix socket with dgram] transmission length - */ - if (workers[0].pipe_master->get_option(SOL_SOCKET, SO_SNDBUF, &bufsize) != 0) { - bufsize = SW_IPC_MAX_SIZE; + ipc_max_size = SW_IPC_BUFFER_MAX_SIZE; +} + +void Server::init_pipe_sockets(MessageBus *mb) const { + assert(is_started()); + size_t n = get_core_worker_num(); + + SW_LOOP_N(n) { + const auto worker = get_worker(i); + if (i >= worker_num && task_ipc_mode != TASK_IPC_UNIXSOCK) { + continue; + } + mb->init_pipe_socket(worker->pipe_master); + mb->init_pipe_socket(worker->pipe_worker); } - ipc_max_size = SW_MIN(bufsize, SW_IPC_BUFFER_MAX_SIZE) - SW_DGRAM_HEADER_SIZE; -#endif } /** @@ -1989,13 +2194,16 @@ int Server::create_pipe_buffers() { return message_bus.alloc_buffer() ? SW_OK : SW_ERR; } -int Server::get_idle_worker_num() { - uint32_t i; +void Server::release_pipe_buffers() { + message_bus.free_buffer(); +} + +uint32_t Server::get_idle_worker_num() const { uint32_t idle_worker_num = 0; - for (i = 0; i < worker_num; i++) { + for (uint32_t i = 0; i < worker_num; i++) { Worker *worker = get_worker(i); - if (worker->status == SW_WORKER_IDLE) { + if (worker->is_idle()) { idle_worker_num++; } } @@ -2003,12 +2211,12 @@ int Server::get_idle_worker_num() { return idle_worker_num; } -int Server::get_idle_task_worker_num() { +int Server::get_idle_task_worker_num() const { uint32_t idle_worker_num = 0; for (uint32_t i = worker_num; i < (worker_num + task_worker_num); i++) { - Worker *worker = get_worker(i); - if (worker->status == SW_WORKER_IDLE) { + const Worker *worker = get_worker(i); + if (worker->is_idle()) { idle_worker_num++; } } @@ -2016,7 +2224,7 @@ int Server::get_idle_task_worker_num() { return idle_worker_num; } -int Server::get_task_count() { +int Server::get_tasking_num() const { // TODO Why need to reset ? int tasking_num = gs->tasking_num; if (tasking_num < 0) { diff --git a/src/server/message_bus.cc b/src/server/message_bus.cc deleted file mode 100644 index a274d869fb..0000000000 --- a/src/server/message_bus.cc +++ /dev/null @@ -1,285 +0,0 @@ -#include "swoole_server.h" - -#include - -using swoole::network::Address; -using swoole::network::Socket; - -namespace swoole { - -PacketPtr MessageBus::get_packet() const { - PacketPtr pkt; - if (buffer_->info.flags & SW_EVENT_DATA_PTR) { - memcpy(&pkt, buffer_->data, sizeof(pkt)); - } else if (buffer_->info.flags & SW_EVENT_DATA_OBJ_PTR) { - String *object; - memcpy(&object, buffer_->data, sizeof(object)); - pkt.data = object->str; - pkt.length = object->length; - } else { - pkt.data = buffer_->data; - pkt.length = buffer_->info.len; - } - - return pkt; -} - -String *MessageBus::get_packet_buffer() { - String *packet_buffer = nullptr; - - auto iter = packet_pool_.find(buffer_->info.msg_id); - if (iter == packet_pool_.end()) { - if (!buffer_->is_begin()) { - return nullptr; - } - packet_buffer = make_string(buffer_->info.len, allocator_); - packet_pool_.emplace(buffer_->info.msg_id, std::shared_ptr(packet_buffer)); - } else { - packet_buffer = iter->second.get(); - } - - return packet_buffer; -} - -ReturnCode MessageBus::prepare_packet(uint16_t &recv_chunk_count, String *packet_buffer) { - recv_chunk_count++; - if (!buffer_->is_end()) { - /** - * if the reactor thread sends too many chunks to the worker process, - * the worker process may receive chunks all the time, - * resulting in the worker process being unable to handle other tasks. - * in order to make the worker process handle tasks fairly, - * the maximum number of consecutive chunks received by the worker is limited. - */ - if (recv_chunk_count >= SW_WORKER_MAX_RECV_CHUNK_COUNT) { - swoole_trace_log(SW_TRACE_WORKER, - "worker process[%u] receives the chunk data to the maximum[%d], return to event loop", - SwooleG.process_id, - recv_chunk_count); - return SW_WAIT; - } - return SW_CONTINUE; - } else { - /** - * Because we don't want to split the EventData parameters into DataHead and data, - * we store the value of the worker_buffer pointer in EventData.data. - * The value of this pointer will be fetched in the Server::get_pipe_packet() function. - */ - buffer_->info.flags |= SW_EVENT_DATA_OBJ_PTR; - memcpy(buffer_->data, &packet_buffer, sizeof(packet_buffer)); - swoole_trace("msg_id=%" PRIu64 ", len=%u", buffer_->info.msg_id, buffer_->info.len); - - return SW_READY; - } -} - -ssize_t MessageBus::read(Socket *sock) { - ssize_t recv_n = 0; - uint16_t recv_chunk_count = 0; - DataHead *info = &buffer_->info; - struct iovec buffers[2]; - -_read_from_pipe: - recv_n = recv(sock->get_fd(), info, sizeof(buffer_->info), MSG_PEEK); - if (recv_n < 0) { - if (sock->catch_read_error(errno) == SW_WAIT) { - return SW_OK; - } - return SW_ERR; - } else if (recv_n == 0) { - swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); - return SW_ERR; - } - - if (!buffer_->is_chunked()) { - return sock->read(buffer_, sizeof(buffer_->info) + buffer_->info.len); - } - - auto packet_buffer = get_packet_buffer(); - if (packet_buffer == nullptr) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, - "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", - info->msg_id, - sock->get_fd(), - info->reactor_id); - return SW_OK; - } - - size_t remain_len = buffer_->info.len - packet_buffer->length; - buffers[0].iov_base = info; - buffers[0].iov_len = sizeof(buffer_->info); - buffers[1].iov_base = packet_buffer->str + packet_buffer->length; - buffers[1].iov_len = SW_MIN(buffer_size_ - sizeof(buffer_->info), remain_len); - - recv_n = readv(sock->get_fd(), buffers, 2); - if (recv_n == 0) { - swoole_warning("receive pipeline data error, pipe_fd=%d, reactor_id=%d", sock->get_fd(), info->reactor_id); - return SW_ERR; - } - if (recv_n < 0 && sock->catch_read_error(errno) == SW_WAIT) { - return SW_OK; - } - if (recv_n > 0) { - packet_buffer->length += (recv_n - sizeof(buffer_->info)); - swoole_trace("append msgid=%" PRIu64 ", buffer=%p, n=%ld", buffer_->info.msg_id, packet_buffer, recv_n); - } - - switch (prepare_packet(recv_chunk_count, packet_buffer)) { - case SW_READY: - return recv_n; - case SW_CONTINUE: - goto _read_from_pipe; - case SW_WAIT: - return SW_OK; - default: - assert(0); - return SW_ERR; - } -} - -/** - * Notice: only supports dgram type socket - */ -ssize_t MessageBus::read_with_buffer(network::Socket *sock) { - ssize_t recv_n; - uint16_t recv_chunk_count = 0; - -_read_from_pipe: - recv_n = sock->read(buffer_, buffer_size_); - if (recv_n < 0) { - if (sock->catch_read_error(errno) == SW_WAIT) { - return SW_OK; - } - return SW_ERR; - } else if (recv_n == 0) { - swoole_warning("receive data from socket#%d returns 0", sock->get_fd()); - return SW_ERR; - } - - recv_chunk_count++; - - if (!buffer_->is_chunked()) { - return recv_n; - } - - String *packet_buffer = get_packet_buffer(); - if (packet_buffer == nullptr) { - swoole_error_log(SW_LOG_WARNING, - SW_ERROR_SERVER_WORKER_ABNORMAL_PIPE_DATA, - "abnormal pipeline data, msg_id=%" PRIu64 ", pipe_fd=%d, reactor_id=%d", - buffer_->info.msg_id, - sock->get_fd(), - buffer_->info.reactor_id); - return SW_ERR; - } - packet_buffer->append(buffer_->data, recv_n - sizeof(buffer_->info)); - - switch (prepare_packet(recv_chunk_count, packet_buffer)) { - case SW_READY: - return recv_n; - case SW_CONTINUE: - goto _read_from_pipe; - case SW_WAIT: - return SW_OK; - default: - assert(0); - return SW_ERR; - } -} - -bool MessageBus::write(Socket *sock, SendData *resp) { - const char *payload = resp->data; - uint32_t l_payload = resp->info.len; - off_t offset = 0; - uint32_t copy_n; - - struct iovec iov[2]; - - uint64_t msg_id = id_generator_(); - uint32_t max_length = buffer_size_ - sizeof(resp->info); - resp->info.msg_id = msg_id; - - auto send_fn = [](Socket *sock, const iovec *iov, size_t iovcnt) { - if (swoole_event_is_available()) { - return swoole_event_writev(sock, iov, iovcnt); - } else { - return sock->writev_blocking(iov, iovcnt); - } - }; - - if (l_payload == 0 || payload == nullptr) { - resp->info.flags = 0; - resp->info.len = 0; - iov[0].iov_base = &resp->info; - iov[0].iov_len = sizeof(resp->info); - return send_fn(sock, iov, 1) == (ssize_t) iov[0].iov_len; - } - - if (!always_chunked_transfer_ && l_payload <= max_length) { - resp->info.flags = 0; - resp->info.len = l_payload; - iov[0].iov_base = &resp->info; - iov[0].iov_len = sizeof(resp->info); - iov[1].iov_base = (void *) payload; - iov[1].iov_len = l_payload; - - if (send_fn(sock, iov, 2) == (ssize_t)(sizeof(resp->info) + l_payload)) { - return true; - } - if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_BUFFER_SIZE_STD) { - max_length = SW_IPC_BUFFER_SIZE; - } else { - return false; - } - } - - resp->info.flags = SW_EVENT_DATA_CHUNK | SW_EVENT_DATA_BEGIN; - resp->info.len = l_payload; - - while (l_payload > 0) { - if (l_payload > max_length) { - copy_n = max_length; - } else { - resp->info.flags |= SW_EVENT_DATA_END; - copy_n = l_payload; - } - - iov[0].iov_base = &resp->info; - iov[0].iov_len = sizeof(resp->info); - iov[1].iov_base = (void *) (payload + offset); - iov[1].iov_len = copy_n; - - swoole_trace("finish, type=%d|len=%u", resp->info.type, copy_n); - - if (send_fn(sock, iov, 2) < 0) { - if (sock->catch_write_pipe_error(errno) == SW_REDUCE_SIZE && max_length > SW_BUFFER_SIZE_STD) { - max_length = SW_IPC_BUFFER_SIZE; - if (resp->info.flags & SW_EVENT_DATA_END) { - resp->info.flags &= ~SW_EVENT_DATA_END; - } - continue; - } - return false; - } - - if (resp->info.flags & SW_EVENT_DATA_BEGIN) { - resp->info.flags &= ~SW_EVENT_DATA_BEGIN; - } - - l_payload -= copy_n; - offset += copy_n; - } - - return true; -} - -size_t MessageBus::get_memory_size() { - size_t size = buffer_size_; - for (auto p : packet_pool_) { - size += p.second->size; - } - return size; -} - -} // namespace swoole diff --git a/src/server/port.cc b/src/server/port.cc index 84443371e6..e0868ffb96 100644 --- a/src/server/port.cc +++ b/src/server/port.cc @@ -18,8 +18,10 @@ #include "swoole_http.h" #include "swoole_http2.h" #include "swoole_websocket.h" +#include "swoole_client.h" #include "swoole_mqtt.h" #include "swoole_redis.h" +#include "swoole_ssl.h" using swoole::http_server::Request; using swoole::network::Address; @@ -27,13 +29,8 @@ using swoole::network::Socket; namespace swoole { -static int Port_onRead_raw(Reactor *reactor, ListenPort *lp, Event *event); -static int Port_onRead_check_length(Reactor *reactor, ListenPort *lp, Event *event); -static int Port_onRead_check_eof(Reactor *reactor, ListenPort *lp, Event *event); -static int Port_onRead_http(Reactor *reactor, ListenPort *lp, Event *event); -static int Port_onRead_redis(Reactor *reactor, ListenPort *lp, Event *event); - -ListenPort::ListenPort() { +ListenPort::ListenPort(Server *server) { + object_id = 0; protocol.package_length_type = 'N'; protocol.package_length_size = 4; protocol.package_body_offset = 4; @@ -41,66 +38,64 @@ ListenPort::ListenPort() { protocol.package_eof_len = sizeof(SW_DATA_EOF) - 1; memcpy(protocol.package_eof, SW_DATA_EOF, protocol.package_eof_len); -} -#ifdef SW_USE_OPENSSL + protocol.private_data_2 = server; +} -bool ListenPort::ssl_add_sni_cert(const std::string &name, SSLContext *ctx) { - if (!ssl_create_context(ctx)) { +bool ListenPort::ssl_add_sni_cert(const std::string &name, const std::shared_ptr &ctx) { + if (!ssl_context_create(ctx.get())) { return false; } - sni_contexts.emplace(name, std::shared_ptr(ctx)); + sni_contexts.emplace(name, ctx); return true; } -static bool ssl_matches_wildcard_name(const char *subjectname, const char *certname) { - const char *wildcard = NULL; - ptrdiff_t prefix_len; - size_t suffix_len, subject_len; +bool ListenPort::ssl_matches_wildcard_name(const char *subject_name, const char *cert_name) { + const char *wildcard = nullptr; - if (strcasecmp(subjectname, certname) == 0) { - return 1; + if (strcasecmp(subject_name, cert_name) == 0) { + return true; } /* wildcard, if present, must only be present in the left-most component */ - if (!(wildcard = strchr(certname, '*')) || memchr(certname, '.', wildcard - certname)) { - return 0; + if (!((wildcard = strchr(cert_name, '*'))) || memchr(cert_name, '.', wildcard - cert_name)) { + return false; } /* 1) prefix, if not empty, must match subject */ - prefix_len = wildcard - certname; - if (prefix_len && strncasecmp(subjectname, certname, prefix_len) != 0) { - return 0; + ptrdiff_t prefix_len = wildcard - cert_name; + if (prefix_len && strncasecmp(subject_name, cert_name, prefix_len) != 0) { + return false; } - suffix_len = strlen(wildcard + 1); - subject_len = strlen(subjectname); + size_t suffix_len = strlen(wildcard + 1); + size_t subject_len = strlen(subject_name); if (suffix_len <= subject_len) { /* 2) suffix must match * 3) no . between prefix and suffix **/ - return strcasecmp(wildcard + 1, subjectname + subject_len - suffix_len) == 0 && - memchr(subjectname + prefix_len, '.', subject_len - suffix_len - prefix_len) == NULL; + return strcasecmp(wildcard + 1, subject_name + subject_len - suffix_len) == 0 && + memchr(subject_name + prefix_len, '.', subject_len - suffix_len - prefix_len) == nullptr; } - return 0; + return false; } -static int ssl_server_sni_callback(SSL *ssl, int *al, void *arg) { +int ListenPort::ssl_server_sni_callback(SSL *ssl, int *al, void *arg) { const char *server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (!server_name) { return SSL_TLSEXT_ERR_NOACK; } - ListenPort *port = (ListenPort *) SSL_get_ex_data(ssl, swoole_ssl_get_ex_port_index()); + auto *port = static_cast(SSL_get_ex_data(ssl, swoole_ssl_get_ex_port_index())); if (port->sni_contexts.empty()) { return SSL_TLSEXT_ERR_NOACK; } - for (auto i = port->sni_contexts.begin(); i != port->sni_contexts.end(); i++) { - if (ssl_matches_wildcard_name(server_name, i->first.c_str())) { - SSL_set_SSL_CTX(ssl, i->second->get_context()); + for (auto &sni_context : port->sni_contexts) { + if (ssl_matches_wildcard_name(server_name, sni_context.first.c_str())) { + SSL_set_SSL_CTX(ssl, sni_context.second->get_context()); return SSL_TLSEXT_ERR_OK; } } @@ -108,21 +103,54 @@ static int ssl_server_sni_callback(SSL *ssl, int *al, void *arg) { return SSL_TLSEXT_ERR_NOACK; } -bool ListenPort::ssl_init() { - if (!ssl_create_context(ssl_context)) { +#ifdef SW_SUPPORT_DTLS +dtls::Session *ListenPort::create_dtls_session(Socket *sock) const { + auto *session = new dtls::Session(sock, ssl_context); + if (!session->init()) { + delete session; + return nullptr; + } + dtls_sessions->emplace(sock->get_fd(), session); + return session; +} +#endif + +bool ListenPort::ssl_context_init() { + ssl_context = std::make_shared(); + ssl_context->prefer_server_ciphers = 1; + ssl_context->session_tickets = 0; + ssl_context->stapling = 1; + ssl_context->stapling_verify = 1; + ssl_context->ciphers = SW_SSL_CIPHER_LIST; + ssl_context->ecdh_curve = SW_SSL_ECDH_CURVE; + + if (is_dgram()) { +#ifdef SW_SUPPORT_DTLS + ssl_context->protocols = SW_SSL_DTLS; + dtls_sessions = new std::unordered_map; +#else + swoole_warning("DTLS support require openssl-1.1 or later"); return false; +#endif } - if (sni_contexts.size() > 0) { + return true; +} + +bool ListenPort::ssl_init() const { + if (!ssl_context_create(ssl_context.get())) { + return false; + } + if (!sni_contexts.empty()) { SSL_CTX_set_tlsext_servername_callback(ssl_context->get_context(), ssl_server_sni_callback); } return true; } -bool ListenPort::ssl_create(Connection *conn, Socket *sock) { - if (sock->ssl_create(ssl_context, SW_SSL_SERVER) < 0) { +bool ListenPort::ssl_create(Socket *sock) { + if (sock->ssl_create(ssl_context.get(), SW_SSL_SERVER) < 0) { + swoole_set_last_error(SW_ERROR_SSL_CREATE_SESSION_FAILED); return false; } - conn->ssl = 1; if (SSL_set_ex_data(sock->ssl, swoole_ssl_get_ex_port_index(), this) == 0) { swoole_warning("SSL_set_ex_data() failed"); return false; @@ -130,9 +158,9 @@ bool ListenPort::ssl_create(Connection *conn, Socket *sock) { return true; } -bool ListenPort::ssl_create_context(SSLContext *context) { +bool ListenPort::ssl_context_create(SSLContext *context) const { if (context->cert_file.empty() || context->key_file.empty()) { - swoole_warning("SSL error, require ssl_cert_file and ssl_key_file"); + swoole_error_log(SW_LOG_ERROR, SW_ERROR_WRONG_OPERATION, "require `ssl_cert_file` and `ssl_key_file` options"); return false; } if (open_http_protocol) { @@ -142,12 +170,11 @@ bool ListenPort::ssl_create_context(SSLContext *context) { context->http_v2 = 1; } if (!context->create()) { - swoole_warning("swSSL_get_context() error"); + swoole_warning("failed to create ssl content"); return false; } return true; } -#endif int ListenPort::listen() { // listen stream socket @@ -208,77 +235,115 @@ int ListenPort::listen() { } #endif - buffer_high_watermark = socket_buffer_size * 0.8; - buffer_low_watermark = 0; + if (buffer_high_watermark == 0) { + buffer_high_watermark = socket_buffer_size * 0.8; + } return SW_OK; } -void Server::init_port_protocol(ListenPort *ls) { - ls->protocol.private_data_2 = this; - // Thread mode must copy the data. - // will free after onFinish - if (ls->open_eof_check) { - if (ls->protocol.package_eof_len > SW_DATA_EOF_MAXLEN) { - ls->protocol.package_eof_len = SW_DATA_EOF_MAXLEN; - } - ls->protocol.onPackage = Server::dispatch_task; - ls->onRead = Port_onRead_check_eof; - } else if (ls->open_length_check) { - if (ls->protocol.package_length_type != '\0') { - ls->protocol.get_package_length = Protocol::default_length_func; - } - ls->protocol.onPackage = Server::dispatch_task; - ls->onRead = Port_onRead_check_length; - } else if (ls->open_http_protocol) { - if (ls->open_http2_protocol && ls->open_websocket_protocol) { - ls->protocol.get_package_length = http_server::get_package_length; - ls->protocol.get_package_length_size = http_server::get_package_length_size; - ls->protocol.onPackage = http_server::dispatch_frame; - } else if (ls->open_http2_protocol) { - ls->protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; - ls->protocol.get_package_length = http2::get_frame_length; - ls->protocol.onPackage = Server::dispatch_task; - } else if (ls->open_websocket_protocol) { - ls->protocol.package_length_size = SW_WEBSOCKET_HEADER_LEN + SW_WEBSOCKET_MASK_LEN + sizeof(uint64_t); - ls->protocol.get_package_length = websocket::get_package_length; - ls->protocol.onPackage = websocket::dispatch_frame; - } - ls->protocol.package_length_offset = 0; - ls->protocol.package_body_offset = 0; - ls->onRead = Port_onRead_http; - } else if (ls->open_mqtt_protocol) { - mqtt::set_protocol(&ls->protocol); - ls->protocol.onPackage = Server::dispatch_task; - ls->onRead = Port_onRead_check_length; - } else if (ls->open_redis_protocol) { - ls->protocol.onPackage = Server::dispatch_task; - ls->onRead = Port_onRead_redis; +void ListenPort::init_protocol() { + if (is_dgram() && !is_dtls()) { + return; + } + + if (open_eof_check) { + if (protocol.package_eof_len > SW_DATA_EOF_MAXLEN) { + protocol.package_eof_len = SW_DATA_EOF_MAXLEN; + } + protocol.onPackage = Server::dispatch_task; + onRead = readable_callback_eof; + } else if (open_length_check) { + if (protocol.package_length_type != '\0') { + protocol.get_package_length = Protocol::default_length_func; + } + protocol.onPackage = Server::dispatch_task; + onRead = readable_callback_length; + } else if (open_http_protocol) { + if (open_http2_protocol && open_websocket_protocol) { + protocol.get_package_length = http_server::get_package_length; + protocol.get_package_length_size = http_server::get_package_length_size; + protocol.onPackage = http_server::dispatch_frame; + } else if (open_http2_protocol) { + protocol.package_length_size = SW_HTTP2_FRAME_HEADER_SIZE; + protocol.get_package_length = http2::get_frame_length; + protocol.onPackage = Server::dispatch_task; + } else if (open_websocket_protocol) { + protocol.package_length_size = SW_WEBSOCKET_FRAME_HEADER_SIZE; + protocol.get_package_length = websocket::get_package_length; + protocol.onPackage = websocket::dispatch_frame; + } + protocol.package_length_offset = 0; + protocol.package_body_offset = 0; + onRead = readable_callback_http; + } else if (open_mqtt_protocol) { + mqtt::set_protocol(&protocol); + protocol.onPackage = Server::dispatch_task; + onRead = readable_callback_length; + } else if (open_redis_protocol) { + protocol.onPackage = Server::dispatch_task; + onRead = readable_callback_redis; } else { - ls->onRead = Port_onRead_raw; + onRead = readable_callback_raw; } } +void ListenPort::set_eof_protocol(const std::string &eof, bool find_from_right) { + open_eof_check = true; + protocol.split_by_eof = !find_from_right; + protocol.package_eof_len = std::min(eof.length(), sizeof(protocol.package_eof)); + memcpy(protocol.package_eof, eof.c_str(), protocol.package_eof_len); +} + +void ListenPort::set_length_protocol(uint32_t length_offset, char length_type, uint32_t body_offset) { + open_length_check = true; + protocol.package_length_type = length_type; + protocol.package_length_size = swoole_type_size(length_type); + protocol.package_length_offset = length_offset; + protocol.package_body_offset = body_offset; +} + +void ListenPort::set_stream_protocol() { + open_length_check = true; + network::Stream::set_protocol(&protocol); +} + /** * @description: import listen port from socket-fd */ bool ListenPort::import(int sock) { - int _type, _family; + int _type; - socket = new Socket(); - socket->fd = sock; + auto tmp_sock = socket = new Socket(); + tmp_sock->fd = sock; // get socket type if (socket->get_option(SOL_SOCKET, SO_TYPE, &_type) < 0) { swoole_sys_warning("getsockopt(%d, SOL_SOCKET, SO_TYPE) failed", sock); + _fail: + tmp_sock->move_fd(); + delete tmp_sock; return false; } - if (socket->get_name(&socket->info) < 0) { + + if (tmp_sock->get_name() < 0) { swoole_sys_warning("getsockname(%d) failed", sock); - return false; + goto _fail; } - _family = socket->info.addr.ss.sa_family; + int optval; + if (tmp_sock->get_option(SOL_SOCKET, SO_ACCEPTCONN, &optval) < 0) { + swoole_sys_warning("getsockopt(%d, SOL_SOCKET, SO_ACCEPTCONN) failed", sock); + goto _fail; + } + + if (optval == 0) { + swoole_error_log(SW_LOG_WARNING, EINVAL, "the socket[%d] is not a listening socket", sock); + goto _fail; + } + + socket = tmp_sock; + int _family = socket->info.addr.ss.sa_family; socket->socket_type = socket->info.type = type = Socket::convert_to_type(_family, _type); host = socket->info.get_addr(); port = socket->info.get_port(); @@ -291,28 +356,23 @@ bool ListenPort::import(int sock) { } void ListenPort::clear_protocol() { - open_eof_check = 0; - open_length_check = 0; - open_http_protocol = 0; - open_websocket_protocol = 0; - open_http2_protocol = 0; - open_mqtt_protocol = 0; - open_redis_protocol = 0; + open_eof_check = false; + open_length_check = false; + open_http_protocol = false; + open_websocket_protocol = false; + open_http2_protocol = false; + open_mqtt_protocol = false; + open_redis_protocol = false; } -static int Port_onRead_raw(Reactor *reactor, ListenPort *port, Event *event) { - ssize_t n; - Socket *_socket = event->socket; - Connection *conn = (Connection *) _socket->object; - Server *serv = (Server *) reactor->ptr; +int ListenPort::readable_callback_raw(Reactor *reactor, ListenPort *port, Event *event) { + auto _socket = event->socket; + auto conn = static_cast(_socket->object); + auto serv = static_cast(reactor->ptr); + auto buffer = serv->get_recv_buffer(_socket); RecvData rdata{}; - String *buffer = serv->get_recv_buffer(_socket); - if (!buffer) { - return SW_ERR; - } - - n = _socket->recv(buffer->str, buffer->size, 0); + ssize_t n = _socket->recv(buffer->str, buffer->size, 0); if (n < 0) { switch (_socket->catch_read_error(errno)) { case SW_ERROR: @@ -336,17 +396,12 @@ static int Port_onRead_raw(Reactor *reactor, ListenPort *port, Event *event) { } } -static int Port_onRead_check_length(Reactor *reactor, ListenPort *port, Event *event) { - Socket *_socket = event->socket; - Connection *conn = (Connection *) _socket->object; - Protocol *protocol = &port->protocol; - Server *serv = (Server *) reactor->ptr; - - String *buffer = serv->get_recv_buffer(_socket); - if (!buffer) { - reactor->trigger_close_event(event); - return SW_ERR; - } +int ListenPort::readable_callback_length(Reactor *reactor, ListenPort *port, Event *event) { + auto _socket = event->socket; + auto conn = static_cast(_socket->object); + auto protocol = &port->protocol; + auto serv = static_cast(reactor->ptr); + auto buffer = serv->get_recv_buffer(_socket); if (protocol->recv_with_length_protocol(_socket, buffer) < 0) { swoole_trace("Close Event.FD=%d|From=%d", event->fd, event->reactor_id); @@ -372,23 +427,23 @@ static int Port_onRead_check_length(Reactor *reactor, ListenPort *port, Event *e /** * For Http Protocol */ -static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { +int ListenPort::readable_callback_http(Reactor *reactor, ListenPort *port, Event *event) { Socket *_socket = event->socket; - Connection *conn = (Connection *) _socket->object; - Server *serv = (Server *) reactor->ptr; + auto *conn = static_cast(_socket->object); + auto *serv = static_cast(reactor->ptr); RecvData dispatch_data{}; if (conn->websocket_status >= websocket::STATUS_HANDSHAKE) { if (conn->http_upgrade == 0) { - serv->destroy_http_request(conn); + port->destroy_http_request(conn); conn->websocket_status = websocket::STATUS_ACTIVE; conn->http_upgrade = 1; } - return Port_onRead_check_length(reactor, port, event); + return readable_callback_length(reactor, port, event); } if (conn->http2_stream) { - return Port_onRead_check_length(reactor, port, event); + return readable_callback_length(reactor, port, event); } Request *request = nullptr; @@ -398,15 +453,11 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { request = new Request(); conn->object = request; } else { - request = reinterpret_cast(conn->object); + request = static_cast(conn->object); } if (!request->buffer_) { request->buffer_ = serv->get_recv_buffer(_socket); - if (!request->buffer_) { - reactor->trigger_close_event(event); - return SW_ERR; - } } String *buffer = request->buffer_; @@ -427,20 +478,20 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { } if (n == 0) { - if (0) { + if (false) { _bad_request: _socket->send(SW_STRL(SW_HTTP_BAD_REQUEST_PACKET), 0); } - if (0) { + if (false) { _too_large: _socket->send(SW_STRL(SW_HTTP_REQUEST_ENTITY_TOO_LARGE_PACKET), 0); } - if (0) { + if (false) { _unavailable: _socket->send(SW_STRL(SW_HTTP_SERVICE_UNAVAILABLE_PACKET), 0); } _close_fd: - serv->destroy_http_request(conn); + port->destroy_http_request(conn); reactor->trigger_close_event(event); return SW_OK; } @@ -476,14 +527,14 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { conn->http2_stream = 1; http2::send_setting_frame(protocol, _socket); if (buffer->length == sizeof(SW_HTTP2_PRI_STRING) - 1) { - serv->destroy_http_request(conn); + port->destroy_http_request(conn); buffer->clear(); return SW_OK; } buffer->reduce(buffer->offset); - serv->destroy_http_request(conn); + port->destroy_http_request(conn); conn->socket->skip_recv = 1; - return Port_onRead_check_length(reactor, port, event); + return readable_callback_length(reactor, port, event); } // http header is not the end @@ -503,6 +554,7 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { // parse http header and got http body length if (!request->header_parsed) { request->parse_header_info(); + request->max_length_ = protocol->package_max_length; swoole_trace_log(SW_TRACE_SERVER, "content-length=%" PRIu64 ", keep-alive=%u, chunked=%u", request->content_length_, @@ -510,8 +562,9 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { request->chunked); if (request->form_data_) { if (serv->upload_max_filesize > 0 && - request->header_length_ + request->content_length_ > protocol->package_max_length) { + request->header_length_ + request->content_length_ > request->max_length_) { request->init_multipart_parser(serv); + buffer = request->buffer_; } else { delete request->form_data_; @@ -527,10 +580,10 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { if (!request->parse_multipart_data(buffer)) { goto _bad_request; } - if (request->too_large || request->form_data_->multipart_buffer_->length > protocol->package_max_length) { + if (request->too_large) { goto _too_large; } - if (request->excepted) { + if (request->unavailable) { goto _unavailable; } if (!request->tried_to_dispatch) { @@ -547,7 +600,7 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { if (buffer->length < request->header_length_ + (sizeof(SW_HTTP_CHUNK_EOF) - 1)) { goto _recv_data; } - request->header_length_ += (sizeof("0\r\n\r\n") - 1); + request->header_length_ += (sizeof(SW_HTTP_CHUNK_EOF) - 1); } request->tried_to_dispatch = 1; // (know content-length is equal to 0) or (no content-length field and no chunked) @@ -571,7 +624,7 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { request->clean(); goto _parse; } else { - serv->destroy_http_request(conn); + port->destroy_http_request(conn); buffer->clear(); return SW_OK; } @@ -589,7 +642,7 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { CLIENT_INFO_ARGS); goto _bad_request; } - request_length = request->header_length_ + request->content_length_; + request_length = buffer->size + SW_BUFFER_SIZE_BIG; if (request_length > protocol->package_max_length) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_HTTP_INVALID_PROTOCOL, @@ -599,11 +652,8 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { CLIENT_INFO_ARGS); goto _too_large; } - if (buffer->length == buffer->size && !buffer->extend()) { - goto _unavailable; - } - if (request_length > buffer->size && !buffer->extend_align(request_length)) { - goto _unavailable; + if (buffer->length == buffer->size) { + buffer->extend(request_length); } goto _recv_data; } else { @@ -626,24 +676,22 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { goto _too_large; } - if (request_length > buffer->size && !buffer->extend(request_length)) { - goto _unavailable; + if (request_length > buffer->size) { + buffer->extend(request_length); } if (buffer->length < request_length) { -#ifdef SW_HTTP_100_CONTINUE // Expect: 100-continue if (request->has_expect_header()) { _socket->send(SW_STRL(SW_HTTP_100_CONTINUE_PACKET), 0); } else { swoole_trace_log( SW_TRACE_SERVER, - "PostWait: request->content_length=%d, buffer->length=%zu, request->header_length=%d\n", - request->content_length, - buffer_->length, - request->header_length); + "PostWait: request->content_length=%" PRIu64 ", buffer->length=%zu, request->header_length=%d\n", + request->content_length_, + buffer->length, + request->header_length_); } -#endif goto _recv_data; } } @@ -667,7 +715,7 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { } if (conn->active && !_socket->removed) { - serv->destroy_http_request(conn); + port->destroy_http_request(conn); if (_socket->recv_buffer && _socket->recv_buffer->size > SW_BUFFER_SIZE_BIG * 2) { delete _socket->recv_buffer; _socket->recv_buffer = nullptr; @@ -679,17 +727,12 @@ static int Port_onRead_http(Reactor *reactor, ListenPort *port, Event *event) { return SW_OK; } -static int Port_onRead_redis(Reactor *reactor, ListenPort *port, Event *event) { - Socket *_socket = event->socket; - Connection *conn = (Connection *) _socket->object; - Protocol *protocol = &port->protocol; - Server *serv = (Server *) reactor->ptr; - - String *buffer = serv->get_recv_buffer(_socket); - if (!buffer) { - reactor->trigger_close_event(event); - return SW_ERR; - } +int ListenPort::readable_callback_redis(Reactor *reactor, ListenPort *port, Event *event) { + auto _socket = event->socket; + auto conn = static_cast(_socket->object); + auto protocol = &port->protocol; + auto serv = static_cast(reactor->ptr); + auto buffer = serv->get_recv_buffer(_socket); if (redis::recv_packet(protocol, conn, buffer) < 0) { conn->close_errno = errno; @@ -699,11 +742,11 @@ static int Port_onRead_redis(Reactor *reactor, ListenPort *port, Event *event) { return SW_OK; } -static int Port_onRead_check_eof(Reactor *reactor, ListenPort *port, Event *event) { +int ListenPort::readable_callback_eof(Reactor *reactor, ListenPort *port, Event *event) { Socket *_socket = event->socket; - Connection *conn = (Connection *) _socket->object; + auto *conn = static_cast(_socket->object); Protocol *protocol = &port->protocol; - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); String *buffer = serv->get_recv_buffer(_socket); if (!buffer) { @@ -727,18 +770,14 @@ static int Port_onRead_check_eof(Reactor *reactor, ListenPort *port, Event *even } void ListenPort::close() { -#ifdef SW_USE_OPENSSL if (ssl) { if (ssl_context) { - delete ssl_context; + ssl_context.reset(); } #ifdef SW_SUPPORT_DTLS - if (dtls_sessions) { - delete dtls_sessions; - } + delete dtls_sessions; #endif } -#endif if (socket) { socket->free(); @@ -751,7 +790,7 @@ void ListenPort::close() { } } -const char *ListenPort::get_protocols() { +const char *ListenPort::get_protocols() const { if (is_dgram()) { return "dgram"; } @@ -778,4 +817,94 @@ const char *ListenPort::get_protocols() { } } +size_t ListenPort::get_connection_num() const { + if (gs->connection_nums) { + size_t num = 0; + for (uint32_t i = 0; i < sw_server()->worker_num; i++) { + num += gs->connection_nums[i]; + } + return num; + } else { + return gs->connection_num; + } +} + +int ListenPort::create_socket() { + auto *server = static_cast(protocol.private_data_2); + if (socket) { +#if defined(__linux__) && defined(HAVE_REUSEPORT) + if (server->enable_reuse_port) { + close_socket(); + } else +#endif + { + return SW_OK; + } + } + + socket = + make_socket(type, is_dgram() ? SW_FD_DGRAM_SERVER : SW_FD_STREAM_SERVER, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); + if (socket == nullptr) { + swoole_set_last_error(errno); + return SW_ERR; + } + +#if defined(SW_SUPPORT_DTLS) && defined(HAVE_KQUEUE) + if (is_dtls()) { + socket->set_reuse_port(); + } +#endif + +#if defined(__linux__) && defined(HAVE_REUSEPORT) + if (server->enable_reuse_port) { + if (socket->set_reuse_port() < 0) { + goto __cleanup; + } + } +#endif + + Address addr; + if (!addr.assign(type, host, port, true)) { + auto type_str = Address::type_str(type); + swoole_warning("Invalid %s address '%s:%d'", type_str, host.c_str(), port); + goto __cleanup; + } + + if (socket->set_reuse_addr() < 0) { + swoole_sys_warning("setsockopt(%d, SO_REUSEADDR) failed", socket->get_fd()); + } + + if (socket->bind(addr) < 0) { + goto __cleanup; + } + + if (socket->get_name() < 0) { + __cleanup: + swoole_set_last_error(errno); + socket->free(); + return SW_ERR; + } + + port = socket->get_port(); + + return SW_OK; +} + +void ListenPort::close_socket() { + if (::close(socket->fd) < 0) { + swoole_sys_warning("close(%d) failed", socket->fd); + } + delete socket; + socket = nullptr; +} + +void ListenPort::destroy_http_request(Connection *conn) { + const auto request = static_cast(conn->object); + if (!request) { + return; + } + delete request; + conn->object = nullptr; +} + } // namespace swoole diff --git a/src/server/process.cc b/src/server/process.cc index 997fd2d754..a2b70b0189 100644 --- a/src/server/process.cc +++ b/src/server/process.cc @@ -14,88 +14,220 @@ +----------------------------------------------------------------------+ */ -#include - #include "swoole_server.h" namespace swoole { using network::Socket; +Factory *Server::create_process_factory() { + /** + * init reactor thread pool + */ + reactor_threads = new ReactorThread[reactor_num](); + /** + * alloc the memory for connection_list + */ + connection_list = static_cast(sw_shm_calloc(max_connection, sizeof(Connection))); + if (connection_list == nullptr) { + swoole_sys_warning("sw_shm_calloc(%u, %zu) for connection_list failed", max_connection, sizeof(Connection)); + return nullptr; + } + reactor_pipe_num = worker_num / reactor_num; + + reactor_thread_barrier.init(false, reactor_num + 1); + gs->manager_barrier.init(true, 2); + + return new ProcessFactory(this); +} + +void Server::destroy_process_factory() { + sw_shm_free(connection_list); + delete[] reactor_threads; + + reactor_thread_barrier.destroy(); + gs->manager_barrier.destroy(); + + if (get_event_worker_pool()->message_box) { + get_event_worker_pool()->message_box->destroy(); + } +} + ProcessFactory::ProcessFactory(Server *server) : Factory(server) {} -bool ProcessFactory::shutdown() { - int status; +ProcessFactory::~ProcessFactory() = default; - if (swoole_kill(server_->gs->manager_pid, SIGTERM) < 0) { - swoole_sys_warning("kill(%d) failed", server_->gs->manager_pid); +/** + * kill and wait all user process + */ +void Factory::kill_user_workers() const { + if (server_->user_worker_map.empty()) { + return; } - if (swoole_waitpid(server_->gs->manager_pid, &status, 0) < 0) { - swoole_sys_warning("waitpid(%d) failed", server_->gs->manager_pid); + for (const auto &kv : server_->user_worker_map) { + swoole_kill(kv.second->pid, SIGTERM); + } + + for (const auto &kv : server_->user_worker_map) { + int _stat_loc; + if (swoole_waitpid(kv.second->pid, &_stat_loc, 0) < 0) { + swoole_sys_warning("waitpid(%d) failed", kv.second->pid); + } + } +} + +/** + * [Manager] kill and wait all event worker process + */ +void Factory::kill_event_workers() const { + int status; + + if (server_->worker_num == 0) { + return; } SW_LOOP_N(server_->worker_num) { - Worker *worker = &server_->workers[i]; - server_->destroy_worker(worker); + swoole_trace_log(SW_TRACE_SERVER, "kill worker#%d[pid=%d]", server_->workers[i].id, server_->workers[i].pid); + swoole_kill(server_->workers[i].pid, SIGTERM); + } + SW_LOOP_N(server_->worker_num) { + swoole_trace_log(SW_TRACE_SERVER, "wait worker#%d[pid=%d]", server_->workers[i].id, server_->workers[i].pid); + if (swoole_waitpid(server_->workers[i].pid, &status, 0) < 0) { + swoole_sys_warning("waitpid(%d) failed", server_->workers[i].pid); + } } +} - return SW_OK; +/** + * [Manager] kill and wait task worker process + */ +void Factory::kill_task_workers() const { + int status; + if (server_->task_worker_num == 0) { + return; + } + + auto pool = server_->get_task_worker_pool(); + pool->kill_all_workers(SIGTERM); + + SW_LOOP_N(server_->task_worker_num) { + swoole_trace_log(SW_TRACE_SERVER, "wait worker#%d[pid=%d]", pool->workers[i].id, pool->workers[i].pid); + if (swoole_waitpid(pool->workers[i].pid, &status, 0) < 0) { + swoole_sys_warning("waitpid(%d) failed", pool->workers[i].pid); + } + } } -ProcessFactory::~ProcessFactory() { - if (server_->stream_socket_file) { - unlink(server_->stream_socket_file); - sw_free(server_->stream_socket_file); - server_->stream_socket->free(); +pid_t Factory::spawn_event_worker(Worker *worker) const { + pid_t pid = swoole_fork(0); + + if (pid < 0) { + swoole_sys_warning("failed to fork event worker"); + return SW_ERR; + } else if (pid == 0) { + worker->pid = getpid(); + swoole_set_worker_id(worker->id); + swoole_set_worker_pid(worker->pid); + swoole_set_worker_type(SW_EVENT_WORKER); + SwooleWG.worker = worker; + } else { + worker->pid = pid; + return pid; } + + if (server_->is_base_mode()) { + server_->get_event_worker_pool()->main_loop(server_->get_event_worker_pool(), worker); + } else { + server_->start_event_worker(worker); + } + + exit(0); + return 0; } -bool ProcessFactory::start() { - if (server_->dispatch_mode == Server::DISPATCH_STREAM) { - server_->stream_socket_file = swoole_string_format(64, "/tmp/swoole.%d.sock", server_->gs->master_pid); - if (server_->stream_socket_file == nullptr) { - return false; - } - Socket *sock = swoole::make_server_socket(SW_SOCK_UNIX_STREAM, server_->stream_socket_file); - if (sock == nullptr) { - return false; - } - sock->set_fd_option(1, 1); - server_->stream_socket = sock; +pid_t Factory::spawn_user_worker(Worker *worker) const { + pid_t pid = swoole_fork(0); + if (worker->pid) { + server_->user_worker_map.erase(worker->pid); + } + if (pid < 0) { + swoole_sys_warning("failed to spawn the user worker"); + return SW_ERR; + } + // child + else if (pid == 0) { + worker->pid = getpid(); + swoole_set_worker_type(SW_USER_WORKER); + swoole_set_worker_id(worker->id); + swoole_set_worker_pid(worker->pid); + SwooleWG.worker = worker; + server_->onUserWorkerStart(server_, worker); + exit(0); + } + // parent + else { + /** + * worker: local memory + * user_workers: shared memory + */ + server_->get_worker(worker->id)->pid = worker->pid = pid; + server_->user_worker_map.emplace(pid, worker); + return pid; + } +} + +pid_t Factory::spawn_task_worker(Worker *worker) const { + return server_->get_task_worker_pool()->spawn(worker); +} + +void Factory::check_worker_exit_status(Worker *worker, const ExitStatus &exit_status) const { + if (exit_status.get_status() != 0) { + worker->report_error(exit_status); + server_->call_worker_error_callback(worker, exit_status); } +} - SW_LOOP_N(server_->worker_num) { - server_->create_worker(server_->get_worker(i)); +bool ProcessFactory::shutdown() { + int status; + + if (swoole_kill(server_->gs->manager_pid, SIGTERM) < 0) { + swoole_sys_warning("kill(%d) failed", server_->gs->manager_pid); } - SW_LOOP_N(server_->worker_num) { + if (swoole_waitpid(server_->gs->manager_pid, &status, 0) < 0) { + swoole_sys_warning("waitpid(%d) failed", server_->gs->manager_pid); + } + + return SW_OK; +} + +bool Server::create_worker_pipes() { + SW_LOOP_N(worker_num) { auto _sock = new UnixSocket(true, SOCK_DGRAM); if (!_sock->ready()) { delete _sock; return false; } - pipes.emplace_back(_sock); - server_->workers[i].pipe_master = _sock->get_socket(true); - server_->workers[i].pipe_worker = _sock->get_socket(false); - server_->workers[i].pipe_object = _sock; - server_->store_pipe_fd(server_->workers[i].pipe_object); + worker_pipes.emplace_back(_sock); + workers[i].pipe_master = _sock->get_socket(true); + workers[i].pipe_worker = _sock->get_socket(false); + workers[i].pipe_object = _sock; } - server_->init_ipc_max_size(); - if (server_->create_pipe_buffers() < 0) { + init_ipc_max_size(); + if (create_pipe_buffers() < 0) { return false; } + return true; +} - /** - * The manager process must be started first, otherwise it will have a thread fork - */ - if (server_->start_manager_process() < 0) { - swoole_warning("failed to start"); +bool ProcessFactory::start() { + if (!server_->create_worker_pipes()) { return false; } - return true; + return server_->start_manager_process() == SW_OK; } /** @@ -112,7 +244,8 @@ bool ProcessFactory::notify(DataHead *ev) { * [ReactorThread] dispatch request to worker */ bool ProcessFactory::dispatch(SendData *task) { - int fd = task->info.fd; + // the task->info.fd is real fd, not session_id, it will be converted to session after dispatch + int fd = static_cast(task->info.fd); int target_worker_id = server_->schedule_worker(fd, task); if (target_worker_id < 0) { @@ -136,9 +269,9 @@ bool ProcessFactory::dispatch(SendData *task) { } // server active close, discard data. if (conn->closed) { - // Connection has been clsoed by server + // Connection has been closed by server if (!(task->info.type == SW_SERVER_EVENT_CLOSE && conn->close_force)) { - return true; + return false; } } // converted fd to session_id @@ -155,16 +288,16 @@ bool ProcessFactory::dispatch(SendData *task) { SendData _task; memcpy(&_task, task, sizeof(SendData)); - network::Socket *pipe_socket = - server_->is_reactor_thread() ? server_->get_worker_pipe_socket(worker) : worker->pipe_master; - return server_->message_bus.write(pipe_socket, &_task); + MessageBus *mb = &server_->get_thread(swoole_get_thread_id())->message_bus; + Socket *sock = mb->get_pipe_socket(worker->pipe_master); + return mb->write(sock, &_task); } -static bool inline process_is_supported_send_yield(Server *serv, Connection *conn) { +static bool process_is_supported_send_yield(Server *serv, const Connection *conn) { if (!serv->is_hash_dispatch_mode()) { return false; } else { - return serv->schedule_worker(conn->fd, nullptr) == (int) SwooleG.process_id; + return serv->schedule_worker(conn->fd, nullptr) == static_cast(swoole_get_worker_id()); } } @@ -217,28 +350,13 @@ bool ProcessFactory::finish(SendData *resp) { return false; } - if (server_->last_stream_socket) { - uint32_t _len = resp->info.len; - uint32_t _header = htonl(_len + sizeof(resp->info)); - if (swoole_event_write(server_->last_stream_socket, (char *) &_header, sizeof(_header)) < 0) { - return false; - } - if (swoole_event_write(server_->last_stream_socket, &resp->info, sizeof(resp->info)) < 0) { - return false; - } - if (_len > 0 && swoole_event_write(server_->last_stream_socket, resp->data, _len) < 0) { - return false; - } - return true; - } - SendData task; memcpy(&task, resp, sizeof(SendData)); task.info.fd = session_id; task.info.reactor_id = conn->reactor_id; - task.info.server_fd = SwooleG.process_id; + task.info.server_fd = swoole_get_worker_id(); - swoole_trace("worker_id=%d, type=%d", SwooleG.process_id, task.info.type); + swoole_trace("worker_id=%d, type=%d", task.info.server_fd, task.info.type); return server_->message_bus.write(server_->get_reactor_pipe_socket(session_id, task.info.reactor_id), &task); } @@ -253,7 +371,7 @@ bool ProcessFactory::end(SessionId session_id, int flags) { Connection *conn = server_->get_connection_verify_no_ssl(session_id); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_NOT_EXIST, "session#%ld does not exists", session_id); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session#%ld does not exists", session_id); return false; } // Reset send buffer, Immediately close the connection. @@ -267,7 +385,6 @@ bool ProcessFactory::end(SessionId session_id, int flags) { swoole_trace_log(SW_TRACE_CLOSE, "session_id=%ld, fd=%d", session_id, conn->fd); - Worker *worker; DataHead ev = {}; /** @@ -276,12 +393,10 @@ bool ProcessFactory::end(SessionId session_id, int flags) { * MUST forward to the correct worker process */ if (conn->close_actively) { - if (server_->last_stream_socket) { - goto _close; - } + Worker *worker; bool hash = server_->is_hash_dispatch_mode(); int worker_id = hash ? server_->schedule_worker(conn->fd, nullptr) : conn->fd % server_->worker_num; - if (server_->is_worker() && (!hash || worker_id == (int) SwooleG.process_id)) { + if (server_->is_worker() && (!hash || worker_id == (int) swoole_get_worker_id())) { goto _close; } worker = server_->get_worker(worker_id); @@ -300,7 +415,7 @@ bool ProcessFactory::end(SessionId session_id, int flags) { return false; } - if (server_->onClose != nullptr && !conn->closed) { + if (server_->if_do_close_callback(conn)) { info.fd = session_id; if (conn->close_actively) { info.reactor_id = -1; @@ -312,7 +427,6 @@ bool ProcessFactory::end(SessionId session_id, int flags) { server_->onClose(server_, &info); conn->closing = 0; } - conn->closed = 1; conn->close_errno = 0; return finish(&_send); } diff --git a/src/server/reactor_process.cc b/src/server/reactor_process.cc index b657e46b1a..17fe768fed 100644 --- a/src/server/reactor_process.cc +++ b/src/server/reactor_process.cc @@ -15,68 +15,38 @@ */ #include "swoole_server.h" -#include "swoole_memory.h" namespace swoole { using network::Socket; -static int ReactorProcess_loop(ProcessPool *pool, Worker *worker); static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event); static int ReactorProcess_onClose(Reactor *reactor, Event *event); static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode); -#ifdef HAVE_REUSEPORT -static int ReactorProcess_reuse_port(ListenPort *ls); -#endif - -static bool Server_is_single(Server *serv) { - return serv->worker_num == 1 && serv->task_worker_num == 0 && serv->max_request == 0 && - serv->user_worker_list.empty(); -} - -int Server::create_reactor_processes() { - reactor_num = worker_num; - connection_list = (Connection *) sw_calloc(max_connection, sizeof(Connection)); - if (connection_list == nullptr) { - swoole_sys_warning("calloc[2](%d) failed", (int) (max_connection * sizeof(Connection))); - return SW_ERR; - } - return SW_OK; -} - -void Server::destroy_reactor_processes() { - sw_free(connection_list); -} - int Server::start_reactor_processes() { - single_thread = 1; + single_thread = true; // listen TCP if (have_stream_sock == 1) { for (auto ls : ports) { - if (ls->is_dgram()) { - continue; - } -#ifdef HAVE_REUSEPORT - if (enable_reuse_port) { - if (::close(ls->socket->fd) < 0) { - swoole_sys_warning("close(%d) failed", ls->socket->fd); - } - delete ls->socket; - ls->socket = nullptr; - continue; - } else + if (ls->is_stream()) { +#if defined(__linux__) && defined(HAVE_REUSEPORT) + if (!enable_reuse_port) { #endif - { - // listen server socket - if (ls->listen() < 0) { - return SW_ERR; + // listen server socket + if (ls->listen() < 0) { + return SW_ERR; + } +#if defined(__linux__) && defined(HAVE_REUSEPORT) + } else { + ls->close_socket(); } +#endif } } } - ProcessPool *pool = &gs->event_workers; + ProcessPool *pool = get_event_worker_pool(); *pool = {}; if (pool->create(worker_num, 0, SW_IPC_UNIXSOCK) < 0) { return SW_ERR; @@ -86,18 +56,18 @@ int Server::start_reactor_processes() { /** * store to ProcessPool object */ - gs->event_workers.ptr = this; - gs->event_workers.max_wait_time = max_wait_time; - gs->event_workers.use_msgqueue = 0; - gs->event_workers.main_loop = ReactorProcess_loop; - gs->event_workers.onWorkerNotFound = Server::wait_other_worker; - memcpy(workers, gs->event_workers.workers, sizeof(*workers) * worker_num); - gs->event_workers.workers = workers; + pool->ptr = this; + pool->max_wait_time = max_wait_time; + pool->use_msgqueue = 0; + pool->main_loop = reactor_process_main_loop; + pool->onWorkerNotFound = wait_other_worker; + memcpy(workers, pool->workers, sizeof(*workers) * worker_num); + pool->workers = workers; SW_LOOP_N(worker_num) { - gs->event_workers.workers[i].pool = &gs->event_workers; - gs->event_workers.workers[i].id = i; - gs->event_workers.workers[i].type = SW_PROCESS_WORKER; + pool->workers[i].pool = pool; + pool->workers[i].id = i; + pool->workers[i].type = SW_WORKER; } init_ipc_max_size(); @@ -105,83 +75,25 @@ int Server::start_reactor_processes() { return SW_ERR; } - // single worker - if (Server_is_single(this)) { - int retval = ReactorProcess_loop(&gs->event_workers, &gs->event_workers.workers[0]); + if (is_single_worker()) { + Worker *worker = &pool->workers[0]; + SwooleWG.worker = worker; + int retval = reactor_process_main_loop(pool, worker); if (retval == SW_OK) { - gs->event_workers.destroy(); + pool->destroy(); } return retval; } - SW_LOOP_N(worker_num) { - create_worker(&gs->event_workers.workers[i]); - } - - // task workers - if (task_worker_num > 0) { - if (create_task_workers() < 0) { - return SW_ERR; - } - if (gs->task_workers.start() < 0) { - return SW_ERR; - } - } - - // create user worker process - if (!user_worker_list.empty()) { - user_workers = (Worker *) sw_shm_calloc(get_user_worker_num(), sizeof(Worker)); - if (user_workers == nullptr) { - swoole_sys_warning("gmalloc[server->user_workers] failed"); - return SW_ERR; - } - for (auto worker : user_worker_list) { - /** - * store the pipe object - */ - if (worker->pipe_object) { - store_pipe_fd(worker->pipe_object); - } - spawn_user_worker(worker); - } - } - - /** - * manager process is the same as the master process - */ - SwooleG.pid = gs->manager_pid = getpid(); - SwooleG.process_type = SW_PROCESS_MANAGER; - - gs->event_workers.onWorkerMessage = read_worker_message; - gs->event_workers.start(); - - init_signal_handler(); - - if (onManagerStart) { - onManagerStart(this); - } - - gs->event_workers.wait(); - gs->event_workers.shutdown(); - - kill_user_workers(); - - if (onManagerStop) { - onManagerStop(this); - } - - SW_LOOP_N(worker_num) { - destroy_worker(&gs->event_workers.workers[i]); - } - - return SW_OK; + return start_manager_process(); } static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event) { SendData _send; - Server *serv = (Server *) reactor->ptr; - Factory *factory = serv->factory; - PipeBuffer *pipe_buffer = serv->message_bus.get_buffer(); + auto *serv = static_cast(reactor->ptr); + auto *factory = serv->factory_; + auto *pipe_buffer = serv->message_bus.get_buffer(); + auto *worker = serv->get_worker(reactor->id); ssize_t retval = serv->message_bus.read(event->socket); if (retval <= 0) { @@ -190,11 +102,15 @@ static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event) { switch (pipe_buffer->info.type) { case SW_SERVER_EVENT_PIPE_MESSAGE: { - serv->onPipeMessage(serv, (EventData *) pipe_buffer); + serv->onPipeMessage(serv, reinterpret_cast(pipe_buffer)); break; } case SW_SERVER_EVENT_FINISH: { - serv->onFinish(serv, (EventData *) pipe_buffer); + serv->onFinish(serv, reinterpret_cast(pipe_buffer)); + break; + } + case SW_SERVER_EVENT_SHUTDOWN: { + serv->stop_async_worker(worker); break; } case SW_SERVER_EVENT_SEND_FILE: { @@ -216,12 +132,13 @@ static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event) { factory->finish(&_send); break; } - case SW_SERVER_EVENT_CLOSE: { + case SW_SERVER_EVENT_CLOSE: + case SW_SERVER_EVENT_CLOSE_FORWARD: { factory->end(pipe_buffer->info.fd, Server::CLOSE_ACTIVELY); break; } case SW_SERVER_EVENT_COMMAND_REQUEST: { - serv->call_command_handler(serv->message_bus, SwooleWG.worker->id, serv->get_worker(0)->pipe_master); + serv->call_command_handler(serv->message_bus, sw_worker()->id, serv->get_worker(0)->pipe_master); break; } case SW_SERVER_EVENT_COMMAND_RESPONSE: { @@ -239,21 +156,13 @@ static int ReactorProcess_onPipeRead(Reactor *reactor, Event *event) { return SW_OK; } -static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { - Server *serv = (Server *) pool->ptr; +int Server::reactor_process_main_loop(ProcessPool *pool, Worker *worker) { + auto *serv = static_cast(pool->ptr); + swoole_set_worker_type(SW_EVENT_WORKER); + swoole_set_worker_id(worker->id); + swoole_set_worker_pid(getpid()); - SwooleG.process_type = SW_PROCESS_WORKER; - SwooleG.pid = getpid(); - - SwooleG.process_id = worker->id; - if (serv->max_request > 0) { - SwooleWG.run_always = false; - } - SwooleWG.max_request = serv->max_request; - SwooleWG.worker = worker; - SwooleTG.id = 0; - - serv->init_worker(worker); + serv->init_event_worker(worker); if (!SwooleTG.reactor) { if (swoole_event_init(0) < 0) { @@ -264,20 +173,27 @@ static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { Reactor *reactor = SwooleTG.reactor; if (SwooleTG.timer && SwooleTG.timer->get_reactor() == nullptr) { - SwooleTG.timer->reinit(reactor); + SwooleTG.timer->reinit(); } - Server::worker_signal_init(); + serv->worker_signal_init(); + + serv->gs->connection_nums[worker->id] = 0; for (auto ls : serv->ports) { -#ifdef HAVE_REUSEPORT +#if defined(__linux__) and defined(HAVE_REUSEPORT) if (ls->is_stream() && serv->enable_reuse_port) { - if (ReactorProcess_reuse_port(ls) < 0) { + if (ls->create_socket() < 0) { swoole_event_free(); return SW_ERR; } + + if (ls->listen() < 0) { + return SW_ERR; + } } #endif + ls->gs->connection_nums[worker->id] = 0; if (reactor->add(ls->socket, SW_EVENT_READ) < 0) { return SW_ERR; } @@ -287,15 +203,15 @@ static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { reactor->ptr = serv; reactor->max_socket = serv->get_max_connection(); - reactor->close = Server::close_connection; + reactor->close = close_connection; // set event handler // connect - reactor->set_handler(SW_FD_STREAM_SERVER, Server::accept_connection); + reactor->set_handler(SW_FD_STREAM_SERVER, SW_EVENT_READ, accept_connection); // close reactor->default_error_handler = ReactorProcess_onClose; // pipe - reactor->set_handler(SW_FD_PIPE | SW_EVENT_READ, ReactorProcess_onPipeRead); + reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, ReactorProcess_onPipeRead); serv->store_listen_socket(); @@ -313,8 +229,8 @@ static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { // task workers if (serv->task_worker_num > 0) { if (serv->task_ipc_mode == Server::TASK_IPC_UNIXSOCK) { - SW_LOOP_N(serv->gs->task_workers.worker_num) { - serv->gs->task_workers.workers[i].pipe_master->set_nonblock(); + SW_LOOP_N(serv->get_task_worker_pool()->worker_num) { + serv->get_task_worker_pool()->workers[i].pipe_master->set_nonblock(); } } } @@ -322,31 +238,33 @@ static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { serv->init_reactor(reactor); if (worker->id == 0) { - if (serv->onStart) { + serv->gs->master_pid = getpid(); + if (serv->onStart && !serv->gs->onstart_called) { + serv->gs->onstart_called = true; serv->onStart(serv); } } - if ((serv->master_timer = swoole_timer_add(1000, true, Server::timer_callback, serv)) == nullptr) { + if ((serv->master_timer = swoole_timer_add(1000L, true, timer_callback, serv)) == nullptr) { _fail: swoole_event_free(); return SW_ERR; } - serv->worker_start_callback(); + serv->worker_start_callback(worker); /** * for heartbeat check */ if (serv->heartbeat_check_interval > 0) { serv->heartbeat_timer = - swoole_timer_add((long) (serv->heartbeat_check_interval * 1000), true, ReactorProcess_onTimeout, reactor); + swoole_timer_add(sec2msec(serv->heartbeat_check_interval), true, ReactorProcess_onTimeout, reactor); if (serv->heartbeat_timer == nullptr) { goto _fail; } } - int retval = reactor->wait(nullptr); + int retval = reactor->wait(); /** * Close all connections @@ -356,22 +274,22 @@ static int ReactorProcess_loop(ProcessPool *pool, Worker *worker) { /** * call internal serv hooks */ - if (serv->isset_hook(Server::HOOK_WORKER_CLOSE)) { + if (serv->isset_hook(HOOK_WORKER_CLOSE)) { void *hook_args[2]; hook_args[0] = serv; - hook_args[1] = (void *) (uintptr_t) SwooleG.process_id; - serv->call_hook(Server::HOOK_WORKER_CLOSE, hook_args); + hook_args[1] = (void *) (uintptr_t) worker->id; + serv->call_hook(HOOK_WORKER_CLOSE, hook_args); } swoole_event_free(); - serv->worker_stop_callback(); + serv->worker_stop_callback(worker); return retval; } static int ReactorProcess_onClose(Reactor *reactor, Event *event) { int fd = event->fd; - Server *serv = (Server *) reactor->ptr; + auto *serv = (Server *) reactor->ptr; Connection *conn = serv->get_connection(fd); if (conn == nullptr || conn->active == 0) { return SW_ERR; @@ -383,6 +301,11 @@ static int ReactorProcess_onClose(Reactor *reactor, Event *event) { if (conn->close_queued) { return Server::close_connection(reactor, event->socket); } else { + /** + * peer_closed indicates that the client has closed the connection + * and the connection is no longer available. + */ + conn->peer_closed = 1; return serv->notify(conn, SW_SERVER_EVENT_CLOSE) ? SW_OK : SW_ERR; } } else { @@ -391,8 +314,8 @@ static int ReactorProcess_onClose(Reactor *reactor, Event *event) { } static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode) { - Reactor *reactor = (Reactor *) tnode->data; - Server *serv = (Server *) reactor->ptr; + auto *reactor = static_cast(tnode->data); + auto *serv = static_cast(reactor->ptr); Event notify_ev{}; double now = microtime(); @@ -402,12 +325,10 @@ static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode) { if (serv->is_healthy_connection(now, conn)) { return; } -#ifdef SW_USE_OPENSSL if (conn->socket->ssl && conn->socket->ssl_state != SW_SSL_STATE_READY) { Server::close_connection(reactor, conn->socket); return; } -#endif if (serv->disable_notify || conn->close_force) { Server::close_connection(reactor, conn->socket); return; @@ -419,21 +340,4 @@ static void ReactorProcess_onTimeout(Timer *timer, TimerNode *tnode) { ReactorProcess_onClose(reactor, ¬ify_ev); }); } - -#ifdef HAVE_REUSEPORT -static int ReactorProcess_reuse_port(ListenPort *ls) { - ls->socket = swoole::make_socket( - ls->type, ls->is_dgram() ? SW_FD_DGRAM_SERVER : SW_FD_STREAM_SERVER, SW_SOCK_CLOEXEC | SW_SOCK_NONBLOCK); - if (ls->socket->set_reuse_port() < 0) { - ls->socket->free(); - return SW_ERR; - } - if (ls->socket->bind(ls->host, &ls->port) < 0) { - ls->socket->free(); - return SW_ERR; - } - return ls->listen(); -} -#endif - } // namespace swoole diff --git a/src/server/reactor_thread.cc b/src/server/reactor_thread.cc index 575b81531f..ad9786f73f 100644 --- a/src/server/reactor_thread.cc +++ b/src/server/reactor_thread.cc @@ -15,32 +15,26 @@ */ #include "swoole_server.h" -#include "swoole_memory.h" #include "swoole_hash.h" -#include "swoole_client.h" #include "swoole_util.h" -#include +#include using std::unordered_map; namespace swoole { using namespace network; -static void ReactorThread_loop(Server *serv, int reactor_id); static int ReactorThread_onPipeWrite(Reactor *reactor, Event *ev); static int ReactorThread_onPipeRead(Reactor *reactor, Event *ev); static int ReactorThread_onRead(Reactor *reactor, Event *ev); static int ReactorThread_onWrite(Reactor *reactor, Event *ev); static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event); static int ReactorThread_onClose(Reactor *reactor, Event *event); -static void ReactorThread_onStreamResponse(Stream *stream, const char *data, uint32_t length); -static void ReactorThread_shutdown(Reactor *reactor); static void ReactorThread_resume_data_receiving(Timer *timer, TimerNode *tnode); -#ifdef SW_USE_OPENSSL static inline ReturnCode ReactorThread_verify_ssl_state(Reactor *reactor, ListenPort *port, Socket *_socket) { - Server *serv = (Server *) reactor->ptr; + auto serv = static_cast(reactor->ptr); if (!_socket->ssl || _socket->ssl_state == SW_SSL_STATE_READY) { return SW_CONTINUE; } @@ -50,22 +44,22 @@ static inline ReturnCode ReactorThread_verify_ssl_state(Reactor *reactor, Listen return code; } - Connection *conn = (Connection *) _socket->object; + auto conn = static_cast(_socket->object); conn->ssl_ready = 1; - if (!port->ssl_context->client_cert_file.empty()) { + if (!port->get_ssl_client_cert_file().empty()) { if (!_socket->ssl_get_peer_certificate(sw_tg_buffer())) { - if (port->ssl_context->verify_peer) { + if (port->get_ssl_verify_peer()) { return SW_ERROR; } } else { - if (!port->ssl_context->verify_peer || _socket->ssl_verify(port->ssl_context->allow_self_signed)) { + if (!port->get_ssl_verify_peer() || _socket->ssl_verify(port->get_ssl_allow_self_signed())) { SendData task; task.info.fd = _socket->fd; task.info.type = SW_SERVER_EVENT_CONNECT; task.info.reactor_id = reactor->id; task.info.len = sw_tg_buffer()->length; task.data = sw_tg_buffer()->str; - serv->factory->dispatch(&task); + serv->factory_->dispatch(&task); goto _delay_receive; } else { return SW_ERROR; @@ -74,7 +68,7 @@ static inline ReturnCode ReactorThread_verify_ssl_state(Reactor *reactor, Listen } if (serv->onConnect) { - serv->notify((Connection *) _socket->object, SW_SERVER_EVENT_CONNECT); + serv->notify(static_cast(_socket->object), SW_SERVER_EVENT_CONNECT); } _delay_receive: if (serv->enable_delay_receive) { @@ -85,46 +79,18 @@ static inline ReturnCode ReactorThread_verify_ssl_state(Reactor *reactor, Listen return SW_READY; } -#endif - -static void ReactorThread_onStreamResponse(Stream *stream, const char *data, uint32_t length) { - SendData response; - Server *serv = (Server *) stream->private_data; - Connection *conn = (Connection *) stream->private_data_2; - SessionId session_id = stream->private_data_fd; - - if (!conn->active || session_id != conn->session_id) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_NOT_EXIST, "session#%ld does not exists", session_id); - return; - } - if (data == nullptr) { - Event _ev = {}; - _ev.fd = conn->fd; - _ev.socket = conn->socket; - sw_reactor()->trigger_close_event(&_ev); - return; - } - - DataHead *pkg_info = (DataHead *) data; - response.info.fd = conn->session_id; - response.info.type = pkg_info->type; - response.info.len = length - sizeof(DataHead); - response.data = data + sizeof(DataHead); - serv->send_to_connection(&response); -} /** * for udp */ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { int fd = event->fd; - int ret; - Server *serv = (Server *) reactor->ptr; + auto serv = static_cast(reactor->ptr); Connection *server_sock = serv->get_connection(fd); - network::Socket *sock = server_sock->socket; + Socket *sock = server_sock->socket; SendData task = {}; - DgramPacket *pkt = (DgramPacket *) sw_tg_buffer()->str; + auto pkt = reinterpret_cast(sw_tg_buffer()->str); task.info.server_fd = fd; task.info.reactor_id = SwooleTG.id; @@ -135,7 +101,7 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { _do_recvfrom: - ret = sock->recvfrom(pkt->data, sw_tg_buffer()->size - sizeof(*pkt), 0, &pkt->socket_addr); + ssize_t ret = sock->recvfrom(pkt->data, sw_tg_buffer()->size - sizeof(*pkt), 0, &pkt->socket_addr); if (ret <= 0) { if (errno == EAGAIN) { return SW_OK; @@ -146,7 +112,7 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { } #ifdef SW_SUPPORT_DTLS - ListenPort *port = (ListenPort *) server_sock->object; + auto port = static_cast(server_sock->object); if (port->is_dtls()) { dtls::Session *session = serv->accept_dtls_connection(port, &pkt->socket_addr); @@ -160,7 +126,7 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { return Server::close_connection(reactor, session->socket); } - Connection *conn = (Connection *) session->socket->object; + auto conn = static_cast(session->socket->object); if (serv->single_thread) { if (serv->connection_incoming(reactor, conn) < 0) { reactor->close(reactor, session->socket); @@ -171,7 +137,7 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { ev.type = SW_SERVER_EVENT_INCOMING; ev.fd = conn->session_id; ev.reactor_id = conn->reactor_id; - if (serv->send_to_reactor_thread((EventData *) &ev, sizeof(ev), conn->session_id) < 0) { + if (serv->send_to_reactor_thread(reinterpret_cast(&ev), sizeof(ev), conn->session_id) < 0) { reactor->close(reactor, session->socket); return SW_OK; } @@ -182,16 +148,16 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { #endif if (pkt->socket_type == SW_SOCK_UDP) { - task.info.fd = *(int *) &pkt->socket_addr.addr.inet_v4.sin_addr; + task.info.fd = *reinterpret_cast(&pkt->socket_addr.addr.inet_v4.sin_addr); } else { task.info.fd = swoole_crc32(pkt->socket_addr.get_addr(), pkt->socket_addr.len); } pkt->length = ret; task.info.len = sizeof(*pkt) + ret; - task.data = (char *) pkt; + task.data = reinterpret_cast(pkt); - if (!serv->factory->dispatch(&task)) { + if (!serv->factory_->dispatch(&task)) { return SW_ERR; } else { goto _do_recvfrom; @@ -202,8 +168,8 @@ static int ReactorThread_onPacketReceived(Reactor *reactor, Event *event) { * close connection */ int Server::close_connection(Reactor *reactor, Socket *socket) { - Server *serv = (Server *) reactor->ptr; - Connection *conn = (Connection *) socket->object; + auto serv = static_cast(reactor->ptr); + auto conn = static_cast(socket->object); ListenPort *port = serv->get_port_by_fd(socket->fd); if (conn->timer) { @@ -215,14 +181,18 @@ int Server::close_connection(Reactor *reactor, Socket *socket) { } sw_atomic_fetch_add(&serv->gs->close_count, 1); - sw_atomic_fetch_sub(&serv->gs->connection_num, 1); - sw_atomic_fetch_add(&port->gs->close_count, 1); - sw_atomic_fetch_sub(&port->gs->connection_num, 1); + + if (serv->is_base_mode()) { + sw_atomic_fetch_sub(&serv->gs->connection_nums[reactor->id], 1); + sw_atomic_fetch_sub(&port->gs->connection_nums[reactor->id], 1); + } else { + sw_atomic_fetch_sub(&serv->gs->connection_num, 1); + sw_atomic_fetch_sub(&port->gs->connection_num, 1); + } swoole_trace("Close Event.fd=%d|from=%d", socket->fd, reactor->id); -#ifdef SW_USE_OPENSSL if (socket->ssl) { conn->socket->ssl_quiet_shutdown = conn->peer_closed; conn->socket->ssl_close(); @@ -233,17 +203,16 @@ int Server::close_connection(Reactor *reactor, Socket *socket) { port->dtls_sessions->erase(socket->fd); delete session; } -#endif #endif - // free the receive memory buffer + // free the reception memory buffer if (socket->recv_buffer) { delete socket->recv_buffer; socket->recv_buffer = nullptr; } if (port->open_http_protocol && conn->object) { - serv->destroy_http_request(conn); + port->destroy_http_request(conn); } if (port->open_redis_protocol && conn->object) { sw_free(conn->object); @@ -271,7 +240,7 @@ int Server::close_connection(Reactor *reactor, Socket *socket) { serv->lock(); if (fd == serv->get_maxfd()) { int find_max_fd = fd - 1; - swoole_trace("set_maxfd=%d|close_fd=%d\n", find_max_fd, fd); + swoole_trace_log(SW_TRACE_SERVER, "set_maxfd=%d|close_fd=%d", find_max_fd, fd); // find the new max_fd for (; !serv->is_valid_connection(serv->get_connection(find_max_fd)) && find_max_fd > serv->get_minfd(); find_max_fd--) { @@ -289,7 +258,7 @@ int Server::close_connection(Reactor *reactor, Socket *socket) { * close the connection */ static int ReactorThread_onClose(Reactor *reactor, Event *event) { - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); int fd = event->fd; DataHead notify_ev{}; Socket *socket = event->socket; @@ -319,15 +288,15 @@ static int ReactorThread_onClose(Reactor *reactor, Event *event) { * and the connection is no longer available. */ conn->peer_closed = 1; - return serv->factory->notify(¬ify_ev); + return serv->factory_->notify(¬ify_ev); } } else { return SW_ERR; } } -static void ReactorThread_shutdown(Reactor *reactor) { - Server *serv = (Server *) reactor->ptr; +void ReactorThread::shutdown(Reactor *reactor) { + auto *serv = static_cast(reactor->ptr); // stop listen UDP Port if (serv->have_dgram_sock == 1) { for (auto ls : serv->ports) { @@ -335,9 +304,37 @@ static void ReactorThread_shutdown(Reactor *reactor) { if (ls->socket->fd % serv->reactor_num != reactor->id) { continue; } - reactor->del(ls->socket); + if (!ls->socket->removed) { + reactor->del(ls->socket); + } + } + } + } + + if (heartbeat_timer) { + swoole_timer_del(heartbeat_timer); + heartbeat_timer = nullptr; + } + + reactor->add_destroy_callback([serv, reactor](void *) { + serv->foreach_connection([reactor](Connection *conn) { + if (conn->reactor_id == reactor->id) { + Server::close_connection(reactor, conn->socket); } + }); + }); + + if (serv->is_thread_mode()) { + serv->stop_async_worker(serv->get_worker(reactor->id)); + return; + } + + SW_LOOP_N(serv->worker_num) { + if (i % serv->reactor_num != reactor->id) { + continue; } + Socket *socket = message_bus.get_pipe_socket(serv->get_worker_pipe_master(i)); + reactor->remove_read_event(socket); } serv->foreach_connection([serv, reactor](Connection *conn) { @@ -352,71 +349,100 @@ static void ReactorThread_shutdown(Reactor *reactor) { reactor->set_wait_exit(true); } +int ReactorThread::close_connection(Reactor *reactor, SessionId session_id) { + auto *serv = static_cast(reactor->ptr); + Connection *conn = serv->get_connection_verify_no_ssl(session_id); + if (!conn) { + swoole_error_log(SW_LOG_TRACE, + SW_ERROR_SESSION_NOT_EXIST, + "force close connection failed, session#%ld does not exist", + session_id); + return SW_OK; + } + + if (serv->disable_notify || conn->close_force) { + return Server::close_connection(reactor, conn->socket); + } + + /** + * SSL connections that have not completed the handshake, + * do not need to notify the workers, just close + */ + if (conn->ssl && !conn->ssl_ready) { + return Server::close_connection(reactor, conn->socket); + } + conn->close_force = 1; + Event _ev = {}; + _ev.fd = conn->fd; + _ev.socket = conn->socket; + reactor->trigger_close_event(&_ev); + + return SW_OK; +} + /** * receive data from worker process pipe */ static int ReactorThread_onPipeRead(Reactor *reactor, Event *ev) { SendData _send; - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); ReactorThread *thread = serv->get_thread(reactor->id); -#ifdef SW_REACTOR_RECV_AGAIN - while (1) -#endif - { + SW_LOOP { PipeBuffer *resp = thread->message_bus.get_buffer(); ssize_t n = thread->message_bus.read_with_buffer(ev->socket); if (n <= 0) { - return n; + return SW_OK; } - if (resp->info.type == SW_SERVER_EVENT_INCOMING) { + switch (resp->info.type) { + case SW_SERVER_EVENT_INCOMING: { Connection *conn = serv->get_connection_verify_no_ssl(resp->info.fd); if (conn && serv->connection_incoming(reactor, conn) < 0) { - return reactor->close(reactor, conn->socket); + reactor->close(reactor, conn->socket); } - } else if (resp->info.type == SW_SERVER_EVENT_COMMAND_REQUEST) { - return serv->call_command_handler(thread->message_bus, thread->id, thread->pipe_command); - } else if (resp->info.type == SW_SERVER_EVENT_COMMAND_RESPONSE) { + break; + } + case SW_SERVER_EVENT_COMMAND_REQUEST: { + serv->call_command_handler(thread->message_bus, thread->id, thread->pipe_command); + break; + } + case SW_SERVER_EVENT_COMMAND_RESPONSE: { auto packet = thread->message_bus.get_packet(); serv->call_command_callback(resp->info.fd, std::string(packet.data, packet.length)); - return SW_OK; - } else if (resp->info.type == SW_SERVER_EVENT_SHUTDOWN) { - ReactorThread_shutdown(reactor); - } else if (resp->info.type == SW_SERVER_EVENT_CLOSE_FORCE) { - SessionId session_id = resp->info.fd; - Connection *conn = serv->get_connection_verify_no_ssl(session_id); - if (!conn) { - swoole_error_log(SW_LOG_NOTICE, - SW_ERROR_SESSION_NOT_EXIST, - "force close connection failed, session#%ld does not exist", - session_id); - return SW_OK; - } - - if (serv->disable_notify || conn->close_force) { - return Server::close_connection(reactor, conn->socket); - } - -#ifdef SW_USE_OPENSSL - /** - * SSL connections that have not completed the handshake, - * do not need to notify the workers, just close - */ - if (conn->ssl && !conn->ssl_ready) { - return Server::close_connection(reactor, conn->socket); - } -#endif - conn->close_force = 1; - Event _ev = {}; - _ev.fd = conn->fd; - _ev.socket = conn->socket; - reactor->trigger_close_event(&_ev); - } else { + break; + } + case SW_SERVER_EVENT_SHUTDOWN: { + thread->shutdown(reactor); + break; + } + case SW_SERVER_EVENT_SHUTDOWN_SIGNAL: { + swoole_kill(getpid(), SIGTERM); + break; + } + case SW_SERVER_EVENT_FINISH: { + serv->onFinish(serv, reinterpret_cast(resp)); + break; + } + case SW_SERVER_EVENT_PIPE_MESSAGE: { + serv->onPipeMessage(serv, reinterpret_cast(resp)); + break; + } + case SW_SERVER_EVENT_CLOSE_FORCE: { + thread->close_connection(reactor, resp->info.fd); + break; + } + case SW_SERVER_EVENT_CLOSE_FORWARD: { + serv->factory_->end(resp->info.fd, Server::CLOSE_ACTIVELY); + break; + } + default: { PacketPtr packet = thread->message_bus.get_packet(); _send.info = resp->info; _send.info.len = packet.length; _send.data = packet.data; serv->send_to_connection(&_send); + break; + } } thread->message_bus.pop(); } @@ -430,12 +456,12 @@ static int ReactorThread_onPipeRead(Reactor *reactor, Event *ev) { static int ReactorThread_onPipeWrite(Reactor *reactor, Event *ev) { int ret; - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); Buffer *buffer = ev->socket->out_buffer; while (!Buffer::empty(buffer)) { - BufferChunk *chunk = buffer->front(); - EventData *send_data = (EventData *) chunk->value.ptr; + const BufferChunk *chunk = buffer->front(); + const auto *send_data = reinterpret_cast(chunk->value.str); // server actively closed connection, should discard the data if (Server::is_stream_event(send_data->info.type)) { @@ -462,7 +488,7 @@ static int ReactorThread_onPipeWrite(Reactor *reactor, Event *ev) { } } - ret = ev->socket->send(chunk->value.ptr, chunk->length, 0); + ret = ev->socket->send(chunk->value.str, chunk->length, 0); if (ret < 0) { return (ev->socket->catch_write_error(errno) == SW_WAIT) ? SW_OK : SW_ERR; } else { @@ -485,31 +511,20 @@ void Server::init_reactor(Reactor *reactor) { sw_tg_buffer()->extend(); } // UDP Packet - reactor->set_handler(SW_FD_DGRAM_SERVER, ReactorThread_onPacketReceived); + reactor->set_handler(SW_FD_DGRAM_SERVER, SW_EVENT_READ, ReactorThread_onPacketReceived); // Write - reactor->set_handler(SW_FD_SESSION | SW_EVENT_WRITE, ReactorThread_onWrite); + reactor->set_handler(SW_FD_SESSION, SW_EVENT_WRITE, ReactorThread_onWrite); // Read - reactor->set_handler(SW_FD_SESSION | SW_EVENT_READ, ReactorThread_onRead); - - if (dispatch_mode == DISPATCH_STREAM) { - Client::init_reactor(reactor); - } + reactor->set_handler(SW_FD_SESSION, SW_EVENT_READ, ReactorThread_onRead); // listen the all tcp port for (auto port : ports) { - if (port->is_dgram() -#ifdef SW_SUPPORT_DTLS - && !(port->is_dtls()) -#endif - ) { - continue; - } - init_port_protocol(port); + port->init_protocol(); } } static int ReactorThread_onRead(Reactor *reactor, Event *event) { - Server *serv = (Server *) reactor->ptr; + auto *serv = static_cast(reactor->ptr); Connection *conn = serv->get_connection(event->fd); /** * invalid event @@ -519,10 +534,9 @@ static int ReactorThread_onRead(Reactor *reactor, Event *event) { return SW_OK; } ListenPort *port = serv->get_port_by_fd(event->fd); -#ifdef SW_USE_OPENSSL #ifdef SW_SUPPORT_DTLS if (port->is_dtls()) { - dtls::Buffer *buffer = (dtls::Buffer *) sw_malloc(sizeof(*buffer) + SW_BUFFER_SIZE_UDP); + dtls::Buffer *buffer = static_cast(sw_malloc(sizeof(*buffer) + SW_BUFFER_SIZE_UDP)); buffer->length = event->socket->read(buffer->data, SW_BUFFER_SIZE_UDP); dtls::Session *session = port->dtls_sessions->find(event->fd)->second; session->append(buffer); @@ -551,7 +565,6 @@ static int ReactorThread_onRead(Reactor *reactor, Event *event) { default: abort(); } -#endif conn->last_recv_time = microtime(); long last_recv_bytes = event->socket->total_recv_bytes; @@ -568,7 +581,8 @@ static int ReactorThread_onRead(Reactor *reactor, Event *event) { } if (serv->is_process_mode() && serv->max_queued_bytes && conn->recv_queued_bytes > serv->max_queued_bytes) { conn->waiting_time = 1; - conn->timer = swoole_timer_add(conn->waiting_time, false, ReactorThread_resume_data_receiving, event->socket); + conn->timer = + swoole_timer_add((long) conn->waiting_time, false, ReactorThread_resume_data_receiving, event->socket); if (conn->timer) { reactor->remove_read_event(event->socket); } @@ -578,9 +592,10 @@ static int ReactorThread_onRead(Reactor *reactor, Event *event) { static int ReactorThread_onWrite(Reactor *reactor, Event *ev) { int ret; - Server *serv = (Server *) reactor->ptr; - Socket *socket = ev->socket; + auto serv = static_cast(reactor->ptr); + auto socket = ev->socket; int fd = ev->fd; + auto port = serv->get_port_by_fd(fd); if (serv->is_process_mode()) { assert(fd % serv->reactor_num == reactor->id); @@ -600,11 +615,9 @@ static int ReactorThread_onWrite(Reactor *reactor, Event *ev) { conn->close_force); if (conn->close_notify) { -#ifdef SW_USE_OPENSSL if (socket->ssl && socket->ssl_state != SW_SSL_STATE_READY) { return Server::close_connection(reactor, socket); } -#endif serv->notify(conn, SW_SERVER_EVENT_CLOSE); conn->close_notify = 0; return SW_OK; @@ -635,21 +648,24 @@ static int ReactorThread_onWrite(Reactor *reactor, Event *ev) { } } - if (conn->overflow && socket->out_buffer->length() < socket->buffer_size) { + if (conn->overflow && socket->get_out_buffer_length() < socket->buffer_size) { conn->overflow = 0; } if (serv->onBufferEmpty && conn->high_watermark) { - ListenPort *port = serv->get_port_by_fd(fd); - if (socket->out_buffer->length() <= port->buffer_low_watermark) { + if (socket->get_out_buffer_length() <= port->buffer_low_watermark) { conn->high_watermark = 0; serv->notify(conn, SW_SERVER_EVENT_BUFFER_EMPTY); } } if (socket->send_timer) { - swoole_timer_del(socket->send_timer); - socket->send_timer = nullptr; + if (Buffer::empty(socket->out_buffer)) { + swoole_timer_del(socket->send_timer); + socket->send_timer = nullptr; + } else { + swoole_timer_delay(socket->send_timer, port->max_idle_time); + } } // remove EPOLLOUT event @@ -659,21 +675,25 @@ static int ReactorThread_onWrite(Reactor *reactor, Event *ev) { return SW_OK; } -int Server::create_reactor_threads() { - /** - * init reactor thread pool - */ - reactor_threads = new ReactorThread[reactor_num](); - /** - * alloc the memory for connection_list - */ - connection_list = (Connection *) sw_shm_calloc(max_connection, sizeof(Connection)); - if (connection_list == nullptr) { - swoole_error("calloc[1] failed"); - return SW_ERR; - } - reactor_pipe_num = worker_num / reactor_num; - return SW_OK; +void Server::heartbeat_check(Timer *timer, TimerNode *tnode) { + double now = microtime(); + auto reactor = static_cast(tnode->data); + auto serv = static_cast(reactor->ptr); + ReactorThread *thread = serv->get_thread(reactor->id); + + serv->foreach_connection([=](Connection *conn) { + SessionId session_id = conn->session_id; + if (session_id <= 0) { + return; + } + if (conn->reactor_id != reactor->id) { + return; + } + if (serv->is_healthy_connection(now, conn)) { + return; + } + thread->close_connection(reactor, session_id); + }); } /** @@ -686,8 +706,7 @@ int Server::start_reactor_threads() { Reactor *reactor = sw_reactor(); - for (auto iter = ports.begin(); iter != ports.end(); iter++) { - auto port = *iter; + for (const auto port : ports) { if (port->is_dgram()) { continue; } @@ -716,7 +735,11 @@ int Server::start_reactor_threads() { } SW_LOOP_N(reactor_num) { - get_thread(i)->thread = std::thread(ReactorThread_loop, this, i); + get_thread(i)->thread = std::thread([=]() { + swoole_thread_init(false); + reactor_thread_main_loop(this, i); + swoole_thread_clean(false); + }); } _init_master_thread: @@ -725,27 +748,26 @@ int Server::start_reactor_threads() { * heartbeat thread */ if (heartbeat_check_interval >= 1) { - start_heartbeat_thread(); + if (single_thread) { + heartbeat_timer = swoole_timer_add(sec2msec(heartbeat_check_interval), true, heartbeat_check, reactor); + } else { + start_heartbeat_thread(); + } } - return start_master_thread(); + return start_master_thread(reactor); } int ReactorThread::init(Server *serv, Reactor *reactor, uint16_t reactor_id) { reactor->ptr = serv; reactor->id = reactor_id; - reactor->wait_exit = 0; + reactor->wait_exit = false; reactor->max_socket = serv->get_max_connection(); reactor->close = Server::close_connection; - - reactor->set_exit_condition(Reactor::EXIT_CONDITION_DEFAULT, [this](Reactor *reactor, size_t &event_num) -> bool { - return event_num == (size_t) pipe_num; - }); - reactor->default_error_handler = ReactorThread_onClose; - reactor->set_handler(SW_FD_PIPE | SW_EVENT_READ, ReactorThread_onPipeRead); - reactor->set_handler(SW_FD_PIPE | SW_EVENT_WRITE, ReactorThread_onPipeWrite); + reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, ReactorThread_onPipeRead); + reactor->set_handler(SW_FD_PIPE, SW_EVENT_WRITE, ReactorThread_onPipeWrite); // listen UDP port if (serv->have_dgram_sock == 1) { @@ -758,11 +780,6 @@ int ReactorThread::init(Server *serv, Reactor *reactor, uint16_t reactor_id) { continue; } Connection *serv_sock = serv->get_connection(server_fd); - if (ls->type == SW_SOCK_UDP) { - serv_sock->info.addr.inet_v4.sin_port = htons(ls->port); - } else if (ls->type == SW_SOCK_UDP6) { - serv_sock->info.addr.inet_v6.sin6_port = htons(ls->port); - } serv_sock->fd = server_fd; serv_sock->socket_type = ls->type; serv_sock->object = ls; @@ -774,20 +791,28 @@ int ReactorThread::init(Server *serv, Reactor *reactor, uint16_t reactor_id) { } serv->init_reactor(reactor); + serv->init_pipe_sockets(&message_bus); - int max_pipe_fd = serv->get_worker(serv->worker_num - 1)->pipe_master->fd + 2; - pipe_sockets = (Socket *) sw_calloc(max_pipe_fd, sizeof(Socket)); - if (!pipe_sockets) { - swoole_sys_error("calloc(%d, %ld) failed", max_pipe_fd, sizeof(Socket)); - return SW_ERR; + if (serv->is_thread_mode()) { + Worker *worker = serv->get_worker(reactor_id); + serv->init_event_worker(worker); + auto pipe_worker = message_bus.get_pipe_socket(worker->pipe_worker); + reactor->add(pipe_worker, SW_EVENT_READ); + + if (serv->heartbeat_check_interval > 0) { + heartbeat_timer = + swoole_timer_add(sec2msec(serv->heartbeat_check_interval), true, Server::heartbeat_check, reactor); + } } if (serv->pipe_command) { - pipe_command = make_socket(serv->pipe_command->get_socket(false)->get_fd(), SW_FD_PIPE); + auto pipe_socket = serv->pipe_command->get_socket(false); + message_bus.init_pipe_socket(pipe_socket); + pipe_command = message_bus.get_pipe_socket(pipe_socket); pipe_command->buffer_size = UINT_MAX; } - message_bus.set_id_generator([serv]() { return sw_atomic_fetch_add(&serv->gs->pipe_packet_msg_id, 1); }); + message_bus.set_id_generator(serv->msg_id_generator); message_bus.set_buffer_size(serv->ipc_max_size); message_bus.set_always_chunked_transfer(); if (!message_bus.alloc_buffer()) { @@ -795,100 +820,71 @@ int ReactorThread::init(Server *serv, Reactor *reactor, uint16_t reactor_id) { } SW_LOOP_N(serv->worker_num) { - int pipe_fd = serv->workers[i].pipe_master->fd; - Socket *socket = &pipe_sockets[pipe_fd]; - - socket->fd = pipe_fd; - socket->fd_type = SW_FD_PIPE; - socket->buffer_size = UINT_MAX; - if (i % serv->reactor_num != reactor_id) { continue; } - - socket->set_nonblock(); - + Socket *socket = message_bus.get_pipe_socket(serv->get_worker_pipe_master(i)); if (reactor->add(socket, SW_EVENT_READ) < 0) { return SW_ERR; } + /** + * It will only send data to the notify pipeline synchronously, + * which is thread-safe and does not require separate memory + */ if (notify_pipe == nullptr) { notify_pipe = serv->workers[i].pipe_worker; } - pipe_num++; } return SW_OK; } -/** - * ReactorThread main Loop - */ -static void ReactorThread_loop(Server *serv, int reactor_id) { - SwooleTG.id = reactor_id; - SwooleTG.type = Server::THREAD_REACTOR; +void ReactorThread::clean() { + message_bus.free_buffer(); +} - SwooleTG.buffer_stack = new String(SW_STACK_BUFFER_SIZE); - ON_SCOPE_EXIT { - delete SwooleTG.buffer_stack; - SwooleTG.buffer_stack = nullptr; - }; +void Server::reactor_thread_main_loop(Server *serv, int reactor_id) { + ReactorThread *thread = serv->get_thread(reactor_id); + thread->id = reactor_id; + SwooleTG.message_bus = &thread->message_bus; if (swoole_event_init(0) < 0) { return; } - ReactorThread *thread = serv->get_thread(reactor_id); - thread->id = reactor_id; - Reactor *reactor = sw_reactor(); - -#ifdef HAVE_CPU_AFFINITY - // cpu affinity setting - if (serv->open_cpu_affinity) { - cpu_set_t cpu_set; - CPU_ZERO(&cpu_set); - - if (serv->cpu_affinity_available_num) { - CPU_SET(serv->cpu_affinity_available[reactor_id % serv->cpu_affinity_available_num], &cpu_set); - } else { - CPU_SET(reactor_id % SW_CPU_NUM, &cpu_set); - } - - if (0 != pthread_setaffinity_np(pthread_self(), sizeof(cpu_set), &cpu_set)) { - swoole_sys_warning("pthread_setaffinity_np() failed"); - } + if (serv->is_thread_mode()) { + serv->call_worker_start_callback(serv->get_worker(reactor_id)); + } else { + swoole_set_thread_id(reactor_id); + swoole_set_thread_type(Server::THREAD_REACTOR); } -#endif - - swoole_signal_block_all(); + Reactor *reactor = sw_reactor(); if (thread->init(serv, reactor, reactor_id) < 0) { return; } // wait other thread -#ifdef HAVE_PTHREAD_BARRIER - pthread_barrier_wait(&serv->reactor_thread_barrier); -#else - SW_START_SLEEP; -#endif + if (serv->is_process_mode()) { + serv->reactor_thread_barrier.wait(); + } // main loop swoole_event_wait(); - sw_free(thread->pipe_sockets); - if (thread->pipe_command) { - thread->pipe_command->fd = -1; - delete thread->pipe_command; + if (serv->is_thread_mode()) { + serv->call_worker_stop_callback(serv->get_worker(reactor_id)); } + thread->clean(); } static void ReactorThread_resume_data_receiving(Timer *timer, TimerNode *tnode) { - Socket *_socket = (Socket *) tnode->data; - Connection *conn = (Connection *) _socket->object; + auto *_socket = static_cast(tnode->data); + auto *conn = static_cast(_socket->object); if (conn->recv_queued_bytes > sw_server()->max_queued_bytes) { if (conn->waiting_time != 1024) { conn->waiting_time *= 2; } - conn->timer = swoole_timer_add(conn->waiting_time, false, ReactorThread_resume_data_receiving, _socket); + conn->timer = swoole_timer_add((long) conn->waiting_time, false, ReactorThread_resume_data_receiving, _socket); if (conn->timer) { return; } @@ -902,11 +898,11 @@ static void ReactorThread_resume_data_receiving(Timer *timer, TimerNode *tnode) * dispatch request data [only data frame] */ int Server::dispatch_task(const Protocol *proto, Socket *_socket, const RecvData *rdata) { - Server *serv = (Server *) proto->private_data_2; + auto *serv = static_cast(proto->private_data_2); SendData task; - Connection *conn = (Connection *) _socket->object; - ListenPort *port = serv->get_port_by_fd(conn->fd); + auto *conn = static_cast(_socket->object); + const ListenPort *port = serv->get_port_by_fd(conn->fd); sw_memset_zero(&task.info, sizeof(task.info)); task.info.server_fd = conn->server_fd; @@ -915,119 +911,75 @@ int Server::dispatch_task(const Protocol *proto, Socket *_socket, const RecvData task.info.type = SW_SERVER_EVENT_RECV_DATA; task.info.time = conn->last_recv_time; - int return_code = SW_OK; + swoole_trace("dispatch task, size=%u bytes", rdata->info.len); - swoole_trace("send string package, size=%u bytes", rdata->info.len); + task.info.fd = conn->fd; + task.info.len = rdata->info.len; + task.data = rdata->data; - if (serv->stream_socket_file) { - Stream *stream = Stream::create(serv->stream_socket_file, 0, SW_SOCK_UNIX_STREAM); - if (!stream) { - return_code = SW_ERR; - goto _return; - } - stream->response = ReactorThread_onStreamResponse; - stream->private_data = serv; - stream->private_data_2 = conn; - stream->private_data_fd = conn->session_id; - stream->set_max_length(port->protocol.package_max_length); - - task.info.fd = conn->session_id; - - if (stream->send((char *) &task.info, sizeof(task.info)) < 0) { - _cancel: - stream->cancel = 1; - delete stream; - return_code = SW_ERR; - goto _return; - } - if (rdata->data && rdata->info.len > 0 && stream->send(rdata->data, rdata->info.len) < 0) { - goto _cancel; - } - } else { - task.info.fd = conn->fd; - task.info.len = rdata->info.len; - task.data = rdata->data; - if (rdata->info.len > 0) { - sw_atomic_fetch_add(&conn->recv_queued_bytes, rdata->info.len); - swoole_trace_log(SW_TRACE_SERVER, - "session_id=%ld, len=%d, qb=%d", - conn->session_id, - rdata->info.len, - conn->recv_queued_bytes); - } - if (!serv->factory->dispatch(&task)) { - return_code = SW_ERR; - if (rdata->info.len > 0) { - sw_atomic_fetch_sub(&conn->recv_queued_bytes, rdata->info.len); - } - } + if (rdata->info.len > 0) { + sw_atomic_fetch_add(&conn->recv_queued_bytes, rdata->info.len); + swoole_trace_log(SW_TRACE_SERVER, + "session_id=%ld, len=%d, qb=%d", + conn->session_id, + rdata->info.len, + conn->recv_queued_bytes); } -_return: - if (return_code == SW_OK) { + if (!serv->factory_->dispatch(&task)) { + if (rdata->info.len > 0) { + sw_atomic_fetch_sub(&conn->recv_queued_bytes, rdata->info.len); + } + return SW_ERR; + } else { if (serv->is_process_mode()) { ReactorThread *thread = serv->get_thread(conn->reactor_id); thread->dispatch_count++; } sw_atomic_fetch_add(&serv->gs->dispatch_count, 1); sw_atomic_fetch_add(&port->gs->dispatch_count, 1); + return SW_OK; } - - return return_code; } -void Server::join_reactor_thread() { - if (single_thread) { - return; - } - ReactorThread *thread; +void Server::join_heartbeat_thread() { /** * Shutdown heartbeat thread */ if (heartbeat_thread.joinable()) { swoole_trace_log(SW_TRACE_SERVER, "terminate heartbeat thread"); - if (pthread_cancel(heartbeat_thread.native_handle()) < 0) { - swoole_sys_warning("pthread_cancel(%ld) failed", (ulong_t) heartbeat_thread.native_handle()); - } - // wait thread heartbeat_thread.join(); } - /** - * kill threads - */ +} + +void Server::join_reactor_thread() { + if (single_thread) { + return; + } + + if (heartbeat_check_interval > 0) { + join_heartbeat_thread(); + } + for (int i = 0; i < reactor_num; i++) { - thread = get_thread(i); + ReactorThread *thread = get_thread(i); + if (!thread->thread.joinable()) { + continue; + } if (thread->notify_pipe) { DataHead ev = {}; ev.type = SW_SERVER_EVENT_SHUTDOWN; - if (thread->notify_pipe->send_blocking((void *) &ev, sizeof(ev)) < 0) { - goto _cancel; - } - } else { - _cancel: - if (pthread_cancel(thread->thread.native_handle()) < 0) { - swoole_sys_warning("pthread_cancel(%ld) failed", (long) thread->thread.native_handle()); - } + thread->notify_pipe->send_sync((void *) &ev, sizeof(ev)); } thread->thread.join(); } } -void Server::destroy_reactor_threads() { - sw_shm_free(connection_list); - delete[] reactor_threads; - - if (gs->event_workers.message_box) { - gs->event_workers.message_box->destroy(); - } -} - void Server::start_heartbeat_thread() { heartbeat_thread = std::thread([this]() { swoole_signal_block_all(); - - SwooleTG.type = THREAD_HEARTBEAT; - SwooleTG.id = reactor_num + 1; + swoole_set_thread_type(THREAD_HEARTBEAT); + swoole_set_thread_type(reactor_num + 1); while (running) { double now = microtime(); @@ -1043,7 +995,7 @@ void Server::start_heartbeat_thread() { ev.type = SW_SERVER_EVENT_CLOSE_FORCE; // convert fd to session_id, in order to verify the connection before the force close connection ev.fd = session_id; - get_reactor_pipe_socket(session_id, conn->reactor_id)->send_blocking(&ev, sizeof(ev)); + get_reactor_pipe_socket(session_id, conn->reactor_id)->send_sync(&ev, sizeof(ev)); }); sleep(heartbeat_check_interval); } diff --git a/src/server/static_handler.cc b/src/server/static_handler.cc index 852e2e7f56..0614fcfef1 100644 --- a/src/server/static_handler.cc +++ b/src/server/static_handler.cc @@ -20,11 +20,12 @@ #include #include #include +#include +#include namespace swoole { - namespace http_server { -bool StaticHandler::is_modified(const std::string &date_if_modified_since) { +bool StaticHandler::is_modified(const std::string &date_if_modified_since) const { char date_tmp[64]; if (date_if_modified_since.empty() || date_if_modified_since.length() > sizeof(date_tmp) - 1) { return false; @@ -45,47 +46,87 @@ bool StaticHandler::is_modified(const std::string &date_if_modified_since) { } else if (strptime(date_tmp, SW_HTTP_ASCTIME_DATE, &tm3) != nullptr) { date_format = SW_HTTP_ASCTIME_DATE; } - return date_format && mktime(&tm3) - (int) serv->timezone_ >= get_file_mtime(); + return date_format && mktime(&tm3) - (time_t) serv->timezone_ >= get_file_mtime(); +} + +bool StaticHandler::is_modified_range(const std::string &date_range) const { + if (date_range.empty()) { + return false; + } + + tm tm3{}; + const char *date_format = nullptr; + + if (strptime(date_range.c_str(), SW_HTTP_RFC1123_DATE_GMT, &tm3) != nullptr) { + date_format = SW_HTTP_RFC1123_DATE_GMT; + } else if (strptime(date_range.c_str(), SW_HTTP_RFC1123_DATE_UTC, &tm3) != nullptr) { + date_format = SW_HTTP_RFC1123_DATE_UTC; + } else if (strptime(date_range.c_str(), SW_HTTP_RFC850_DATE, &tm3) != nullptr) { + date_format = SW_HTTP_RFC850_DATE; + } else if (strptime(date_range.c_str(), SW_HTTP_ASCTIME_DATE, &tm3) != nullptr) { + date_format = SW_HTTP_ASCTIME_DATE; + } + time_t file_mtime = get_file_mtime(); + tm *tm_file_mtime = gmtime(&file_mtime); + return date_format && mktime(&tm3) != mktime(tm_file_mtime); } std::string StaticHandler::get_date() { char date_[64]; time_t now = ::time(nullptr); - struct tm *tm1 = gmtime(&now); + tm *tm1 = gmtime(&now); strftime(date_, sizeof(date_), "%a, %d %b %Y %H:%M:%S %Z", tm1); - return std::string(date_); + return date_; } -std::string StaticHandler::get_date_last_modified() { +std::string StaticHandler::get_date_last_modified() const { char date_last_modified[64]; time_t file_mtime = get_file_mtime(); - struct tm *tm2 = gmtime(&file_mtime); + tm *tm2 = gmtime(&file_mtime); strftime(date_last_modified, sizeof(date_last_modified), "%a, %d %b %Y %H:%M:%S %Z", tm2); - return std::string(date_last_modified); + return date_last_modified; +} + +bool StaticHandler::get_absolute_path() { + char abs_path[PATH_MAX]; + if (!realpath(filename, abs_path)) { + return false; + } + + size_t abs_path_len = strlen(abs_path); + if (abs_path_len >= PATH_MAX) { + return false; + } + memcpy(filename, abs_path, abs_path_len + 1); + l_filename = abs_path_len; + return true; } -bool StaticHandler::hit() { - char *p = task.filename; +bool StaticHandler::try_serve() { + serv->apply_rewrite_rules(this); + + char *p = filename; const char *url = request_url.c_str(); size_t url_length = request_url.length(); /** * discard the url parameter * [/test.jpg?version=1#position] -> [/test.jpg] */ - char *params = (char *) memchr(url, '?', url_length); + auto params = (char *) memchr(url, '?', url_length); if (params == nullptr) { params = (char *) memchr(url, '#', url_length); } size_t n = params ? params - url : url_length; const std::string &document_root = serv->get_document_root(); + const size_t l_document_root = document_root.length(); - memcpy(p, document_root.c_str(), document_root.length()); - p += document_root.length(); + memcpy(p, document_root.c_str(), l_document_root); + p += l_document_root; - if (serv->locations->size() > 0) { - for (auto i = serv->locations->begin(); i != serv->locations->end(); i++) { - if (swoole_strcasect(url, url_length, i->c_str(), i->size())) { + if (!serv->locations->empty()) { + for (const auto &i : *serv->locations) { + if (swoole_str_istarts_with(url, url_length, i.c_str(), i.size())) { last = true; } } @@ -94,65 +135,42 @@ bool StaticHandler::hit() { } } - if (document_root.length() + n >= PATH_MAX) { - return false; + if (l_document_root + n >= PATH_MAX) { + return catch_error(); } memcpy(p, url, n); p += n; *p = '\0'; - if (dir_path != "") { + if (!dir_path.empty()) { dir_path.clear(); } dir_path = std::string(url, n); - l_filename = http_server::url_decode(task.filename, p - task.filename); - task.filename[l_filename] = '\0'; - - if (swoole_strnpos(url, n, SW_STRL("..")) == -1) { - goto _detect_mime_type; - } - - char real_path[PATH_MAX]; - if (!realpath(task.filename, real_path)) { - if (last) { - status_code = SW_HTTP_NOT_FOUND; - return true; - } else { - return false; - } - } - - if (real_path[document_root.length()] != '/') { - return false; - } + l_filename = url_decode(filename, p - filename); + filename[l_filename] = '\0'; - if (swoole_streq(real_path, strlen(real_path), document_root.c_str(), document_root.length()) != 0) { - return false; + // The file does not exist + if (lstat(filename, &file_stat) < 0) { + return catch_error(); } -// non-static file -_detect_mime_type: -// file does not exist -check_stat: - if (lstat(task.filename, &file_stat) < 0) { - if (last) { - status_code = SW_HTTP_NOT_FOUND; - return true; - } else { - return false; + // The filename is relative path, allows for the resolution of symbolic links. + // This path is formed by concatenating the document root and that is permitted for access. + if (is_absolute_path()) { + if (is_link()) { + // Use the realpath function to resolve a symbolic link to its actual path. + if (!get_absolute_path()) { + return catch_error(); + } + if (lstat(filename, &file_stat) < 0) { + return catch_error(); + } } - } - - if (S_ISLNK(file_stat.st_mode)) { - char buf[PATH_MAX]; - ssize_t byte = ::readlink(task.filename, buf, sizeof(buf) - 1); - if (byte <= 0) { - return false; + } else { + if (!get_absolute_path() || !is_located_in_document_root()) { + return catch_error(); } - buf[byte] = 0; - swoole_strlcpy(task.filename, buf, sizeof(task.filename)); - goto check_stat; } if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) { @@ -163,19 +181,18 @@ bool StaticHandler::hit() { return true; } - if (!swoole::mime_type::exists(task.filename) && !last) { + if (!mime_type::exists(filename) && !last) { return false; } - if (!S_ISREG(file_stat.st_mode)) { + if (!is_file()) { return false; } - task.length = get_filesize(); return true; } -bool StaticHandler::hit_index_file() { +bool StaticHandler::try_serve_index_file() { if (serv->http_index_files && !serv->http_index_files->empty() && is_dir()) { if (!get_dir_files()) { return false; @@ -209,15 +226,15 @@ size_t StaticHandler::make_index_page(String *buffer) { dir_path.c_str(), dir_path.c_str()); - for (auto iter = dir_files.begin(); iter != dir_files.end(); iter++) { - if (*iter == "." || (dir_path == "/" && *iter == "..")) { + for (const auto &dir_file : dir_files) { + if (dir_file == "." || (dir_path == "/" && dir_file == "..")) { continue; } buffer->format_impl(String::FORMAT_APPEND | String::FORMAT_GROW, "\t\t
  • %s
  • \n", dir_path.c_str(), - (*iter).c_str(), - (*iter).c_str()); + dir_file.c_str(), + dir_file.c_str()); } buffer->append(SW_STRL("\t\n" SW_HTTP_POWER_BY "\n\n")); @@ -234,7 +251,7 @@ bool StaticHandler::get_dir_files() { return false; } - DIR *dir = opendir(task.filename); + DIR *dir = opendir(filename); if (dir == nullptr) { return false; } @@ -249,31 +266,203 @@ bool StaticHandler::get_dir_files() { return true; } -bool StaticHandler::set_filename(std::string &filename) { - char *p = task.filename + l_filename; +bool StaticHandler::set_filename(const std::string &_filename) { + char *p = filename + l_filename; if (*p != '/') { *p = '/'; p += 1; } - memcpy(p, filename.c_str(), filename.length()); - p += filename.length(); + memcpy(p, _filename.c_str(), _filename.length()); + p += _filename.length(); *p = 0; - if (lstat(task.filename, &file_stat) < 0) { + if (lstat(filename, &file_stat) < 0) { return false; } - if (!S_ISREG(file_stat.st_mode)) { + if (!is_file()) { return false; } - task.length = get_filesize(); - return true; } + +void StaticHandler::parse_range(const char *range, const char *if_range) { + task_t _task{}; + _task.length = 0; + // range + if (range && '\0' != *range) { + const char *p = range; + // bytes= + if (!SW_STR_ISTARTS_WITH(p, strlen(range), "bytes=")) { + _task.offset = 0; + _task.length = content_length = get_filesize(); + tasks.push_back(_task); + return; + } + p += 6; + size_t start, end, size = 0, cutoff = SIZE_MAX / 10, cutlim = SIZE_MAX % 10, suffix, + _content_length = get_filesize(); + content_length = 0; + for (;;) { + start = 0; + end = 0; + suffix = 0; + + while (*p == ' ') { + p++; + } + + if (*p != '-') { + if (*p < '0' || *p > '9') { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + while (*p >= '0' && *p <= '9') { + if (start >= cutoff && (start > cutoff || (size_t) (*p - '0') > cutlim)) { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + start = start * 10 + (*p++ - '0'); + } + + while (*p == ' ') { + p++; + } + + if (*p++ != '-') { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + while (*p == ' ') { + p++; + } + + if (*p == ',' || *p == '\0') { + end = _content_length; + goto found; + } + + } else { + suffix = 1; + p++; + } + + if (*p < '0' || *p > '9') { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + while (*p >= '0' && *p <= '9') { + if (end >= cutoff && (end > cutoff || (size_t) (*p - '0') > cutlim)) { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + end = end * 10 + (*p++ - '0'); + } + + while (*p == ' ') { + p++; + } + + if (*p != ',' && *p != '\0' && *p != '\r') { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + + if (suffix) { + start = (end < _content_length) ? _content_length - end : 0; + end = _content_length - 1; + } + + if (end >= _content_length) { + end = _content_length; + + } else { + end++; + } + + found: + if (start < end) { + if (size > SIZE_MAX - (end - start)) { + status_code = SW_HTTP_RANGE_NOT_SATISFIABLE; + return; + } + size += end - start; + _task.offset = start; + _task.length = end - start; + content_length += sw_snprintf(_task.part_header, + sizeof(_task.part_header), + "%s--%s\r\n" + "Content-Type: %s\r\n" + "Content-Range: bytes %zu-%zu/%zu\r\n\r\n", + tasks.empty() ? "" : "\r\n", + get_boundary().c_str(), + get_mimetype().c_str(), + (size_t) _task.offset, + end - 1, + get_filesize()) + + _task.length; + tasks.push_back(_task); + } else if (start == 0) { + break; + } + + if (*p++ != ',' || '\r' == *p || '\0' == *p) { + break; + } + } + } + if (_task.length > 0) { + if (1 == tasks.size()) { + content_length = _task.length; + } else { + end_part = std::string("\r\n--") + get_boundary() + "--\r\n"; + content_length += end_part.size(); + } + status_code = SW_HTTP_PARTIAL_CONTENT; + } else { + _task.offset = 0; + _task.length = content_length = get_filesize(); + tasks.push_back(_task); + } + // if-range + if (if_range) { + if (is_modified_range(if_range)) { + tasks.clear(); + _task.offset = 0; + _task.length = content_length = get_filesize(); + tasks.push_back(_task); + status_code = SW_HTTP_OK; + } + } +} } // namespace http_server + +void Server::add_rewrite_rule(const std::string &pattern, const std::string &replacement) { + if (rewrite_rules == nullptr) { + rewrite_rules = std::make_shared>(); + } + + http_server::RewriteRule rule; + rule.replacement = replacement; + + if (pattern.length() >= 2 && pattern.at(0) == '~' && pattern.at(pattern.length() - 1) == '~') { + rule.pattern = pattern.substr(1, pattern.length() - 2); + rule.is_regex = true; + } else { + rule.pattern = pattern; + rule.is_regex = false; + } + rewrite_rules->emplace_back(rule); +} + void Server::add_static_handler_location(const std::string &location) { if (locations == nullptr) { locations = std::make_shared>(); @@ -291,4 +480,222 @@ void Server::add_static_handler_index_files(const std::string &file) { http_index_files->emplace_back(file); } } + +bool Server::select_static_handler(const http_server::Request *request, const Connection *conn) { + const char *url = request->buffer_->str + request->url_offset_; + size_t url_length = request->url_length_; + + http_server::StaticHandler handler(this, url, url_length); + if (!handler.try_serve()) { + 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(); + + std::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.try_serve_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", + static_cast(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 (SW_HTTP_PARTIAL_CONTENT == handler.status_code) { + header_stream << "Content-Range: bytes " << tasks[0].offset << "-" + << (tasks[0].length + tasks[0].offset - 1) << "/" << handler.get_filesize() << "\r\n"; + } else { + header_stream << "Accept-Ranges: bytes\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().c_str() : handler.get_content_type().c_str(), + 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; + auto task = static_cast(sw_malloc(task_size)); + strcpy(task->filename, handler.get_filename()); + if (tasks.size() > 1) { + for (const auto &i : tasks) { + 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 = reinterpret_cast(task); + send_to_connection(&response); + } + + response.info.type = SW_SERVER_EVENT_SEND_DATA; + response.info.len = handler.get_end_part().length(); + response.data = handler.get_end_part().c_str(); + 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 = reinterpret_cast(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; +} + +bool Server::apply_rewrite_rules(http_server::StaticHandler *handler) { + if (!rewrite_rules || rewrite_rules->empty()) { + return false; + } + + bool rewritten = false; + auto current_url = handler->get_request_url(); + + for (const auto &rule : *rewrite_rules) { + if (rule.is_regex) { + try { + std::regex pattern(rule.pattern); + std::string rewritten_url; + + if (std::regex_search(current_url, pattern)) { + rewritten_url = std::regex_replace(current_url, pattern, rule.replacement); + if (rewritten_url != current_url) { + handler->set_request_url(rewritten_url); + rewritten = true; + break; + } + } + } catch (const std::regex_error &e) { + continue; + } + } else { + if (starts_with(current_url, rule.pattern)) { + std::string rewritten_url = rule.replacement + current_url.substr(rule.pattern.length()); + handler->set_request_url(rewritten_url); + rewritten = true; + break; + } + } + } + + return rewritten; +} } // namespace swoole diff --git a/src/server/task_worker.cc b/src/server/task_worker.cc index ea46f92f8d..324abb3be9 100644 --- a/src/server/task_worker.cc +++ b/src/server/task_worker.cc @@ -22,15 +22,17 @@ using network::Socket; static void TaskWorker_signal_init(ProcessPool *pool); static int TaskWorker_onPipeReceive(Reactor *reactor, Event *event); static int TaskWorker_loop_async(ProcessPool *pool, Worker *worker); -static void TaskWorker_onStart(ProcessPool *pool, int worker_id); -static void TaskWorker_onStop(ProcessPool *pool, int worker_id); -static int TaskWorker_onTask(ProcessPool *pool, EventData *task); +static void TaskWorker_onStart(ProcessPool *pool, Worker *worker); +static void TaskWorker_onStop(ProcessPool *pool, Worker *worker); +static int TaskWorker_onTask(ProcessPool *pool, Worker *worker, EventData *task); + +static SW_THREAD_LOCAL EventData *latest_task = nullptr; /** * after pool->create, before pool->start */ -void Server::init_task_workers() { - ProcessPool *pool = &gs->task_workers; +bool Server::init_task_workers() { + ProcessPool *pool = get_task_worker_pool(); pool->ptr = this; pool->onTask = TaskWorker_onTask; pool->onWorkerStart = TaskWorker_onStart; @@ -40,20 +42,25 @@ void Server::init_task_workers() { */ if (task_enable_coroutine) { if (task_ipc_mode == TASK_IPC_MSGQUEUE || task_ipc_mode == TASK_IPC_PREEMPTIVE) { - swoole_error("cannot use msgqueue when task_enable_coroutine is enable"); - return; + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "cannot use msgqueue when task_enable_coroutine is enable"); + return false; } pool->main_loop = TaskWorker_loop_async; } if (task_ipc_mode == TASK_IPC_PREEMPTIVE) { pool->schedule_by_sysvmsg = true; } + SW_LOOP_N(task_worker_num) { + create_worker(&pool->workers[i]); + } + return true; } -static int TaskWorker_call_command_handler(ProcessPool *pool, EventData *req) { - Server *serv = (Server *) pool->ptr; - int command_id = req->info.server_fd; - auto iter = serv->command_handlers.find(command_id); +static int TaskWorker_call_command_handler(const ProcessPool *pool, const Worker *worker, EventData *req) { + auto *serv = static_cast(pool->ptr); + int command_id = serv->get_command_id(req); + const auto iter = serv->command_handlers.find(command_id); if (iter == serv->command_handlers.end()) { swoole_error_log(SW_LOG_ERROR, SW_ERROR_SERVER_INVALID_COMMAND, "Unknown command[%d]", command_id); return SW_OK; @@ -68,8 +75,8 @@ static int TaskWorker_call_command_handler(ProcessPool *pool, EventData *req) { auto result = handler(serv, std::string(packet.data, packet.length)); SendData task{}; - task.info.fd = req->info.fd; - task.info.reactor_id = SwooleWG.worker->id; + task.info.fd = serv->get_task_id(req); + task.info.reactor_id = worker->id; task.info.server_fd = -1; task.info.type = SW_SERVER_EVENT_COMMAND_RESPONSE; task.info.len = result.length(); @@ -78,27 +85,52 @@ static int TaskWorker_call_command_handler(ProcessPool *pool, EventData *req) { return serv->message_bus.write(serv->get_command_reply_socket(), &task) ? SW_OK : SW_ERR; } -static int TaskWorker_onTask(ProcessPool *pool, EventData *task) { +static int TaskWorker_onTask(ProcessPool *pool, Worker *worker, EventData *task) { int ret = SW_OK; - Server *serv = (Server *) pool->ptr; - serv->last_task = task; + auto *serv = static_cast(pool->ptr); + latest_task = task; + worker->set_status_to_busy(); if (task->info.type == SW_SERVER_EVENT_PIPE_MESSAGE) { serv->onPipeMessage(serv, task); + } else if (task->info.type == SW_SERVER_EVENT_SHUTDOWN) { + worker->shutdown(); + if (swoole_event_is_available()) { + serv->stop_async_worker(worker); + } + return SW_OK; } else if (task->info.type == SW_SERVER_EVENT_COMMAND_REQUEST) { - ret = TaskWorker_call_command_handler(pool, task); + ret = TaskWorker_call_command_handler(pool, worker, task); } else { ret = serv->onTask(serv, task); + /** + * only server task as requests, + * do not increase the count for pipeline communication and command processing. + */ + worker->add_request_count(); } + worker->set_status_to_idle(); return ret; } +void Server::task_dump(EventData *task) { + char buf[1024]; + task->info.dump(buf, sizeof(buf)); + sw_printf("%s", buf); + + if (task->info.ext_flags & SW_TASK_TMPFILE) { + auto pkg = reinterpret_cast(task->data); + sw_printf("Task[tmpfile]=%.*s\n", (int) pkg->length, pkg->tmpfile); + } +} + bool Server::task_pack(EventData *task, const void *_data, size_t _length) { + task->info = {}; task->info.type = SW_SERVER_EVENT_TASK; task->info.fd = SwooleG.current_task_id++; - task->info.reactor_id = SwooleG.process_id; - task->info.time = swoole::microtime(); + task->info.reactor_id = swoole_get_worker_id(); + task->info.time = microtime(); if (_length < SW_IPC_MAX_SIZE - sizeof(task->info)) { memcpy(task->data, _data, _length); @@ -126,6 +158,155 @@ bool Server::task_pack(EventData *task, const void *_data, size_t _length) { return true; } +bool Server::task(EventData *_task, int *dst_worker_id, bool blocking) { + sw_atomic_fetch_add(&gs->tasking_num, 1); + + swResultCode retval; + if (blocking) { + retval = get_task_worker_pool()->dispatch_sync(_task, dst_worker_id); + } else { + retval = get_task_worker_pool()->dispatch(_task, dst_worker_id); + } + + if (retval == SW_OK) { + sw_atomic_fetch_add(&gs->task_count, 1); + return true; + } + + sw_atomic_fetch_sub(&gs->tasking_num, 1); + return false; +} + +bool Server::task_sync(EventData *_task, int *dst_worker_id, double timeout) { + uint64_t notify; + EventData *task_result = get_task_result(); + sw_memset_zero(task_result, sizeof(*task_result)); + Pipe *pipe = task_notify_pipes.at(swoole_get_worker_id()).get(); + TaskId task_id = get_task_id(_task); + + pipe->clean(); + pipe->set_timeout(timeout); + + if (!task(_task, dst_worker_id, true)) { + return false; + } + + SW_LOOP { + if (pipe->read(¬ify, sizeof(notify)) > 0) { + if (get_task_id(task_result) != task_id) { + continue; + } + return true; + } + break; + } + + return false; +} + +int Server::MultiTask::find(TaskId task_id) { + auto iter = map.find(task_id); + if (iter != map.end()) { + return iter->second; + } else { + return -1; + } +} + +bool Server::task_sync(MultiTask &mtask, double timeout) { + WorkerId worker_id = swoole_get_worker_id(); + uint64_t notify; + EventData *task_result = get_task_result(); + task_result->info = {}; + Pipe *pipe = task_notify_pipes.at(worker_id).get(); + Worker *worker = get_worker(worker_id); + int dst_worker_id; + + File fp = make_tmpfile(); + if (!fp.ready()) { + swoole_set_last_error(errno); + return false; + } + + std::string file_path = fp.get_path(); + fp.close(); + + auto finish_count = reinterpret_cast(task_result->data); + + worker->lock->lock(); + *finish_count = 0; + + swoole_strlcpy(task_result->data + 4, file_path.c_str(), SW_TASK_TMP_PATH_SIZE); + worker->lock->unlock(); + + // clear history task + pipe->clean(); + + auto n_task = mtask.count; + SW_LOOP_N(mtask.count) { + EventData buf; + TaskId task_id = mtask.pack(i, &buf); + if (task_id < 0) { + swoole_warning("task pack failed"); + goto _fail; + } + buf.info.ext_flags |= SW_TASK_WAITALL; + sw_atomic_fetch_add(&gs->tasking_num, 1); + dst_worker_id = -1; + if (!task(&buf, &dst_worker_id, true)) { + swoole_warning("failed to dispatch task"); + task_id = -1; + _fail: + mtask.fail(i); + n_task--; + } else { + sw_atomic_fetch_sub(&gs->tasking_num, 1); + } + mtask.map[task_id] = i; + } + + if (n_task == 0) { + swoole_set_last_error(SW_ERROR_TASK_DISPATCH_FAIL); + return false; + } + + if (timeout > 0) { + pipe->set_timeout(timeout); + } + + double stated_at = microtime(); + while (*finish_count < n_task) { + const int ret = pipe->read(¬ify, sizeof(notify)); + if (ret <= 0) { + break; + } + if (timeout > 0 && microtime() - stated_at > timeout) { + break; + } + } + + worker->lock->lock(); + auto content = file_get_contents(file_path); + worker->lock->unlock(); + + if (!content) { + return false; + } + + do { + auto *result = reinterpret_cast(content->str + content->offset); + int index = mtask.find(get_task_id(result)); + if (index != -1) { + mtask.unpack(index, result); + } + content->offset += result->size(); + } while (content->offset < 0 || (size_t) content->offset < content->length); + // delete tmp file + unlink(file_path.c_str()); + + return true; +} + bool Server::task_unpack(EventData *task, String *buffer, PacketPtr *packet) { if (!(task->info.ext_flags & SW_TASK_TMPFILE)) { packet->data = task->data; @@ -141,8 +322,8 @@ bool Server::task_unpack(EventData *task, String *buffer, PacketPtr *packet) { swoole_sys_warning("open(%s) failed", _pkg.tmpfile); return false; } - if (buffer->size < _pkg.length && !buffer->extend(_pkg.length)) { - return false; + if (buffer->size < _pkg.length) { + buffer->extend(_pkg.length); } if (fp.read_all(buffer->str, _pkg.length) != _pkg.length) { return false; @@ -157,20 +338,26 @@ bool Server::task_unpack(EventData *task, String *buffer, PacketPtr *packet) { } static void TaskWorker_signal_init(ProcessPool *pool) { + auto *serv = static_cast(pool->ptr); + if (serv->is_thread_mode()) { + return; + } swoole_signal_set(SIGHUP, nullptr); swoole_signal_set(SIGPIPE, nullptr); - swoole_signal_set(SIGUSR1, Server::worker_signal_handler); + swoole_signal_set(SIGUSR1, nullptr); swoole_signal_set(SIGUSR2, nullptr); swoole_signal_set(SIGTERM, Server::worker_signal_handler); + swoole_signal_set(SIGWINCH, Server::worker_signal_handler); #ifdef SIGRTMIN swoole_signal_set(SIGRTMIN, Server::worker_signal_handler); #endif } -static void TaskWorker_onStart(ProcessPool *pool, int worker_id) { - Server *serv = (Server *) pool->ptr; - SwooleG.process_id = worker_id; +static void TaskWorker_onStart(ProcessPool *pool, Worker *worker) { + auto serv = static_cast(pool->ptr); + swoole_set_worker_id(worker->id); + swoole_set_worker_type(SW_TASK_WORKER); /** * Make the task worker support asynchronous */ @@ -186,28 +373,16 @@ static void TaskWorker_onStart(ProcessPool *pool, int worker_id) { } TaskWorker_signal_init(pool); - serv->worker_start_callback(); + serv->worker_start_callback(worker); - Worker *worker = pool->get_worker(worker_id); - worker->start_time = ::time(nullptr); - worker->request_count = 0; - SwooleWG.worker = worker; - SwooleWG.worker->status = SW_WORKER_IDLE; - /** - * task_max_request - */ - if (pool->max_request > 0) { - SwooleWG.run_always = false; - SwooleWG.max_request = pool->get_max_request(); - } else { - SwooleWG.run_always = true; - } + worker->init(); + worker->set_max_request(pool->max_request, pool->max_request_grace); } -static void TaskWorker_onStop(ProcessPool *pool, int worker_id) { +static void TaskWorker_onStop(ProcessPool *pool, Worker *worker) { swoole_event_free(); - Server *serv = (Server *) pool->ptr; - serv->worker_stop_callback(); + auto *serv = static_cast(pool->ptr); + serv->worker_stop_callback(worker); } /** @@ -215,17 +390,14 @@ static void TaskWorker_onStop(ProcessPool *pool, int worker_id) { */ static int TaskWorker_onPipeReceive(Reactor *reactor, Event *event) { EventData task; - ProcessPool *pool = (ProcessPool *) reactor->ptr; - Worker *worker = SwooleWG.worker; - Server *serv = (Server *) pool->ptr; + auto *pool = static_cast(reactor->ptr); + Worker *worker = sw_worker(); + auto *serv = static_cast(pool->ptr); if (event->socket->read(&task, sizeof(task)) > 0) { - worker->status = SW_WORKER_BUSY; - int retval = TaskWorker_onTask(pool, &task); - worker->status = SW_WORKER_IDLE; - worker->request_count++; + int retval = pool->onTask(pool, worker, &task); // maximum number of requests, process will exit. - if (!SwooleWG.run_always && worker->request_count >= SwooleWG.max_request) { + if (worker->has_exceeded_max_request()) { serv->stop_async_worker(worker); } return retval; @@ -239,19 +411,19 @@ static int TaskWorker_onPipeReceive(Reactor *reactor, Event *event) { * async task worker */ static int TaskWorker_loop_async(ProcessPool *pool, Worker *worker) { - Server *serv = (Server *) pool->ptr; - Socket *socket = worker->pipe_worker; - worker->status = SW_WORKER_IDLE; + auto *serv = static_cast(pool->ptr); + Socket *socket = serv->get_worker_pipe_worker_in_message_bus(worker); + worker->set_status_to_idle(); socket->set_nonblock(); sw_reactor()->ptr = pool; swoole_event_add(socket, SW_EVENT_READ); - swoole_event_set_handler(SW_FD_PIPE, TaskWorker_onPipeReceive); + swoole_event_set_handler(SW_FD_PIPE, SW_EVENT_READ, TaskWorker_onPipeReceive); for (uint i = 0; i < serv->worker_num + serv->task_worker_num; i++) { worker = serv->get_worker(i); - worker->pipe_master->buffer_size = UINT_MAX; - worker->pipe_worker->buffer_size = UINT_MAX; + serv->get_worker_pipe_worker_in_message_bus(worker)->buffer_size = UINT_MAX; + serv->get_worker_pipe_master_in_message_bus(worker)->buffer_size = UINT_MAX; } return swoole_event_wait(); @@ -260,23 +432,21 @@ static int TaskWorker_loop_async(ProcessPool *pool, Worker *worker) { /** * Send the task result to worker */ -int Server::reply_task_result(const char *data, size_t data_len, int flags, EventData *current_task) { - EventData buf; - sw_memset_zero(&buf.info, sizeof(buf.info)); +bool Server::finish(const char *data, size_t data_len, int flags, const EventData *current_task) { if (task_worker_num < 1) { swoole_warning("cannot use Server::task()/Server::finish() method, because no set [task_worker_num]"); - return SW_ERR; + return false; } if (current_task == nullptr) { - current_task = last_task; + current_task = latest_task; } if (current_task->info.type == SW_SERVER_EVENT_PIPE_MESSAGE) { swoole_warning("Server::task()/Server::finish() is not supported in onPipeMessage callback"); - return SW_ERR; + return false; } if (current_task->info.ext_flags & SW_TASK_NOREPLY) { swoole_warning("Server::finish() can only be used in the worker process"); - return SW_ERR; + return false; } uint16_t source_worker_id = current_task->info.reactor_id; @@ -284,16 +454,17 @@ int Server::reply_task_result(const char *data, size_t data_len, int flags, Even if (worker == nullptr) { swoole_warning("invalid worker_id[%d]", source_worker_id); - return SW_ERR; + return false; } - int ret; + ssize_t retval; // for swoole_server_task if (current_task->info.ext_flags & SW_TASK_NONBLOCK) { // write to file + EventData buf; if (!task_pack(&buf, data, data_len)) { swoole_warning("large task pack failed()"); - return SW_ERR; + return false; } // callback function if (current_task->info.ext_flags & SW_TASK_CALLBACK) { @@ -303,16 +474,16 @@ int Server::reply_task_result(const char *data, size_t data_len, int flags, Even } buf.info.ext_flags |= flags; buf.info.type = SW_SERVER_EVENT_FINISH; - buf.info.fd = current_task->info.fd; + buf.info.fd = get_task_id(current_task); if (worker->pool->use_socket && worker->pool->stream_info_->last_connection) { uint32_t _len = htonl(data_len); - ret = worker->pool->stream_info_->last_connection->send_blocking((void *) &_len, sizeof(_len)); - if (ret > 0) { - ret = worker->pool->stream_info_->last_connection->send_blocking(data, data_len); + retval = worker->pool->stream_info_->last_connection->send_sync((void *) &_len, sizeof(_len)); + if (retval > 0) { + retval = worker->pool->stream_info_->last_connection->send_sync(data, data_len); } } else { - ret = send_to_worker_from_worker(worker, &buf, sizeof(buf.info) + buf.info.len, SW_PIPE_MASTER); + retval = send_to_worker_from_worker(worker, &buf, buf.size(), SW_PIPE_MASTER); } } else { uint64_t flag = 1; @@ -320,25 +491,28 @@ int Server::reply_task_result(const char *data, size_t data_len, int flags, Even /** * Use worker shm store the result */ - EventData *result = &(task_result[source_worker_id]); + EventData *result = &(task_results[source_worker_id]); Pipe *pipe = task_notify_pipes.at(source_worker_id).get(); // lock worker worker->lock->lock(); if (current_task->info.ext_flags & SW_TASK_WAITALL) { - sw_atomic_t *finish_count = (sw_atomic_t *) result->data; + auto *finish_count = reinterpret_cast(result->data); char *_tmpfile = result->data + 4; File file(_tmpfile, O_APPEND | O_WRONLY); if (file.ready()) { + EventData buf; if (!task_pack(&buf, data, data_len)) { swoole_warning("large task pack failed()"); buf.info.len = 0; } buf.info.ext_flags |= flags; buf.info.type = SW_SERVER_EVENT_FINISH; - buf.info.fd = current_task->info.fd; - size_t bytes = sizeof(buf.info) + buf.info.len; + buf.info.fd = get_task_id(current_task); + size_t bytes = buf.size(); + swoole_trace_log(SW_TRACE_SERVER, "write %zu bytes to tmp file '%s'", bytes, _tmpfile); + if (file.write_all(&buf, bytes) != bytes) { swoole_sys_warning("write(%s, %ld) failed", _tmpfile, bytes); } @@ -349,20 +523,20 @@ int Server::reply_task_result(const char *data, size_t data_len, int flags, Even // unlock worker worker->lock->unlock(); swoole_warning("large task pack failed()"); - return SW_ERR; + return false; } result->info.ext_flags |= flags; result->info.type = SW_SERVER_EVENT_FINISH; - result->info.fd = current_task->info.fd; + result->info.fd = get_task_id(current_task); } // unlock worker worker->lock->unlock(); - while (1) { - ret = pipe->write(&flag, sizeof(flag)); + while (true) { + retval = pipe->write(&flag, sizeof(flag)); auto _sock = pipe->get_socket(true); - if (ret < 0 && _sock->catch_write_error(errno) == SW_WAIT) { + if (retval < 0 && _sock->catch_write_error(errno) == SW_WAIT) { if (_sock->wait_event(-1, SW_EVENT_WRITE) == 0) { continue; } @@ -370,13 +544,14 @@ int Server::reply_task_result(const char *data, size_t data_len, int flags, Even break; } } - if (ret < 0) { - if (swoole_get_last_error() == EAGAIN || swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT) { + if (retval < 0) { + if (errno == EAGAIN || errno == ETIMEDOUT || swoole_get_last_error() == SW_ERROR_SOCKET_POLL_TIMEOUT) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_SEND_TO_WOKER_TIMEOUT, "send result to worker timed out"); } else { swoole_sys_warning("send result to worker failed"); } + return false; } - return ret; + return true; } } // namespace swoole diff --git a/src/server/thread.cc b/src/server/thread.cc new file mode 100644 index 0000000000..dce266c8ee --- /dev/null +++ b/src/server/thread.cc @@ -0,0 +1,436 @@ +/* + +----------------------------------------------------------------------+ + | 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 "swoole_server.h" +#include "swoole_thread.h" + +namespace swoole { +using network::Socket; + +enum ManagerCommand { + CMD_RELOAD = 0x1001, + CMD_MANAGER_EXIT = 0x1002, +}; + +static inline Worker *cmd_ptr(const ManagerCommand cmd) { + return reinterpret_cast(cmd); +} + +Factory *Server::create_thread_factory() { +#ifndef SW_THREAD + swoole_error_log(SW_LOG_ERROR, + SW_ERROR_OPERATION_NOT_SUPPORT, + "Thread support is not enabled, cannot create server with MODE_THREAD"); + return nullptr; +#endif + reactor_num = worker_num; + connection_list = static_cast(sw_calloc(max_connection, sizeof(Connection))); + if (connection_list == nullptr) { + swoole_sys_warning("calloc[2](%d) failed", static_cast(max_connection * sizeof(Connection))); + return nullptr; + } + reactor_threads = new ReactorThread[reactor_num](); + reactor_pipe_num = 1; + return new ThreadFactory(this); +} + +void Server::destroy_thread_factory() const { + sw_free(connection_list); + delete[] reactor_threads; +} + +ThreadFactory::ThreadFactory(Server *server) : BaseFactory(server) { + threads_.resize(server_->get_all_worker_num() + 1); + SW_LOOP_N(server_->get_all_worker_num() + 1) { + threads_[i] = std::make_shared(); + } +} + +ThreadFactory::~ThreadFactory() { + ThreadFactory::shutdown(); +} + +bool ThreadFactory::start() { + if (!server_->create_worker_pipes()) { + return false; + } + if (!server_->create_event_workers()) { + return false; + } + if (server_->task_worker_num > 0 && server_->get_task_worker_pool()->start_check() < 0) { + return false; + } + if (server_->get_user_worker_num() > 0 && !server_->create_user_workers()) { + return false; + } + return true; +} + +bool ThreadFactory::shutdown() { + for (const auto &thread : threads_) { + if (thread->joinable()) { + thread->join(); + } + } + return true; +} + +void ThreadFactory::at_thread_enter(WorkerId id, int worker_type) { + swoole_thread_init(false); + + swoole_set_worker_type(worker_type); + swoole_set_worker_id(id); + swoole_set_worker_pid(swoole_thread_get_native_id()); + + swoole_set_thread_id(id); + swoole_set_thread_type(Server::THREAD_WORKER); + + swoole_trace_log(SW_TRACE_THREAD, "at_thread_enter=%d", id); +} + +void ThreadFactory::push_to_wait_queue(Worker *worker) { + lock_.lock(); + queue_.push(worker); + lock_.unlock(); + cv_.notify_one(); + + swoole_trace_log(SW_TRACE_THREAD, "push [%p] to wait queue", worker); +} + +void ThreadFactory::at_thread_exit(Worker *worker) { + if (worker) { + push_to_wait_queue(worker); + } + + swoole_trace_log(SW_TRACE_THREAD, "at_thread_exit=%d", worker->id); + swoole_thread_clean(false); +} + +void ThreadFactory::create_message_bus() const { + auto mb = new MessageBus(); + mb->set_id_generator(server_->msg_id_generator); + mb->set_buffer_size(server_->ipc_max_size); + mb->set_always_chunked_transfer(); + if (!mb->alloc_buffer()) { + throw std::bad_alloc(); + } + server_->init_pipe_sockets(mb); + SwooleTG.message_bus = mb; +} + +void ThreadFactory::destroy_message_bus() { + SwooleTG.message_bus->clear(); + delete SwooleTG.message_bus; + SwooleTG.message_bus = nullptr; +} + +void ThreadFactory::spawn_event_worker(WorkerId i) { + threads_[i]->start([=]() { + at_thread_enter(i, SW_EVENT_WORKER); + + Worker *worker = server_->get_worker(i); + worker->type = SW_EVENT_WORKER; + worker->pid = swoole_get_worker_pid(); + SwooleWG.worker = worker; + server_->worker_thread_start(threads_[i], [=]() { Server::reactor_thread_main_loop(server_, i); }); + + at_thread_exit(worker); + }); +} + +void ThreadFactory::spawn_task_worker(WorkerId i) { + threads_[i]->start([=]() { + at_thread_enter(i, SW_TASK_WORKER); + + create_message_bus(); + Worker *worker = server_->get_worker(i); + worker->type = SW_TASK_WORKER; + worker->pid = swoole_get_worker_pid(); + worker->set_status_to_idle(); + SwooleWG.worker = worker; + const auto pool = server_->get_task_worker_pool(); + server_->worker_thread_start(threads_[i], [=]() { + if (pool->onWorkerStart != nullptr) { + pool->onWorkerStart(pool, worker); + } + pool->main_loop(pool, worker); + if (pool->onWorkerStop != nullptr) { + pool->onWorkerStop(pool, worker); + } + }); + destroy_message_bus(); + + at_thread_exit(worker); + }); +} + +void ThreadFactory::spawn_user_worker(WorkerId i) { + threads_[i]->start([=]() { + at_thread_enter(i, SW_USER_WORKER); + + create_message_bus(); + Worker *worker = server_->get_worker(i); + worker->type = SW_USER_WORKER; + worker->pid = swoole_get_worker_pid(); + SwooleWG.worker = worker; + server_->worker_thread_start(threads_[i], [=]() { server_->onUserWorkerStart(server_, worker); }); + destroy_message_bus(); + + at_thread_exit(worker); + }); +} + +void ThreadFactory::spawn_manager_thread(WorkerId i) { + threads_[i]->start([=]() { + at_thread_enter(i, SW_MANAGER); + + swoole_timer_create(true); + + server_->worker_thread_start(threads_[i], [=]() { + if (server_->onManagerStart) { + server_->onManagerStart(server_); + } + wait(); + if (server_->onManagerStop) { + server_->onManagerStop(server_); + } + }); + + if (server_->running) { + swoole_warning("Fatal Error: manager thread exits abnormally"); + } + + /* + * In the function that closes the timer, the scheduler is called again; + * therefore, it is essential to set the scheduler to null after the timer has been consumed. + */ + if (swoole_timer_is_available()) { + swoole_timer_free(); + } + + at_thread_exit(nullptr); + }); +} + +void ThreadFactory::wait() { + while (true) { + std::unique_lock lock(lock_); + int64_t cv_timeout_ms_ = swoole_timer_get_next_msec(); + if (cv_timeout_ms_ > 0) { + cv_.wait_for(lock, std::chrono::milliseconds(cv_timeout_ms_), [this] { return !queue_.empty(); }); + } else { + cv_.wait(lock, [this] { return !queue_.empty(); }); + } + + swoole_trace_log(SW_TRACE_THREAD, "manager thread is waiting for worker exit, queue size: %zu", queue_.size()); + + if (!queue_.empty()) { + Worker *exited_worker = queue_.front(); + queue_.pop(); + lock.unlock(); + + if (exited_worker == cmd_ptr(CMD_RELOAD)) { + goto _do_reload; + } + if (exited_worker == cmd_ptr(CMD_MANAGER_EXIT)) { + break; + } + + swoole_trace_log(SW_TRACE_THREAD, + "worker(type=%d, tid=%d, id=%d) exit, status=%d", + exited_worker->type, + exited_worker->pid, + exited_worker->id, + exited_worker->status); + + auto thread = threads_[exited_worker->id]; + int status_code = thread->get_exit_status(); + if (status_code != 0) { + ExitStatus exit_status(exited_worker->pid, status_code << 8); + server_->call_worker_error_callback(exited_worker, exit_status); + swoole_trace_log(SW_TRACE_THREAD, + "worker(tid=%d, id=%d) abnormal exit, status=%d", + exit_status.get_pid(), + exited_worker->id, + exit_status.get_code()); + } + + thread->join(); + swoole_trace_log(SW_TRACE_THREAD, "thread=%d join", exited_worker->id); + + switch (exited_worker->type) { + case SW_EVENT_WORKER: + spawn_event_worker(exited_worker->id); + break; + case SW_TASK_WORKER: + spawn_task_worker(exited_worker->id); + break; + case SW_USER_WORKER: + spawn_user_worker(exited_worker->id); + break; + default: + abort(); + break; + } + } + + if (sw_timer()) { + sw_timer()->select(); + } + if (server_->running && reloading) { + _do_reload: + do_reload(); + } + } +} + +ThreadReloadTask::ThreadReloadTask(Server *_server, bool _reload_all_workers) { + server_ = _server; + worker_num = server_->get_core_worker_num(); + // If only reloading task workers, skip the event workers. + reloaded_num = _reload_all_workers ? 0 : server_->worker_num; +} + +void ThreadFactory::do_reload() { + if (!reload_task) { + reload_task = std::make_shared(server_, reload_all_workers); + if (server_->onBeforeReload) { + server_->onBeforeReload(server_); + } + } + server_->kill_worker(reload_task->reloaded_num++); + if (reload_task->is_completed()) { + reload_task.reset(); + reloading = 0; + if (server_->onAfterReload) { + server_->onAfterReload(server_); + } + } +} + +bool ThreadFactory::reload(bool _reload_all_workers) { + auto _what = _reload_all_workers ? "all" : "task"; + + if (server_->task_worker_num == 0 && !_reload_all_workers) { + swoole_error_log(SW_LOG_WARNING, + SW_ERROR_OPERATION_NOT_SUPPORT, + "Cannot reload %s workers, task workers are not started", + _what); + return false; + } + + // Prevent duplicate submission of reload requests. + if (!sw_atomic_cmp_set(&reloading, 0, 1)) { + swoole_set_last_error(SW_ERROR_OPERATION_NOT_SUPPORT); + return false; + } + + reload_all_workers = _reload_all_workers; + if (!server_->is_manager()) { + swoole_info("Send a notification to the manager process to prepare for restarting %s worker processes.", _what); + push_to_wait_queue(cmd_ptr(CMD_RELOAD)); + } else { + swoole_info("Server is reloading %s workers now", _what); + do_reload(); + } + + return true; +} + +WorkerId ThreadFactory::get_manager_thread_id() const { + return server_->get_all_worker_num(); +} + +WorkerId ThreadFactory::get_master_thread_id() const { + return server_->get_all_worker_num() + 1; +} + +void ThreadFactory::terminate_manager_thread() { + swoole_trace_log(SW_TRACE_THREAD, "notify manager thread to exit"); + push_to_wait_queue(cmd_ptr(CMD_MANAGER_EXIT)); + + /** + * When terminating the service, the management thread may still be joining other worker threads, + * so it is essential to first reclaim the management thread to ensure it has exited. + * During the shutdown, the running flag has already been set to false, + * which means the management thread might not have reclaimed all worker threads and may have exited prematurely. + * At this point, it is necessary to loop through and reclaim the remaining worker threads. + */ + auto manager_thread_id = get_manager_thread_id(); + threads_[manager_thread_id]->join(); + + swoole_trace_log(SW_TRACE_THREAD, "manager thread is exited"); +} + +int Server::start_worker_threads() { + auto *_factory = dynamic_cast(factory_); + + if (task_worker_num > 0) { + SW_LOOP_N(task_worker_num) { + _factory->spawn_task_worker(worker_num + i); + } + } + + SW_LOOP_N(worker_num) { + _factory->spawn_event_worker(i); + } + + if (!user_worker_list.empty()) { + for (size_t i = 0; i < user_worker_list.size(); i++) { + _factory->spawn_user_worker(task_worker_num + worker_num + i); + } + } + + auto manager_thread_id = _factory->get_manager_thread_id(); + _factory->spawn_manager_thread(manager_thread_id); + + if (swoole_event_init(0) < 0) { + return SW_ERR; + } + + Reactor *reactor = sw_reactor(); + for (const auto port : ports) { + if (port->is_dgram()) { + continue; + } + if (port->listen() < 0) { + swoole_event_free(); + return SW_ERR; + } + reactor->add(port->socket, SW_EVENT_READ); + } + + SwooleTG.id = reactor->id = _factory->get_master_thread_id(); + store_listen_socket(); + + return start_master_thread(reactor); +} + +void Server::stop_worker_threads() { + auto *_factory = dynamic_cast(factory_); + _factory->terminate_manager_thread(); + + SW_LOOP_N(get_core_worker_num()) { + kill_worker(i); + } +} + +bool Server::reload_worker_threads(bool reload_all_workers) const { + auto *_factory = dynamic_cast(factory_); + return _factory->reload(reload_all_workers); +} + +} // namespace swoole diff --git a/src/server/worker.cc b/src/server/worker.cc index f805999a5a..c885907e7d 100644 --- a/src/server/worker.cc +++ b/src/server/worker.cc @@ -14,79 +14,68 @@ +----------------------------------------------------------------------+ */ -#include -#include #include #include #include "swoole_server.h" #include "swoole_memory.h" -#include "swoole_msg_queue.h" -#include "swoole_client.h" #include "swoole_coroutine.h" -swoole::WorkerGlobal SwooleWG = {}; +#include "swoole_api.h" namespace swoole { using namespace network; static int Worker_onPipeReceive(Reactor *reactor, Event *event); -static int Worker_onStreamAccept(Reactor *reactor, Event *event); -static int Worker_onStreamRead(Reactor *reactor, Event *event); -static int Worker_onStreamPackage(const Protocol *proto, Socket *sock, const RecvData *rdata); -static int Worker_onStreamClose(Reactor *reactor, Event *event); static void Worker_reactor_try_to_exit(Reactor *reactor); -void Server::worker_signal_init(void) { +static void Worker_reopen_logger() { + if (sw_logger()) { + sw_logger()->reopen(); + } +} + +void Server::worker_signal_init() const { + if (is_thread_mode()) { + return; + } swoole_signal_set(SIGHUP, nullptr); swoole_signal_set(SIGPIPE, SIG_IGN); swoole_signal_set(SIGUSR1, nullptr); swoole_signal_set(SIGUSR2, nullptr); - // swSignal_set(SIGINT, Server::worker_signal_handler); - swoole_signal_set(SIGTERM, Server::worker_signal_handler); - // for test - swoole_signal_set(SIGVTALRM, Server::worker_signal_handler); + swoole_signal_set(SIGTERM, worker_signal_handler); + swoole_signal_set(SIGWINCH, worker_signal_handler); #ifdef SIGRTMIN - swoole_signal_set(SIGRTMIN, Server::worker_signal_handler); + swoole_signal_set(SIGRTMIN, worker_signal_handler); #endif } void Server::worker_signal_handler(int signo) { - if (!SwooleG.running or !sw_server()) { + if (!SwooleG.running || !sw_server() || !sw_worker() || !sw_server()->is_running()) { return; } switch (signo) { case SIGTERM: - // Event worker if (swoole_event_is_available()) { - sw_server()->stop_async_worker(SwooleWG.worker); - } - // Task worker - else { - SwooleWG.shutdown = true; + sw_server()->stop_async_worker(sw_worker()); + } else { + sw_worker()->shutdown(); } break; - // for test - case SIGVTALRM: - swoole_warning("SIGVTALRM coming"); - break; - case SIGUSR1: - case SIGUSR2: - if (sw_logger()) { - sw_logger()->reopen(); - } + case SIGWINCH: + Worker_reopen_logger(); break; default: #ifdef SIGRTMIN - if (signo == SIGRTMIN && sw_logger()) { - sw_logger()->reopen(); + if (signo == SIGRTMIN) { + Worker_reopen_logger(); } #endif break; } } -static sw_inline bool Worker_discard_data(Server *serv, Connection *conn, DataHead *info) { +static sw_inline bool Worker_discard_data(const Server *serv, const Connection *conn, const DataHead *info) { if (conn == nullptr) { if (serv->disable_notify && !serv->discard_timeout_request) { return false; @@ -108,115 +97,31 @@ static sw_inline bool Worker_discard_data(Server *serv, Connection *conn, DataHe return true; } -static int Worker_onStreamAccept(Reactor *reactor, Event *event) { - Socket *sock = event->socket->accept(); - if (sock == nullptr) { - switch (errno) { - case EINTR: - case EAGAIN: - return SW_OK; - default: - swoole_sys_warning("accept() failed"); - return SW_OK; - } - } - - sock->fd_type = SW_FD_STREAM; - sock->socket_type = SW_SOCK_UNIX_STREAM; - - return reactor->add(sock, SW_EVENT_READ); -} - -static int Worker_onStreamRead(Reactor *reactor, Event *event) { - Socket *conn = event->socket; - Server *serv = (Server *) reactor->ptr; - Protocol *protocol = &serv->stream_protocol; - String *buffer; - - if (!event->socket->recv_buffer) { - if (serv->buffer_pool->empty()) { - buffer = new String(SW_BUFFER_SIZE_STD); - } else { - buffer = serv->buffer_pool->front(); - serv->buffer_pool->pop(); - } - event->socket->recv_buffer = buffer; - } else { - buffer = event->socket->recv_buffer; - } - - if (protocol->recv_with_length_protocol(conn, buffer) < 0) { - Worker_onStreamClose(reactor, event); - } - - return SW_OK; -} - -static int Worker_onStreamClose(Reactor *reactor, Event *event) { - Socket *sock = event->socket; - Server *serv = (Server *) reactor->ptr; - - sock->recv_buffer->clear(); - serv->buffer_pool->push(sock->recv_buffer); - sock->recv_buffer = nullptr; - - reactor->del(sock); - reactor->close(reactor, sock); - - if (serv->last_stream_socket == sock) { - serv->last_stream_socket = nullptr; - } - - return SW_OK; -} - -static int Worker_onStreamPackage(const Protocol *proto, Socket *sock, const RecvData *rdata) { - Server *serv = (Server *) proto->private_data_2; - - SendData task{}; - memcpy(&task.info, rdata->data + proto->package_length_size, sizeof(task.info)); - task.info.len = rdata->info.len - (uint32_t) sizeof(task.info) - proto->package_length_size; - if (task.info.len > 0) { - task.data = (char *) (rdata->data + proto->package_length_size + sizeof(task.info)); - } - - serv->last_stream_socket = sock; - serv->message_bus.pass(&task); - serv->worker_accept_event(&serv->message_bus.get_buffer()->info); - serv->last_stream_socket = nullptr; - - int _end = 0; - swoole_event_write(sock, (void *) &_end, sizeof(_end)); - - return SW_OK; -} - typedef std::function TaskCallback; -static sw_inline void Worker_do_task(Server *serv, Worker *worker, DataHead *info, const TaskCallback &callback) { +static sw_inline void Worker_do_task(Server *serv, Worker *worker, const DataHead *info, const TaskCallback &callback) { RecvData recv_data; - auto packet = serv->message_bus.get_packet(); + auto packet = serv->get_worker_message_bus()->get_packet(); recv_data.info = *info; recv_data.info.len = packet.length; recv_data.data = packet.data; if (callback(serv, &recv_data) == SW_OK) { - worker->request_count++; + worker->add_request_count(); sw_atomic_fetch_add(&serv->gs->request_count, 1); } } void Server::worker_accept_event(DataHead *info) { - Worker *worker = SwooleWG.worker; - // worker busy - worker->status = SW_WORKER_BUSY; + Worker *worker = sw_worker(); + worker->set_status_to_busy(); switch (info->type) { case SW_SERVER_EVENT_RECV_DATA: { Connection *conn = get_connection_verify(info->fd); if (conn) { if (info->len > 0) { - auto packet = message_bus.get_packet(); + auto packet = get_worker_message_bus()->get_packet(); sw_atomic_fetch_sub(&conn->recv_queued_bytes, packet.length); swoole_trace_log(SW_TRACE_SERVER, "[Worker] session_id=%ld, len=%lu, qb=%d", @@ -236,28 +141,24 @@ void Server::worker_accept_event(DataHead *info) { break; } case SW_SERVER_EVENT_CLOSE: { -#ifdef SW_USE_OPENSSL Connection *conn = get_connection_verify_no_ssl(info->fd); - if (conn && conn->ssl_client_cert && conn->ssl_client_cert_pid == SwooleG.pid) { + if (conn && conn->ssl_client_cert && conn->ssl_client_cert_pid == swoole_get_worker_pid()) { delete conn->ssl_client_cert; conn->ssl_client_cert = nullptr; } -#endif - factory->end(info->fd, false); + factory_->end(info->fd, false); break; } case SW_SERVER_EVENT_CONNECT: { -#ifdef SW_USE_OPENSSL // SSL client certificate if (info->len > 0) { Connection *conn = get_connection_verify_no_ssl(info->fd); if (conn) { - auto packet = message_bus.get_packet(); + auto packet = get_worker_message_bus()->get_packet(); conn->ssl_client_cert = new String(packet.data, packet.length); - conn->ssl_client_cert_pid = SwooleG.pid; + conn->ssl_client_cert_pid = swoole_get_worker_pid(); } } -#endif if (onConnect) { onConnect(this, info); } @@ -277,127 +178,158 @@ void Server::worker_accept_event(DataHead *info) { break; } case SW_SERVER_EVENT_FINISH: { - onFinish(this, (EventData *) message_bus.get_buffer()); + onFinish(this, reinterpret_cast(get_worker_message_bus()->get_buffer())); break; } case SW_SERVER_EVENT_PIPE_MESSAGE: { - onPipeMessage(this, (EventData *) message_bus.get_buffer()); + onPipeMessage(this, reinterpret_cast(get_worker_message_bus()->get_buffer())); break; } case SW_SERVER_EVENT_COMMAND_REQUEST: { call_command_handler(message_bus, worker->id, pipe_command->get_socket(false)); break; } + case SW_SERVER_EVENT_SHUTDOWN: { + stop_async_worker(worker); + break; + } default: swoole_warning("[Worker] error event[type=%d]", (int) info->type); break; } - // worker idle - worker->status = SW_WORKER_IDLE; + worker->set_status_to_idle(); // maximum number of requests, process will exit. - if (!SwooleWG.run_always && worker->request_count >= SwooleWG.max_request) { - stop_async_worker(worker); + if (worker->has_exceeded_max_request()) { + if (is_thread_mode()) { + Reactor *reactor = sw_reactor(); + get_thread(reactor->id)->shutdown(reactor); + } else { + stop_async_worker(worker); + } } } -void Server::worker_start_callback() { - if (SwooleG.process_id >= worker_num) { - SwooleG.process_type = SW_PROCESS_TASKWORKER; - } else { - SwooleG.process_type = SW_PROCESS_WORKER; - } - - int is_root = !geteuid(); - struct passwd *_passwd = nullptr; - struct group *_group = nullptr; - - if (is_root) { - // get group info - if (!group_.empty()) { - _group = getgrnam(group_.c_str()); - if (!_group) { - swoole_warning("get group [%s] info failed", group_.c_str()); - } - } - // get user info - if (!user_.empty()) { - _passwd = getpwnam(user_.c_str()); - if (!_passwd) { - swoole_warning("get user [%s] info failed", user_.c_str()); - } - } - // set process group - if (_group && setgid(_group->gr_gid) < 0) { - swoole_sys_warning("setgid to [%s] failed", group_.c_str()); - } - // set process user - if (_passwd && setuid(_passwd->pw_uid) < 0) { - swoole_sys_warning("setuid to [%s] failed", user_.c_str()); - } - // chroot - if (!chroot_.empty()) { - if (::chroot(chroot_.c_str()) == 0) { - if (chdir("/") < 0) { - swoole_sys_warning("chdir(\"/\") failed"); - } - } else { - swoole_sys_warning("chroot(\"%s\") failed", chroot_.c_str()); - } - } +void Server::worker_start_callback(Worker *worker) { + if (swoole_is_root_user()) { + swoole_set_isolation(group_, user_, chroot_); } SW_LOOP_N(worker_num + task_worker_num) { - Worker *worker = get_worker(i); - if (SwooleG.process_id == i) { + if (worker->id == i) { continue; } - if (is_worker() && worker->pipe_master) { - worker->pipe_master->set_nonblock(); + Worker *other_worker = get_worker(i); + if (is_worker() && other_worker->pipe_master) { + other_worker->pipe_master->set_nonblock(); } } - if (sw_logger()->is_opened()) { - sw_logger()->reopen(); - } - - SwooleWG.worker = get_worker(SwooleG.process_id); - SwooleWG.worker->status = SW_WORKER_IDLE; + worker->set_status_to_idle(); if (is_process_mode()) { sw_shm_protect(session_list, PROT_READ); } - call_worker_start_callback(SwooleWG.worker); + call_worker_start_callback(worker); +} + +void Server::worker_stop_callback(Worker *worker) { + call_worker_stop_callback(worker); +} + +void Server::call_worker_start_callback(Worker *worker) { + void *hook_args[2]; + hook_args[0] = this; + hook_args[1] = (void *) (uintptr_t) worker->id; + + if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START)) { + swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_START, hook_args); + } + if (isset_hook(HOOK_WORKER_START)) { + call_hook(HOOK_WORKER_START, hook_args); + } + + swoole_clear_last_error(); + swoole_clear_last_error_msg(); + + if (onWorkerStart) { + onWorkerStart(this, worker); + } } -void Server::worker_stop_callback() { +void Server::call_worker_stop_callback(Worker *worker) { void *hook_args[2]; hook_args[0] = this; - hook_args[1] = (void *) (uintptr_t) SwooleG.process_id; + hook_args[1] = (void *) (uintptr_t) worker->id; + if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_STOP)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_WORKER_STOP, hook_args); } if (onWorkerStop) { - onWorkerStop(this, SwooleG.process_id); + onWorkerStop(this, worker); } - if (!message_bus.empty()) { + + if (!get_worker_message_bus()->empty()) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_UNPROCESSED_DATA, "unprocessed data in the worker process buffer"); - message_bus.clear(); + get_worker_message_bus()->clear(); } -} -void Server::stop_async_worker(Worker *worker) { - worker->status = SW_WORKER_EXIT; - Reactor *reactor = SwooleTG.reactor; + SwooleWG.running = false; + if (SwooleWG.worker_copy) { + delete SwooleWG.worker_copy; + SwooleWG.worker_copy = nullptr; + SwooleWG.worker = nullptr; + } +} +void Server::call_worker_error_callback(Worker *worker, const ExitStatus &status) { + if (onWorkerError != nullptr) { + onWorkerError(this, worker, status); + } /** - * force to end. + * The work process has exited unexpectedly, requiring a cleanup of the shared memory state. + * This must be done between the termination of the old process and the initiation of the new one; + * otherwise, data contention may occur. */ + if (worker->type == SW_EVENT_WORKER) { + abort_worker(worker); + } +} + +bool Server::kill_worker(int worker_id) { + auto current_worker = sw_worker(); + if (!current_worker && worker_id < 0) { + swoole_error_log( + SW_LOG_WARNING, SW_ERROR_WRONG_OPERATION, "kill worker in non worker process requires specifying an id"); + return false; + } + + worker_id = worker_id < 0 ? swoole_get_worker_id() : worker_id; + const Worker *worker = get_worker(worker_id); + if (worker == nullptr) { + swoole_error_log(SW_LOG_WARNING, SW_ERROR_INVALID_PARAMS, "the worker_id[%d] is invalid", worker_id); + return false; + } + + swoole_trace_log(SW_TRACE_SERVER, "kill worker#%d", worker_id); + + DataHead event = {}; + event.type = SW_SERVER_EVENT_SHUTDOWN; + return send_to_worker_from_worker(worker, &event, sizeof(event), SW_PIPE_MASTER) != -1; +} + +void Server::stop_async_worker(Worker *worker) { + worker->shutdown(); + if (worker->type == SW_EVENT_WORKER) { + reset_worker_counter(worker); + } + + // forced termination + Reactor *reactor = sw_reactor(); if (reload_async == 0) { - running = false; reactor->running = false; return; } @@ -408,23 +340,18 @@ void Server::stop_async_worker(Worker *worker) { } // Separated from the event worker process pool - worker = (Worker *) sw_malloc(sizeof(*worker)); - *worker = *SwooleWG.worker; + SwooleWG.worker_copy = new Worker{}; + *SwooleWG.worker_copy = *worker; SwooleWG.worker = worker; + auto pipe_worker = get_worker_pipe_worker_in_message_bus(worker); - if (stream_socket) { - reactor->del(stream_socket); - stream_socket->free(); - stream_socket = nullptr; - } - - if (worker->pipe_worker && !worker->pipe_worker->removed) { - reactor->remove_read_event(worker->pipe_worker); + if (pipe_worker && !pipe_worker->removed) { + reactor->remove_read_event(pipe_worker); } if (is_base_mode()) { - if (is_worker()) { - if (worker->id == 0 && gs->event_workers.running == 0) { + if (is_event_worker()) { + if (worker->id == 0 && get_event_worker_pool()->running == 0) { if (swoole_isset_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN)) { swoole_call_hook(SW_GLOBAL_HOOK_BEFORE_SERVER_SHUTDOWN, this); } @@ -432,12 +359,12 @@ void Server::stop_async_worker(Worker *worker) { onBeforeShutdown(this); } } - for (auto ls : ports) { - reactor->del(ls->socket); - } if (worker->pipe_master && !worker->pipe_master->removed) { reactor->remove_read_event(worker->pipe_master); } + for (auto ls : ports) { + reactor->del(ls->socket); + } foreach_connection([reactor](Connection *conn) { if (!conn->peer_closed && !conn->socket->removed) { reactor->remove_read_event(conn->socket); @@ -445,14 +372,42 @@ void Server::stop_async_worker(Worker *worker) { }); clear_timer(); } - } else { + } else if (is_process_mode()) { WorkerStopMessage msg; - msg.pid = SwooleG.pid; - msg.worker_id = SwooleG.process_id; + msg.pid = getpid(); + msg.worker_id = worker->id; - if (gs->event_workers.push_message(SW_WORKER_MESSAGE_STOP, &msg, sizeof(msg)) < 0) { - running = 0; + if (get_event_worker_pool()->push_message(SW_WORKER_MESSAGE_STOP, &msg, sizeof(msg)) < 0) { + swoole_sys_warning("failed to push WORKER_STOP message"); + } + } else if (is_thread_mode()) { + if (is_event_worker()) { + /** + * The thread mode will use the master pipe to forward messages, + * and it may listen for writable events on this pipe, + * which need to be removed before the worker thread exits. + */ + SW_LOOP_N(worker_num) { + if (i % reactor_num == reactor->id) { + auto pipe_master = get_worker_pipe_master_in_message_bus(i); + if (!pipe_master->removed) { + reactor->remove_read_event(pipe_master); + } + } + } + /** + * Only the readable events are removed; + * at this point, there may still be ongoing events for sending data. + * The connection will be completely closed only when the reactor is destroyed. + */ + foreach_connection([reactor](Connection *conn) { + if (conn->reactor_id == reactor->id && !conn->peer_closed && !conn->socket->removed) { + reactor->remove_read_event(conn->socket); + } + }); } + } else { + assert(0); } reactor->set_wait_exit(true); @@ -460,29 +415,25 @@ void Server::stop_async_worker(Worker *worker) { SwooleWG.exit_time = ::time(nullptr); Worker_reactor_try_to_exit(reactor); - if (!reactor->running) { - running = false; - } } static void Worker_reactor_try_to_exit(Reactor *reactor) { Server *serv; - if (SwooleG.process_type == SW_PROCESS_TASKWORKER) { - ProcessPool *pool = (ProcessPool *) reactor->ptr; - serv = (Server *) pool->ptr; + if (sw_likely(swoole_get_worker_type() != SW_TASK_WORKER)) { + serv = static_cast(reactor->ptr); } else { - serv = (Server *) reactor->ptr; + auto pool = static_cast(reactor->ptr); + serv = static_cast(pool->ptr); } - uint8_t call_worker_exit_func = 0; - while (1) { + bool has_call_worker_exit_func = false; + while (true) { if (reactor->if_exit()) { reactor->running = false; - break; } else { - if (serv->onWorkerExit && call_worker_exit_func == 0) { - serv->onWorkerExit(serv, SwooleG.process_id); - call_worker_exit_func = 1; + if (serv->onWorkerExit && !has_call_worker_exit_func) { + has_call_worker_exit_func = true; + serv->onWorkerExit(serv, sw_worker()); continue; } int remaining_time = serv->max_wait_time - (::time(nullptr) - SwooleWG.exit_time); @@ -490,7 +441,6 @@ static void Worker_reactor_try_to_exit(Reactor *reactor) { swoole_error_log( SW_LOG_WARNING, SW_ERROR_SERVER_WORKER_EXIT_TIMEOUT, "worker exit timeout, forced termination"); reactor->running = false; - break; } else { int timeout_msec = remaining_time * 1000; if (reactor->timeout_msec < 0 || reactor->timeout_msec > timeout_msec) { @@ -502,7 +452,7 @@ static void Worker_reactor_try_to_exit(Reactor *reactor) { } } -void Server::drain_worker_pipe() { +void Server::drain_worker_pipe() const { for (uint32_t i = 0; i < worker_num + task_worker_num; i++) { Worker *worker = get_worker(i); if (sw_reactor()) { @@ -516,14 +466,29 @@ void Server::drain_worker_pipe() { } } +void Server::clean_worker_connections(Worker *worker) { + swoole_trace_log(SW_TRACE_WORKER, "clean connections"); + sw_reactor()->destroyed = true; + if (sw_likely(is_base_mode())) { + foreach_connection([](Connection *conn) { close_connection(sw_reactor(), conn->socket); }); + } else if (is_thread_mode()) { + foreach_connection([worker](Connection *conn) { + if (conn->reactor_id == worker->id) { + close_connection(sw_reactor(), conn->socket); + } + }); + } +} + /** * main loop [Worker] + * Only used in SWOOLE_PROCESS mode */ int Server::start_event_worker(Worker *worker) { - // worker_id - SwooleG.process_id = worker->id; + swoole_set_worker_id(worker->id); + swoole_set_worker_type(SW_EVENT_WORKER); - init_worker(worker); + init_event_worker(worker); if (swoole_event_init(0) < 0) { return SW_ERR; @@ -536,7 +501,7 @@ int Server::start_event_worker(Worker *worker) { * set pipe buffer size */ for (uint32_t i = 0; i < worker_num + task_worker_num; i++) { - Worker *_worker = get_worker(i); + const Worker *_worker = get_worker(i); if (_worker->pipe_master) { _worker->pipe_master->buffer_size = UINT_MAX; } @@ -548,103 +513,63 @@ int Server::start_event_worker(Worker *worker) { worker->pipe_worker->set_nonblock(); reactor->ptr = this; reactor->add(worker->pipe_worker, SW_EVENT_READ); - reactor->set_handler(SW_FD_PIPE, Worker_onPipeReceive); - - if (dispatch_mode == DISPATCH_STREAM) { - reactor->add(stream_socket, SW_EVENT_READ); - reactor->set_handler(SW_FD_STREAM_SERVER, Worker_onStreamAccept); - reactor->set_handler(SW_FD_STREAM, Worker_onStreamRead); - network::Stream::set_protocol(&stream_protocol); - stream_protocol.private_data_2 = this; - stream_protocol.package_max_length = UINT_MAX; - stream_protocol.onPackage = Worker_onStreamPackage; - buffer_pool = new std::queue; - } else if (dispatch_mode == DISPATCH_CO_CONN_LB || dispatch_mode == DISPATCH_CO_REQ_LB) { + reactor->set_handler(SW_FD_PIPE, SW_EVENT_READ, Worker_onPipeReceive); + + if (dispatch_mode == DISPATCH_CO_CONN_LB || dispatch_mode == DISPATCH_CO_REQ_LB) { reactor->set_end_callback(Reactor::PRIORITY_WORKER_CALLBACK, [worker](Reactor *) { worker->coroutine_num = Coroutine::count(); }); } - worker->status = SW_WORKER_IDLE; - worker_start_callback(); + worker_start_callback(worker); // main loop - reactor->wait(nullptr); + const auto rv = reactor->wait(); // drain pipe buffer drain_worker_pipe(); // reactor free swoole_event_free(); // worker shutdown - worker_stop_callback(); + worker_stop_callback(worker); - if (buffer_pool) { - delete buffer_pool; - } + delete buffer_pool; - return SW_OK; + return rv; } /** * [Worker/TaskWorker/Master] Send data to ReactorThread */ -ssize_t Server::send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id) { +ssize_t Server::send_to_reactor_thread(const EventData *ev_data, size_t sendn, SessionId session_id) const { Socket *pipe_sock = get_reactor_pipe_socket(session_id, ev_data->info.reactor_id); if (swoole_event_is_available()) { return swoole_event_write(pipe_sock, ev_data, sendn); } else { - return pipe_sock->send_blocking(ev_data, sendn); + return pipe_sock->send_sync(ev_data, sendn); } } /** * send message from worker to another worker */ -ssize_t Server::send_to_worker_from_worker(Worker *dst_worker, const void *buf, size_t len, int flags) { +ssize_t Server::send_to_worker_from_worker(const Worker *dst_worker, const void *buf, size_t len, int flags) { return dst_worker->send_pipe_message(buf, len, flags); } /** * receive data from reactor + * This function is intended solely for process mode; in thread or base mode, `ReactorThread_onRead()` will be executed. */ static int Worker_onPipeReceive(Reactor *reactor, Event *event) { - Server *serv = (Server *) reactor->ptr; - PipeBuffer *pipe_buffer = serv->message_bus.get_buffer(); + auto *serv = static_cast(reactor->ptr); + auto *pipe_buffer = serv->get_worker_message_bus()->get_buffer(); - if (serv->message_bus.read(event->socket) <= 0) { + if (serv->get_worker_message_bus()->read(event->socket) <= 0) { return SW_OK; } serv->worker_accept_event(&pipe_buffer->info); - serv->message_bus.pop(); + serv->get_worker_message_bus()->pop(); return SW_OK; } - -ssize_t Worker::send_pipe_message(const void *buf, size_t n, int flags) { - Socket *pipe_sock; - - if (flags & SW_PIPE_MASTER) { - pipe_sock = pipe_master; - } else { - pipe_sock = pipe_worker; - } - - // message-queue - if (pool->use_msgqueue) { - struct { - long mtype; - EventData buf; - } msg; - - msg.mtype = id + 1; - memcpy(&msg.buf, buf, n); - - return pool->queue->push((QueueNode *) &msg, n) ? n : -1; - } - - if ((flags & SW_PIPE_NONBLOCK) && swoole_event_is_available()) { - return swoole_event_write(pipe_sock, buf, n); - } else { - return pipe_sock->send_blocking(buf, n); - } -} } // namespace swoole diff --git a/src/wrapper/event.cc b/src/wrapper/event.cc index beecf1fce9..4a8e94484d 100644 --- a/src/wrapper/event.cc +++ b/src/wrapper/event.cc @@ -14,44 +14,29 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_reactor.h" #include "swoole_client.h" #include "swoole_coroutine_socket.h" #include "swoole_coroutine_system.h" -#include - -using namespace swoole; - +using swoole::Callback; +using swoole::Reactor; +using swoole::ReactorHandler; using swoole::network::Socket; -static std::mutex init_lock; - -#ifdef __MACH__ -Reactor *sw_reactor() { - return SwooleTG.reactor; -} -#endif - int swoole_event_init(int flags) { - if (!SwooleG.init) { - std::unique_lock lock(init_lock); - swoole_init(); - } - - Reactor *reactor = new Reactor(SW_REACTOR_MAXEVENTS); + auto *reactor = new Reactor(SW_REACTOR_MAXEVENTS); if (!reactor->ready()) { return SW_ERR; } if (flags & SW_EVENTLOOP_WAIT_EXIT) { - reactor->wait_exit = 1; + reactor->wait_exit = true; } - coroutine::Socket::init_reactor(reactor); - coroutine::System::init_reactor(reactor); - network::Client::init_reactor(reactor); + swoole::coroutine::Socket::init_reactor(reactor); + swoole::coroutine::System::init_reactor(reactor); + swoole::network::Client::init_reactor(reactor); SwooleTG.reactor = reactor; @@ -62,7 +47,7 @@ int swoole_event_add(Socket *socket, int events) { return SwooleTG.reactor->add(socket, events); } -int swoole_event_add_or_update(swoole::network::Socket *_socket, int event) { +int swoole_event_add_or_update(Socket *_socket, int event) { if (event == SW_EVENT_READ) { return SwooleTG.reactor->add_read_event(_socket); } else if (event == SW_EVENT_WRITE) { @@ -85,7 +70,7 @@ int swoole_event_wait() { Reactor *reactor = SwooleTG.reactor; int retval = 0; if (!reactor->wait_exit or !reactor->if_exit()) { - retval = reactor->wait(nullptr); + retval = reactor->wait(); } swoole_event_free(); return retval; @@ -100,7 +85,7 @@ int swoole_event_free() { return SW_OK; } -void swoole_event_defer(Callback cb, void *private_data) { +void swoole_event_defer(const Callback &cb, void *private_data) { SwooleTG.reactor->defer(cb, private_data); } @@ -108,20 +93,24 @@ ssize_t swoole_event_write(Socket *socket, const void *data, size_t len) { return SwooleTG.reactor->write(SwooleTG.reactor, socket, data, len); } -ssize_t swoole_event_writev(swoole::network::Socket *socket, const iovec *iov, size_t iovcnt) { +ssize_t swoole_event_writev(Socket *socket, const iovec *iov, size_t iovcnt) { return SwooleTG.reactor->writev(SwooleTG.reactor, socket, iov, iovcnt); } -bool swoole_event_set_handler(int fdtype, ReactorHandler handler) { - return SwooleTG.reactor->set_handler(fdtype, handler); +void swoole_event_set_handler(const int fd_type, const int event, const ReactorHandler handler) { + SwooleTG.reactor->set_handler(fd_type, event, handler); } -bool swoole_event_isset_handler(int fdtype) { - return SwooleTG.reactor->isset_handler(fdtype); +bool swoole_event_isset_handler(const int fd_type, const int event) { + return SwooleTG.reactor->isset_handler(fd_type, event); } bool swoole_event_is_available() { - return SwooleTG.reactor and !SwooleTG.reactor->destroyed; + return SwooleTG.reactor && !SwooleTG.reactor->destroyed; +} + +bool swoole_event_is_running() { + return SwooleTG.reactor && SwooleTG.reactor->running; } Socket *swoole_event_get_socket(int fd) { diff --git a/src/wrapper/http.cc b/src/wrapper/http.cc index 170a0d9408..f426a00be2 100644 --- a/src/wrapper/http.cc +++ b/src/wrapper/http.cc @@ -14,23 +14,21 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_http.h" #include "swoole_server.h" -#include "thirdparty/swoole_http_parser.h" +#include "swoole_llhttp.h" #include "thirdparty/multipart_parser.h" namespace swoole { namespace http_server { -static int http_request_on_path(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_query_string(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_body(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_header_field(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_header_value(swoole_http_parser *parser, const char *at, size_t length); -static int http_request_on_headers_complete(swoole_http_parser *parser); -static int http_request_message_complete(swoole_http_parser *parser); +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length); +static int http_request_on_headers_complete(llhttp_t *parser); +static int http_request_message_complete(llhttp_t *parser); static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length); static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length); @@ -39,22 +37,36 @@ static int multipart_body_on_header_complete(multipart_parser *p); static int multipart_body_on_data_end(multipart_parser *p); // clang-format off -static const swoole_http_parser_settings http_parser_settings = +static constexpr llhttp_settings_t http_parser_settings = { - nullptr, - http_request_on_path, - http_request_on_query_string, - nullptr, - nullptr, - http_request_on_header_field, - http_request_on_header_value, - http_request_on_headers_complete, - http_request_on_body, - http_request_message_complete + nullptr, // on_message_begin + nullptr, // on_protocol + http_request_on_url, // on_url + nullptr, // on_status + nullptr, // on_method + nullptr, // on_version + http_request_on_header_field, // on_header_field + http_request_on_header_value, // on_header_value + nullptr, // on_chunk_extension_name + nullptr, // on_chunk_extension_value + http_request_on_headers_complete, // on_headers_complete + http_request_on_body, // on_body + http_request_message_complete, // on_message_complete + nullptr, // on_protocol_complete + nullptr, // on_url_complete + nullptr, // on_status_complete + nullptr, // on_method_complete + nullptr, // on_version_complete + nullptr, // on_header_field_complete + nullptr, // on_header_value_complete + nullptr, // on_chunk_extension_name_complete + nullptr, // on_chunk_extension_value_complete + nullptr, // on_chunk_header + nullptr, // on_chunk_complete + nullptr, // on_reset }; -static const multipart_parser_settings mt_parser_settings = -{ +static constexpr multipart_parser_settings mt_parser_settings = { multipart_body_on_header_field, multipart_body_on_header_value, multipart_body_on_data, @@ -66,7 +78,7 @@ static const multipart_parser_settings mt_parser_settings = // clang-format on struct ContextImpl { - swoole_http_parser parser; + llhttp_t parser; multipart_parser *mt_parser; std::string current_header_name; @@ -74,39 +86,50 @@ struct ContextImpl { std::string current_form_data_name; String *form_data_buffer; + bool completed = false; bool is_beginning = true; bool parse(Context &ctx, const char *at, size_t length) { - parser.data = &ctx; - swoole_http_parser_init(&parser, PHP_HTTP_REQUEST); - swoole_http_parser_execute(&parser, &http_parser_settings, at, length); - return true; + swoole_llhttp_parser_init(&parser, HTTP_REQUEST, static_cast(&ctx)); + swoole_llhttp_parser_execute(&parser, &http_parser_settings, at, length); + return parser.error == HPE_OK && completed; } }; -static int http_request_on_path(swoole_http_parser *parser, const char *at, size_t length) { - Context *ctx = (Context *) parser->data; - ctx->request_path = std::string(at, length); +static int http_request_on_url(llhttp_t *parser, const char *at, size_t length) { + const char *query_start = (const char *) memchr(at, '?', length); + size_t path_len = query_start ? (size_t) (query_start - at) : length; + + auto *ctx = static_cast(parser->data); + ctx->request_path = std::string(at, path_len); + + if (!query_start || (length - path_len) <= 1) { + return 0; + } + + const char *query_str = query_start + 1; + size_t query_len = length - path_len - 1; + ctx->query_string = std::string(query_str, query_len); return 0; } -static int http_request_on_header_field(swoole_http_parser *parser, const char *at, size_t length) { - Context *ctx = (Context *) parser->data; +static int http_request_on_header_field(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); ctx->impl->current_header_name = std::string(at, length); return 0; } -static int http_request_on_header_value(swoole_http_parser *parser, const char *at, size_t length) { - Context *ctx = (Context *) parser->data; +static int http_request_on_header_value(llhttp_t *parser, const char *at, size_t length) { + auto *ctx = static_cast(parser->data); ContextImpl *impl = ctx->impl; ctx->headers[impl->current_header_name] = std::string(at, length); - if ((parser->method == PHP_HTTP_POST || parser->method == PHP_HTTP_PUT || parser->method == PHP_HTTP_DELETE || - parser->method == PHP_HTTP_PATCH) && + if ((parser->method == HTTP_POST || parser->method == HTTP_PUT || parser->method == HTTP_DELETE || + parser->method == HTTP_PATCH) && SW_STRCASEEQ(impl->current_header_name.c_str(), impl->current_header_name.length(), "content-type")) { - if (SW_STRCASECT(at, length, "application/x-www-form-urlencoded")) { - ctx->post_form_urlencoded = 1; - } else if (SW_STRCASECT(at, length, "multipart/form-data")) { + if (SW_STR_ISTARTS_WITH(at, length, "application/x-www-form-urlencoded")) { + ctx->post_form_urlencoded = true; + } else if (SW_STR_ISTARTS_WITH(at, length, "multipart/form-data")) { size_t offset = sizeof("multipart/form-data") - 1; char *boundary_str; int boundary_len; @@ -122,27 +145,21 @@ static int http_request_on_header_value(swoole_http_parser *parser, const char * return 0; } -static int http_request_on_query_string(swoole_http_parser *parser, const char *at, size_t length) { - Context *ctx = (Context *) parser->data; - ctx->query_string = std::string(at, length); - return 0; -} - -static int http_request_on_headers_complete(swoole_http_parser *parser) { - Context *ctx = (Context *) parser->data; +static int http_request_on_headers_complete(llhttp_t *parser) { + auto *ctx = static_cast(parser->data); ctx->version = parser->http_major * 100 + parser->http_minor; ctx->server_protocol = std::string(ctx->version == 101 ? "HTTP/1.1" : "HTTP/1.0"); - ctx->keepalive = swoole_http_should_keep_alive(parser); + ctx->keepalive = llhttp_should_keep_alive(parser); return 0; } -static int http_request_on_body(swoole_http_parser *parser, const char *at, size_t length) { +static int http_request_on_body(llhttp_t *parser, const char *at, size_t length) { if (length == 0) { return 0; } - Context *ctx = (Context *) parser->data; - ContextImpl *impl = ctx->impl; + auto *ctx = static_cast(parser->data); + auto *impl = ctx->impl; if (impl->mt_parser != nullptr) { multipart_parser *multipart_parser = impl->mt_parser; @@ -158,7 +175,7 @@ static int http_request_on_body(swoole_http_parser *parser, const char *at, size impl->is_beginning = false; } size_t n = multipart_parser_execute(multipart_parser, at, length); - if (n != length) { + if (sw_unlikely(n != length)) { swoole_error_log(SW_LOG_WARNING, SW_ERROR_SERVER_INVALID_REQUEST, "parse multipart body failed, %zu/%zu bytes processed", @@ -169,17 +186,17 @@ static int http_request_on_body(swoole_http_parser *parser, const char *at, size ctx->body.append(at, length); } - return 0; + return impl->completed ? HPE_PAUSED : 0; } static int multipart_body_on_header_field(multipart_parser *p, const char *at, size_t length) { - Context *ctx = (Context *) p->data; + auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; return http_request_on_header_field(&impl->parser, at, length); } static int multipart_body_on_header_value(multipart_parser *p, const char *at, size_t length) { - Context *ctx = (Context *) p->data; + auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; const char *header_name = impl->current_header_name.c_str(); size_t header_len = impl->current_header_name.length(); @@ -190,7 +207,7 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s info[std::string(key, key_len)] = std::string(value, value_len); return true; }; - swoole::http_server::parse_cookie(at, length, cb); + parse_cookie(at, length, cb); auto name = info.find("name"); auto filename = info.find("filename"); if (filename == info.end()) { @@ -199,6 +216,14 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s impl->current_input_name = filename->second; } } else if (SW_STRCASEEQ(header_name, header_len, SW_HTTP_UPLOAD_FILE)) { + /** + * When the "SW_HTTP_UPLOAD_FILE" header appears in the request, it indicates that the uploaded file has been + * saved in a temporary file. The binary content in the message body will be replaced with the temporary + * filename. However, the Content-Length still reflects the original message size, causing llhttp to believe + * there is still data to be received. As a result, llhttp fails to trigger the message callback. Therefore, we + * need to set `ctx->completed = 1` to indicate that the message processing is complete. + */ + impl->completed = true; ctx->files[impl->current_form_data_name] = std::string(at, length); } @@ -206,8 +231,8 @@ static int multipart_body_on_header_value(multipart_parser *p, const char *at, s } static int multipart_body_on_data(multipart_parser *p, const char *at, size_t length) { - Context *ctx = (Context *) p->data; - ContextImpl *impl = ctx->impl; + auto *ctx = static_cast(p->data); + const auto *impl = ctx->impl; if (!impl->current_form_data_name.empty()) { impl->form_data_buffer->append(at, length); return 0; @@ -226,8 +251,8 @@ static int multipart_body_on_data(multipart_parser *p, const char *at, size_t le } static int multipart_body_on_header_complete(multipart_parser *p) { - Context *ctx = (Context *) p->data; - ContextImpl *impl = ctx->impl; + auto *ctx = static_cast(p->data); + const auto *impl = ctx->impl; if (impl->current_input_name.empty()) { return 0; } @@ -254,7 +279,7 @@ static int multipart_body_on_header_complete(multipart_parser *p) { } static int multipart_body_on_data_end(multipart_parser *p) { - Context *ctx = (Context *) p->data; + auto *ctx = static_cast(p->data); ContextImpl *impl = ctx->impl; if (!impl->current_form_data_name.empty()) { @@ -274,16 +299,17 @@ static int multipart_body_on_data_end(multipart_parser *p) { return 0; } -static int http_request_message_complete(swoole_http_parser *p) { - Context *ctx = (Context *) p->data; - ContextImpl *impl = ctx->impl; +static int http_request_message_complete(llhttp_t *p) { + const auto *ctx = static_cast(p->data); + auto *impl = ctx->impl; if (impl->form_data_buffer) { delete impl->form_data_buffer; impl->form_data_buffer = nullptr; } - return 1; + impl->completed = true; + return HPE_PAUSED; } bool Context::end(const char *data, size_t length) { @@ -295,10 +321,11 @@ bool Context::end(const char *data, size_t length) { if (length > 0) { response.headers["Content-Length"] = std::to_string(length); } - for (auto iter : response.headers) { + for (auto &iter : response.headers) { size_t n = sw_snprintf(buf, sizeof(buf), "%s: %s\r\n", iter.first.c_str(), iter.second.c_str()); sw_tg_buffer()->append(buf, n); } + sw_tg_buffer()->append(SW_STRL("\r\n")); if (!server_->send(session_id_, sw_tg_buffer()->str, sw_tg_buffer()->length)) { swoole_warning("failed to send HTTP header"); return false; @@ -307,21 +334,26 @@ bool Context::end(const char *data, size_t length) { swoole_warning("failed to send HTTP body"); return false; } + if (!keepalive) { + server_->close(session_id_, false); + } return true; } Context::~Context() { - for (auto kv : files) { + for (auto &kv : files) { if (file_exists(kv.second)) { unlink(kv.second.c_str()); } } } -std::shared_ptr listen(const std::string addr, std::function cb, int mode) { - Server *server = new Server((Server::Mode) mode); +static std::function http_server_on_request; + +std::shared_ptr listen(const std::string &addr, const std::function &cb, int mode) { + auto server = std::make_shared(static_cast(mode)); auto index = addr.find(':'); - if (index == addr.npos) { + if (index == std::string::npos) { swoole_warning("incorrect server listening address"); return nullptr; } @@ -331,23 +363,27 @@ std::shared_ptr listen(const std::string addr, std::functionadd_port(SW_SOCK_TCP, host.c_str(), port); if (!port_object) { return nullptr; } - server->onReceive = [&cb](Server *server, RecvData *req) { + http_server_on_request = cb; + + server->onReceive = [](Server *server, RecvData *req) { SessionId session_id = req->info.fd; Connection *conn = server->get_connection_verify_no_ssl(session_id); if (!conn) { - swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); + swoole_error_log(SW_LOG_TRACE, SW_ERROR_SESSION_NOT_EXIST, "session[%ld] is closed", session_id); return SW_OK; } ContextImpl impl; Context ctx(server, session_id, &impl); if (impl.parse(ctx, req->data, req->info.len)) { - cb(ctx); + http_server_on_request(ctx); + } else { + server->send(req->session_id(), SW_STRL(SW_HTTP_BAD_REQUEST_PACKET)); } return SW_OK; }; @@ -358,7 +394,7 @@ std::shared_ptr listen(const std::string addr, std::function(server); + return server; } } // namespace http_server } // namespace swoole diff --git a/src/wrapper/timer.cc b/src/wrapper/timer.cc index 79a352f4e7..016d6c13cc 100644 --- a/src/wrapper/timer.cc +++ b/src/wrapper/timer.cc @@ -14,35 +14,45 @@ +----------------------------------------------------------------------+ */ -#include "swoole_api.h" #include "swoole_timer.h" -using namespace swoole; +using swoole::sec2msec; +using swoole::Timer; +using swoole::TimerCallback; +using swoole::TimerNode; -#ifdef __MACH__ -Timer *sw_timer() { +bool swoole_timer_is_available() { + return SwooleTG.timer != nullptr; +} + +TimerNode *swoole_timer_add(double timeout, bool persistent, const TimerCallback &callback, void *private_data) { + if (sw_unlikely(timeout < SW_TIMER_MIN_SEC)) { + timeout = SW_TIMER_MIN_SEC; + } + return swoole_timer_add(sec2msec(timeout), persistent, callback, private_data); +} + +Timer *swoole_timer_create(bool manually_trigger) { + SwooleTG.timer = new Timer(manually_trigger); return SwooleTG.timer; } -#endif -bool swoole_timer_is_available() { - return SwooleTG.timer != nullptr; +SW_API int64_t swoole_timer_get_next_msec() { + if (sw_unlikely(!swoole_timer_is_available())) { + return -1; + } + return SwooleTG.timer->get_next_msec(); } TimerNode *swoole_timer_add(long ms, bool persistent, const TimerCallback &callback, void *private_data) { if (sw_unlikely(!swoole_timer_is_available())) { - SwooleTG.timer = new Timer(); - if (sw_unlikely(!SwooleTG.timer->init())) { - delete SwooleTG.timer; - SwooleTG.timer = nullptr; - return nullptr; - } + swoole_timer_create(false); } return SwooleTG.timer->add(ms, persistent, private_data, callback); } bool swoole_timer_del(TimerNode *tnode) { - if (!swoole_timer_is_available()) { + if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } @@ -50,7 +60,7 @@ bool swoole_timer_del(TimerNode *tnode) { } void swoole_timer_delay(TimerNode *tnode, long delay_ms) { - if (!swoole_timer_is_available()) { + if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return; } @@ -62,29 +72,27 @@ long swoole_timer_after(long ms, const TimerCallback &callback, void *private_da swoole_warning("Timer must be greater than 0"); return SW_ERR; } - TimerNode *tnode = swoole_timer_add(ms, false, callback, private_data); - if (tnode == nullptr) { + const auto tnode = swoole_timer_add(ms, false, callback, private_data); + if (sw_unlikely(!tnode)) { return SW_ERR; - } else { - return tnode->id; } + return tnode->id; } long swoole_timer_tick(long ms, const TimerCallback &callback, void *private_data) { - if (ms <= 0) { + if (sw_unlikely(ms <= 0)) { swoole_warning("Timer must be greater than 0"); return SW_ERR; } - TimerNode *tnode = swoole_timer_add(ms, true, callback, private_data); - if (tnode == nullptr) { + const auto tnode = swoole_timer_add(ms, true, callback, private_data); + if (sw_unlikely(!tnode)) { return SW_ERR; - } else { - return tnode->id; } + return tnode->id; } bool swoole_timer_exists(long timer_id) { - if (!swoole_timer_is_available()) { + if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } @@ -93,7 +101,7 @@ bool swoole_timer_exists(long timer_id) { } bool swoole_timer_clear(long timer_id) { - if (!swoole_timer_is_available()) { + if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return false; } @@ -101,7 +109,7 @@ bool swoole_timer_clear(long timer_id) { } TimerNode *swoole_timer_get(long timer_id) { - if (!swoole_timer_is_available()) { + if (sw_unlikely(!swoole_timer_is_available())) { swoole_warning("timer is not available"); return nullptr; } @@ -110,18 +118,16 @@ TimerNode *swoole_timer_get(long timer_id) { void swoole_timer_free() { if (!swoole_timer_is_available()) { + swoole_print_backtrace(); swoole_warning("timer is not available"); return; } delete SwooleTG.timer; SwooleTG.timer = nullptr; - SwooleG.signal_alarm = false; } -int swoole_timer_select() { - if (!swoole_timer_is_available()) { - swoole_warning("timer is not available"); - return SW_ERR; +void swoole_timer_select() { + if (sw_likely(swoole_timer_is_available())) { + SwooleTG.timer->select(); } - return SwooleTG.timer->select(); } diff --git a/stubs/php_swoole_coroutine_system.stub.php b/stubs/php_swoole_coroutine_system.stub.php deleted file mode 100644 index 3b146f799c..0000000000 --- a/stubs/php_swoole_coroutine_system.stub.php +++ /dev/null @@ -1,32 +0,0 @@ -setRiskyAllowed(true) - ->setRules([ - '@PSR2' => true, - '@Symfony' => true, - '@DoctrineAnnotation' => true, - '@PhpCsFixer' => true, - 'header_comment' => [ - 'comment_type' => 'PHPDoc', - 'header' => $header, - 'separate' => 'bottom', - 'location' => 'after_open', - ], - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'list_syntax' => [ - 'syntax' => 'short' - ], - 'concat_space' => [ - 'spacing' => 'one' - ], - 'blank_line_before_statement' => [ - 'statements' => [ - 'declare', - ], - ], - 'blank_line_after_namespace' => true, - 'general_phpdoc_annotation_remove' => [ - 'annotations' => [ - 'author' - ], - ], - 'ordered_imports' => [ - 'imports_order' => [ - 'class', - 'function', - 'const', - ], - 'sort_algorithm' => 'alpha', - ], - 'single_line_comment_style' => [ - 'comment_types' => [ - ], - ], - 'yoda_style' => [ - 'always_move_variable' => false, - 'equal' => false, - 'identical' => false, - ], - 'phpdoc_align' => [ - 'align' => 'left', - ], - 'multiline_whitespace_before_semicolons' => [ - 'strategy' => 'no_multi_line', - ], - 'constant_case' => [ - 'case' => 'lower', - ], - 'class_attributes_separation' => true, - 'combine_consecutive_unsets' => true, - 'declare_strict_types' => true, - 'linebreak_after_opening_tag' => true, - 'lowercase_static_reference' => true, - 'no_useless_else' => true, - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => false, - 'not_operator_with_space' => false, - 'ordered_class_elements' => true, - 'php_unit_strict' => false, - 'phpdoc_separation' => false, - 'phpdoc_summary' => false, - 'single_quote' => true, - 'increment_style' => [], - 'standardize_increment' => false, - 'standardize_not_equals' => true, - 'multiline_comment_opening_closing' => true, - 'lambda_not_used_import' => false, - ]) - ->setFinder( - PhpCsFixer\Finder::create() - ->exclude(['html', 'vendor']) - ->in(__DIR__) - ) - ->setUsingCache(false); diff --git a/tests/README.md b/tests/README.md index 9cb4e9ec67..fc3ce7a826 100644 --- a/tests/README.md +++ b/tests/README.md @@ -8,7 +8,7 @@ try to run `./init` to initialize the databases. | | mysql | redis | | ------------- | ------------------------------------- | ----------------------------------- | | path (env) | $MYSQL_SERVER_PATH | $REDIS_SERVER_PATH | -| path (travis) | ${travis}/data/run/mysqld/mysqld.sock | ${travis}/data/run/redis/redis.sock | +| path (actions) | ${actions}/data/run/mysqld/mysqld.sock | ${actions}/data/run/redis/redis.sock | | host (raw) | 127.0.0.1 | 127.0.0.1 | | host (docker) | mysql | redis | | port | 3306 | 6379 | diff --git a/tests/include/api/curl_multi.php b/tests/include/api/curl_multi.php index e36d5c275b..30d457e8d8 100644 --- a/tests/include/api/curl_multi.php +++ b/tests/include/api/curl_multi.php @@ -10,11 +10,11 @@ function swoole_test_curl_multi_ex($mh, $options = []) { $ch2 = curl_init(); // 设置URL和相应的选项 - curl_setopt($ch1, CURLOPT_URL, "http://www.baidu.com/"); + curl_setopt($ch1, CURLOPT_URL, "https://www.baidu.com/"); curl_setopt($ch1, CURLOPT_HEADER, 0); curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch2, CURLOPT_URL, "http://www.gov.cn/"); + curl_setopt($ch2, CURLOPT_URL, "https://www.gov.cn/"); curl_setopt($ch2, CURLOPT_HEADER, 0); curl_setopt($ch2, CURLOPT_RETURNTRANSFER, 1); @@ -61,7 +61,7 @@ function swoole_test_curl_multi_ex($mh, $options = []) { Assert::eq($info3, false); Assert::contains(curl_multi_getcontent($ch1), 'baidu.com'); - Assert::contains(curl_multi_getcontent($ch2), '中央人民政府门户网站'); + Assert::contains(curl_multi_getcontent($ch2), '中国政府网'); curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); diff --git a/tests/include/api/exit.php b/tests/include/api/exit.php new file mode 100644 index 0000000000..6641293b9f --- /dev/null +++ b/tests/include/api/exit.php @@ -0,0 +1,75 @@ += 80400) { + $exit_status_list = [ + 1, + 'exit', + 0, + ]; +} else { + $exit_status_list = [ + 'undef', + null, + true, + false, + 1, + 1.1, + 'exit', + ['exit' => 'ok'], + (object)['exit' => 'ok'], + STDIN, + 0 + ]; +} + +function route() +{ + controller(); +} + +function controller() +{ + your_code(); +} + +function your_code() +{ + global $exit_status_list; + co::sleep(.001); + $exit_status = array_shift($exit_status_list); + if ($exit_status === 'undef') { + exit; + } else { + exit($exit_status); + } +} + +$chan = new Swoole\Coroutine\Channel; + +go(function () use ($chan, $exit_status_list) { + foreach ($exit_status_list as $val) { + $chan->push($val); + } +}); + +for ($i = 0; $i < count($exit_status_list); $i++) { + go(function () use ($exit_status_list, $chan) { + try { + // in coroutine + route(); + } catch (\Swoole\ExitException $e) { + Assert::assert($e->getFlags() & SWOOLE_EXIT_IN_COROUTINE); + $exit_status = $chan->pop(); + if (PHP_VERSION_ID < 80400) { + $exit_status = $exit_status === 'undef' ? null : $exit_status; + } + Assert::same($e->getStatus(), $exit_status); + var_dump($e->getStatus()); + // exit coroutine + return; + } + echo "never here\n"; + }); +} + +Swoole\Event::wait(); diff --git a/tests/include/api/http_test_cases.php b/tests/include/api/http_test_cases.php index a4f21b067c..cf6819d878 100644 --- a/tests/include/api/http_test_cases.php +++ b/tests/include/api/http_test_cases.php @@ -63,19 +63,35 @@ function http2_compression_types_test(ProcessManager $pm) /** * @param ProcessManager $pm + * @param array $sizes * @throw RuntimeException */ -function form_data_test_1(ProcessManager $pm) +function form_data_test(ProcessManager $pm, array $sizes = []) { - run(function () use ($pm) { + if (count($sizes) == 0) { + throw new \RuntimeException("size array cannot be empty"); + } + run(function () use ($pm, $sizes) { $client = new Client(SWOOLE_SOCK_TCP); Assert::true($client->connect('127.0.0.1', $pm->getFreePort())); $req = file_get_contents(SOURCE_ROOT_PATH . '/core-tests/fuzz/cases/req1.bin'); + $len = strlen($req); + + $begin = 0; + foreach ($sizes as $end) { + if ($end >= $len) { + throw new \RuntimeException("error offset[$end]"); + } + Assert::eq($client->send(substr($req, $begin, $end)), $end - $begin); + usleep(10000); + } + + $end = $sizes[count($sizes) - 1]; + if ($len - $end > 0) { + Assert::eq($client->send(substr($req, $end)), $len - $end); + usleep(10000); + } - Assert::eq($client->send(substr($req, 0, OFFSET)), OFFSET); - usleep(10000); - Assert::eq($client->send(substr($req, OFFSET)), strlen($req) - OFFSET); - usleep(10000); $resp = ''; $length = 0; $header = ''; @@ -237,3 +253,45 @@ function chunked_request(ProcessManager $pm) echo "SUCCESS\n"; $pm->kill(); } + +function http_get_with_co_socket(string $domain, ?callable $cb = null) +{ + $cli = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); + $cli->setProtocol(['open_ssl' => true,]); + + if (!$cli->connect($domain, 443)) { + echo "ERROR\n"; + } + + $http = "GET / HTTP/1.1\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: $domain\r\nConnection: Keep-Alive\r\n" + . "Keep-Alive: on\r\n\r\n"; + if (!$cli->send($http)) { + echo "ERROR\n"; + } + + $content = ''; + $length = 0; + while (true) { + $read = $cli->recv(); + if (empty($read)) { + var_dump($read); + break; + } + $content .= $read; + if ($length == 0) { + if (preg_match('#Content-Length: (\d+)#i', $content, $match)) { + $length = intval($match[1]); + } + } + $header_length = strpos($content, "\r\n\r\n"); + if (strlen($content) == $length + $header_length + 4) { + break; + } + } + + if ($cb) { + $cb($cli, $content); + } + $cli->close(); + return $content; +} diff --git a/tests/include/api/swoole_client/connect_twice.php b/tests/include/api/swoole_client/connect_twice.php index 4eb522cb4f..7aa951c031 100644 --- a/tests/include/api/swoole_client/connect_twice.php +++ b/tests/include/api/swoole_client/connect_twice.php @@ -2,17 +2,17 @@ $start = microtime(true); -$cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC); -$cli->on("connect", function(Swoole\Client $cli) { +$cli = new Swoole\Async\Client(SWOOLE_SOCK_TCP); +$cli->on("connect", function(Swoole\Async\Client $cli) { Assert::true(false, 'never here'); }); -$cli->on("receive", function(Swoole\Client $cli, $data) { +$cli->on("receive", function(Swoole\Async\Client $cli, $data) { Assert::true(false, 'never here'); }); -$cli->on("error", function(Swoole\Client $cli) { +$cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); -$cli->on("close", function(Swoole\Client $cli) { +$cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); diff --git a/tests/include/api/swoole_client/http_get.php b/tests/include/api/swoole_client/http_get.php new file mode 100644 index 0000000000..da81bdf113 --- /dev/null +++ b/tests/include/api/swoole_client/http_get.php @@ -0,0 +1,21 @@ +connect('httpbin.org', 80, 10)); + Assert::assert($client->send("GET / HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n")); + + $resp = ''; + while (true) { + $data = $client->recv(); + if ($data === '' || $data === false) { + break; + } + $resp .= $data; + } + + Assert::assert(str_starts_with($resp, 'HTTP/1.1 200 OK')); + Assert::assert(str_contains($resp, 'https://github.com/requests/httpbin')); +} diff --git a/tests/include/api/swoole_server/opcode_server.php b/tests/include/api/swoole_server/opcode_server.php index 5d6fa362c0..19513d4eba 100644 --- a/tests/include/api/swoole_server/opcode_server.php +++ b/tests/include/api/swoole_server/opcode_server.php @@ -110,6 +110,22 @@ public function onReceive(Swoole\Server $swooleServer, $fd, $fromReactorId, $rec Assert::true($r); return; + case "shutdown": + $r = $swooleServer->shutdown(); + assert($r !== false); + Assert::true($r); + $r = $swooleServer->send($fd, opcode_encode("return", $r)); + Assert::true($r); + return; + + case "stop": + $r = $swooleServer->stop(); + assert($r !== false); + Assert::true($r); + $r = $swooleServer->send($fd, opcode_encode("return", $r)); + Assert::true($r); + return; + default: if (method_exists($swooleServer, $op)) { $r = call_user_func_array([$swooleServer, $op], $args); @@ -117,6 +133,7 @@ public function onReceive(Swoole\Server $swooleServer, $fd, $fromReactorId, $rec $r = true; } $r = $swooleServer->send($fd, opcode_encode("return", $r)); + Assert::true($r); return; } else { diff --git a/tests/include/api/swoole_server/tcp_serv.php b/tests/include/api/swoole_server/tcp_serv.php index 73f2f0ae32..e7156e6abf 100644 --- a/tests/include/api/swoole_server/tcp_serv.php +++ b/tests/include/api/swoole_server/tcp_serv.php @@ -10,7 +10,9 @@ class TcpServer public function __construct() { - $this->swooleServer = new Swoole\Server('127.0.0.1', 9001, SWOOLE_PROCESS, SWOOLE_SOCK_TCP); + global $argv; + + $this->swooleServer = new Swoole\Server($argv[1], $argv[2], SWOOLE_PROCESS, SWOOLE_SOCK_TCP); $this->swooleServer->set([ // "output_buffer_size" => 1024 * 1024 * 1024, // 输出限制 diff --git a/tests/include/api/swoole_thread/putenv.php b/tests/include/api/swoole_thread/putenv.php new file mode 100644 index 0000000000..b82fee992b --- /dev/null +++ b/tests/include/api/swoole_thread/putenv.php @@ -0,0 +1,6 @@ + 1, 'disable_dns_cache' => true, 'dns_lookup_random' => true, ]); + +// Run default remote object server +swoole_library_set_option('default_remote_object_server_worker_num', 2); +swoole_init_default_remote_object_server(); + Co::set([ 'socket_timeout' => 5 ]); + if (empty(getenv('SWOOLE_DEBUG'))) { Co::set([ 'log_level' => SWOOLE_LOG_INFO, @@ -34,6 +39,20 @@ ]); } +$traceFlags = getenv('SWOOLE_TRACE_FLAGS'); +if ($traceFlags) { + $_traceFlags = 0; + if (is_numeric($traceFlags)) { + $_traceFlags = intval($traceFlags); + } else { + eval('$_traceFlags = ' . $traceFlags . ';'); + } + Co::set([ + 'log_level' => 0, + 'trace_flags' => $_traceFlags + ]); +} + // Components require __DIR__ . '/lib/vendor/autoload.php'; diff --git a/tests/include/config.php b/tests/include/config.php index 6f4b3ce2c0..64231d5c90 100644 --- a/tests/include/config.php +++ b/tests/include/config.php @@ -1,40 +1,59 @@ &1 | grep httpbin 2>&1`) && - preg_match('/\s+?[^:]+:(\d+)->\d+\/tcp\s+/', $info, $matches) && - is_numeric($matches[1])) { - define('HTTPBIN_SERVER_PORT_IN_DOCKER', (int)$matches[1]); +if (getenv('SWOOLE_TEST_IN_DOCKER')) { + if (!empty($info = shell_exec('docker ps 2>&1 | grep httpbin 2>&1')) + && preg_match('/\s+?[^:]+:(\d+)->\d+\/tcp\s+/', $info, $matches) + && is_numeric($matches[1])) { + define('HTTPBIN_SERVER_PORT_IN_DOCKER', (int) $matches[1]); } } -/** ============== HttpBin ============== */ -if (IS_IN_TRAVIS) { +/* ============== ODBC ============== */ +if (IS_IN_CI) { + define('ODBC_DSN', 'odbc:mysql-test'); +} else { + define('ODBC_DSN', 'odbc:mysql-test'); +} + +define('SWOOLE_TEST_ECHO', empty(getenv('SWOOLE_TEST_NO_ECHO'))); + +/* ============== Http ============== */ +if (IS_IN_CI && !IS_MAC_OS) { define('HTTPBIN_SERVER_HOST', 'httpbin'); define('HTTPBIN_SERVER_PORT', 80); define('HTTPBIN_LOCALLY', true); @@ -90,49 +159,68 @@ define('HTTPBIN_LOCALLY', true); } elseif (getenv('HTTPBIN_SERVER_HOST')) { define('HTTPBIN_SERVER_HOST', getenv('HTTPBIN_SERVER_HOST')); - define('HTTPBIN_SERVER_PORT', (int)getenv('HTTPBIN_SERVER_PORT')); + define('HTTPBIN_SERVER_PORT', (int) getenv('HTTPBIN_SERVER_PORT')); define('HTTPBIN_LOCALLY', true); } else { define('HTTPBIN_SERVER_HOST', 'httpbin.org'); define('HTTPBIN_SERVER_PORT', 80); } -if (IS_IN_TRAVIS) { +if (IS_IN_CI) { define('TEST_HTTP2_SERVERPUSH_URL', 'https://golang-h2demo:4430/serverpush'); define('TEST_NAME_RESOLVER', [ - 'class' => Swoole\NameResolver\Redis::class, + 'class' => Redis::class, 'server_url' => 'tcp://' . REDIS_SERVER_HOST . ':' . REDIS_SERVER_PORT, ]); } else { define('TEST_HTTP2_SERVERPUSH_URL', 'https://127.0.0.1:4430/serverpush'); define('TEST_NAME_RESOLVER', [ - 'class' => Swoole\NameResolver\Consul::class, + 'class' => Consul::class, 'server_url' => 'http://127.0.0.1:8500', ]); } -/** =============== IP ================ */ -define('IP_REGEX', '/^(?:[\d]{1,3}\.){3}[\d]{1,3}$/'); +if (IS_IN_CI) { + define('TEST_DOMAIN_1', 'www.google.com'); + define('TEST_DOMAIN_2', 'www.yahoo.com'); +} else { + define('TEST_DOMAIN_1', 'www.baidu.com'); + define('TEST_DOMAIN_2', 'www.qq.com'); +} -/** ============= Proxy ============== */ -define('HTTP_PROXY_HOST', IS_IN_TRAVIS ? 'tinyproxy' : '127.0.0.1'); -define('HTTP_PROXY_PORT', IS_IN_TRAVIS ? 8888 : (IS_MAC_OS ? 1087 : 8888)); -define('SOCKS5_PROXY_HOST', IS_IN_TRAVIS ? 'socks5' : '127.0.0.1'); -define('SOCKS5_PROXY_PORT', IS_MAC_OS ? 1086 : 1080); +define('TEST_DOMAIN_3', 'www.gov.cn'); +define('TEST_MAX_CPU_EXEC_DURATION', 12); // msec -/** ============== Pressure ============== */ +/* =============== IP ================ */ +define('IP_REGEX', '/^(?:[\d]{1,3}\.){3}[\d]{1,3}$/'); + +/* ============= Proxy ============== */ +define('HTTP_PROXY_HOST', IS_IN_CI ? 'tinyproxy' : '127.0.0.1'); +define('HTTP_PROXY_PORT', IS_IN_CI ? 8888 : 1080); +define('SOCKS5_PROXY_HOST', IS_IN_CI ? 'socks5' : '127.0.0.1'); +define('SOCKS5_PROXY_PORT', 1080); + +/* ============== Pressure ============== */ define('PRESSURE_LOW', 1); define('PRESSURE_MID', 2); define('PRESSURE_NORMAL', 3); -define('PRESSURE_LEVEL', - USE_VALGRIND ? (IS_IN_TRAVIS ? PRESSURE_LOW - 1 : PRESSURE_LOW) : ((IS_IN_TRAVIS || swoole_cpu_num() === 1) ? PRESSURE_MID : PRESSURE_NORMAL)); -/** ============== Time ============== */ +if (IS_MAC_OS) { + define('PRESSURE_LEVEL', 1); +} else { + define( + 'PRESSURE_LEVEL', + USE_VALGRIND ? (IS_IN_CI ? PRESSURE_LOW - 1 : PRESSURE_LOW) : ((IS_IN_CI || swoole_cpu_num() === 1) ? PRESSURE_MID : PRESSURE_NORMAL) + ); +} + + +/* ============== Time ============== */ define('SERVER_PREHEATING_TIME', 0.1); define('REQUESTS_WAIT_TIME', [0.005, 0.005, 0.05, 0.1][PRESSURE_LEVEL]); -/** ============== Times ============== */ +/* ============== Times ============== */ define('MAX_CONCURRENCY', [16, 32, 64, 256][PRESSURE_LEVEL]); define('MAX_CONCURRENCY_MID', [8, 16, 32, 128][PRESSURE_LEVEL]); define('MAX_CONCURRENCY_LOW', [4, 8, 16, 64][PRESSURE_LEVEL]); @@ -142,3 +230,10 @@ define('MAX_LOOPS', [12, 24, 100, 1000][PRESSURE_LEVEL] * 1000); define('MAX_PROCESS_NUM', [2, 4, 6, 8][PRESSURE_LEVEL]); define('MAX_PACKET_NUM', [1024, 2048, 4096, 10000][PRESSURE_LEVEL]); + +/* ============== FTP ============== */ +define('FTP_HOST', IS_IN_CI ? 'ftp' : '127.0.0.1'); +define('FTP_PORT', 21); +define('FTP_USER', 'admin'); +define('FTP_PASS', 'admin'); +define('FTP_TEST_FILE', IS_IN_CI ? 'test.txt' : 'work/test.txt'); diff --git a/tests/include/functions.php b/tests/include/functions.php index 523c96d99e..b7c88ef237 100644 --- a/tests/include/functions.php +++ b/tests/include/functions.php @@ -1,44 +1,63 @@ /dev/null 2>&1`; + shell_exec("ps -A | grep php | grep -v phpstorm | grep -v 'run-tests' | awk '{print $1}' | xargs kill -9 > /dev/null 2>&1"); } -function puts($msg) { - echo $msg."\n"; +function puts($msg) +{ + echo $msg . "\n"; } function top(int $pid) { static $available; - $available = $available ?? !(IS_MAC_OS || empty(`top help 2>&1 | grep -i usage`)); + $available = $available ?? !(IS_MAC_OS || empty(shell_exec('top help 2>&1 | grep -i usage'))); if (!$available) { return false; } - do { - $top = @`top -b -n 1 -p {$pid}`; + while (true) { + $top = @shell_exec("top -b -n 1 -p {$pid}"); if (empty($top)) { trigger_error("top {$pid} failed: " . swoole_strerror(swoole_errno()), E_USER_WARNING); return false; - } else { - break; } - } while (true); + break; + } $top = explode("\n", $top); $top = array_combine(preg_split('/\s+/', trim($top[6])), preg_split('/\s+/', trim($top[7]))); return $top; @@ -47,7 +66,7 @@ function top(int $pid) function is_busybox_ps(): bool { static $bool; - $bool = $bool ?? !empty(`ps --help 2>&1 | grep -i busybox`); + $bool = $bool ?? !empty(shell_exec('ps --help 2>&1 | grep -i busybox')); return $bool; } @@ -56,42 +75,46 @@ function kill_process_by_name(string $name) shell_exec('ps aux | grep "' . $name . '" | grep -v grep | awk \'{ print $' . (is_busybox_ps() ? '1' : '2') . '}\' | xargs kill'); } -function get_process_pid_by_name(string $name): bool +function get_process_pid_by_name(string $name): int { - return (int)shell_exec('ps aux | grep "' . $name . '" | grep -v grep | awk \'{ print $' . (is_busybox_ps() ? '1' : '2') . '}\''); + return (int) shell_exec('ps aux | grep "' . $name . '" | grep -v grep | awk \'{ print $' . (is_busybox_ps() ? '1' : '2') . '}\''); } function is_musl_libc(): bool { static $bool; - $bool = $bool ?? !empty(`ldd 2>&1 | grep -i musl`); + $bool = $bool ?? !empty(shell_exec('ldd 2>&1 | grep -i musl')); return $bool; } function get_one_free_port(): int { - $hookFlags = Swoole\Runtime::getHookFlags(); - Swoole\Runtime::enableCoroutine(false); - $server = @stream_socket_server('tcp://127.0.0.1:0'); - if (!$server) { - $port = -1; - } else { - $name = stream_socket_get_name($server, false); - if (empty($name)) { - $port = -1; - } - else { - $port = (parse_url($name)['port'] ?? -1) ?: -1; - } - } - Swoole\Runtime::enableCoroutine($hookFlags); + /** + * The Swoole coroutine socket delays releasing file descriptors (fd), + * which prevents ports from being released immediately. + * Therefore, it is essential to disable runtime hooks. + */ + $flags = Runtime::getHookFlags(); + Runtime::enableCoroutine(0); + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or exit('Unable to create socket: ' . socket_strerror(socket_last_error()) . PHP_EOL); + socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) or exit('Unable to set socket option: ' . socket_strerror(socket_last_error()) . PHP_EOL); + socket_set_option($socket, SOL_SOCKET, SO_REUSEPORT, 1) or exit('Unable to set socket option: ' . socket_strerror(socket_last_error()) . PHP_EOL); + socket_bind($socket, '127.0.0.1', 0) or exit('Unable to bind socket: ' . socket_strerror(socket_last_error()) . PHP_EOL); + socket_getsockname($socket, $addr, $port); + socket_close($socket); + Runtime::enableCoroutine($flags); return $port; } +function get_constant_port(string $str, int $base = 9500): int +{ + return $base + crc32($str) % 10000; +} + function get_one_free_port_ipv6(): int { - $hookFlags = Swoole\Runtime::getHookFlags(); - Swoole\Runtime::enableCoroutine(false); + $hookFlags = Runtime::getHookFlags(); + Runtime::enableCoroutine(0); $server = @stream_socket_server('tcp://[::1]:0'); if (!$server) { $port = -1; @@ -99,17 +122,16 @@ function get_one_free_port_ipv6(): int $name = stream_socket_get_name($server, false); if (empty($name)) { $port = -1; - } - else { + } else { $port = explode(']:', $name)[1]; } } - Swoole\Runtime::enableCoroutine($hookFlags); + Runtime::enableCoroutine($hookFlags); return $port; } -function set_socket_coro_buffer_size(Swoole\Coroutine\Socket $cosocket, int $size) +function set_socket_coro_buffer_size(Socket $cosocket, int $size) { $cosocket->setOption(SOL_SOCKET, SO_SNDBUF, $size); $cosocket->setOption(SOL_SOCKET, SO_RCVBUF, $size); @@ -131,7 +153,7 @@ function time_approximate($expect, $actual, float $ratio = 0.1) function ms_random(float $a, float $b): float { - return mt_rand($a * 1000, $b * 1000) / 1000; + return mt_rand(intval($a * 1000), intval($b * 1000)) / 1000; } function string_pop_front(string &$s, int $length): string @@ -148,10 +170,18 @@ function array_random(array $array) function phpt_echo(...$args) { + if (!SWOOLE_TEST_ECHO) { + return; + } global $argv; if (substr($argv[0], -5) === '.phpt') { foreach ($args as $arg) { - echo $arg; + if (!is_string($arg)) { + var_export($arg); + echo PHP_EOL; + } else { + echo $arg; + } } } } @@ -159,18 +189,27 @@ function phpt_echo(...$args) function phpt_var_dump(...$args) { global $argv; - if (substr($argv[0], -5) === '.phpt') { + if (str_ends_with($argv[0], '.phpt')) { var_dump(...$args); } } +function phpt_show_usage() +{ + global $argv; + if (str_ends_with($argv[0], '.phpt')) { + var_dump('memory:' . memory_get_usage()); + var_dump('coroutine:' . var_export(co::stats(), true)); + } +} + function httpPost($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $res = curl_exec($ch); @@ -185,12 +224,12 @@ function httpRequest(string $uri, array $options = []) $domain = $url_info['host'] ?? '127.0.0.1'; $path = $url_info['path'] ?? null ?: '/'; $query = $url_info['query'] ?? null ? "?{$url_info['query']}" : ''; - $port = (int)($url_info['port'] ?? null ?: 80); + $port = (int) ($url_info['port'] ?? null ?: 80); $http2 = $options['http2'] ?? false; $connect_args = [$domain, $port, $scheme === 'https' || $port === 443]; if ($http2) { $cli = new Swoole\Coroutine\Http2\Client(...$connect_args); - $request = new Swoole\Http2\Request; + $request = new Request(); } else { $cli = new Swoole\Coroutine\Http\Client(...$connect_args); $request = null; @@ -239,28 +278,27 @@ function httpRequest(string $uri, array $options = []) 'statusCode' => $response->statusCode, 'headers' => $response->headers, 'set_cookie_headers' => $response->set_cookie_headers, - 'body' => $response->data + 'body' => $response->data, ]; - } else { - $redirect_times = $options['redirect'] ?? 3; - while (true) { - if (!$cli->execute($path . $query)) { - throw new RuntimeException("HTTP execute {$uri} failed: {$cli->errMsg}"); - } - if ($redirect_times-- && ($cli->headers['location'] ?? null) && $cli->headers['location'][0] === '/') { - $path = $cli->headers['location']; - $query = ''; - continue; - } - break; + } + $redirect_times = $options['redirect'] ?? 3; + while (true) { + if (!$cli->execute($path . $query)) { + throw new RuntimeException("HTTP execute {$uri} failed: {$cli->errMsg}"); } - return [ - 'statusCode' => $cli->statusCode, - 'headers' => $cli->headers, - 'set_cookie_headers' => $cli->set_cookie_headers, - 'body' => $cli->body - ]; + if ($redirect_times-- && ($cli->headers['location'] ?? null) && $cli->headers['location'][0] === '/') { + $path = $cli->headers['location']; + $query = ''; + continue; + } + break; } + return [ + 'statusCode' => $cli->statusCode, + 'headers' => $cli->headers, + 'set_cookie_headers' => $cli->set_cookie_headers, + 'body' => $cli->body, + ]; } function httpGetStatusCode(string $uri, array $options = []) @@ -281,7 +319,7 @@ function httpGetBody(string $uri, array $options = []) function content_hook_replace(string $content, array $kv_map): string { foreach ($kv_map as $key => $val) { - $content = str_replace("{{{$key}}}", $val, $content); + $content = str_replace("{{{$key}}}", (string) $val, $content); } return $content; } @@ -307,13 +345,12 @@ function tcp_type_length(string $type = 'n'): int $map = tcp_length_types(); if (strlen($type) === 1) { return $map[$type] ?? 0; - } else { - $len = 0; - for ($n = 0; $n < strlen($type); $n++) { - $len += $map[$type[$n]] ?? 0; - } - return $len; } + $len = 0; + for ($n = 0; $n < strlen($type); $n++) { + $len += $map[$type[$n]] ?? 0; + } + return $len; } function tcp_head(int $length, string $type = 'n'): string @@ -363,10 +400,10 @@ function get_big_random(int $length = 1024 * 1024) return str_repeat(get_safe_random(1024), $length / 1024); } -function makeCoTcpClient($host, $port, callable $onConnect = null, callable $onReceive = null) +function makeCoTcpClient($host, $port, ?callable $onConnect = null, ?callable $onReceive = null) { go(function () use ($host, $port, $onConnect, $onReceive) { - $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); assert($cli->set([ 'open_length_check' => 1, 'package_length_type' => 'N', @@ -390,7 +427,7 @@ function opcode_encode($op, $data) { $r = json_encode([$op, $data]); Assert::same(json_last_error(), JSON_ERROR_NONE); - return pack("N", strlen($r) + 4) . $r; + return pack('N', strlen($r) + 4) . $r; } function opcode_decode($raw) @@ -404,7 +441,7 @@ function opcode_decode($raw) function kill_self_and_descendant($pid) { - if (PHP_OS === "Darwin") { + if (PHP_OS === 'Darwin') { return; } $pids = findDescendantPids($pid); @@ -416,20 +453,18 @@ function kill_self_and_descendant($pid) /** * fork 一个进程把父进程pid通过消息队列传给子进程,延时把父进程干掉 - * @param int $after - * @param int $sig */ -function killself_in_syncmode($lifetime = 1000, $sig = SIGKILL) +function killself_in_syncmode(int $lifetime = 1000, int $sig = SIGKILL): void { - $proc = new Swoole\Process(function (Swoole\Process $proc) use ($lifetime, $sig) { + $proc = new Process(function (Process $proc) use ($lifetime, $sig) { $pid = $proc->pop(); $proc->freeQueue(); usleep($lifetime * 1000); - Swoole\Process::kill($pid, $sig); + Process::kill((int) $pid, $sig); $proc->exit(); }, true); $proc->useQueue(); - $proc->push(posix_getpid()); + $proc->push((string) posix_getpid()); $proc->start(); } @@ -437,16 +472,15 @@ function killself_in_syncmode($lifetime = 1000, $sig = SIGKILL) * 异步模式用定时器干掉自己 * @param int $lifetime * @param int $sig - * @param callable $cb * @return mixed */ -function suicide($lifetime, $sig = SIGKILL, callable $cb = null) +function suicide($lifetime, $sig = SIGKILL, ?callable $cb = null) { - return Swoole\Timer::after($lifetime, function () use ($lifetime, $sig, $cb) { + return Timer::after($lifetime, function () use ($lifetime, $sig, $cb) { if ($cb) { $cb(); } - echo "suicide after $lifetime ms\n"; + echo "suicide after {$lifetime} ms\n"; posix_kill(posix_getpid(), $sig); }); } @@ -454,56 +488,57 @@ function suicide($lifetime, $sig = SIGKILL, callable $cb = null) // 查找某pid的所有子孙pid function findDescendantPids($pid) { - list($pinfo,) = pstree(); + [$pinfo] = pstree(); $y = function ($pid) use (&$y, $pinfo) { if (isset($pinfo[$pid])) { - list(, $childs) = $pinfo[$pid]; + [, $childs] = $pinfo[$pid]; $pids = $childs; foreach ($childs as $child) { $pids = array_merge($pids, $y($child)); } return $pids; - } else { - return []; } + return []; }; return $y($pid); } /** * @return array [pinfo, tree] - * tree [ - * ppid - * [...child pids] - * ] - * list(ppid, array childs) = tree[pid] + * tree [ + * ppid + * [...child pids] + * ] + * list(ppid, array childs) = tree[pid] */ function pstree() { $pinfo = []; - $iter = new DirectoryIterator("/proc"); + $iter = new DirectoryIterator('/proc'); foreach ($iter as $item) { $pid = $item->getFilename(); if ($item->isDir() && ctype_digit($pid)) { - $stat = file_get_contents("/proc/$pid/stat"); - $info = explode(" ", $stat); - $pinfo[$pid] = [intval($info[3]), []/*, $info*/]; + $stat = file_get_contents("/proc/{$pid}/stat"); + $info = explode(' ', $stat); + $pinfo[$pid] = [intval($info[3]), []/* , $info */]; } } foreach ($pinfo as $pid => $info) { - list($ppid,) = $info; + [$ppid] = $info; $ppid = intval($ppid); $pinfo[$ppid][1][] = $pid; } $y = function ($pid, $path = []) use (&$y, $pinfo) { if (isset($pinfo[$pid])) { - list($ppid,) = $pinfo[$pid]; - $ppid = $ppid; + if (isset($pinfo[$pid][0])) { + [$ppid] = $pinfo[$pid]; + } else { + $ppid = null; + } $path[] = $pid; return $y($ppid, $path); - } else { - return array_reverse($path); } + return array_reverse($path); }; $tree = []; foreach ($pinfo as $pid => $info) { @@ -527,9 +562,9 @@ function debug_log($str, $handle = STDERR) $tpl = "[%d %s] %s\n"; } if (is_resource($handle)) { - fprintf($handle, $tpl, posix_getpid(), date("Y-m-d H:i:s", time()), $str); + fprintf($handle, $tpl, posix_getpid(), date('Y-m-d H:i:s', time()), $str); } else { - printf($tpl, posix_getpid(), date("Y-m-d H:i:s", time()), $str); + printf($tpl, posix_getpid(), date('Y-m-d H:i:s', time()), $str); } } @@ -551,21 +586,20 @@ function arrayEqual(array $a, array $b, $strict = true) } } return true; - } else { - $aks = array_keys($a); - $bks = array_keys($b); - sort($aks); - sort($bks); - return $aks === $bks; } + $aks = array_keys($a); + $bks = array_keys($b); + sort($aks); + sort($bks); + return $aks === $bks; } function check_tcp_port(string $host, int $port): bool { - return !!@fsockopen($host, $port); + return (bool) @fsockopen($host, $port); } -function start_server($file, $host, $port, $redirect_file = "/dev/null", $ext1 = null, $ext2 = null, $debug = false) +function start_server($file, $host, $port, $redirect_file = '/dev/null', $ext1 = null, $ext2 = null, $debug = false) { $php_executable = getenv('TEST_PHP_EXECUTABLE') ?: PHP_BINARY; $cmd_args = getenv('TEST_PHP_ARGS'); @@ -584,27 +618,27 @@ function start_server($file, $host, $port, $redirect_file = "/dev/null", $ext1 = }*/ // 必须加exec, 否咋proc_terminate结束不了server进程 !!!!!! if ($debug) { - $cmd = "exec $php_executable $file $host $port $ext1 $ext2"; - echo "[SHELL_EXEC]" . $cmd . "\n"; + $cmd = "exec {$php_executable} {$file} {$host} {$port} {$ext1} {$ext2}"; + echo '[SHELL_EXEC]' . $cmd . "\n"; } else { - $cmd = "exec $php_executable $file $host $port $ext1 $ext2 > $redirect_file 2>&1"; + $cmd = "exec {$php_executable} {$file} {$host} {$port} {$ext1} {$ext2} > {$redirect_file} 2>&1"; } // $cmd = "exec $php_executable $file $host $port"; $handle = proc_open($cmd, $fdSpec, $pipes); if ($handle === false) { - exit(__FUNCTION__ . " fail"); + exit(__FUNCTION__ . ' fail'); } make_sure_server_listen_success: - { + $i = 0; - $fp = null; - while (($i++ < 30) && !($fp = @fsockopen($host, $port))) { - usleep(10000); - } - if ($fp) { - fclose($fp); - } + $fp = null; + while (($i++ < 30) && !($fp = @fsockopen($host, $port))) { + usleep(10000); + } + if ($fp) { + fclose($fp); } + // linux上有问题,client端事件循环还没起起来就会先调用这个shutdown回调, 结束了子进程 // 第二个shutdown_function swoole才会把子进程的事件循环起来 // register_shutdown_function(function() use($handle, $redirect_file) { @@ -615,32 +649,32 @@ function start_server($file, $host, $port, $redirect_file = "/dev/null", $ext1 = return function () use ($handle, $redirect_file) { // @unlink($redirect_file); proc_terminate($handle, SIGTERM); - Swoole\Event::exit(); + Event::exit(); exit; }; } function swoole_fork_exec(callable $fn, bool $redirect_stdin_and_stdout = false, int $pipe_type = SOCK_DGRAM, bool $enable_coroutine = false) { - $process = new Swoole\Process(...func_get_args()); + $process = new Process(...func_get_args()); if (!$process->start()) { return false; } return $process::wait(); } -function php_fork_exec(callable $fn, $f_stdout = "/dev/null", $f_stderr = null) +function php_fork_exec(callable $fn, $f_stdout = '/dev/null', $f_stderr = null) { $pid = pcntl_fork(); if ($pid < 0) { - exit("fork fail"); + exit('fork fail'); } if ($pid === 0) { fclose(STDOUT); - $STDOUT = fopen($f_stdout, "w"); + $STDOUT = fopen($f_stdout, 'w'); if ($f_stderr !== null) { fclose(STDERR); - $STDERR = fopen($f_stderr, "w"); + $STDERR = fopen($f_stderr, 'w'); } $fn(); exit; @@ -656,17 +690,17 @@ function php_fork_exec(callable $fn, $f_stdout = "/dev/null", $f_stderr = null) * @param null|int $tv_sec timeout sec * @param null|int $tv_usec timeout usec * @param null|string $cwd change work dir - * @param array|null $env env + * @param null|array $env env * @return array [out, err] */ -function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, array $env = null) +function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = null, ?array $env = null) { $out = $err = null; $winOpt = ['suppress_errors' => true, 'binary_pipes' => true]; $proc = proc_open($cmd, [ - 0 => ["pipe", "r"], - 1 => ["pipe", "w"], - 2 => ["pipe", "w"], + 0 => ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], ], $pipes, $cwd, $env, $winOpt); assert($proc !== false); if ($input !== null) { @@ -684,39 +718,35 @@ function spawn_exec($cmd, $input = null, $tv_sec = null, $tv_usec = null, $cwd = $r = $pipes; $w = null; $e = null; - /* 隐藏被信号或者其他系统调用打断 产生的错误*/ - set_error_handler(function () { - }); + /* 隐藏被信号或者其他系统调用打断 产生的错误 */ + set_error_handler(function () {}); $n = @stream_select($r, $w, $e, $tv_sec, $tv_usec); restore_error_handler(); if ($n === false) { break; - } else { - if ($n === 0) { - // 超时kill -9 - assert(proc_terminate($proc, SIGKILL)); - throw new \RuntimeException("exec $cmd time out"); - } else { - if ($n > 0) { - foreach ($r as $handle) { - if ($handle === $pipes[1]) { - $_ = &$out; - } else { - if ($handle === $pipes[2]) { - $_ = &$err; - } else { - $_ = ""; - } - } - $line = fread($handle, 8192); - $isEOF = $line === ""; - if ($isEOF) { - break 2; - } else { - $_ .= $line; - } + } + if ($n === 0) { + // 超时kill -9 + assert(proc_terminate($proc, SIGKILL)); + throw new RuntimeException("exec {$cmd} time out"); + } + if ($n > 0) { + foreach ($r as $handle) { + if ($handle === $pipes[1]) { + $_ = &$out; + } else { + if ($handle === $pipes[2]) { + $_ = &$err; + } else { + $_ = ''; } } + $line = fread($handle, 8192); + $isEOF = $line === ''; + if ($isEOF) { + break 2; + } + $_ .= $line; } } } @@ -734,20 +764,19 @@ function parent_child($parentFunc, $childFunc) { $pid = pcntl_fork(); if ($pid < 0) { - echo "ERROR"; + echo 'ERROR'; exit; } if ($pid === 0) { $childFunc(); exit; - } else { - $parentFunc($pid); } + $parentFunc($pid); } -function readfile_with_lock($file) +function readfile_with_lock($file): string { - $fp = fopen($file, "r+"); + $fp = fopen($file, 'r+'); flock($fp, LOCK_SH); $data = ''; while (!feof($fp)) { @@ -759,7 +788,7 @@ function readfile_with_lock($file) function dump_to_file($file, $data) { - $fp = fopen($file, "w+"); + $fp = fopen($file, 'w+'); $out = bin2hex($data); $lines = str_split($out, 160); foreach ($lines as $l) { @@ -768,7 +797,7 @@ function dump_to_file($file, $data) fclose($fp); } -function curl_type_assert($ch, $resource_type, $class_type) +function curl_type_assert($ch, $resource_type, $class_type): void { if (PHP_VERSION_ID >= 80000) { Assert::isInstanceOf($ch, $class_type); @@ -777,36 +806,37 @@ function curl_type_assert($ch, $resource_type, $class_type) } } -function swoole_get_variance($avg, $array, $is_swatch = false) +function swoole_get_variance($avg, $array, $is_swatch = false): bool|float { $count = count($array); - if ($count == 1 && $is_swatch == true) { + if ($count == 1 && $is_swatch) { return false; - } elseif ($count > 0) { + } + if ($count > 0) { $total_var = 0; foreach ($array as $lv) { - $total_var += pow(($lv - $avg), 2); + $total_var += pow($lv - $avg, 2); } - if ($count == 1 && $is_swatch == true) { + if ($count == 1 && $is_swatch) { return false; } return $is_swatch ? sqrt($total_var / (count($array) - 1)) : sqrt($total_var / count($array)); - } else { - return false; } + return false; } -function swoole_get_average($array) +function swoole_get_average($array): float|int { return array_sum($array) / count($array); } -function assert_server_stats($stats) { +function assert_server_stats($stats): void +{ Assert::keyExists($stats, 'connection_num'); Assert::keyExists($stats, 'request_count'); } -function assert_upload_file($file, $tmp_name, $name, $type, $size, $error = 0) +function assert_upload_file($file, $tmp_name, $name, $type, $size, $error = 0): void { Assert::notEmpty($file); Assert::eq($file['tmp_name'], $tmp_name); @@ -816,7 +846,7 @@ function assert_upload_file($file, $tmp_name, $name, $type, $size, $error = 0) Assert::eq($file['error'], $error); } -function swoole_loop_n($n, $fn) +function swoole_loop_n($n, $fn): void { for ($i = 0; $i < $n; $i++) { $fn($i); @@ -830,3 +860,27 @@ function swoole_loop($fn) $fn($i++); } } + +function build_ftp_url(string $path = ''): string +{ + return 'ftp://' . FTP_USER . ':' . FTP_PASS . '@' . FTP_HOST . ':' . FTP_PORT . '/' . $path; +} + +function get_thread_name(): string +{ + return trim(file_get_contents('/proc/' . posix_getpid() . '/task/' . Thread::getNativeId() . '/comm')); +} + +function mkdir_if_not_exists(string $string): void +{ + if (!is_dir($string)) { + mkdir($string, 0777, true); + } +} + +function array_arrange($array): array +{ + $list = array_keys(array_flip($array)); + sort($list); + return $list; +} diff --git a/tests/include/lib/composer.json b/tests/include/lib/composer.json index 4b76e7f145..634505deba 100644 --- a/tests/include/lib/composer.json +++ b/tests/include/lib/composer.json @@ -24,6 +24,13 @@ "psr/http-factory": "^1.0", "symfony/http-client": "^5.3", "nyholm/psr7": "^1.4", - "friendsofphp/php-cs-fixer": "^3.3" + "friendsofphp/php-cs-fixer": "^3.3", + "php-http/message-factory": "^1.1", + "predis/predis": "^2.2" + }, + "config": { + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/tests/include/lib/src/ChildProcess.php b/tests/include/lib/src/ChildProcess.php new file mode 100644 index 0000000000..dd4905f3b1 --- /dev/null +++ b/tests/include/lib/src/ChildProcess.php @@ -0,0 +1,33 @@ +process = new Process(function (Process $worker) use ($script) { + $worker->exec('/bin/sh', ['-c', $script]); + }, true, SOCK_STREAM, false); + $this->process->start(); + } + + public function read() + { + return $this->process->read(); + } + + public function write(string $data): void + { + $this->process->write($data); + } + + static function exec(string $script): ChildProcess + { + return new self($script); + } +} diff --git a/tests/include/lib/src/DbWrapper.php b/tests/include/lib/src/DbWrapper.php index 8309875b93..ad6921292e 100644 --- a/tests/include/lib/src/DbWrapper.php +++ b/tests/include/lib/src/DbWrapper.php @@ -15,8 +15,14 @@ class DbWrapper public function connect($config) { - $mysql = new MySQL([]); - $res = $mysql->connect($config); + $mysql = new \mysqli(); + $res = $mysql->connect( + $config['host'], + $config['user'], + $config['password'], + $config['database'], + $config['port'], + ); if (false === $res) { throw new RuntimeException($mysql->connect_error, $mysql->errno); @@ -31,20 +37,18 @@ public function connect($config) public function __call($name, $arguments) { // $result = $this->mysql->{$name}(...$arguments); - $result = call_user_func_array([$this->mysql, $name], $arguments); + // $result = call_user_func_array([$this->mysql, $name], $arguments); $result = $this->mysql->query($arguments[0]); if (false === $result) { if (!$this->mysql->connected) { - $this->mysql->connect($this->config); - + $this->connect($this->config); return call_user_func_array([$this->mysql, $name], $arguments); } - - if (!empty($this->mysql->errno)) { //有错误码,则抛出弃常 + if (!empty($this->mysql->errno)) { throw new RuntimeException($this->mysql->error, $this->mysql->errno); } } - return $result; + return $result->fetch_all(); } } diff --git a/tests/include/lib/src/MysqlPool.php b/tests/include/lib/src/MysqlPool.php index 00f2484901..153094157f 100644 --- a/tests/include/lib/src/MysqlPool.php +++ b/tests/include/lib/src/MysqlPool.php @@ -53,7 +53,7 @@ public function put($mySQL) public function get() { /** - * @var \Swoole\Coroutine\Mysql $mysql + * @var mysqli */ $mysql = $this->pool->pop($this->config['pool_get_timeout']); if ($mysql === false) { diff --git a/tests/include/lib/src/ProcessManager.php b/tests/include/lib/src/ProcessManager.php index 06ec400fe6..3c190cbe26 100644 --- a/tests/include/lib/src/ProcessManager.php +++ b/tests/include/lib/src/ProcessManager.php @@ -251,14 +251,19 @@ public function kill(bool $force = false) } } - public function initFreePorts(int $num = 1) + /** + * @param int $num + * @param int $increment Only used for constant port number, must be a constant + * @return void + */ + public function initFreePorts(int $num = 1, int $increment = 0): void { for ($i = $num; $i--;) { - $this->freePorts[] = $this->useConstantPorts ? (9500 + $num - $i + count($this->freePorts)) : get_one_free_port(); + $this->freePorts[] = $this->useConstantPorts ? (9500 + $num - $i + count($this->freePorts) + $increment) : get_one_free_port(); } } - public function initFreeIPv6Ports(int $num = 1) + public function initFreeIPv6Ports(int $num = 1): void { for ($i = $num; $i--;) { $this->freePorts[] = $this->useConstantPorts ? (9500 + $num - $i + count($this->freePorts)) : get_one_free_port_ipv6(); diff --git a/tests/include/lib/src/ThreadManager.php b/tests/include/lib/src/ThreadManager.php new file mode 100644 index 0000000000..9fbf19771b --- /dev/null +++ b/tests/include/lib/src/ThreadManager.php @@ -0,0 +1,20 @@ +parentFunc)(); + } else { + ($this->childFunc)(...$args); + } + } +} diff --git a/tests/include/skipif.inc b/tests/include/skipif.inc index eeb195d6da..1e2189856e 100644 --- a/tests/include/skipif.inc +++ b/tests/include/skipif.inc @@ -9,7 +9,7 @@ define('SWOOLE_COLOR_WHITE', 7); skip( 'Linux only', - substr(PHP_OS, 0, 3) === 'WIN', + str_starts_with(PHP_OS, 'WIN'), SWOOLE_COLOR_RED ); skip( @@ -26,8 +26,8 @@ skip( (function () { global $argv; skip_if_php_version_lower_than('7.0'); - if (!getenv('PHPT') && substr($argv[0], -4) === '.php') { - skip('please read ' . dirname(dirname(__FILE__)) . '/README.md and try again'); + if (!getenv('PHPT') && str_ends_with($argv[0], '.php')) { + skip('please read ' . dirname(__FILE__, 2) . '/README.md and try again'); } if (preg_match('#/(swoole_[^/]+)/#', $_SERVER['SCRIPT_FILENAME'], $dir)) { $dir = $dir[1]; @@ -45,7 +45,7 @@ function swoole_color(string $content, int $color): string function skip(string $reason, bool $is_skip = true, int $color = SWOOLE_COLOR_YELLOW) { if ($is_skip) { - exit('skip ' . swoole_color($reason, $color)); + exit('skip ' . swoole_color($reason, $color) . "\n"); } } @@ -56,6 +56,13 @@ function skip_if_php_version_lower_than($require_version = '7.0') } } +function skip_if_php_version_ge($require_version = '7.0') +{ + if (version_compare(PHP_VERSION, $require_version, '>=')) { + skip('need php version >= ' . $require_version); + } +} + function skip_if_php_version_between($a, $b) { if (version_compare(PHP_VERSION, $a, '>=') && version_compare(PHP_VERSION, $b, '<=')) { @@ -76,7 +83,14 @@ function skip_if_ini_bool_equal_to(string $name, bool $value) } } -function skip_if_constant_not_defined(string $constant_name) +function skip_if_no_nghttp(): void +{ + if (!str_contains(shell_exec("nghttp --version 2>&1"), 'nghttp2')) { + skip('no nghttp'); + } +} + +function skip_if_constant_not_defined(string $constant_name): void { require_once __DIR__ . '/config.php'; skip("{$constant_name} is not defined", !defined($constant_name)); @@ -104,7 +118,7 @@ function skip_if_file_not_exist(string $filename) function skip_if_command_not_found(string $command) { - skip("command {$command} not found", empty(`{$command} --help 2>&1 | grep -i usage`)); + skip("command {$command} not found", empty(shell_exec("{$command} --help 2>&1 | grep -i usage"))); } function skip_if_no_ssl() @@ -117,7 +131,7 @@ function skip_if_no_ssl() function skip_if_openssl_version_lower_than($version = '1.0.0') { skip_if_no_ssl(); - $exist = preg_match('/openssl => openssl ([\d\.]+)/i', `php --ri swoole`, $match); + $exist = preg_match('/openssl => openssl ([\d\.]+)/i', shell_exec("php --ri swoole"), $match); assert($exist); if (version_compare($match[1], $version, '<')) { skip("openssl version {$match[1]} is lower than {$version}"); @@ -138,8 +152,8 @@ function skip_if_no_ipv6() function skip_if_no_top() { skip_if_darwin(); - skip('top provided by busybox (not support)', !empty(`top --help 2>&1 | grep -i busybox`)); - skip('no top', empty(`top help 2>&1 | grep -i usage`)); + skip('top provided by busybox (not support)', !empty(shell_exec("top --help 2>&1 | grep -i busybox"))); + skip('no top', empty(shell_exec("top help 2>&1 | grep -i usage"))); } function skip_if_darwin() @@ -147,9 +161,29 @@ function skip_if_darwin() skip('not support on darwin', stripos(PHP_OS, 'Darwin') !== false); } +function skip_if_darwin_todo($msg = 'Need to review') +{ + skip($msg, getenv('MACOS_DEV') === false && stripos(PHP_OS, 'Darwin') !== false); +} + +function skip_if_not_darwin() +{ + skip('only support darwin', stripos(PHP_OS, 'Darwin') === false); +} + +function skip_if_nts() +{ + skip('not support in nts', !defined('SWOOLE_THREAD')); +} + +function skip_if_not_linux() +{ + skip('only support linux', PHP_OS !== 'Linux'); +} + function skip_if_musl_libc() { - skip('not support when use musl libc', !empty(`ldd 2>&1 | grep -i musl`)); + skip('not support when use musl libc', !empty(shell_exec("ldd 2>&1 | grep -i musl"))); } function skip_if_no_process_affinity() @@ -162,9 +196,9 @@ function skip_if_in_valgrind(string $reason = 'valgrind is too slow') skip($reason, getenv('USE_ZEND_ALLOC') === '0'); } -function skip_if_in_travis(string $reason = 'not support in travis') +function skip_if_in_ci(string $reason = 'not support in CI') { - skip($reason, file_exists('/.travisenv')); + skip($reason, !!getenv('GITHUB_ACTIONS') or file_exists('/.cienv')); } function skip_if_in_docker(string $reason = 'not support in docker') @@ -198,6 +232,7 @@ function skip_if_no_socks5_proxy() function skip_if_pdo_not_support_mysql8() { + skip_if_no_database(); require_once __DIR__ . '/config.php'; try { new PDO( @@ -211,8 +246,14 @@ function skip_if_pdo_not_support_mysql8() } } +function skip_if_no_database() +{ + skip("no database", !!getenv('SWOOLE_CI_IN_MACOS')); +} + function skip_if_not_mysql8() { + skip_if_no_database(); require_once __DIR__ . '/config.php'; $skip = true; Swoole\Coroutine\run(function () use (&$skip) { @@ -262,3 +303,23 @@ function skip_if_no_coroutine_get_execute_time() { skip('no Swoole\Coroutine::getExecuteTime', !method_exists(Swoole\Coroutine::class, 'getExecuteTime')); } + +function skip_if_no_ftp() +{ + require_once __DIR__ . '/config.php'; + skip('no available ftp server', !check_tcp_port(FTP_HOST, FTP_PORT)); +} + +function skip_if_not_root() +{ + skip('not root user', posix_geteuid() !== 0); +} + +function skip_if_no_iouring() { + skip('no io-uring supports', !defined('SWOOLE_IOURING_DEFAULT')); +} + +function skip_if_not_defined(string $name) +{ + skip("`$name` is not defined", !defined($name)); +} diff --git a/tests/include/api/ssl-ca/ca-cert.pem b/tests/include/ssl_certs/ca-cert.pem similarity index 100% rename from tests/include/api/ssl-ca/ca-cert.pem rename to tests/include/ssl_certs/ca-cert.pem diff --git a/tests/include/api/ssl-ca/ca-key.pem b/tests/include/ssl_certs/ca-key.pem similarity index 100% rename from tests/include/api/ssl-ca/ca-key.pem rename to tests/include/ssl_certs/ca-key.pem diff --git a/tests/include/api/ssl-ca/ca-req.csr b/tests/include/ssl_certs/ca-req.csr similarity index 100% rename from tests/include/api/ssl-ca/ca-req.csr rename to tests/include/ssl_certs/ca-req.csr diff --git a/tests/include/api/ssl-ca/client-cert.pem b/tests/include/ssl_certs/client-cert.pem similarity index 100% rename from tests/include/api/ssl-ca/client-cert.pem rename to tests/include/ssl_certs/client-cert.pem diff --git a/tests/include/ssl_certs/client-expired.crt b/tests/include/ssl_certs/client-expired.crt new file mode 100644 index 0000000000..c296388337 --- /dev/null +++ b/tests/include/ssl_certs/client-expired.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFtTCCA52gAwIBAgIBATANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJDTjEL +MAkGA1UECAwCTkUxCzAJBgNVBAoMAk5FMQ8wDQYDVQQLDAZSb290Q0ExDzANBgNV +BAMMBlJvb3RDQTAeFw0yMDAxMTYwMzA3MTlaFw0yMDAxMTYwMzA3MTlaMEgxCzAJ +BgNVBAYTAkNOMQswCQYDVQQIDAJORTELMAkGA1UECgwCTkUxCzAJBgNVBAsMAk5F +MRIwEAYDVQQDDAkxMjcuMC4wLjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQC/GbFTW4fv4YtzjPpVVdXbKTkJv+od8A6azyI9sWwl5nj1NXV4rLw28/ku +1xka5La67TQ5y44D4StGSsfLuVzehqWed9NqY2t+u/kE82TXyQ90OuabXZlUXfzV +T6U2o1l36ko97Q9MYws4izVX4vE8zsCdatNM74kyEsUbHJ3Gut2IKLM148D7UuQo +iUhwmmvFCCTL9YdW1HjEtbgg3mzg8XFtAA4mzKv+VgHe4VnAA/Fh1dPtNXpoa7IA +hheWS8DC2GN44/BegnBqG877tKfbooCyjBxkE9HKroj1y+f3FrGWCQGEAsHn6Za3 +61OQkYcjLQ2eZ+CC3rX0HBk+yY5S6hnTjQ7Z4aFBgsxnqg9WFICjXtI5RmkC6NoO +wnFr8DJ3MUEhDE957wjSNPirlvrLxX6kyONVKHsChvBKu0iFgpd5VlLKoCxqwOib +gEAJS960wkMVvY91LxDfp8waoAhT02S7ysiCll5aA4UyyHA4hOavOeHsv3hA0sq8 +HlbJ+duw/IHLGFlEXgBC/zVsKbVHliIU03oUBEEA0oimcDJGSClTzcPXL43xpqAg +kb3I4GPFLhu1r/IexeLoUrkA4KHx1LSLzXf+P9Y1VFGOrRvI7zMS8R8LryqGr7mF +xgOxcm2uS0i5/OTYYSk+mQVu609epLl5GgfHeNIQTQdnWHC6NQIDAQABo4GoMIGl +MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENl +cnRpZmljYXRlMB0GA1UdDgQWBBTNzUGh5F+06+7lm/pnTMcjKC/6ODAfBgNVHSME +GDAWgBRI2TJRKjGyUUSNv1C0TESpV5guwDALBgNVHQ8EBAMCBDAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQC33cEcngVk +N2lqPiZ0fuHh/Rs+LbUv6OWBIdhdHiojVaN4kq3Z0eIvZ5x1saXca9ff/rk4Brk6 +K+qntCWNg9DAZ9ahjhsXAeDQJE1l7999cCJxJRa6MoBTHwHejC7XAhpVEf95Gl3C +fNkDgEnBEisoy5/XL96TIOcZC/trkESXEKrPrKy+BwyikOaB1m3M2OFPFx+HomWO +w4ah4c8BNK/MIRb0zj4TJ2djxOsX1lXt5DC/2AACX2n1H6l/iVJ5oxwzNJSycX9M +/zAA0+lsedCiAiWZSkThinHbmu/c2QOmVIfwos/KlgXZTc7A36sYJ1wge8xYMR/0 +LV6PqVdoAmq8e9uUZFgyCl1Ojb4HojLRyuUUKYOV7ywODVcezNhk5z7ft2p3Kkgh +brQAd0crn3rmDoM8Uay/tovQgg84nIYWag6vpONDPR3XbQG3OvBTTdpsnphfMFvF +Clsam9jV8WGi+ZEg4ODnsE3eSbG1hBjEBQw7dKq5jPEZkedNyqTNGPhIOevYWITg +nOSESi4ksKroJJCAG3IRA7B10maKItK+NuDXKELze/Xj9uE1kfuEUYm9DnjuxPqI +pMLTLS/1Z0NmCuwATcRJrm9gXZGdpVvrjuS2Fc5SkqZTbcRIzSbUf2cE2OJAKyj7 +VkKhgAJRKDCNQZ6wru5nzGgs/zyxH20t0A== +-----END CERTIFICATE----- diff --git a/tests/include/ssl_certs/client-expired.csr b/tests/include/ssl_certs/client-expired.csr new file mode 100644 index 0000000000..6b10ec97b0 --- /dev/null +++ b/tests/include/ssl_certs/client-expired.csr @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBmzCCAQQCAQAwWzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw +DwYDVQQHEwhIYW5nemhvdTESMBAGA1UEChMJTXkgQ2xpZW50MRIwEAYDVQQDEwls +b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEUnBzXTTiyUmDb +yhkQoQ/yH1zTnuIk5Meg1Bp0fp1l4kwiizdPbZkk4YkTT/HXdTE6822Cqho+CwGE +VqWZyyd2AZmj87OGb4ZRCyyFzzjfEwdCTvyqZSUBoc1gvSGdEiaA4mXE87Y0XcMB +BasOrfmO76nuzyaXLT7xDjrB+Qw5AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQAx +rsaWSV81/SCf+0af57Wr+BJfiGEutZpdmIe0ofPKfVfz7c8QKjqK+/xQb0INUaYd +MUPjuLfvp06iCWyDPsfhsBRZMSDfFZDp8bnoVloVbP+yLL2Gd+h/a5iYjKTJ2FEt +mDaoIXqbw7oHXXxfKKLP2iyUQCqbfJTC0XeJtFWJ3w== +-----END CERTIFICATE REQUEST----- diff --git a/tests/include/ssl_certs/client-expired.key b/tests/include/ssl_certs/client-expired.key new file mode 100644 index 0000000000..d9ced60ef4 --- /dev/null +++ b/tests/include/ssl_certs/client-expired.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAvxmxU1uH7+GLc4z6VVXV2yk5Cb/qHfAOms8iPbFsJeZ49TV1 +eKy8NvP5LtcZGuS2uu00OcuOA+ErRkrHy7lc3oalnnfTamNrfrv5BPNk18kPdDrm +m12ZVF381U+lNqNZd+pKPe0PTGMLOIs1V+LxPM7AnWrTTO+JMhLFGxydxrrdiCiz +NePA+1LkKIlIcJprxQgky/WHVtR4xLW4IN5s4PFxbQAOJsyr/lYB3uFZwAPxYdXT +7TV6aGuyAIYXlkvAwthjeOPwXoJwahvO+7Sn26KAsowcZBPRyq6I9cvn9xaxlgkB +hALB5+mWt+tTkJGHIy0Nnmfggt619BwZPsmOUuoZ040O2eGhQYLMZ6oPVhSAo17S +OUZpAujaDsJxa/AydzFBIQxPee8I0jT4q5b6y8V+pMjjVSh7AobwSrtIhYKXeVZS +yqAsasDom4BACUvetMJDFb2PdS8Q36fMGqAIU9Nku8rIgpZeWgOFMshwOITmrznh +7L94QNLKvB5WyfnbsPyByxhZRF4AQv81bCm1R5YiFNN6FARBANKIpnAyRkgpU83D +1y+N8aagIJG9yOBjxS4bta/yHsXi6FK5AOCh8dS0i813/j/WNVRRjq0byO8zEvEf +C68qhq+5hcYDsXJtrktIufzk2GEpPpkFbutPXqS5eRoHx3jSEE0HZ1hwujUCAwEA +AQKCAgEAjiXhZn0GX+Qoo/ow17syiYRfXiY4Uq6XXoZHFRbduE94mAV87ReoJURT +jYkIrp2EYwVAvODyMWaTlwTXG241a31Cwt6lD1UGx82xDQKzd2OisDWEBfuYq04C +XPWSklLtoSpQsUGeCJb/6L58HnS8Nxwf4LhulqLzLaESpvkhT4r+cGK9848HpKri +9lgv3zqugXBuVpy7YFZJwyDc5u4slSJj2c2ZuTizk0uMtqpv5UnJipnQu3j1Jak+ +Te9wqiaNKyJk067RxKtITVWEIG0gE1sMWkOodrTbb+VmEVfsozppPI5UZ0rZy9VQ +fFx622W03PCNWdAxC4f4Vq10BeVNjIsF3w8l/ZUzKuUbMXhTGtIFLzK3jNpw+GMJ +fqhEgvJJldWrJkRYiqlP8nbzwlGL1IYQJ4kwZaRzoqMAEKZT5dJ886sF/fYDCAwz +0960gtX7m5TMrSOKxdEGX3lCqovYAkxgz9HdTVg1ITzshJE8A62wqTK18lnrzhXH +7efh6QcXDJMkXZL850RDM7Wq6j6SZqpN6DDAU7+4JssLaAWuyWMAoRqhWrASg4Hr +koVe+ctb8uydbKRGZMSkvXDXwPfXc811jzoxAsf6lJeJgl20+eUCofzxina5KqHA +vaYbul6IXhUKPjHhRywcUa2axstwRf9UNLakLsNjjDcQn1Y5fH0CggEBAPK+utiR +EWvRncRttDA7BHCb9uMd1iVz4ZsOZzbSqaW2vc9heHGMjhlDA4v7F7MHhkMxjD3S +0hjePGr+uwUM0jkHzN3/jRLgyPNqjalH9eubFQTaJuzm5MXEbJ7Y2GyXW9kEdz7E +25D4RO0VQwzQh/ND104uBWhJvsuHQLJMwfvVcPZjBUeCEwvIynUvhev1zIqaS0lb +ENxkEe2ipUK2v2e5+tVsU033VkhEVqAgHOcg8yYzREfbqEU5CO7r53XIfF+kTZN9 +Jfx035zwnBITD1WaUI/No/lwx/MlAzXnEyh5Ya9fHvcp0+Rp/mk6eamcEgkgiiKb +Ck4bfdX/4QSZFqsCggEBAMmJCQTfxSf0jm/zUVBvVXvLKqdUSf7h17dT/MbVfOEP +EScYTkHMcnyYe9NB/yNRaa5eBJi9Tfhg+Hd2DO82kaZxmSJwwjJfd1rADELyNQDa +xZLyIDFXngd8OVBIzKwSw2N6Dml8WWnS55LVXU3wsV+4SRNhqU0j47uPOtS3UoJu +u8bzmOdvUEb4kzo24oKZzlFgS1GlHUwA7GtxedVH++5oqB4fifcfigKt6xM7kddL +31G25RCVaj7jIiHHmMlmBPZkjO+E52ncSbH9gKuZtRww1ZTaM6Fk5YC5CrS8/kW9 +IqtW4sAe8inEbKk+m3edtWAHoe3uSIkltEEinqnK8p8CggEAFYnU3Fhq2k08GWy+ +ezURXMiJ7Hb+RE526yIpCFVC1kBTsS3t2O1mwlkO3IHIBYuaXyXrUqT9HVXCuJ+n +ingrwwGtLNl68/zb97Uxop6O9HvLLqYIpCaMj/uww5tsAJ6heK0Lw5cGAGP3Aa5M +R7vNBEm1teshcbJxam+yzt8+qxCyODprAoBJHlePlzxDPr+NiZLNMVISOevrJ1Gy +Jiw+6X44Am3dKmLS1hCyOvz+c4eZ637LBTE5SgMYhtKJtAq49meGtpp5H2BjYvJ6 +dqQYTTknAst1TDG0nB9hnIvHSGUUxNHE1ptwgKji+QVGPEVr2EF54D4veOS5kp5k +odrPhwKCAQAtyrhE5j9a68NthoDEYPewcCJZ8Nn7Uv97xVabKyj7zjucgXZOlxCw +ZMlF4CoDsVzs5Knwtpq/w/DQ2/7mX6TKh+c+nxZQ8cYh1Z8zmusWLZ9U2zUbYQgk +NUKkjlYLBxxkPviFWV7Ln8NEZryTmAwFbs8T203nnC8Dj/DTInBZ+KUr+G+WroE0 +DwkM4imneSEa9mFGxNiaS19srlT7dCa3Lgp9BbeIch8eqrvOG+vwWmn8nC0CDqEc +dulreXmq0UZxYblwNueJ8ya/jZUt6HfxVttAvi+bDVzkWacoGAVCfU4iVlShSKbH +fL0f23zhufDbSACwGkNa/jktz5rzg4yLAoIBAHVPjXB16AaRl7Uk1e5585kD7Vpd +4K2uGnJsX7aMX1lGGm8OmlamhppgsIqECsgqM9uHLDDnW3CcZ/XTuxD/SQ1IBnZd +7v9JZwPzMJp0JXr3nn/RqCnMZytd/PHE3z0tQvwz8IYtpehEGQekfJKD/wiuOwzn +WHJMTr4zbbSQk7Pp+RDb7zXxODLj5YVoCasG1RyM7JtrnvAFadS8oEghKgApJUak +eSED2HxCpW5mj389qNkNgBQ9AeDizBHxSU7e2Kj0kvBCqnLiqnjlwYXQxTSdQ/sn +Kuea9MuJJe7+iKL4nttqzZSa0Vz4JRpPdkAZfTcr8CeNQSdEddPpEVmnJUw= +-----END RSA PRIVATE KEY----- diff --git a/tests/include/api/ssl-ca/client-key.pem b/tests/include/ssl_certs/client-key.pem similarity index 100% rename from tests/include/api/ssl-ca/client-key.pem rename to tests/include/ssl_certs/client-key.pem diff --git a/tests/include/api/ssl-ca/client-req.csr b/tests/include/ssl_certs/client-req.csr similarity index 100% rename from tests/include/api/ssl-ca/client-req.csr rename to tests/include/ssl_certs/client-req.csr diff --git a/tests/include/ssl_certs/client.crt b/tests/include/ssl_certs/client.crt index c296388337..4311f890be 100644 --- a/tests/include/ssl_certs/client.crt +++ b/tests/include/ssl_certs/client.crt @@ -1,33 +1,25 @@ -----BEGIN CERTIFICATE----- -MIIFtTCCA52gAwIBAgIBATANBgkqhkiG9w0BAQsFADBJMQswCQYDVQQGEwJDTjEL -MAkGA1UECAwCTkUxCzAJBgNVBAoMAk5FMQ8wDQYDVQQLDAZSb290Q0ExDzANBgNV -BAMMBlJvb3RDQTAeFw0yMDAxMTYwMzA3MTlaFw0yMDAxMTYwMzA3MTlaMEgxCzAJ -BgNVBAYTAkNOMQswCQYDVQQIDAJORTELMAkGA1UECgwCTkUxCzAJBgNVBAsMAk5F -MRIwEAYDVQQDDAkxMjcuMC4wLjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQC/GbFTW4fv4YtzjPpVVdXbKTkJv+od8A6azyI9sWwl5nj1NXV4rLw28/ku -1xka5La67TQ5y44D4StGSsfLuVzehqWed9NqY2t+u/kE82TXyQ90OuabXZlUXfzV -T6U2o1l36ko97Q9MYws4izVX4vE8zsCdatNM74kyEsUbHJ3Gut2IKLM148D7UuQo -iUhwmmvFCCTL9YdW1HjEtbgg3mzg8XFtAA4mzKv+VgHe4VnAA/Fh1dPtNXpoa7IA -hheWS8DC2GN44/BegnBqG877tKfbooCyjBxkE9HKroj1y+f3FrGWCQGEAsHn6Za3 -61OQkYcjLQ2eZ+CC3rX0HBk+yY5S6hnTjQ7Z4aFBgsxnqg9WFICjXtI5RmkC6NoO -wnFr8DJ3MUEhDE957wjSNPirlvrLxX6kyONVKHsChvBKu0iFgpd5VlLKoCxqwOib -gEAJS960wkMVvY91LxDfp8waoAhT02S7ysiCll5aA4UyyHA4hOavOeHsv3hA0sq8 -HlbJ+duw/IHLGFlEXgBC/zVsKbVHliIU03oUBEEA0oimcDJGSClTzcPXL43xpqAg -kb3I4GPFLhu1r/IexeLoUrkA4KHx1LSLzXf+P9Y1VFGOrRvI7zMS8R8LryqGr7mF -xgOxcm2uS0i5/OTYYSk+mQVu609epLl5GgfHeNIQTQdnWHC6NQIDAQABo4GoMIGl -MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENl -cnRpZmljYXRlMB0GA1UdDgQWBBTNzUGh5F+06+7lm/pnTMcjKC/6ODAfBgNVHSME -GDAWgBRI2TJRKjGyUUSNv1C0TESpV5guwDALBgNVHQ8EBAMCBDAwHQYDVR0lBBYw -FAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4ICAQC33cEcngVk -N2lqPiZ0fuHh/Rs+LbUv6OWBIdhdHiojVaN4kq3Z0eIvZ5x1saXca9ff/rk4Brk6 -K+qntCWNg9DAZ9ahjhsXAeDQJE1l7999cCJxJRa6MoBTHwHejC7XAhpVEf95Gl3C -fNkDgEnBEisoy5/XL96TIOcZC/trkESXEKrPrKy+BwyikOaB1m3M2OFPFx+HomWO -w4ah4c8BNK/MIRb0zj4TJ2djxOsX1lXt5DC/2AACX2n1H6l/iVJ5oxwzNJSycX9M -/zAA0+lsedCiAiWZSkThinHbmu/c2QOmVIfwos/KlgXZTc7A36sYJ1wge8xYMR/0 -LV6PqVdoAmq8e9uUZFgyCl1Ojb4HojLRyuUUKYOV7ywODVcezNhk5z7ft2p3Kkgh -brQAd0crn3rmDoM8Uay/tovQgg84nIYWag6vpONDPR3XbQG3OvBTTdpsnphfMFvF -Clsam9jV8WGi+ZEg4ODnsE3eSbG1hBjEBQw7dKq5jPEZkedNyqTNGPhIOevYWITg -nOSESi4ksKroJJCAG3IRA7B10maKItK+NuDXKELze/Xj9uE1kfuEUYm9DnjuxPqI -pMLTLS/1Z0NmCuwATcRJrm9gXZGdpVvrjuS2Fc5SkqZTbcRIzSbUf2cE2OJAKyj7 -VkKhgAJRKDCNQZ6wru5nzGgs/zyxH20t0A== +MIIETDCCAjQCFEFiPP89vh9xURoM0YYsuD6rFewiMA0GCSqGSIb3DQEBCwUAMEkx +CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJORTELMAkGA1UECgwCTkUxDzANBgNVBAsM +BlJvb3RDQTEPMA0GA1UEAwwGUm9vdENBMB4XDTI1MDQwODA2MTEzMFoXDTI2MDQw +ODA2MTEzMFowfDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYD +VQQHDAhTaGFuZ2hhaTEPMA0GA1UECgwGU3dvb2xlMRMwEQYDVQQDDApVbml0IFRl +c3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5jb20wggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvyeKPL5krERLYq+L1YmU5DMfuBnZ3JKv9 +flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD6229g9qlR12xW9clheX7N+3 +wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYFxwUJHfJlhpY9yD/JTFZs +ZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1KgGiyZ501qjpfKril6bkS +Tx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgKzCh3X4pAqxAqSO8obxqS +VWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv0cS4PhAvAgMBAAEwDQYJ +KoZIhvcNAQELBQADggIBAHL+qmFwWY6Z8Y9A4btmP5nAqugyufJCAflQpPBSJ8Dq +UunztrYA4sJZMMXDLCOr5ZhVT9zDCJgNELqgJChgVEKgj3TBsoEeOVTPxH84gUIX +BcKMEZktNy/XudX3MlMWYL3avtjWeCcl91q2Vw4VgEzrnt1Rdq47XSHBt0jayF/I +1OLxt2HiN74GiNkBl8lyMfx0umk1qFecKda42vkF0nsWeF20giaj3zsze0BRqgmw +zQ2IuC9oiiufbnrExM0cjqIpGky+vAplw7jPFUMJ6jadjq5XxC5ROGmHdcXjHq9/ +mLZ2Cvb3m2LUgefyqq5T2tGBIJMK8OE7byGwsLlfZ8B50SCLKtaukvnbS9pMIS3H +S459IoQS4ma24QHk0fBo0ond7cSXxGfrlmDKGEJxLp6hX5i2VpPT/RR2cjynM8XG +DAyj4IzYbfQjDPgNGCXcfj1IydFreXzBM9ndf9eweJW32vbFmAwDV2FP6IaMkMv0 +ycj6YyoSYSxs3tOur5JXAPTCzMIgGFhWTvHzuN0P7b0380aSzKyH8fmN5NqwcGUk +Xg29NSGtrf0yX39jkCYyewGd4eA7NvoDooZgFAaY9U+myCHXttYDN2DC41qxC+9F +bK+RfzkG6fR5YIVhYtItGVWgOGwuu03exfBu4tzkqKCCwlEVoYXJJdCMVYQPGhPV -----END CERTIFICATE----- diff --git a/tests/include/ssl_certs/client.csr b/tests/include/ssl_certs/client.csr index 6b10ec97b0..895fb7a5f1 100644 --- a/tests/include/ssl_certs/client.csr +++ b/tests/include/ssl_certs/client.csr @@ -1,11 +1,17 @@ -----BEGIN CERTIFICATE REQUEST----- -MIIBmzCCAQQCAQAwWzELMAkGA1UEBhMCQ04xETAPBgNVBAgTCFpoZWppYW5nMREw -DwYDVQQHEwhIYW5nemhvdTESMBAGA1UEChMJTXkgQ2xpZW50MRIwEAYDVQQDEwls -b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJEUnBzXTTiyUmDb -yhkQoQ/yH1zTnuIk5Meg1Bp0fp1l4kwiizdPbZkk4YkTT/HXdTE6822Cqho+CwGE -VqWZyyd2AZmj87OGb4ZRCyyFzzjfEwdCTvyqZSUBoc1gvSGdEiaA4mXE87Y0XcMB -BasOrfmO76nuzyaXLT7xDjrB+Qw5AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQAx -rsaWSV81/SCf+0af57Wr+BJfiGEutZpdmIe0ofPKfVfz7c8QKjqK+/xQb0INUaYd -MUPjuLfvp06iCWyDPsfhsBRZMSDfFZDp8bnoVloVbP+yLL2Gd+h/a5iYjKTJ2FEt -mDaoIXqbw7oHXXxfKKLP2iyUQCqbfJTC0XeJtFWJ3w== +MIICwTCCAakCAQAwfDELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFNoYW5naGFpMREw +DwYDVQQHDAhTaGFuZ2hhaTEPMA0GA1UECgwGU3dvb2xlMRMwEQYDVQQDDApVbml0 +IFRlc3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvyeKPL5krERLYq+L1YmU5DMfuBnZ3 +JKv9flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD6229g9qlR12xW9clheX +7N+3wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYFxwUJHfJlhpY9yD/J +TFZsZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1KgGiyZ501qjpfKril +6bkSTx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgKzCh3X4pAqxAqSO8o +bxqSVWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv0cS4PhAvAgMBAAGg +ADANBgkqhkiG9w0BAQsFAAOCAQEABvjAKmCbJLEUxsLB3z2Nn3qiWZ8G879/Z2sx +hmbOnp2/h64LfXF+xneg6CN7aOaBEGujbuTAj3dIQQjhnO9xRS5I4cfylTcNz9MK +zKeNV+Ukag4TnUpqpE6MiB3cosvanqpFeHVp9P98DDasxXYnSayIGUqWfJ9B8AFh +SphMqDCn1mLD69DIi+Rk1C9Vj/+jb3ec3EuHpaP64/LV7fmvErxIfgSD2uLQtGeo +36CKgxlae7t+OSN+4PczdBzrTY4asiS7QNnoHmtI92EHGNlgy4nxCjNXTSk987Iq +nCZLAUxPxw2MAqKRClQxnQ7BIQVaWCd+NpM46LQULJp4C+QT1w== -----END CERTIFICATE REQUEST----- diff --git a/tests/include/ssl_certs/client.key b/tests/include/ssl_certs/client.key index d9ced60ef4..fdff74d939 100644 --- a/tests/include/ssl_certs/client.key +++ b/tests/include/ssl_certs/client.key @@ -1,51 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAvxmxU1uH7+GLc4z6VVXV2yk5Cb/qHfAOms8iPbFsJeZ49TV1 -eKy8NvP5LtcZGuS2uu00OcuOA+ErRkrHy7lc3oalnnfTamNrfrv5BPNk18kPdDrm -m12ZVF381U+lNqNZd+pKPe0PTGMLOIs1V+LxPM7AnWrTTO+JMhLFGxydxrrdiCiz -NePA+1LkKIlIcJprxQgky/WHVtR4xLW4IN5s4PFxbQAOJsyr/lYB3uFZwAPxYdXT -7TV6aGuyAIYXlkvAwthjeOPwXoJwahvO+7Sn26KAsowcZBPRyq6I9cvn9xaxlgkB -hALB5+mWt+tTkJGHIy0Nnmfggt619BwZPsmOUuoZ040O2eGhQYLMZ6oPVhSAo17S -OUZpAujaDsJxa/AydzFBIQxPee8I0jT4q5b6y8V+pMjjVSh7AobwSrtIhYKXeVZS -yqAsasDom4BACUvetMJDFb2PdS8Q36fMGqAIU9Nku8rIgpZeWgOFMshwOITmrznh -7L94QNLKvB5WyfnbsPyByxhZRF4AQv81bCm1R5YiFNN6FARBANKIpnAyRkgpU83D -1y+N8aagIJG9yOBjxS4bta/yHsXi6FK5AOCh8dS0i813/j/WNVRRjq0byO8zEvEf -C68qhq+5hcYDsXJtrktIufzk2GEpPpkFbutPXqS5eRoHx3jSEE0HZ1hwujUCAwEA -AQKCAgEAjiXhZn0GX+Qoo/ow17syiYRfXiY4Uq6XXoZHFRbduE94mAV87ReoJURT -jYkIrp2EYwVAvODyMWaTlwTXG241a31Cwt6lD1UGx82xDQKzd2OisDWEBfuYq04C -XPWSklLtoSpQsUGeCJb/6L58HnS8Nxwf4LhulqLzLaESpvkhT4r+cGK9848HpKri -9lgv3zqugXBuVpy7YFZJwyDc5u4slSJj2c2ZuTizk0uMtqpv5UnJipnQu3j1Jak+ -Te9wqiaNKyJk067RxKtITVWEIG0gE1sMWkOodrTbb+VmEVfsozppPI5UZ0rZy9VQ -fFx622W03PCNWdAxC4f4Vq10BeVNjIsF3w8l/ZUzKuUbMXhTGtIFLzK3jNpw+GMJ -fqhEgvJJldWrJkRYiqlP8nbzwlGL1IYQJ4kwZaRzoqMAEKZT5dJ886sF/fYDCAwz -0960gtX7m5TMrSOKxdEGX3lCqovYAkxgz9HdTVg1ITzshJE8A62wqTK18lnrzhXH -7efh6QcXDJMkXZL850RDM7Wq6j6SZqpN6DDAU7+4JssLaAWuyWMAoRqhWrASg4Hr -koVe+ctb8uydbKRGZMSkvXDXwPfXc811jzoxAsf6lJeJgl20+eUCofzxina5KqHA -vaYbul6IXhUKPjHhRywcUa2axstwRf9UNLakLsNjjDcQn1Y5fH0CggEBAPK+utiR -EWvRncRttDA7BHCb9uMd1iVz4ZsOZzbSqaW2vc9heHGMjhlDA4v7F7MHhkMxjD3S -0hjePGr+uwUM0jkHzN3/jRLgyPNqjalH9eubFQTaJuzm5MXEbJ7Y2GyXW9kEdz7E -25D4RO0VQwzQh/ND104uBWhJvsuHQLJMwfvVcPZjBUeCEwvIynUvhev1zIqaS0lb -ENxkEe2ipUK2v2e5+tVsU033VkhEVqAgHOcg8yYzREfbqEU5CO7r53XIfF+kTZN9 -Jfx035zwnBITD1WaUI/No/lwx/MlAzXnEyh5Ya9fHvcp0+Rp/mk6eamcEgkgiiKb -Ck4bfdX/4QSZFqsCggEBAMmJCQTfxSf0jm/zUVBvVXvLKqdUSf7h17dT/MbVfOEP -EScYTkHMcnyYe9NB/yNRaa5eBJi9Tfhg+Hd2DO82kaZxmSJwwjJfd1rADELyNQDa -xZLyIDFXngd8OVBIzKwSw2N6Dml8WWnS55LVXU3wsV+4SRNhqU0j47uPOtS3UoJu -u8bzmOdvUEb4kzo24oKZzlFgS1GlHUwA7GtxedVH++5oqB4fifcfigKt6xM7kddL -31G25RCVaj7jIiHHmMlmBPZkjO+E52ncSbH9gKuZtRww1ZTaM6Fk5YC5CrS8/kW9 -IqtW4sAe8inEbKk+m3edtWAHoe3uSIkltEEinqnK8p8CggEAFYnU3Fhq2k08GWy+ -ezURXMiJ7Hb+RE526yIpCFVC1kBTsS3t2O1mwlkO3IHIBYuaXyXrUqT9HVXCuJ+n -ingrwwGtLNl68/zb97Uxop6O9HvLLqYIpCaMj/uww5tsAJ6heK0Lw5cGAGP3Aa5M -R7vNBEm1teshcbJxam+yzt8+qxCyODprAoBJHlePlzxDPr+NiZLNMVISOevrJ1Gy -Jiw+6X44Am3dKmLS1hCyOvz+c4eZ637LBTE5SgMYhtKJtAq49meGtpp5H2BjYvJ6 -dqQYTTknAst1TDG0nB9hnIvHSGUUxNHE1ptwgKji+QVGPEVr2EF54D4veOS5kp5k -odrPhwKCAQAtyrhE5j9a68NthoDEYPewcCJZ8Nn7Uv97xVabKyj7zjucgXZOlxCw -ZMlF4CoDsVzs5Knwtpq/w/DQ2/7mX6TKh+c+nxZQ8cYh1Z8zmusWLZ9U2zUbYQgk -NUKkjlYLBxxkPviFWV7Ln8NEZryTmAwFbs8T203nnC8Dj/DTInBZ+KUr+G+WroE0 -DwkM4imneSEa9mFGxNiaS19srlT7dCa3Lgp9BbeIch8eqrvOG+vwWmn8nC0CDqEc -dulreXmq0UZxYblwNueJ8ya/jZUt6HfxVttAvi+bDVzkWacoGAVCfU4iVlShSKbH -fL0f23zhufDbSACwGkNa/jktz5rzg4yLAoIBAHVPjXB16AaRl7Uk1e5585kD7Vpd -4K2uGnJsX7aMX1lGGm8OmlamhppgsIqECsgqM9uHLDDnW3CcZ/XTuxD/SQ1IBnZd -7v9JZwPzMJp0JXr3nn/RqCnMZytd/PHE3z0tQvwz8IYtpehEGQekfJKD/wiuOwzn -WHJMTr4zbbSQk7Pp+RDb7zXxODLj5YVoCasG1RyM7JtrnvAFadS8oEghKgApJUak -eSED2HxCpW5mj389qNkNgBQ9AeDizBHxSU7e2Kj0kvBCqnLiqnjlwYXQxTSdQ/sn -Kuea9MuJJe7+iKL4nttqzZSa0Vz4JRpPdkAZfTcr8CeNQSdEddPpEVmnJUw= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvyeKPL5krERLY +q+L1YmU5DMfuBnZ3JKv9flrdNOn1h9ETrasyMl3RCfsUf9d++kBTp9OZLx3SD622 +9g9qlR12xW9clheX7N+3wNydkVgAG6G+Tx07l3DqJj4U6trh+Fh251Mc5zld1dYF +xwUJHfJlhpY9yD/JTFZsZZFB4Kjie5+RjnpwgiuZLzJw3GmF+ZoEppNQAbBLWM1K +gGiyZ501qjpfKril6bkSTx+oFW8tVhv4MpfTbVArjxcl3ax01mM69ATZcChDcxgK +zCh3X4pAqxAqSO8obxqSVWMwikmJ+f9fC4ZhbbVnMeYqtBd5BVj3IzOtK7ydyghv +0cS4PhAvAgMBAAECggEALT2dZ7Fdz5Ygzg+KpRFyMJkH+UvDgE05UE+NDgIYtj6C +gfh/pzcMKwjRDAkMylqYZI88J1/XYmDzmbNA63K99vu/+cmM/+YP+4gQnyKxTbHO +0h4lTVlfz3sRHiwFMgPKrKtDGhyuCGbIoosCUkLZR+S31OWs+N8DHPmUxSoO/N5b +ytaS6Kpu1nC+l7YPf6YPRXkvLZYxLAZl7Z2KOecgSCpHQ6OMlZR8PvCndSfVX3yx +K4nRftjhim83S4DnWXG4y4ENuMCI+biXvsQrGiYKtJmESNxQ1EsBChI7t931uku9 +QGMkpbYmExm0JVuF5P0B8uGTdQzS7WzUMs5hk5LsXQKBgQDOc1RHVWBP3cReD1y/ +9UiHSmPPTzW6rw5W/ctXDeTywSE261lAlenDdh6aptMguukp0DxH0ok6UT1i4O3S +gswhY9A8ul0xxpciAjkcY986QA4SZZmuhH/W6JaGwVUu7w0kc0j+xN3mLMq6IyQy +y0NE2OKZaaRAJ1JVh1KOUfWFOwKBgQDZ+qMNnsf7TeYqaRfLx+adZLfaqOcd1ifu +zidLIVYvEZa8SLFXYdyFed+hPxrhkAeAUzg82plEXG3FvlHxBqSk8wv+jX2AYEkb +UBZBahzFC6bhqwZNmcpoM/e0uDf0uXyVKS3n9Mtulf+EgFLKVFxwoT7vepVOXQSK +l+SStJBhnQKBgF3MaG5AjBG9YIqb0Dgm6V0On2qBQpgfERTVGp3i4jKpLL4XD+YY +Nq1n7V+2jBZC1cz5n2nCbjbg0lMqfuM2KijBgMsWoWEMKX56JBiSB8ofcEVLhBeW +e77YzgtLq7g3RHFbL78REwCROUp4zOmHEHknUU0Cgm3jb7UMVqXy9uohAoGASCsl +ROMnDtrxs2TsDfn3LnEkNe7p5gT7X6M9ezBkWl0WbOwcWvP5kO+SzM4Z1khbU286 +FVvCltGLPocOliA+7qXReMvbm19FlRdk5u986vg6nkiDY7nhO7LI9jpvvESVLMVf +f4aau9hJqtLZlkdr+5ZHQf5x5GoDkOdNNA04KDkCgYBUwPXCdHPdoVHeHtcfYVnx +lOZMWbH8dRNsd/8o+lw6OIJqdrKk2hbjukBjrL5SMG6wucgHGm4UYRO5qpLLywSF +l50yOIZ9bZJvmUazImJ4PAHt7GMzw4GSSBdQwbAxr6Bn6w7ySLmL6tuUJTPszp/g +hTI3L3GGVTaQnQrntKsoyQ== +-----END PRIVATE KEY----- diff --git a/tests/include/ssl_certs/dhparams.pem b/tests/include/ssl_certs/dhparams.pem new file mode 100644 index 0000000000..a1c7055fe7 --- /dev/null +++ b/tests/include/ssl_certs/dhparams.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEApP/KXarP09GXAzdxdCHg8QAbVeGxU2hc0O2zE6900PI7Jd1VDOAm +AZ3UEGfwv7WLxrRUJC31/EbkHH3jjnt28De9r3vNdpt30nYQfYHTNnIodi0j566C +CkIY5J0uby/luU5+cX/NpHwA8YCw7wtvt279mACaE99tmfjY7/enDDoO5R+AUZwk +neRUDszaTLPLotYcJm6LSaUvp+22fKazorGx1OiK1/jN1qL+sSwrYW64C4/n17GW +kGYllrHBU6pgknCFhZdzc6SefMNKbVD79kQnDPXvERtOxOR+kv4rwzfmJ6NB5Ahf +Wmz8RdZO5BhrIYqWVjMYi7VZmHtJkzA5FwIBAg== +-----END DH PARAMETERS----- diff --git a/tests/include/ssl_certs/passwd.crt b/tests/include/ssl_certs/passwd.crt new file mode 100644 index 0000000000..1fefe06bf7 --- /dev/null +++ b/tests/include/ssl_certs/passwd.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID/TCCAuWgAwIBAgIUI7h0MOJPvQH1ox33VVoLeul7iXowDQYJKoZIhvcNAQEL +BQAwgY0xCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwI +U2hhbmdoYWkxDzANBgNVBAoMBnN3b29sZTEPMA0GA1UECwwGc3dvb2xlMRMwEQYD +VQQDDApjb3JlLXRlc3RzMSEwHwYJKoZIhvcNAQkBFhJzZXJ2aWNlQHN3b29sZS5j +b20wHhcNMjUwNTE4MTAwNDA5WhcNMzUwNTE2MTAwNDA5WjCBjTELMAkGA1UEBhMC +Q04xETAPBgNVBAgMCFNoYW5naGFpMREwDwYDVQQHDAhTaGFuZ2hhaTEPMA0GA1UE +CgwGc3dvb2xlMQ8wDQYDVQQLDAZzd29vbGUxEzARBgNVBAMMCmNvcmUtdGVzdHMx +ITAfBgkqhkiG9w0BCQEWEnNlcnZpY2VAc3dvb2xlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANbc0dtA8QGKlUcAVf/kwU62EWi0uemeoHUN3kS+ +q/QXaRY6QsH8bKPnk+0wv7M8maVL7Fp6QpHQVfIe3Nt9D446zasLZeMr//MzS1Pw +Zkq+pJFZNO60bcJeT1VV4Ey+zlORoTWqjZjo6eRHoe/FR20NLZnJLWtxlSeUM+cV +U3TYC/1ovsdopM94YiRDl66TUC7yxNbD7iHZ0gZP6v0yPfcRgzME+nSeWWQEG4OR +n5xJT5XX5tIAV4NqyUfdEqIFBPJxqhbxoJc77jRAd1USCsKF+GU7DN9J1h0ZsVZP +rl+G3Ur3n38VwfNxlhatIfmS9DK556q6OUE2a2ih9on0VwMCAwEAAaNTMFEwHQYD +VR0OBBYEFEy+sFb7FcfBaFXRR1/QBZQrnWmDMB8GA1UdIwQYMBaAFEy+sFb7FcfB +aFXRR1/QBZQrnWmDMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AKe6PqJbDU5vHN97qM3xbdPFEfSDqFIyuCrdk5XiqdPaqWn7FomQELfAkUVsW+gD +m2LuYl9UDG2KSemr/TuualaP+2uVkUeBq8mMqDHV3iM/bGGabpXYvvdlIo/RDAhi +1K3Oh/TvLRv8YXmYC4kWXHEmYxaLjDFGqYCN1VgQjQ81+h0sT0liq9A+g8/heajF +5QQAVjp8QjiqooS3PwTRtXpg8KrccAJwLJXjAMzBa4apd2aJ8NjHuxUhdW4orUAr +V8hr/l482OtuKpakcdDbzLqn1SKh5Oq+cwqnDEr5JYCVb2jM5furKHBq0bfFRu5i +5HxGsJON3fpgyq76dkRN7G8= +-----END CERTIFICATE----- diff --git a/tests/include/ssl_certs/passwd_key.pem b/tests/include/ssl_certs/passwd_key.pem new file mode 100644 index 0000000000..4bd8cb85ce --- /dev/null +++ b/tests/include/ssl_certs/passwd_key.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,97585DACA30B6F5D + +WPt38qe6lbdheiiW2uHV1G9l9A2Yr3GugE5iEaZTNQS9BjsZP6rpJRj6JdKwqkSr +0Or4B5g+AsTMw1LQES+ap5l27RC4vBcjiSb3NqXoZvvp2qhfUcfjxrwILULTC+Zj +sy6g6lD44cwbe1gs42nZrMTKdOUDgxwnH/1f4Nnz3kt7z2Sj/jnPGGr8HbWIazzx +aE1KMrBAwfHXyeg8FpMRJZnmK9Dim53X6qjEEi9qrAa2KFX/NU5mr8hpFVw6n4WV +2HMvCr8qoHJuR9lrGaLFEwJyq5WT143L1pLBPV6FI+DppFM8nh6Ze1UqNW4kAH1N +xCDl542Hv0XoTl26Yl/YnlBJneSfqm4KpI8RioYfekp5ygEJv9JXFJ7WRfPCJxAz +N9kLS5ggEF4etcDr9jtIwMwvkGylL8Bg7BmkPPIwyelv2HudWQaV7NHqxotQSuIm +TiLAeEfIPPMdAwuVRR5yHy8tPSwwwqiaf4GrY0hHfv6wMS8Auq0pqIpKToQKMB3J +/FdLqbyEWS2PpwPWoHVXCr94wEGS2eUdTW4KxxFrCBoizZC7tS1NiznOJmmKMSme +2ACEy7uVNFecT1d//5MfVW7hwEZIxnF0w3WKv20uOuREwBFh8TtQ/DPZr6hoxUBz +8rizJrqnkQa1WoC2m0zM1ag5BU/Y6kNFiWbEh0O9ShRNlO3mZeB1KwfFooTpZqX9 +sldXrhg2Tikus9jRUM/XnnNb5d/iL/CYJIkS7lO1NItDlNUe09OjrWNqWm2oJPKB +gAiaBUz3v/eSRviX1YdWrGxFGTZFB6HVnGhBpiWOYD7eRu/jVCAlZuK8XdF+dO66 +4eSD/51GDoRtgBz3fR7UWuXfmQCIbnjgTYOVrK25gZdbl9US4bBK3MC66wsG1K5U +U8i/Ko+h9YwNJPnHDdiOT9S7l9QczStg10pnmj8jASZzmrD/Pm2FU6mqq0EXV4pF +Q+3uSuRo74G9vkQBZNjyBDrDO6YwkNyHULL4+hN6QGcDZD1pLDNN2GnLasJi5ccb +E6ptfIU8YrkV9pXdEALf64lE3AshNhREHrUSFBpNia7bDxjEePtMX/J0JTo/Z0b/ +DwKSFGMMPbDq3JkHcCzfrKArmjFoIja15A2Q0eDk6xRM++DZeBIduZu9j3oj7lzd +a/f24ODzw25SEOj6mxnfx5MxwNgNTnV4SkFy9R5EMkBIpMYuZ9P/nWfo4c7vhOQc +2CrFi4uQxIz7pka0SJUUMuDFlVbHXDLQwYwf/NUwjwl2iyZaykPuk+xBhZKicGkk +lFz59jgOPSPEp4FPsmvdEScrJXNa5y6Prmxl2RqY6cA7CJW6iAJvh3XLDuaWDEA7 +yQrtJB0R8MQw8tvpBCKl7CuDWOM3x4nMMjTM1ZgFMZdAJQbqqPPyfsuBbJ0JMGSh +xCRlkrLuHj2zWKjKtFlVbUh7oCHz0aKQAsa2dBBAb8YliA+Bh+JMxXRPfbNC3W0E +xmOHSIMoLfSt/jkxNZpaQGR/Hccu7AJ+DlPpOoJtevpps3BfFBlMiZTCrLkXFYwa +bRE3IMBgNanbwivLCkkWP4MQEAy316G7aruJCx24ebidSGdu/ThNBQ== +-----END RSA PRIVATE KEY----- diff --git a/tests/include/api/ssl-ca/server-cert.pem b/tests/include/ssl_certs/server-cert.pem similarity index 100% rename from tests/include/api/ssl-ca/server-cert.pem rename to tests/include/ssl_certs/server-cert.pem diff --git a/tests/include/api/ssl-ca/server-key.pem b/tests/include/ssl_certs/server-key.pem similarity index 100% rename from tests/include/api/ssl-ca/server-key.pem rename to tests/include/ssl_certs/server-key.pem diff --git a/tests/include/api/ssl-ca/server-req.csr b/tests/include/ssl_certs/server-req.csr similarity index 100% rename from tests/include/api/ssl-ca/server-req.csr rename to tests/include/ssl_certs/server-req.csr diff --git a/tests/init b/tests/init index a87604d8ac..a26e8c7d0a 100755 --- a/tests/init +++ b/tests/init @@ -3,7 +3,7 @@ function read_sql_file(string $file) { - $comment_regex = '/(?connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - if (!$connected) { - echo "[DB-init] Connect failed! Error#{$mysql->connect_errno}: {$mysql->connect_error}\n"; - exit(1); +$has_specific_option = isset($options['mysql']) || isset($options['firebird']) || isset($options['odbc-mysql']); +$load_mysql = !$has_specific_option || isset($options['mysql']); +$load_firebird = !$has_specific_option || isset($options['firebird']); +$load_odbc_mysql = !$has_specific_option || isset($options['odbc-mysql']); + +require __DIR__ . '/swoole_ssh2/ssh2_test.inc'; +if ($load_firebird) { + require __DIR__ . '/swoole_pdo_firebird/pdo_firebird.inc'; +} + +swoole_library_set_option('default_remote_object_server_worker_num', 8); +swoole_init_default_remote_object_server(); + +Swoole\Coroutine\run(function () use ($load_mysql, $load_firebird, $load_odbc_mysql) { + echo "[SSH2-init] Setting up SSH2 test environment...\n"; + + $ssh_user = TEST_SSH2_USER ; + $ssh_password = TEST_SSH2_PASS; + + $user_exists = trim(shell_exec("id -u $ssh_user 2>/dev/null")); + if (empty($user_exists)) { + echo "[SSH2-init] Creating test user: $ssh_user\n"; + exec("useradd -m $ssh_user"); + exec("echo '$ssh_user:$ssh_password' | chpasswd"); + + exec("mkdir -p /home/$ssh_user/.ssh"); + + echo "[SSH2-init] Copying SSH test key pair...\n"; + + exec("cat " . __DIR__ . '/swoole_ssh2/testkey_ed25519.pub' . " > /home/$ssh_user/.ssh/authorized_keys"); + readfile("/home/$ssh_user/.ssh/authorized_keys"); + readfile('/etc/ssh/sshd_config'); + + exec("chmod 700 /home/$ssh_user/.ssh"); + exec("chown -R $ssh_user:$ssh_user /home/$ssh_user/.ssh"); + exec("ssh -vvv -i " . __DIR__ . '/swoole_ssh2/testkey_ed25519' . " $ssh_user@localhost"); + + readfile('/var/log/auth.log'); + + echo "[SSH2-init] SSH2 test user setup completed\n"; + } else { + echo "[SSH2-init] User $ssh_user already exists, skipping creation\n"; } - $sql_file = read_sql_file(__DIR__ . '/test.sql'); - foreach ($sql_file as $line) { - if (!$mysql->query($line)) { - echo "[DB-init] Failed! Error#{$mysql->errno}: {$mysql->error}\n"; + + // 初始化MySQL数据库 + if ($load_mysql) { + echo "[DB-init] initialization MySQL database...\n"; + $mysql = new mysqli(); + $connected = $mysql->connect(MYSQL_SERVER_HOST, + MYSQL_SERVER_USER, + MYSQL_SERVER_PWD, + MYSQL_SERVER_DB, + MYSQL_SERVER_PORT); + if (!$connected) { + echo "[DB-init] Connect failed! Error#{$mysql->connect_errno}: {$mysql->connect_error}\n"; exit(1); } + $sql_file = read_sql_file(__DIR__ . '/test.sql'); + foreach ($sql_file as $line) { + if (!$mysql->query($line)) { + echo "[DB-init] Failed! Error#{$mysql->errno}: {$mysql->error}\n"; + exit(1); + } + } + echo "[DB-init] MySQL Done!\n"; } - echo "[DB-init] MySQL Done!\n"; - - echo "[DB-init] initialization PostgreSQL database...\n"; - $pgsql = new Swoole\Coroutine\PostgreSQL(); - $connected = $pgsql->connect(PGSQL_CONNECTION_STRING); - if (!$connected) { - echo "[DB-init] Connect failed! Error#{$pgsql->error}: {$pgsql->notices}\n"; - exit(1); + + // 初始化ODBC MySQL配置 - 独立控制 + if ($load_odbc_mysql) { + echo "[DB-init] initialization ODBC...\n"; + echo `set -ex`; + + file_put_contents('/etc/odbcinst.ini', "[mysql]" . PHP_EOL + . "Driver=libmaodbc.so" . PHP_EOL + . "Description=MariaDB Connector/ODBC(Unicode)" . PHP_EOL + . "UsageCount=1" . PHP_EOL + ); + echo `odbcinst -q -d -n "mysql"`; + + file_put_contents('/etc/odbc.ini', "[mysql-test]" . PHP_EOL + . "Description = MySQL test database" . PHP_EOL + . "Trace = On" . PHP_EOL + . "TraceFile = stderr" . PHP_EOL + . "Driver = mysql" . PHP_EOL + . "SERVER = " . MYSQL_SERVER_HOST . PHP_EOL + . "USER = " . MYSQL_SERVER_USER . PHP_EOL + . "PASSWORD =" . MYSQL_SERVER_PWD . PHP_EOL + . "PORT = " . MYSQL_SERVER_PORT . PHP_EOL + . "DATABASE = " . MYSQL_SERVER_DB); + echo `odbcinst -i -d -f /etc/odbc.ini`; + + echo "[DB-init] ODBC Done!\n"; } - $sql_file = read_sql_file(__DIR__ . '/pgsql.sql'); - foreach ($sql_file as $line) { - if (!$pgsql->query($line)) { - echo "[DB-init] Failed! Error#{$pgsql->error}: {$pgsql->notices}\n"; - exit(1); + + // 初始化Firebird数据库 + if ($load_firebird) { + echo "[DB-init] initialization Firebird database...\n"; + try { + $firebird = PdoFirebirdTest::create(); + // 创建一些基础表结构 - 使用Firebird兼容的方式 + try { + // 尝试查询表是否存在 - 使用单引号避免PHP解析$符号 + $result = $firebird->query('SELECT 1 FROM rdb$relations WHERE rdb$relation_name = "SWOOLE_TEST"'); + if (!$result || $result->fetchColumn() === false) { + // 表不存在,创建表 + $firebird->exec('CREATE TABLE swoole_test (id INTEGER PRIMARY KEY, data VARCHAR(255))'); + } + } catch (PDOException $e) { + // 如果查询失败,尝试直接创建表(可能是因为权限问题) + try { + $firebird->exec('CREATE TABLE swoole_test (id INTEGER PRIMARY KEY, data VARCHAR(255))'); + } catch (PDOException $e2) { + // 如果表已存在,忽略此错误 + if (!strpos($e2->getMessage(), 'already exists')) { + throw $e2; + } + } + } + echo "[DB-init] Firebird Done!\n"; + } catch (PDOException $e) { + echo "[DB-init] Firebird initialization failed: " . $e->getMessage() . "\n"; } } - echo "[DB-init] PostgreSQL Done!\n"; }); diff --git a/tests/pgsql.sql b/tests/pgsql.sql index 911e4bcf42..d9c2531ccb 100644 --- a/tests/pgsql.sql +++ b/tests/pgsql.sql @@ -7,3 +7,9 @@ CREATE TABLE weather ( prcp real, date date); INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27') RETURNING id; +INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('Test2', 11, 22, 0.3, '1994-11-28') RETURNING id; + +DROP TABLE IF EXISTS oid; +CREATE TABLE oid ( + id SERIAL primary key NOT NULL, + oid oid); diff --git a/tests/php-cs-fixer b/tests/php-cs-fixer deleted file mode 100755 index 691599580f..0000000000 --- a/tests/php-cs-fixer +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env sh - -include/lib/vendor/friendsofphp/php-cs-fixer/php-cs-fixer "$@" - diff --git a/tests/run-tests b/tests/run-tests index a010a4ebc7..f95b780622 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -28,7 +28,7 @@ /* Let there be no top-level code beyond this point: * Only functions and classes, thanks! * - * Minimum required PHP version: 7.0.0 + * Minimum required PHP version: 8.2.0 */ function show_usage(): void @@ -89,7 +89,7 @@ Options: --temp-source --temp-target [--temp-urlbase ] Write temporary files to by replacing from the - filenames to generate with . In general you want to make + filenames to generate with . In general, you want to make the path to your source files and some patch in your web page hierarchy with pointing to . @@ -156,7 +156,7 @@ function main() // Parallel testing global $workers, $workerID; - define('IS_WINDOWS', substr(PHP_OS, 0, 3) == "WIN"); + define('IS_WINDOWS', str_starts_with(PHP_OS, "WIN")); $workerID = 0; if (getenv("TEST_PHP_WORKER")) { @@ -183,7 +183,7 @@ function main() +-----------------------------------------------------------+ NO_PROC_OPEN_ERROR; - exit(1); + exit(200); } // If timezone is not set, use UTC. @@ -203,7 +203,6 @@ NO_PROC_OPEN_ERROR; // delete as much output buffers as possible while (@ob_end_clean()) { - ; } if (ob_get_level()) { echo "Not all buffers were deleted.\n"; @@ -243,10 +242,10 @@ NO_PROC_OPEN_ERROR; // swoole patch (persistent config) { $environment['PHPT'] = $environment['PHPT'] ?? getenv('PHPT'); - $environment['HAS_NETSTAT'] = getenv('HAS_NETSTAT') ?? (stripos(`netstat --help 2>&1`, 'usage') ? '1' : '0'); + $environment['HAS_NETSTAT'] = getenv('HAS_NETSTAT') ?? (stripos(shell_exec("netstat --help 2>&1"), 'usage') ? '1' : '0'); if ($environment['HAS_NETSTAT']) { - $environment['MYSQL_SERVER_PATH'] = getenv('MYSQL_SERVER_PATH') ?? (trim(`netstat -ln | grep -o -m 1 -E '\S*mysqld?\.sock'`) ?: null); - $environment['REDIS_SERVER_PATH'] = getenv('REDIS_SERVER_PATH') ?? (trim(`netstat -ln | grep -o -m 1 -E '\S*redis.*\.sock'`) ?: null); + $environment['MYSQL_SERVER_PATH'] = getenv('MYSQL_SERVER_PATH') ?? (trim(shell_exec("netstat -ln | grep -o -m 1 -E '\S*mysqld?\.sock'")) ?: null); + $environment['REDIS_SERVER_PATH'] = getenv('REDIS_SERVER_PATH') ?? (trim(shell_exec("netstat -ln | grep -o -m 1 -E '\S*redis.*\.sock'")) ?: null); } } @@ -382,8 +381,8 @@ NO_PROC_OPEN_ERROR; $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); - define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php'); - define('QA_REPORTS_PAGE', 'http://qa.php.net/reports'); + define('QA_SUBMISSION_PAGE', 'https://qa.php.net/buildtest-process.php'); + define('QA_REPORTS_PAGE', 'https://qa.php.net/reports'); define('TRAVIS_CI', (bool) getenv('TRAVIS')); // Determine the tests to be run. @@ -454,7 +453,7 @@ NO_PROC_OPEN_ERROR; for ($i = 1; $i < $argc; $i++) { $is_switch = false; $switch = substr($argv[$i], 1, 1); - $repeat = substr($argv[$i], 0, 1) == '-'; + $repeat = str_starts_with($argv[$i], '-'); while ($repeat) { if (!$is_switch) { @@ -465,7 +464,7 @@ NO_PROC_OPEN_ERROR; if ($repeat) { foreach ($cfgtypes as $type) { - if (strpos($switch, '--' . $type) === 0) { + if (str_starts_with($switch, '--' . $type)) { foreach ($cfgfiles as $file) { if ($switch == '--' . $type . '-' . $file) { $cfg[$type][$file] = true; @@ -643,7 +642,7 @@ NO_PROC_OPEN_ERROR; break; case '--version': echo '$Id: ebcaabba02a5af6f5dc62dda027befb95385d5fc $' . "\n"; - exit(1); + exit(2); default: echo "Illegal switch '$switch' specified!\n"; @@ -651,16 +650,16 @@ NO_PROC_OPEN_ERROR; case '-help': case '--help': show_usage(); - exit(1); + exit(2); } } if (!$is_switch) { $testfile = realpath($argv[$i]); - if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) { + if (!$testfile && str_contains($argv[$i], '*') && function_exists('glob')) { - if (substr($argv[$i], -5) == '.phpt') { + if (str_ends_with($argv[$i], '.phpt')) { $pattern_match = glob($argv[$i]); } else { if (preg_match("/\*$/", $argv[$i])) { @@ -678,7 +677,7 @@ NO_PROC_OPEN_ERROR; if (is_dir($testfile)) { find_files($testfile); } else { - if (substr($testfile, -5) == '.phpt') { + if (str_ends_with($testfile, '.phpt')) { $test_files[] = $testfile; } else { die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); @@ -757,7 +756,7 @@ NO_PROC_OPEN_ERROR; if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['LEAKED'])) { - exit(1); + exit(201); } return; @@ -834,23 +833,14 @@ NO_PROC_OPEN_ERROR; junit_save_xml(); if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && ($sum_results['FAILED'] || $sum_results['LEAKED'])) { - exit(1); + exit(202); } exit(0); } -if (!function_exists("hrtime")) { - function hrtime(bool $as_num = false) - { - $t = microtime(true); - - if ($as_num) { - return $t * 1000000000; - } - - $s = floor($t); - return array(0 => $s, 1 => ($t - $s) * 1000000000); - } +function get_shortname($file): array|string +{ + return str_replace(TEST_PHP_SRCDIR . '/', '', $file); } function verify_config() @@ -884,11 +874,11 @@ More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n" $info_params = array(); settings2array($ini_overwrites, $info_params); $info_params = settings2params($info_params); - $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; - define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); + $php_info = shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\""); + define('TESTED_PHP_VERSION', shell_exec("$php -n -r \"echo PHP_VERSION;\"")); if ($php_cgi && $php != $php_cgi) { - $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; + $php_info_cgi = shell_exec("$php_cgi $pass_options $info_params $no_file_cache -q \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; } else { @@ -896,7 +886,7 @@ More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n" } if ($phpdbg) { - $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`; + $phpdbg_info = shell_exec("$phpdbg $pass_options $info_params $no_file_cache -qrr \"$info_file\""); $php_info_sep = "\n---------------------------------------------------------------------"; $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; } else { @@ -911,7 +901,7 @@ More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n" // load list of enabled extensions save_text($info_file, ''); - $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`); + $exts_to_test = explode(',', shell_exec("$php $pass_options $info_params $no_file_cache \"$info_file\"")); // check for extensions that need special handling and regenerate $info_params_ex = array( 'session' => array('session.auto_start=0'), @@ -959,7 +949,7 @@ function save_or_mail_results() echo "\nYou may have found a problem in PHP."; } echo "\nThis report can be automatically sent to the PHP QA team at\n"; - echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n"; + echo QA_REPORTS_PAGE . " and https://news.php.net/php.qa.reports\n"; echo "This gives us a better understanding of PHP's behavior.\n"; echo "If you don't want to send the report immediately you can choose\n"; echo "option \"s\" to save it. You can then email it to " . PHP_QA_EMAIL . " later.\n"; @@ -1094,13 +1084,13 @@ function find_files($dir, $is_ext_dir = false, $ignore = false) } // Cleanup any left-over tmp files from last run. - if (substr($name, -4) == '.tmp') { + if (str_ends_with($name, '.tmp')) { @unlink("$dir/$name"); continue; } // Otherwise we're only interested in *.phpt files. - if (substr($name, -5) == '.phpt') { + if (str_ends_with($name, '.phpt')) { if ($ignore) { $ignored_by_ext++; } else { @@ -1127,10 +1117,8 @@ function test_sort($a, $b) $a = test_name($a); $b = test_name($b); - $ta = strpos($a, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($a, - TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; - $tb = strpos($b, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($b, - TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; + $ta = str_starts_with($a, TEST_PHP_SRCDIR . "/tests") ? 1 + (str_starts_with($a, TEST_PHP_SRCDIR . "/tests/run-test") ? 1 : 0) : 0; + $tb = str_starts_with($b, TEST_PHP_SRCDIR . "/tests") ? 1 + (str_starts_with($b, TEST_PHP_SRCDIR . "/tests/run-test") ? 1 : 0) : 0; if ($ta == $tb) { return strcmp($a, $b); @@ -1358,6 +1346,13 @@ function run_all_tests($test_files, $env, $redir_tested = null) } $test_idx++; + if ($workerID) { + send_message($workerSock, [ + "type" => "begin", + "file" => $name, + ]); + } + if ($workerID) { $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; ob_start(); @@ -1463,7 +1458,7 @@ function run_all_tests_parallel($test_files, $env, $redir_tested) { // PHP is terrible and returns IPv6 addresses not enclosed by [] $portPos = strrpos($sockName, ":"); $sockHost = substr($sockName, 0, $portPos); - if (FALSE !== strpos($sockHost, ":")) { + if (str_contains($sockHost, ":")) { $sockHost = "[$sockHost]"; } $sockPort = substr($sockName, $portPos + 1); @@ -1566,7 +1561,7 @@ function run_all_tests_parallel($test_files, $env, $redir_tested) { $rawMessage = $rawMessageBuffers[$i] . $rawMessage; $rawMessageBuffers[$i] = ''; } - if (substr($rawMessage, -1) !== "\n") { + if (!str_ends_with($rawMessage, "\n")) { $rawMessageBuffers[$i] = $rawMessage; continue; } @@ -1640,6 +1635,11 @@ function run_all_tests_parallel($test_files, $env, $redir_tested) { goto escape; } break; + case "begin": + if (!$SHOW_ONLY_GROUPS) { + show_test($test_idx, get_shortname($message['file'])); + } + break; case "test_result": list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { @@ -1653,10 +1653,6 @@ function run_all_tests_parallel($test_files, $env, $redir_tested) { echo $resultText; - if (!$SHOW_ONLY_GROUPS) { - show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running"); - } - if (!is_array($name) && $result != 'REDIR') { $test_results[$index] = $result; @@ -1881,7 +1877,7 @@ TEST $file } // Match the beginning of a section. - if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + if (preg_match('/^--([_A-Z0-9]+)--/', $line, $r)) { $section = (string)$r[1]; if (isset($section_text[$section]) && $section_text[$section]) { @@ -1891,6 +1887,7 @@ TEST $file // check for unknown sections if (!in_array($section, array( 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', + 'EXPECT_85', 'EXPECTF_85', 'EXPECTREGEX_85', // PHP 8.5+ 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', @@ -1918,8 +1915,8 @@ TEST $file } } - // the redirect section allows a set of tests to be reused outside of - // a given test dir + // the redirect section allows a set of tests to be reused outside + // a given test directory. if ($bork_info === null) { if (isset($section_text['REDIRECTTEST'])) { @@ -1938,6 +1935,21 @@ TEST $file unset($section_text['FILEEOF']); } + if (PHP_VERSION_ID >= 80500) { + if (isset($section_text['EXPECT_85'])) { + $section_text['EXPECT'] = $section_text['EXPECT_85']; + unset($section_text['EXPECT_85']); + } + if (isset($section_text['EXPECTF_85'])) { + $section_text['EXPECTF'] = $section_text['EXPECTF_85']; + unset($section_text['EXPECTF_85']); + } + if (isset($section_text['EXPECTREGEX_85'])) { + $section_text['EXPECTREGEX'] = $section_text['EXPECTREGEX_85']; + unset($section_text['EXPECTREGEX_85']); + } + } + foreach (array('FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX') as $prefix) { $key = $prefix . '_EXTERNAL'; @@ -1961,7 +1973,7 @@ TEST $file } fclose($fp); - $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); + $shortname = get_shortname($file); $tested_file = $shortname; if ($bork_info !== null) { @@ -2043,6 +2055,7 @@ TEST $file if (!$SHOW_ONLY_GROUPS && !$workerID) { show_test($test_idx, $shortname); + echo PHP_EOL; } if (is_array($IN_REDIRECT)) { @@ -2152,9 +2165,9 @@ TEST $file $ext_params = array(); settings2array($ini_overwrites, $ext_params); $ext_params = settings2params($ext_params); - $ext_dir = `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo ini_get('extension_dir');"`; + $ext_dir = shell_exec("$php $pass_options $extra_options $ext_params -d display_errors=0 -r \"echo ini_get('extension_dir');\""); $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); - $loaded = explode(",", `$php $pass_options $extra_options $ext_params -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); + $loaded = explode(",", shell_exec("$php $pass_options $extra_options $ext_params -d display_errors=0 -r \"echo implode(',', get_loaded_extensions());\"")); $ext_prefix = IS_WINDOWS ? "php_" : ""; foreach ($extensions as $req_ext) { if (!in_array($req_ext, $loaded)) { @@ -2507,7 +2520,7 @@ TEST $file $env['USE_ZEND_ALLOC'] = '0'; $env['ZEND_DONT_UNLOAD_MODULES'] = 1; - $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false); + $cmd = $valgrind->wrapCommand($cmd, $memcheck_filename, str_contains($test_file, "pcre")); } if ($DETAILED) echo " @@ -2588,7 +2601,7 @@ COMMAND $cmd $rh = preg_split("/[\n\r]+/", $match[1]); foreach ($rh as $line) { - if (strpos($line, ':') !== false) { + if (str_contains($line, ':')) { $line = explode(':', $line, 2); $headers[trim($line[0])] = trim($line[1]); } @@ -2603,7 +2616,7 @@ COMMAND $cmd $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); foreach ($lines as $line) { - if (strpos($line, ':') !== false) { + if (str_contains($line, ':')) { $line = explode(':', $line, 2); $want[trim($line[0])] = trim($line[1]); $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); @@ -2785,12 +2798,12 @@ COMMAND $cmd if (!$passed) { // write .exp - if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted) === false) { + if (str_contains($log_format, 'E') && file_put_contents($exp_filename, $wanted) === false) { error("Cannot create expected test output - $exp_filename"); } // write .out - if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output) === false) { + if (str_contains($log_format, 'O') && file_put_contents($output_filename, $output) === false) { error("Cannot create test output - $output_filename"); } @@ -2805,15 +2818,15 @@ COMMAND $cmd // swoole patch: clear port if (preg_match('/bind\([^:]+?:(?\d+?)\)/', $diff, $matches)) { $port = $matches['port']; - @`lsof -i:{$port} | grep LISTEN | awk '{print $2}' | xargs kill -9 > /dev/null 2>&1`; + @shell_exec("lsof -i:{$port} | grep LISTEN | awk '{print $2}' | xargs kill -9 > /dev/null 2>&1"); } - if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff) === false) { + if (str_contains($log_format, 'D') && file_put_contents($diff_filename, $diff) === false) { error("Cannot create test diff - $diff_filename"); } // write .sh - if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh + if (str_contains($log_format, 'S') && file_put_contents($sh_filename, "#!/bin/sh {$cmd} ") === false) { @@ -2822,7 +2835,7 @@ COMMAND $cmd chmod($sh_filename, 0755); // write .log - if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, " + if (str_contains($log_format, 'L') && file_put_contents($log_filename, " ---- EXPECTED OUTPUT $wanted ---- ACTUAL OUTPUT @@ -2992,14 +3005,14 @@ function generate_diff($wanted, $wanted_re, $output) function error($message) { echo "ERROR: {$message}\n"; - exit(1); + exit(240); } function settings2array($settings, &$ini_settings) { foreach ($settings as $setting) { - if (strpos($setting, '=') !== false) { + if (str_contains($setting, '=')) { $setting = explode("=", $setting, 2); $name = trim($setting[0]); $value = trim($setting[1]); @@ -3320,7 +3333,7 @@ function show_test($test_idx, $shortname) global $line_length; // swoole patch: pretty output - $str = "TEST $test_idx/$test_cnt "; + $str = "TEST $test_idx/$test_cnt [$shortname]"; $line_length = strlen($str); echo $str; flush(); @@ -3333,7 +3346,7 @@ function clear_show_test() { if (!$workerID) { // Write over the last line to avoid random trailing chars on next echo - echo str_repeat(" ", $line_length), "\r"; + echo str_repeat(" ", intval($line_length)), "\r"; } } @@ -3767,7 +3780,7 @@ class RuntestsValgrind } // swoole patch: color const -define('SWOOLE_TEST_TIMEOUT', file_exists('/.travisenv') ? 10 : 30); +define('SWOOLE_TEST_TIMEOUT', file_exists('/.cienv') ? 10 : 30); define('SWOOLE_COLOR_RED', 1); define('SWOOLE_COLOR_GREEN', 2); define('SWOOLE_COLOR_YELLOW', 3); @@ -3777,6 +3790,6 @@ define('SWOOLE_COLOR_CYAN', 6); define('SWOOLE_COLOR_WHITE', 7); define('SWOOLE_TEST_OUTPUT_MAX_SIZE', 64 * 1024 * 1024); // swoole patch: libc check -define('SWOOLE_IS_MUSL_LIBC', !empty(`ldd 2>&1 | grep -i musl`)); +define('SWOOLE_IS_MUSL_LIBC', !empty(shell_exec("ldd 2>&1 | grep -i musl"))); main(); diff --git a/tests/start.sh b/tests/start.sh index ae744e3f11..f24c6d7561 100755 --- a/tests/start.sh +++ b/tests/start.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash __CURRENT__=`pwd` __DIR__=$(cd "$(dirname "$0")";pwd) @@ -38,8 +38,6 @@ else swoole_http_server \ swoole_websocket_server \ swoole_redis_server \ - swoole_mysql_coro \ - swoole_redis_coro \ swoole_socket_coro \ swoole_runtime" if [ ${#} -gt 1 ]; then @@ -49,6 +47,9 @@ else fi else glob="$@" + if [ "${glob:0:6}" = "tests/" ]; then + glob="${glob#tests/}" + fi fi fi diff --git a/tests/swoole_channel_coro/coro_wait.phpt b/tests/swoole_channel_coro/coro_wait.phpt index 8d605be670..b714a07dd7 100644 --- a/tests/swoole_channel_coro/coro_wait.phpt +++ b/tests/swoole_channel_coro/coro_wait.phpt @@ -1,14 +1,20 @@ --TEST-- swoole_channel_coro: coroutine wait --SKIPIF-- - --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { @@ -16,66 +22,59 @@ $pm->parentFunc = function () use ($pm) { Assert::assert(!empty($data)); $json = json_decode($data, true); Assert::assert(is_array($json)); - Assert::true(isset($json['www.qq.com']) and $json['www.qq.com'] > 1024); - Assert::true(isset($json['www.163.com']) and $json['www.163.com'] > 1024); + Assert::true(isset($json['www.qq.com'])); + Assert::greaterThan($json['www.qq.com'], 1024); + Assert::true(isset($json['www.163.com'])); + Assert::greaterThan($json['www.163.com'], 1024); $pm->kill(); }); - Swoole\Event::wait(); + Event::wait(); echo "DONE\n"; }; -$pm->childFunc = function () use ($pm) -{ - $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $serv->on("WorkerStart", function () use ($pm) { +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->on('WorkerStart', function () use ($pm) { $pm->wakeup(); }); $serv->on('request', function ($req, $resp) { - $chan = new chan(2); go(function () use ($chan) { - $cli = new Swoole\Coroutine\Http\Client('www.qq.com', 443, true); + $cli = new Client('www.qq.com', 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ - 'Host' => "www.qq.com", - "User-Agent" => 'Chrome/49.0.2587.3', + 'Host' => 'www.qq.com', + 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); - if ($ret) - { + if ($ret) { $chan->push(['www.qq.com' => strlen($cli->body)]); - } - else - { + } else { $chan->push(['www.qq.com' => 0]); } }); go(function () use ($chan) { - $cli = new Swoole\Coroutine\Http\Client('www.163.com', 443, true); + $cli = new Client('www.163.com', 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ - 'Host' => "www.163.com", - "User-Agent" => 'Chrome/49.0.2587.3', + 'Host' => 'www.163.com', + 'User-Agent' => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $ret = $cli->get('/'); - if ($ret) - { + if ($ret) { $chan->push(['www.163.com' => strlen($cli->body)]); - } - else - { + } else { $chan->push(['www.163.com' => 0]); } }); $result = []; - for ($i = 0; $i < 2; $i++) - { + for ($i = 0; $i < 2; $i++) { $result += $chan->pop(); } $resp->end(json_encode($result)); diff --git a/tests/swoole_channel_coro/no_ctor.phpt b/tests/swoole_channel_coro/no_ctor.phpt index 1e497c6a4c..1404f9065d 100644 --- a/tests/swoole_channel_coro/no_ctor.phpt +++ b/tests/swoole_channel_coro/no_ctor.phpt @@ -6,17 +6,21 @@ swoole_channel_coro: no ctor pop(); +$pm = ProcessManager::exec(function () { + go(function () { + $chan = new MyChan(100); + $chan->pop(); + }); }); +Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> ---EXPECTF-- -Fatal error: Swoole\Coroutine\Channel::pop(): you must call Channel constructor first in %s on line %d +--EXPECT-- diff --git a/tests/swoole_channel_coro/pool.phpt b/tests/swoole_channel_coro/pool.phpt index 45007d4dd7..e7622f0594 100644 --- a/tests/swoole_channel_coro/pool.phpt +++ b/tests/swoole_channel_coro/pool.phpt @@ -19,7 +19,7 @@ class RedisPool { $this->pool = new \Swoole\Coroutine\Channel($size); for ($i = 0; $i < $size; $i++) { - $redis = new Swoole\Coroutine\Redis(); + $redis = new \redis(); $res = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); if ($res == false) { throw new \RuntimeException("failed to connect redis server."); @@ -29,12 +29,12 @@ class RedisPool } } - public function get(): \Swoole\Coroutine\Redis + public function get(): \redis { return $this->pool->pop(); } - public function put(\Swoole\Coroutine\Redis $redis) + public function put(\redis $redis) { $this->pool->push($redis); } @@ -47,6 +47,7 @@ class RedisPool } $count = 0; +\Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); go(function () { $pool = new RedisPool(); // max concurrency num is more than max connections diff --git a/tests/swoole_client_async/base.phpt b/tests/swoole_client_async/base.phpt new file mode 100644 index 0000000000..d293056b12 --- /dev/null +++ b/tests/swoole_client_async/base.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_client_async: Swoole\Async\Client connect & send & close +--SKIPIF-- + +--FILE-- +on("connect", function(Swoole\Async\Client $cli) { + Assert::true($cli->isConnected()); + $cli->send(RandStr::gen(1024, RandStr::ALL)); +}); + +$cli->on("receive", function(Swoole\Async\Client $cli, $data){ + $recv_len = strlen($data); + // print("receive: len $recv_len"); + $cli->send(RandStr::gen(1024, RandStr::ALL)); + $cli->close(); + Assert::false($cli->isConnected()); +}); + +$cli->on("error", function(Swoole\Async\Client $cli) { + print("error"); +}); + +$cli->on("close", function(Swoole\Async\Client $cli) { + Swoole\Event::exit(); + echo "SUCCESS"; +}); + +$cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 0.2); +Swoole\Event::wait(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_async/big_package_memory_leak.phpt b/tests/swoole_client_async/big_package_memory_leak.phpt new file mode 100644 index 0000000000..a12d4b91b7 --- /dev/null +++ b/tests/swoole_client_async/big_package_memory_leak.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_client_async: big_package_memory_leak +--SKIPIF-- + +--FILE-- +set(['socket_buffer_size' => 2 * 1024 * 1024]); +$cli->on("connect", function (Swoole\Async\Client $cli) { + $cli->send(str_repeat("\0", 1024 * 1024 * 1.9)); +}); +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { + $cli->send($data); +}); +$cli->on("error", function (Swoole\Async\Client $cli) { + echo "error"; +}); +$cli->on("close", function (Swoole\Async\Client $cli) use ($closeServer) { + echo "closed\n"; + $closeServer(); +}); +$cli->connect('127.0.0.1', $port); +Assert::same(memory_get_usage(true), $mem); +echo "SUCCESS\n"; + +Swoole\Event::wait(); +?> +--EXPECT-- +SUCCESS +closed diff --git a/tests/swoole_client_async/buffer_full.phpt b/tests/swoole_client_async/buffer_full.phpt new file mode 100644 index 0000000000..a4291e972f --- /dev/null +++ b/tests/swoole_client_async/buffer_full.phpt @@ -0,0 +1,90 @@ +--TEST-- +swoole_client_async: onBufferFull & onBufferEmpty +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($port) { + Co::set(['log_level' => 5, 'display_errors' => false]); + $client = new Client(SWOOLE_SOCK_TCP); + $client->set(['socket_buffer_size' => 1 * 1024 * 1024,]); + $client->buffer = array(); + + $countBufferEmpty = 0; + $countBufferFull = 0; + + $client->on("connect", function (Client $cli) { + for ($i = 0; $i < 1024; $i++) { + $data = str_repeat('A', 8192); + if ($cli->send($data) === false and $cli->errCode == 1008) { + $cli->buffer[] = $data; + } + } + }); + + $client->on("receive", function (Client $cli, $data) { + $cli->send(pack('N', 8) . 'shutdown'); + $cli->close(); + Assert::same($data, md5_file(TEST_IMAGE)); + }); + + $client->on("error", function ($cli) { + echo "Connect failed\n"; + }); + + $client->on("close", function ($cli) { + + }); + + $client->on("bufferEmpty", function (Client $cli) use (&$countBufferEmpty) { + $countBufferEmpty++; + foreach ($cli->buffer as $k => $data) { + if ($cli->send($data) === false and $cli->errCode == 1008) { + break; + } else { + unset($cli->buffer[$k]); + } + } + if (count($cli->buffer) == 0) { + $cli->close(); + } + }); + + $client->on("bufferFull", function (Client $cli) use (&$countBufferFull) { + $countBufferFull++; + }); + + $client->connect(TCP_SERVER_HOST, $port, 0.5); + Swoole\Event::wait(); + + Assert::greaterThanEq($countBufferEmpty, 1); + Assert::greaterThanEq($countBufferFull, 1); +}; + +$pm->childFunc = function () use ($pm, $port) { + $socket = stream_socket_server("tcp://0.0.0.0:{$port}", $errno, $errstr) or die("$errstr ($errno)
    \n"); + $pm->wakeup(); + while ($conn = stream_socket_accept($socket)) { + for ($i = 0; $i < 4; $i++) { + usleep(500000); + for ($j = 0; $j < 256; $j++) { + $data = fread($conn, 8192); + } + } + fclose($conn); + break; + } + fclose($socket); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_client_async/connect_dns.phpt b/tests/swoole_client_async/connect_dns.phpt new file mode 100644 index 0000000000..3abc712f69 --- /dev/null +++ b/tests/swoole_client_async/connect_dns.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_client_async: connect & dns +--SKIPIF-- + +--FILE-- +on("connect", function (Swoole\Async\Client $cli) { + Assert::true($cli->isConnected()); + $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); +}); + +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { + Assert::assert(strlen($data) > 0); + $cli->close(); + Assert::false($cli->isConnected()); + echo "DONE\n"; +}); + +$cli->on("error", function (Swoole\Async\Client $cli) { + echo "ERROR\n"; +}); + +$cli->on("close", function (Swoole\Async\Client $cli) { + echo "SUCCESS\n"; +}); + +$cli->connect("www.baidu.com", 80, 2.0); + +Swoole\Event::wait(); +?> +--EXPECT-- +SUCCESS +DONE diff --git a/tests/swoole_client_async/connect_refuse.phpt b/tests/swoole_client_async/connect_refuse.phpt new file mode 100644 index 0000000000..431d586e77 --- /dev/null +++ b/tests/swoole_client_async/connect_refuse.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_client_async: connect refuse +--SKIPIF-- + +--FILE-- +on("connect", function(Swoole\Async\Client $cli) { + Assert::true(false, 'never here'); +}); +$cli->on("receive", function(Swoole\Async\Client $cli, $data) { + Assert::true(false, 'never here'); +}); +$cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); +$cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); + +$cli->connect('127.0.0.1', 65535); +?> +--EXPECT-- +error diff --git a/tests/swoole_client_async/connect_refuse_udg.phpt b/tests/swoole_client_async/connect_refuse_udg.phpt new file mode 100644 index 0000000000..72e3a3a60c --- /dev/null +++ b/tests/swoole_client_async/connect_refuse_udg.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_client_async: connect refuse with unix dgram +--SKIPIF-- + +--FILE-- +on("connect", function(Swoole\Async\Client $cli) { + Assert::true(false, 'never here'); +}); +$cli->on("receive", function(Swoole\Async\Client $cli, $data) { + Assert::true(false, 'never here'); +}); +$cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); +$cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); + +@$cli->connect("/test.sock", 0, 0.5, 1); + +Swoole\Event::wait(); +?> +--EXPECT-- +error diff --git a/tests/swoole_client_async/connect_refuse_unix.phpt b/tests/swoole_client_async/connect_refuse_unix.phpt new file mode 100644 index 0000000000..1c26fa9e05 --- /dev/null +++ b/tests/swoole_client_async/connect_refuse_unix.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_client_async: connect refuse with unix stream +--SKIPIF-- + +--FILE-- +on("connect", function(Swoole\Async\Client $cli) { + Assert::true(false, 'never here'); +}); +$cli->on("receive", function(Swoole\Async\Client $cli, $data) { + Assert::true(false, 'never here'); +}); +$cli->on("error", function(Swoole\Async\Client $cli) { echo "error\n"; }); +$cli->on("close", function(Swoole\Async\Client $cli) { echo "close\n"; }); + +@$cli->connect("/test.sock"); + +Swoole\Event::wait(); +?> +--EXPECT-- +error diff --git a/tests/swoole_client_async/connect_timeout.phpt b/tests/swoole_client_async/connect_timeout.phpt new file mode 100644 index 0000000000..76a1341c98 --- /dev/null +++ b/tests/swoole_client_async/connect_timeout.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_client_async: connect_host_not_found +--SKIPIF-- + +--FILE-- +on("connect", function(Swoole\Async\Client $cli) { + Assert::true(false, 'never here'); +}); +$cli->on("receive", function(Swoole\Async\Client $cli, $data) { + Assert::true(false, 'never here'); +}); +$cli->on("error", function(Swoole\Async\Client $cli) { + echo "error\n"; +}); +$cli->on("close", function(Swoole\Async\Client $cli) { + echo "close\n"; +}); + +$cli->connect("192.0.0.1", 9000, 0.1); +?> +--EXPECT-- +error diff --git a/tests/swoole_client_async/connect_twice.phpt b/tests/swoole_client_async/connect_twice.phpt new file mode 100644 index 0000000000..78a77f0c64 --- /dev/null +++ b/tests/swoole_client_async/connect_twice.phpt @@ -0,0 +1,11 @@ +--TEST-- +swoole_client_async: connect twice +--SKIPIF-- + +--FILE-- + +--EXPECT-- +error diff --git a/tests/swoole_client_async/enableSSL.phpt b/tests/swoole_client_async/enableSSL.phpt new file mode 100644 index 0000000000..fcf1ab26f3 --- /dev/null +++ b/tests/swoole_client_async/enableSSL.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_client_async: enableSSL +--SKIPIF-- + +--FILE-- +on("connect", function (Swoole\Async\Client $cli) { + Assert::true($cli->isConnected()); + echo 'connected' . PHP_EOL; + $cli->enableSSL(function ($cli) { + echo "SSL READY\n"; + $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); + }); +}); + +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { + Assert::assert(strlen($data) > 0); + Assert::contains($data, 'www.baidu.com'); + $cli->close(); + Assert::false($cli->isConnected()); + echo "DONE\n"; +}); + +$cli->on("error", function (Swoole\Async\Client $cli) { + echo "ERROR\n"; +}); + +$cli->on("close", function (Swoole\Async\Client $cli) { + echo "SUCCESS\n"; +}); + +$cli->connect("www.baidu.com", 443, 2.0); + +Swoole\Event::wait(); +?> +--EXPECT-- +connected +SSL READY +SUCCESS +DONE diff --git a/tests/swoole_client_async/enableSSL_bad_callback.phpt b/tests/swoole_client_async/enableSSL_bad_callback.phpt new file mode 100644 index 0000000000..707430db18 --- /dev/null +++ b/tests/swoole_client_async/enableSSL_bad_callback.phpt @@ -0,0 +1,16 @@ +--TEST-- +swoole_client_async: enableSSL with bad callback +--SKIPIF-- + +--FILE-- +enableSSL(); +} catch (Exception $e) { + Assert::contains($e->getMessage(), 'require `onSslReady` callback'); +} +?> +--EXPECTF-- diff --git a/tests/swoole_client_async/enableSSL_before_connect.phpt b/tests/swoole_client_async/enableSSL_before_connect.phpt new file mode 100644 index 0000000000..21d86b1873 --- /dev/null +++ b/tests/swoole_client_async/enableSSL_before_connect.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_client_async: enableSSL before connect +--SKIPIF-- + +--FILE-- +enableSSL(function ($cli) { + echo "SSL READY\n"; + $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); +}); +Assert::false($res); + +?> +--EXPECTF-- +Warning: Swoole\Async\Client::enableSSL(): client is not connected to server in %s on line %d diff --git a/tests/swoole_client_async/eof.phpt b/tests/swoole_client_async/eof.phpt new file mode 100644 index 0000000000..636450adbd --- /dev/null +++ b/tests/swoole_client_async/eof.phpt @@ -0,0 +1,111 @@ +--TEST-- +swoole_client_async: eof protocol [async] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($port) { + $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); + $client->set(['open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "\r\n\r\n"]); + + $client->on("connect", function (Swoole\Async\Client $cli) { + $cli->send("recv\r\n\r\n"); + }); + + $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { + static $i = 0; + $i++; + + Assert::assert($pkg != false); + Assert::assert(str_ends_with($pkg, "\r\n\r\n")); + + //小包 + if ($i <= 1000) { + Assert::assert($pkg and strlen($pkg) <= 2048); + if ($i == 1000) { + echo "SUCCESS\n"; + } + } //慢速发送 + elseif ($i <= 1100) { + Assert::assert($pkg and strlen($pkg) <= 8192); + if ($i == 1100) { + echo "SUCCESS\n"; + } + } //大包 + else { + $_pkg = unserialize(substr($pkg, 0, strlen($pkg) - 4)); + Assert::assert(is_array($_pkg)); + Assert::same($_pkg['i'], $i - 1100 - 1); + Assert::same(md5($_pkg['data']), $_pkg['md5']); + Assert::lengthBetween($_pkg['data'], 20000, 256 * 1024 * 1.5); + if ($i == 2100) { + echo "SUCCESS\n"; + $cli->close(); + Swoole\Process::kill($pid); + } + } + }); + + $client->on("error", function (Swoole\Async\Client $cli) { + echo "ERROR\n"; + }); + + $client->on("close", function (Swoole\Async\Client $cli) { + echo "CLOSE\n"; + Swoole\Event::exit(); + }); + + if (!$client->connect('127.0.0.1', $port, 0.5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } +}; + +$pm->childFunc = function () use ($pm, $port) { + $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE); + $serv->set(array( + 'package_eof' => "\r\n\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_max_length' => 1024 * 1024 * 2, + 'socket_buffer_size' => 128 * 1024 * 1024, + 'worker_num' => 1, + 'log_file' => TEST_LOG_FILE, + 'send_yield' => true, + )); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + //小包 + for ($i = 0; $i < 1000; $i++) { + $serv->send($fd, str_repeat('A', rand(100, 2000)) . "\r\n\r\n"); + } + //慢速发送 + for ($i = 0; $i < 100; $i++) { + $serv->send($fd, str_repeat('A', rand(1000, 2000))); + usleep(rand(10000, 50000)); + $serv->send($fd, str_repeat('A', rand(2000, 4000)) . "\r\n\r\n"); + } + //大包 + for ($i = 0; $i < 1000; $i++) { + $data = base64_encode(random_bytes(random_int(20000, 256 * 1024))); + $md5 = md5($data); + $serv->send($fd, serialize(['i' => $i, 'md5' => $md5, 'data' => $data]) . "\r\n\r\n"); + } + }); + $serv->start(); +}; +$pm->async = true; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS +SUCCESS +SUCCESS +CLOSE diff --git a/tests/swoole_client_async/eof_close.phpt b/tests/swoole_client_async/eof_close.phpt new file mode 100644 index 0000000000..67b0b622d5 --- /dev/null +++ b/tests/swoole_client_async/eof_close.phpt @@ -0,0 +1,63 @@ +--TEST-- +swoole_client_async: eof protocol [async] [close] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); + $client->set(['open_eof_check' => true, 'open_eof_split' => true, "package_eof" => "\r\n\r\n"]); + + $client->on("connect", function (Swoole\Async\Client $cli) { + $cli->send("recv\r\n\r\n"); + }); + + $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid, $pm) { + echo "RECEIVED\n"; + $cli->close(); + $pm->kill(); + }); + + $client->on("error", function (Swoole\Async\Client $cli) { + print("error"); + }); + + $client->on("close", function (Swoole\Async\Client $cli) { + echo "CLOSED\n"; + }); + + if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set(array( + 'package_eof' => "\r\n\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_max_length' => 1024 * 1024 * 2, //2M + 'socket_buffer_size' => 128 * 1024 * 1024, + "worker_num" => 1, + 'log_file' => '/dev/null', + )); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $serv->send($fd, str_repeat('A', rand(100, 2000)) . "\r\n\r\n"); + }); + $serv->start(); +}; +$pm->async = true; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +RECEIVED +CLOSED diff --git a/tests/swoole_client_async/getSocket_bug.phpt b/tests/swoole_client_async/getSocket_bug.phpt new file mode 100644 index 0000000000..c3038e3c10 --- /dev/null +++ b/tests/swoole_client_async/getSocket_bug.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_client_async: getSocket debug +--SKIPIF-- + +--FILE-- +on("connect", function (Swoole\Async\Client $cli) use ($timer) { + // getSocket BUG + $cli->getSocket(); + $cli->getSocket(); + + echo "SUCCESS\n"; + /* + @$cli->getSocket(); + $err = error_get_last(); + Assert::same($err["message"], "swoole_client_async::getSocket(): unable to obtain socket family Error: Bad file descriptor[9]."); + */ + $cli->close(); + Swoole\Timer::clear($timer); +}); + +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { +}); +$cli->on("error", function (Swoole\Async\Client $cli) { + echo "error\n"; +}); +$cli->on("close", function (Swoole\Async\Client $cli) { +}); + +$cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 1); +Swoole\Event::wait(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_async/getpeername.phpt b/tests/swoole_client_async/getpeername.phpt new file mode 100644 index 0000000000..a37ca90698 --- /dev/null +++ b/tests/swoole_client_async/getpeername.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_client_async: getsockpeername +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) { + $cli = new \Swoole\Async\Client(SWOOLE_SOCK_UDP); + + $cli->on("connect", function (\Swoole\Async\Client $cli) { + Assert::true($cli->isConnected()); + $cli->send("test"); + }); + + $cli->on("receive", function (\Swoole\Async\Client $cli, $data) { + $i = $cli->getpeername(); + Assert::assert($i !== false); + $cli->send('shutdown'); + $cli->close(); + }); + + $cli->on("close", function (\Swoole\Async\Client $cli) { + echo "SUCCESS\n"; + }); + + $r = $cli->connect(UDP_SERVER_HOST, UDP_SERVER_PORT, 1); + Assert::assert($r); + Swoole\Event::wait(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new \Swoole\Server(UDP_SERVER_HOST, UDP_SERVER_PORT, SWOOLE_BASE, SWOOLE_SOCK_UDP); + $serv->set(["worker_num" => 1, 'log_file' => '/dev/null']); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on("Packet", function (\Swoole\Server $serv, $data, $clientInfo) { + if (trim($data) == 'shutdown') { + $serv->shutdown(); + return; + } + $serv->sendto($clientInfo['address'], $clientInfo['port'], $data); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_async/getsockname.phpt b/tests/swoole_client_async/getsockname.phpt new file mode 100644 index 0000000000..f4b84a6184 --- /dev/null +++ b/tests/swoole_client_async/getsockname.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_client_async: Swoole\Async\Client getsockname +--SKIPIF-- + +--FILE-- +on("connect", function (Swoole\Async\Client $cli) use ($timer) { + Assert::true($cli->isConnected()); + + $i = $cli->getsockname(); + Assert::assert($i !== false); + Assert::same($i["host"], '127.0.0.1'); + + $cli->close(); + Swoole\Timer::clear($timer); +}); + +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { +}); + +$cli->on("error", function (Swoole\Async\Client $cli) { + echo "error"; +}); + +$cli->on("close", function (Swoole\Async\Client $cli) { + echo "SUCCESS"; + Swoole\Event::exit(); +}); + +$cli->connect(TCP_SERVER_HOST, TCP_SERVER_PORT, 1); +Swoole\Event::wait(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_async/length_protocol.phpt b/tests/swoole_client_async/length_protocol.phpt new file mode 100644 index 0000000000..0e59ba74d9 --- /dev/null +++ b/tests/swoole_client_async/length_protocol.phpt @@ -0,0 +1,111 @@ +--TEST-- +swoole_client_async: length protocol [async] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); + $client->set([ + 'open_length_check' => true, + 'package_max_length' => 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + ]); + + $client->on("connect", function (Swoole\Async\Client $cli) { + $cli->send("recv\r\n\r\n"); + }); + + $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { + static $i = 0; + $i++; + + //小包 + if ($i <= 1000) { + Assert::assert($pkg and strlen($pkg) <= 2048); + if ($i == 1000) { + echo "SUCCESS\n"; + } + return; + } //慢速发送 + elseif ($i <= 1100) { + Assert::assert($pkg and strlen($pkg) <= 8192); + if ($i == 1100) { + echo "SUCCESS\n"; + } + return; + } //大包 + else { + Assert::assert($pkg != false); + $_pkg = unserialize(substr($pkg, 4)); + Assert::assert(is_array($_pkg)); + Assert::same($_pkg['i'], $i - 1100 - 1); + Assert::lengthBetween($_pkg['data'], 20000, 256 * 1024); + if ($i == 2100) { + echo "SUCCESS\n"; + $cli->close(); + Swoole\Process::kill($pid); + } + } + }); + + $client->on("error", function (Swoole\Async\Client $cli) { + print("error"); + }); + + $client->on("close", function (Swoole\Async\Client $cli) { + Swoole\Event::exit(); + }); + + if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set(array( + "worker_num" => 1, + 'send_yield' => true, + 'log_file' => '/tmp/swoole.log', + )); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + //小包 + for ($i = 0; $i < 1000; $i++) { + $data = str_repeat('A', rand(100, 2000)); + $serv->send($fd, pack('N', strlen($data)) . $data); + } + //慢速发送 + for ($i = 0; $i < 100; $i++) { + $data = str_repeat('A', rand(3000, 6000)); + $n = rand(1000, 2000); + $serv->send($fd, pack('N', strlen($data)) . substr($data, 0, $n)); + usleep(rand(10000, 50000)); + $serv->send($fd, substr($data, $n)); + } + //大包 + for ($i = 0; $i < 1000; $i++) { + $data = serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]); + $serv->send($fd, pack('N', strlen($data)) . $data); + } + }); + $serv->start(); +}; + +$pm->async = true; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS +SUCCESS +SUCCESS diff --git a/tests/swoole_client_async/length_protocol_func.phpt b/tests/swoole_client_async/length_protocol_func.phpt new file mode 100644 index 0000000000..aa3a45718f --- /dev/null +++ b/tests/swoole_client_async/length_protocol_func.phpt @@ -0,0 +1,74 @@ +--TEST-- +swoole_client_async: length protocol func +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); + $client->set([ + 'open_length_check' => true, + 'package_max_length' => 1024 * 1024, + 'package_length_func' => function ($data) { + $n = strpos($data, '|'); + if ($n == false) { + return -1; + } else { + return intval(substr($data, 0, $n)) + $n + 1; + } + }, + ]); + $client->on("connect", function (Swoole\Async\Client $cli) { + $int = rand(1000, 5000); + $data = json_encode(['data' => RandStr::gen($int), 'index' => 2, 'len' => $int]); + $cli->send(pack('N', strlen($data) + 4) . $data); + }); + + $client->on("receive", function (Swoole\Async\Client $cli, $pkg) use ($pid) { + Assert::assert($pkg != false and strlen($pkg) > 100); + Swoole\Process::kill($pid); + $cli->close(); + }); + + $client->on("error", function (Swoole\Async\Client $cli) { + print("error"); + }); + + $client->on("close", function (Swoole\Async\Client $cli) { + Swoole\Event::exit(); + }); + + if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_length_check' => true, + 'package_max_length' => 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 0, + ]); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $data = str_repeat('A', rand(100, 2000)); + $serv->send($fd, strlen($data) . "|" . $data); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_client_async/port_invalid.phpt b/tests/swoole_client_async/port_invalid.phpt new file mode 100644 index 0000000000..387f27d863 --- /dev/null +++ b/tests/swoole_client_async/port_invalid.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_client_async: port invalid +--SKIPIF-- + +--FILE-- +on("connect", function (Swoole\Async\Client $cli) { + +}); + +$cli->on("receive", function (Swoole\Async\Client $cli, $data) { +}); + +$cli->on("error", function (Swoole\Async\Client $cli) { + +}); + +$cli->on("close", function (Swoole\Async\Client $cli) { + +}); + +Assert::false(@$cli->connect("www.baidu.com", null, 2.0)); +Assert::same(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); + +Swoole\Event::wait(); +?> +--EXPECT-- diff --git a/tests/swoole_client_async/sendfile.phpt b/tests/swoole_client_async/sendfile.phpt new file mode 100644 index 0000000000..722614ac77 --- /dev/null +++ b/tests/swoole_client_async/sendfile.phpt @@ -0,0 +1,62 @@ +--TEST-- +swoole_client_async: async sendfile +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($port) { + $client = new Swoole\Async\Client(SWOOLE_SOCK_TCP); + $client->on("connect", function (Swoole\Async\Client $cli) { + $cli->send(pack('N', filesize(TEST_IMAGE))); + $ret = $cli->sendfile(TEST_IMAGE); + Assert::assert($ret); + }); + $client->on("receive", function (Swoole\Async\Client $cli, $data) { + $cli->send(pack('N', 8) . 'shutdown'); + $cli->close(); + Assert::same($data, md5_file(TEST_IMAGE)); + }); + $client->on("error", function ($cli) { + echo "Connect failed\n"; + }); + $client->on("close", function ($cli) { + + }); + $client->connect(TCP_SERVER_HOST, $port, 0.5); + Swoole\Event::wait(); +}; + +$pm->childFunc = function () use ($pm, $port) { + $serv = new \Swoole\Server(TCP_SERVER_HOST, $port, SWOOLE_BASE, SWOOLE_SOCK_TCP); + $serv->set([ + "worker_num" => 1, + 'log_file' => '/dev/null', + 'open_length_check' => true, + 'dispatch_mode' => 1, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + 'package_max_length' => 2000000, + ]); + $serv->on("WorkerStart", function (\Swoole\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on("Receive", function (\Swoole\Server $serv, $fd, $rid, $data) { + if (substr($data, 4, 8) == 'shutdown') { + $serv->shutdown(); + return; + } + $serv->send($fd, md5(substr($data, 4))); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_client_async/sleep_wake.phpt b/tests/swoole_client_async/sleep_wake.phpt new file mode 100644 index 0000000000..1c3aed47b4 --- /dev/null +++ b/tests/swoole_client_async/sleep_wake.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_client_async: Swoole\Async\Client sleep & sleep +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $cli = new \Swoole\Async\Client(SWOOLE_SOCK_TCP); + + $cli->on("connect", function (Swoole\Async\Client $cli) { + Assert::true($cli->isConnected()); + $r = $cli->sleep(); + Assert::assert($r); + swoole_timer_after(200, function () use ($cli) { + $r = $cli->wakeup(); + Assert::assert($r); + }); + $cli->send(RandStr::gen(1024, RandStr::ALL)); + }); + + $cli->on("receive", function (Swoole\Async\Client $cli, $data) { + $recv_len = strlen($data); + $cli->send(RandStr::gen(1024, RandStr::ALL)); + $cli->close(); + Assert::false($cli->isConnected()); + }); + + $cli->on("error", function (Swoole\Async\Client $cli) { + echo "error"; + }); + + $cli->on("close", function (Swoole\Async\Client $cli) { + echo "SUCCESS"; + }); + + $cli->connect('127.0.0.1', $pm->getFreePort(), 0.1); + Swoole\Event::wait(); + Swoole\Process::kill($pid); +}; + +$pm->childFunc = function () use ($pm) { + include __DIR__ . "/../include/api/tcp_server.php"; +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_coro/bug_2346.phpt b/tests/swoole_client_coro/bug_2346.phpt index 6d3b68dac2..7819082d41 100644 --- a/tests/swoole_client_coro/bug_2346.phpt +++ b/tests/swoole_client_coro/bug_2346.phpt @@ -29,7 +29,7 @@ $pm->parentFunc = function () use ($pm) { $client->close(); }); Assert::assert(@!$client->recv(-1)); // connection closed - Assert::same($client->errCode, SOCKET_ECONNRESET); + Assert::same($client->errCode, SOCKET_ECANCELED); approximate(0.5, microtime(true) - $s); // canceled echo "DONE\n"; diff --git a/tests/swoole_client_coro/close.phpt b/tests/swoole_client_coro/close.phpt index 5219323027..2290d1e856 100644 --- a/tests/swoole_client_coro/close.phpt +++ b/tests/swoole_client_coro/close.phpt @@ -19,6 +19,7 @@ $pm->parentFunc = function ($pid) use ($pm) 'package_body_offset' => 4, ]); $cli->connect('127.0.0.1', $pm->getFreePort()); + Assert::assert($cli->socket instanceof Swoole\Coroutine\Socket); $data = str_repeat('A', 1025); $cli->send(pack('N', strlen($data)).$data); co::sleep(0.2); @@ -26,6 +27,7 @@ $pm->parentFunc = function ($pid) use ($pm) Assert::assert(is_string($retData) and strlen($retData) > 0); /** use valgrind to check memory */ $cli->close(); + Assert::eq($cli->socket, null); Assert::assert(!$cli->connected); }); Swoole\Event::wait(); diff --git a/tests/swoole_client_coro/close_in_other_co.phpt b/tests/swoole_client_coro/close_in_other_co.phpt index 0eab7b7241..ccbe7396f6 100644 --- a/tests/swoole_client_coro/close_in_other_co.phpt +++ b/tests/swoole_client_coro/close_in_other_co.phpt @@ -21,7 +21,8 @@ go(function () use ($client) { $client->connect('127.0.0.1', 9601); $data = @$client->recv(); //socket is closed - Assert::assert(!$data && $client->errCode === SOCKET_ECONNRESET); + Assert::assert(!$data ); + Assert::eq($client->errCode, SOCKET_ECANCELED); }); go(function () use ($client, $cid) { diff --git a/tests/swoole_client_coro/close_socket_property.phpt b/tests/swoole_client_coro/close_socket_property.phpt new file mode 100644 index 0000000000..8f39c53e66 --- /dev/null +++ b/tests/swoole_client_coro/close_socket_property.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_client_coro: close socket property +--SKIPIF-- + +--FILE-- +connect('www.baidu.com', 80)); + Assert::true($cli->connected); + Assert::true($cli->socket->close()); + Assert::false($cli->close()); + Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); + Assert::false($cli->connected); + Assert::null($cli->socket); + Assert::true($cli->connect('www.baidu.com', 80)); +}); +Swoole\Event::wait(); +?> +--EXPECT-- diff --git a/tests/swoole_client_coro/close_twice.phpt b/tests/swoole_client_coro/close_twice.phpt new file mode 100644 index 0000000000..15cdc44a7e --- /dev/null +++ b/tests/swoole_client_coro/close_twice.phpt @@ -0,0 +1,17 @@ +--TEST-- +swoole_client_coro: close twice +--SKIPIF-- + +--FILE-- +connect('www.baidu.com', 80); + Assert::true($cli->close()); + Assert::false($cli->close()); + Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); +}); +Swoole\Event::wait(); +?> +--EXPECT-- diff --git a/tests/swoole_client_coro/connect_timeout.phpt b/tests/swoole_client_coro/connect_timeout.phpt index c7fd4321a7..b9bd5f2fbd 100644 --- a/tests/swoole_client_coro/connect_timeout.phpt +++ b/tests/swoole_client_coro/connect_timeout.phpt @@ -16,13 +16,13 @@ go(function () { Assert::same($cli->errCode, SOCKET_ETIMEDOUT); $s = microtime(true) - $s; phpt_var_dump($s); - time_approximate($s, 0.1); + time_approximate($s, 0.1, 0.2); $s = microtime(true); Assert::assert(!@$cli->connect('140.207.135.104', 1, $random_timeout = mt_rand(100, 1000) / 1000)); Assert::same($cli->errCode, SOCKET_ETIMEDOUT); $s = microtime(true) - $s; phpt_var_dump($s); - time_approximate($random_timeout, $s); + time_approximate($random_timeout, $s, 0.2); echo "DONE\n"; }); diff --git a/tests/swoole_client_coro/connect_with_dns.phpt b/tests/swoole_client_coro/connect_with_dns.phpt index 0dc742d2ea..c428be567f 100644 --- a/tests/swoole_client_coro/connect_with_dns.phpt +++ b/tests/swoole_client_coro/connect_with_dns.phpt @@ -10,7 +10,7 @@ require __DIR__ . '/../include/bootstrap.php'; go(function () { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); - Assert::true($cli->connect('www.tsinghua.edu.cn', 80)); + Assert::true($cli->connect('www.qq.com', 80)); }); ?> diff --git a/tests/swoole_client_coro/eof_03.phpt b/tests/swoole_client_coro/eof_03.phpt index 32e53254a4..9e5d3ae999 100644 --- a/tests/swoole_client_coro/eof_03.phpt +++ b/tests/swoole_client_coro/eof_03.phpt @@ -3,7 +3,7 @@ swoole_client_coro: eof with smtp qq --SKIPIF-- --FILE-- diff --git a/tests/swoole_client_coro/export_socket.phpt b/tests/swoole_client_coro/export_socket.phpt new file mode 100644 index 0000000000..a502872205 --- /dev/null +++ b/tests/swoole_client_coro/export_socket.phpt @@ -0,0 +1,19 @@ +--TEST-- +swoole_client_coro: close socket property +--SKIPIF-- + +--FILE-- +connect('www.baidu.com', 80)); + Assert::true($cli->connected); + $socket = $cli->exportSocket(); + $socket->close(); + Assert::false($cli->recv()); + Assert::false($cli->close()); +}); +Swoole\Event::wait(); +?> +--EXPECT-- diff --git a/tests/swoole_client_coro/reconnect.phpt b/tests/swoole_client_coro/reconnect.phpt index 2d897bbb66..9dadbaf2bf 100644 --- a/tests/swoole_client_coro/reconnect.phpt +++ b/tests/swoole_client_coro/reconnect.phpt @@ -1,5 +1,5 @@ --TEST-- -swoole_client_coro: reconnect +swoole_client_coro: reconnect 1 --SKIPIF-- --FILE-- @@ -42,10 +42,11 @@ $pm->childFunc = function () use ($pm) { $serv->set([ 'log_file' => '/dev/null', ]); - + $serv->on('start', function () use ($pm) { + $pm->wakeup(); + }); $serv->on('Receive', function () { }); - $serv->start(); }; $pm->childFirst(); diff --git a/tests/swoole_client_coro/reconnect_2.phpt b/tests/swoole_client_coro/reconnect_2.phpt new file mode 100644 index 0000000000..29dd30fb0f --- /dev/null +++ b/tests/swoole_client_coro/reconnect_2.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_client_coro: reconnect 2 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + run(function () use ($pm) { + $flag = 0; + $client = new Client(SWOOLE_SOCK_TCP); + + $n = 2; + while ($n--) { + Assert::true($client->connect('127.0.0.1', 9501)); + Assert::true($client->close()); + } + echo "DONE\n"; + }); + + $pm->kill(); + +}; +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', 9501); + $serv->set([ + 'log_file' => '/dev/null', + ]); + $serv->on('start', function () use ($pm) { + $pm->wakeup(); + }); + $serv->on('Receive', function () { + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_client_coro/reconnect_3.phpt b/tests/swoole_client_coro/reconnect_3.phpt new file mode 100644 index 0000000000..800ea9385c --- /dev/null +++ b/tests/swoole_client_coro/reconnect_3.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_client_coro: reconnect 3 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + run(function () use ($pm) { + $flag = 0; + $client = new Client(SWOOLE_SOCK_TCP); + + $n = 2; + while ($n--) { + Assert::true($client->connect('127.0.0.1', 9501)); + go(function () use ($client) { + while (1) { + if (!$client->recv()) { + break; + } + } + Assert::true($client->close()); + }); + Assert::false($client->close()); + Assert::eq($client->errCode, SWOOLE_ERROR_CO_SOCKET_CLOSE_WAIT); + } + echo "DONE\n"; + }); + + $pm->kill(); + +}; +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', 9501); + $serv->set([ + 'log_file' => '/dev/null', + ]); + $serv->on('start', function () use ($pm) { + $pm->wakeup(); + }); + $serv->on('Receive', function () { + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_client_coro/recvfrom.phpt b/tests/swoole_client_coro/recvfrom.phpt index d5c1b7568d..943888ab8f 100644 --- a/tests/swoole_client_coro/recvfrom.phpt +++ b/tests/swoole_client_coro/recvfrom.phpt @@ -1,5 +1,5 @@ --TEST-- -swoole_client_coro: sendto +swoole_client_coro: recvfrom 1 --SKIPIF-- --FILE-- diff --git a/tests/swoole_client_coro/recvfrom_2.phpt b/tests/swoole_client_coro/recvfrom_2.phpt new file mode 100644 index 0000000000..3c792fdff1 --- /dev/null +++ b/tests/swoole_client_coro/recvfrom_2.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_client_coro: recvfrom 2 +--SKIPIF-- + +--FILE-- +set([ + 'bind_address' => '127.0.0.1', + 'bind_port' => $free_port, + ]); + $n = N; + while ($n--) { + $data = $cli->recvfrom(1024, $addr, $port); + Assert::same($data, 'hello'); + } + echo "DONE\n"; +}); + +go(function () use ($free_port) { + $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); + $n = N; + while ($n--) { + $socket->sendto('127.0.0.1', $free_port, "hello"); + Co::sleep(0.01); + } +}); +Swoole\Event::wait(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_client_sync/bind_address.phpt b/tests/swoole_client_sync/bind_address.phpt index 32e9c739e0..ba3e0d85ac 100644 --- a/tests/swoole_client_sync/bind_address.phpt +++ b/tests/swoole_client_sync/bind_address.phpt @@ -37,5 +37,4 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -Warning: Swoole\Client::connect(): bind address or port error in set method in %s DONE diff --git a/tests/swoole_client_sync/connect_2.phpt b/tests/swoole_client_sync/connect_2.phpt index d0ac6a5935..4ff0d7b4e4 100644 --- a/tests/swoole_client_sync/connect_2.phpt +++ b/tests/swoole_client_sync/connect_2.phpt @@ -9,7 +9,7 @@ require __DIR__ . '/../include/bootstrap.php'; killself_in_syncmode(1000, SIGTERM); $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); -$r = $cli->connect(MYSQL_SERVER_HOST, MYSQL_SERVER_PORT); +$r = $cli->connect(TEST_DOMAIN_3, 443); Assert::assert($r); $cli->close(); echo "SUCCESS"; diff --git a/tests/swoole_client_sync/enableSSL.phpt b/tests/swoole_client_sync/enableSSL.phpt new file mode 100644 index 0000000000..5c9d8ef873 --- /dev/null +++ b/tests/swoole_client_sync/enableSSL.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_client_sync: enableSSL +--SKIPIF-- + +--FILE-- +connect("www.baidu.com", 443, 2.0)); + +if ($cli->enableSSL()) { + echo "SSL READY\n"; + $cli->send("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\nUser-Agent: curl/7.50.1-DEV\r\nAccept: */*\r\n\r\n"); +} + +$resp = ''; +while (true) { + $data = $cli->recv(); + if ($data == false) { + break; + } + $resp .= $data; +} + +Assert::assert(strlen($resp) > 0); +Assert::contains($resp, 'www.baidu.com'); +$cli->close(); +echo "DONE\n"; +?> +--EXPECT-- +SSL READY +DONE diff --git a/tests/swoole_client_sync/enableSSL_2.phpt b/tests/swoole_client_sync/enableSSL_2.phpt new file mode 100644 index 0000000000..c867d2b5cb --- /dev/null +++ b/tests/swoole_client_sync/enableSSL_2.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_client_sync: enableSSL +--SKIPIF-- + +--FILE-- +connect("www.baidu.com", 443, 2.0)); + +try { + $cli->enableSSL(function (){}); +} catch (\Throwable $e) { + Assert::contains($e->getMessage(), 'not support `onSslReady` callback'); +} +?> +--EXPECT-- diff --git a/tests/swoole_client_sync/eof.phpt b/tests/swoole_client_sync/eof.phpt index b022cf1ca2..7da3417047 100644 --- a/tests/swoole_client_sync/eof.phpt +++ b/tests/swoole_client_sync/eof.phpt @@ -34,7 +34,7 @@ $pm->parentFunc = function ($pid) use ($port) { for ($i = 0; $i < 1000; $i++) { $pkg = $client->recv(); Assert::assert($pkg != false); - $_pkg = unserialize($pkg); + $_pkg = swoole_substr_unserialize($pkg, 0, strlen($pkg) - 4); Assert::assert(is_array($_pkg)); Assert::same($_pkg['i'], $i); Assert::assert(strlen($_pkg['data']) > 8192 and strlen($_pkg['data']) <= 256 * 1024); diff --git a/tests/swoole_client_sync/eof_close.phpt b/tests/swoole_client_sync/eof_close.phpt new file mode 100644 index 0000000000..484dc19964 --- /dev/null +++ b/tests/swoole_client_sync/eof_close.phpt @@ -0,0 +1,61 @@ +--TEST-- +swoole_client_sync: eof protocol [sync] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($port, $pkg, $pm) { + $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client->set(['open_eof_check' => true, "package_eof" => EOF]); + if (!$client->connect('127.0.0.1', $port, 5, 0)) { + echo "Over flow. errno=" . $client->errCode; + die("\n"); + } + + $client->send("recv\r\n\r\n"); + $recvPkg = $client->recv(); + Assert::assert($recvPkg != false); + $_pkg = swoole_substr_unserialize($recvPkg, 0, strlen($recvPkg) - 4); + Assert::assert(is_array($_pkg)); + Assert::eq($_pkg['data'], $pkg); + $recvPkg = $client->recv(); + Assert::same($recvPkg, ''); + echo "SUCCESS\n"; + $client->close(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $port, $pkg) { + $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); + $serv->set(array( + 'package_eof' => "\r\n\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_max_length' => 1024 * 1024 * 2, + 'socket_buffer_size' => 256 * 1024 * 1024, + 'log_file' => TEST_LOG_FILE, + )); + $serv->on("WorkerStart", function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pkg) { + $serv->send($fd, serialize(['data' => $pkg]) . EOF); + $serv->close($fd); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_client_sync/http_proxy.phpt b/tests/swoole_client_sync/http_proxy.phpt new file mode 100644 index 0000000000..0da7819d31 --- /dev/null +++ b/tests/swoole_client_sync/http_proxy.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_client_sync: http client with http_proxy +--SKIPIF-- + +--FILE-- +set([ + 'timeout' => 30, + 'http_proxy_host' => HTTP_PROXY_HOST, + 'http_proxy_port' => HTTP_PROXY_PORT +]); +client_http_v10_get($cli); +?> +--EXPECT-- diff --git a/tests/swoole_client_sync/length_protocol.phpt b/tests/swoole_client_sync/length_protocol.phpt index 74f63f1ab6..40d5210ca3 100644 --- a/tests/swoole_client_sync/length_protocol.phpt +++ b/tests/swoole_client_sync/length_protocol.phpt @@ -1,14 +1,24 @@ --TEST-- swoole_client_sync: length protocol [sync] --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($port) { - $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ 'open_length_check' => true, 'package_max_length' => 1024 * 1024, @@ -16,27 +26,27 @@ $pm->parentFunc = function ($pid) use ($port) { 'package_length_offset' => 0, 'package_body_offset' => 4, ]); - if (!$client->connect('127.0.0.1', $port, 0.5, 0)) { - echo "Over flow. errno=" . $client->errCode; - die("\n"); + if (!$client->connect('127.0.0.1', $port, 5, 0)) { + echo 'Over flow. errno=' . $client->errCode; + exit("\n"); } $client->send("recv\r\n\r\n"); - //小包 - for ($i = 0; $i < 1000; $i++) { + // 小包 + for ($i = 0; $i < N_1; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 2048); } echo "SUCCESS\n"; - //慢速发送 - for ($i = 0; $i < 100; $i++) { + // 慢速发送 + for ($i = 0; $i < N_2; $i++) { $pkg = $client->recv(); Assert::assert($pkg and strlen($pkg) <= 8192); } echo "SUCCESS\n"; - //大包 - for ($i = 0; $i < 1000; $i++) { + // 大包 + for ($i = 0; $i < N_3; $i++) { $pkg = $client->recv(); Assert::assert($pkg != false); $_pkg = unserialize(substr($pkg, 4)); @@ -47,36 +57,36 @@ $pm->parentFunc = function ($pid) use ($port) { echo "SUCCESS\n"; $client->close(); - Swoole\Process::kill($pid); + Process::kill($pid); }; $pm->childFunc = function () use ($pm, $port) { - $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE); - $serv->set(array( - 'package_max_length' => 1024 * 1024 * 2, //2M + $serv = new Server('127.0.0.1', $port, SWOOLE_BASE); + $serv->set([ + 'package_max_length' => 1024 * 1024 * 2, // 2M 'socket_buffer_size' => 256 * 1024 * 1024, - "worker_num" => 1, + 'worker_num' => 1, 'log_file' => '/tmp/swoole.log', - )); - $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { - //小包 - for ($i = 0; $i < 1000; $i++) { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + // 小包 + for ($i = 0; $i < N_1; $i++) { $data = str_repeat('A', rand(100, 2000)); $serv->send($fd, pack('N', strlen($data)) . $data); } - //慢速发送 - for ($i = 0; $i < 100; $i++) { + // 慢速发送 + for ($i = 0; $i < N_2; $i++) { $data = str_repeat('A', rand(3000, 6000)); $n = rand(1000, 2000); $serv->send($fd, pack('N', strlen($data)) . substr($data, 0, $n)); usleep(rand(10000, 50000)); $serv->send($fd, substr($data, $n)); } - //大包 - for ($i = 0; $i < 1000; $i++) { + // 大包 + for ($i = 0; $i < N_3; $i++) { $data = serialize(['i' => $i, 'data' => str_repeat('A', rand(20000, 256 * 1024))]); $serv->send($fd, pack('N', strlen($data)) . $data); } diff --git a/tests/swoole_client_sync/recv_with_open_eof_check.phpt b/tests/swoole_client_sync/recv_with_open_eof_check.phpt index db424e34dc..bfab09f852 100644 --- a/tests/swoole_client_sync/recv_with_open_eof_check.phpt +++ b/tests/swoole_client_sync/recv_with_open_eof_check.phpt @@ -22,7 +22,7 @@ $pm->parentFunc = function () use ($pm) { $data = @$client->recv(1024 * 1024 * 2); Assert::false($data); - Assert::eq(11, $client->errCode); + Assert::eq(SOCKET_EAGAIN, $client->errCode); $client->close(); $pm->kill(); echo "DONE\n"; diff --git a/tests/swoole_client_sync/select_null.phpt b/tests/swoole_client_sync/select_null.phpt index 298f06d3f3..c7b2dbaf05 100644 --- a/tests/swoole_client_sync/select_null.phpt +++ b/tests/swoole_client_sync/select_null.phpt @@ -12,12 +12,10 @@ use SwooleTest\ProcessManager; const TIMEOUT = 0.05; -$pm = new ProcessManager; -$pm->parentFunc = function ($pid) use ($pm) -{ +$pm = new ProcessManager(); +$pm->parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - if (!$client->connect(TCP_SERVER_HOST, $pm->getFreePort(), -1)) - { + if (!$client->connect(TCP_SERVER_HOST, $pm->getFreePort(), -1)) { exit("connect failed. Error: {$client->errCode}\n"); } $r = [$client]; @@ -29,17 +27,16 @@ $pm->parentFunc = function ($pid) use ($pm) $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ - "worker_num" => 1, + 'worker_num' => 1, 'log_file' => '/dev/null', ]); - $serv->on("WorkerStart", function (Server $serv) use ($pm) { + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on("Receive", function (Server $serv, $fd, $rid, $data) { + $serv->on('Receive', function (Server $serv, $fd, $rid, $data) { $serv->send($fd, "hello world\n"); }); $serv->start(); diff --git a/tests/swoole_client_sync/sendfile.phpt b/tests/swoole_client_sync/sendfile.phpt index 34c024b1e0..94ea41897a 100644 --- a/tests/swoole_client_sync/sendfile.phpt +++ b/tests/swoole_client_sync/sendfile.phpt @@ -1,14 +1,13 @@ --TEST-- swoole_client_sync: sync sendfile --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($port) -{ +$pm->parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $r = $client->connect(TCP_SERVER_HOST, $port, 0.5); Assert::assert($r); @@ -22,8 +21,7 @@ $pm->parentFunc = function ($pid) use ($port) Assert::same($data, md5_file(TEST_IMAGE)); }; -$pm->childFunc = function () use ($pm, $port) -{ +$pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server(TCP_SERVER_HOST, $port, SWOOLE_BASE, SWOOLE_SOCK_TCP); $serv->set([ "worker_num" => 1, @@ -35,14 +33,11 @@ $pm->childFunc = function () use ($pm, $port) 'package_body_offset' => 4, 'package_max_length' => 2000000, ]); - $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) - { + $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) - { - if (substr($data, 4, 8) == 'shutdown') - { + $serv->on("Receive", function (Swoole\Server $serv, $fd, $rid, $data) { + if (substr($data, 4, 8) == 'shutdown') { $serv->shutdown(); return; } diff --git a/tests/swoole_client_sync/socks5_proxy.phpt b/tests/swoole_client_sync/socks5_proxy.phpt new file mode 100644 index 0000000000..26f7072242 --- /dev/null +++ b/tests/swoole_client_sync/socks5_proxy.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_client_sync: http client with socks5 proxy +--SKIPIF-- + +--FILE-- +set([ + 'timeout' => 30, + 'socks5_host' => SOCKS5_PROXY_HOST, + 'socks5_port' => SOCKS5_PROXY_PORT +]); +client_http_v10_get($cli); +?> +--EXPECT-- diff --git a/tests/swoole_client_sync/udg_send_timeout.phpt b/tests/swoole_client_sync/udg_send_timeout.phpt index f10b923918..964f893a04 100644 --- a/tests/swoole_client_sync/udg_send_timeout.phpt +++ b/tests/swoole_client_sync/udg_send_timeout.phpt @@ -1,15 +1,19 @@ --TEST-- swoole_client_sync: udg send timeout --SKIPIF-- - + --FILE-- connect(SOCKET_FILE, 0, 0.3); $s = microtime(true); diff --git a/tests/swoole_coroutine/all_asleep.phpt b/tests/swoole_coroutine/all_asleep.phpt index b0be8c3192..b68850de71 100644 --- a/tests/swoole_coroutine/all_asleep.phpt +++ b/tests/swoole_coroutine/all_asleep.phpt @@ -35,20 +35,23 @@ Co\run(function () { echo "DONE\n"; ?> --EXPECTF-- -=================================================================== - [FATAL ERROR]: all coroutines (count: 2) are asleep - deadlock! -=================================================================== - [Coroutine-3] --------------------------------------------------------------------- + ================================================================= + [FATAL ERROR]: all coroutines (count: 2) are asleep - deadlock! + ================================================================= + + [Coroutine-3] + ----------------------------------------------------------------- +%A %A %A - - [Coroutine-2] --------------------------------------------------------------------- + [Coroutine-2] + ----------------------------------------------------------------- +%A %A %A %A + ================================================================= DONE diff --git a/tests/swoole_coroutine/async_callback/signal.phpt b/tests/swoole_coroutine/async_callback/signal.phpt index 6a11a84775..7012b47067 100644 --- a/tests/swoole_coroutine/async_callback/signal.phpt +++ b/tests/swoole_coroutine/async_callback/signal.phpt @@ -11,7 +11,8 @@ use Swoole\Process; Co\run(function () { Process::signal(SIGUSR1, function ($signo) { Co::sleep(0.5); - var_dump($signo); + Assert::eq($signo, SIGUSR1); + echo "Done\n"; }); Co::sleep(0.01); @@ -20,4 +21,4 @@ Co\run(function () { }); ?> --EXPECT-- -int(10) +Done diff --git a/tests/swoole_coroutine/autoload.phpt b/tests/swoole_coroutine/autoload.phpt new file mode 100644 index 0000000000..f3bd94d5db --- /dev/null +++ b/tests/swoole_coroutine/autoload.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_coroutine: autoload +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +object(SwooleTestClassA)#%d (0) { +} +object(SwooleTestClassA)#%d (0) { +} diff --git a/tests/swoole_coroutine/autoload_not_found.phpt b/tests/swoole_coroutine/autoload_not_found.phpt new file mode 100644 index 0000000000..eb6aeb40f7 --- /dev/null +++ b/tests/swoole_coroutine/autoload_not_found.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_coroutine: autoload not found +--SKIPIF-- + +--FILE-- +getMessage(), 'Class "SwooleTestClassA2" not found'); + echo "DONE\n"; + } + +}); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_coroutine/autoload_not_found_not_in_coroutine.phpt b/tests/swoole_coroutine/autoload_not_found_not_in_coroutine.phpt new file mode 100644 index 0000000000..4784562eac --- /dev/null +++ b/tests/swoole_coroutine/autoload_not_found_not_in_coroutine.phpt @@ -0,0 +1,26 @@ +--TEST-- +swoole_coroutine: autoload not found and not in coroutine +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "SwooleTestClassA2" not found in %s:%d +Stack trace: +#0 %s(%d): %s +#1 %s(%d): %s +#2 {main} + thrown in %s on line %d diff --git a/tests/swoole_coroutine/autoload_not_in_coroutine.phpt b/tests/swoole_coroutine/autoload_not_in_coroutine.phpt new file mode 100644 index 0000000000..2eff413ae6 --- /dev/null +++ b/tests/swoole_coroutine/autoload_not_in_coroutine.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_coroutine: autoload not in coroutine +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +object(SwooleTestClassA)#%d (0) { +} +object(SwooleTestClassA)#%d (0) { +} diff --git a/tests/swoole_coroutine/bailout/co_redis_in_shutdown_function.phpt b/tests/swoole_coroutine/bailout/co_redis_in_shutdown_function.phpt new file mode 100644 index 0000000000..1ad33d2c79 --- /dev/null +++ b/tests/swoole_coroutine/bailout/co_redis_in_shutdown_function.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_coroutine/bailout: call co redis in shutdown function +--SKIPIF-- + +--FILE-- +connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); + register_shutdown_function(function () use ($redis) { + $redis->get('key'); + }); + usleep(10000); +}); + +Event::wait(); +?> +--EXPECTF-- +Fatal error: Uncaught Swoole\Error: API must be called in the coroutine in %s:%d +Stack trace: +#0 %s(%d): Redis->get('key') +#1 [internal function]: {closure%S}() +#2 {main} + thrown in %s on line %d diff --git a/tests/swoole_coroutine/bailout/error_in.phpt b/tests/swoole_coroutine/bailout/error_in.phpt index d016c99b24..ebbe0387cb 100644 --- a/tests/swoole_coroutine/bailout/error_in.phpt +++ b/tests/swoole_coroutine/bailout/error_in.phpt @@ -1,10 +1,13 @@ --TEST-- swoole_coroutine/bailout: error in the coroutine --SKIPIF-- - + --FILE-- --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function a() in %s:%d diff --git a/tests/swoole_coroutine/bailout/error_internal.phpt b/tests/swoole_coroutine/bailout/error_internal.phpt index e927aa5a36..ef45a5313d 100644 --- a/tests/swoole_coroutine/bailout/error_internal.phpt +++ b/tests/swoole_coroutine/bailout/error_internal.phpt @@ -14,3 +14,9 @@ go(function () { ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted %s +--EXPECTF_85-- +Fatal error: Allowed memory size of %d bytes exhausted %s +Stack trace: +#0 %s(%d): str_repeat('A', %d) +#1 [internal function]: {%s:%d}() +#2 {main} diff --git a/tests/swoole_coroutine/bailout/error_internal2.phpt b/tests/swoole_coroutine/bailout/error_internal2.phpt index d2320688cf..113133a755 100644 --- a/tests/swoole_coroutine/bailout/error_internal2.phpt +++ b/tests/swoole_coroutine/bailout/error_internal2.phpt @@ -16,3 +16,9 @@ go(function () { ?> --EXPECTF-- Fatal error: Allowed memory size of %d bytes exhausted %s +--EXPECTF_85-- +Fatal error: Allowed memory size of %d bytes exhausted %s +Stack trace: +#0 %s(%d): str_repeat('A', %d) +#1 [internal function]: {closure:{closure:%s:%d}:%d}() +#2 {main} diff --git a/tests/swoole_coroutine/bailout/error_out.phpt b/tests/swoole_coroutine/bailout/error_out.phpt index a25770a92d..ab22dd722b 100644 --- a/tests/swoole_coroutine/bailout/error_out.phpt +++ b/tests/swoole_coroutine/bailout/error_out.phpt @@ -1,10 +1,13 @@ --TEST-- swoole_coroutine/bailout: error out of the coroutine --SKIPIF-- - + --FILE-- --EXPECTF-- Fatal error: Uncaught Error: Call to undefined function a() in %s:%d diff --git a/tests/swoole_coroutine/bailout/exit.phpt b/tests/swoole_coroutine/bailout/exit.phpt index 95d1e4e825..ea233b7531 100644 --- a/tests/swoole_coroutine/bailout/exit.phpt +++ b/tests/swoole_coroutine/bailout/exit.phpt @@ -10,12 +10,10 @@ $process = new Swoole\Process(function () { echo 'shutdown' . PHP_EOL; }); go(function () { - try - { + try { exit(0); - } catch (Swoole\ExitException $e) - { - echo $e->getMessage()."\n"; + } catch (Swoole\ExitException $e) { + echo $e->getMessage() . "\n"; } }); }); diff --git a/tests/swoole_coroutine/bug_2387.phpt b/tests/swoole_coroutine/bug_2387.phpt index c705d2975a..8b3b7d7629 100644 --- a/tests/swoole_coroutine/bug_2387.phpt +++ b/tests/swoole_coroutine/bug_2387.phpt @@ -1,7 +1,10 @@ --TEST-- swoole_coroutine: call_user_func_array --SKIPIF-- - + --FILE-- childFunc = function () use ($pm) { $httpServer = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $httpServer->set([ 'log_file' => '/dev/null', - 'worker_num' => 1 + 'worker_num' => 1, + 'hook_flags' => SWOOLE_HOOK_ALL, ]); $httpServer->on('WorkerStart', function (Swoole\Http\Server $server) use ($pm, $config) { try { diff --git a/tests/swoole_coroutine/bug_5699.phpt b/tests/swoole_coroutine/bug_5699.phpt new file mode 100644 index 0000000000..5c7fad3f96 --- /dev/null +++ b/tests/swoole_coroutine/bug_5699.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_coroutine: Github bug #5699 +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_coroutine/cancel/kill.phpt b/tests/swoole_coroutine/cancel/kill.phpt new file mode 100644 index 0000000000..95a52051e2 --- /dev/null +++ b/tests/swoole_coroutine/cancel/kill.phpt @@ -0,0 +1,35 @@ +--TEST-- +swoole_coroutine/cancel: kill +--SKIPIF-- + +--FILE-- + +--EXPECT-- +co 2 running +co 2 running +co 1 end diff --git a/tests/swoole_coroutine/cancel/throw_exception.phpt b/tests/swoole_coroutine/cancel/throw_exception.phpt new file mode 100644 index 0000000000..463328ec6e --- /dev/null +++ b/tests/swoole_coroutine/cancel/throw_exception.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_coroutine/cancel: throw exception +--SKIPIF-- + +--FILE-- + +--EXPECT-- +co 2 running +co 2 running +string(9) "cancelled" +co 1 end diff --git a/tests/swoole_coroutine/cancel/wait.phpt b/tests/swoole_coroutine/cancel/wait.phpt index a847f978e4..75b24b7872 100644 --- a/tests/swoole_coroutine/cancel/wait.phpt +++ b/tests/swoole_coroutine/cancel/wait.phpt @@ -6,19 +6,27 @@ swoole_coroutine/cancel: wait/waitpid start(); + +run(function () use ($proc) { $cid = Coroutine::getCid(); go(function () use ($cid) { System::sleep(0.002); Assert::true(Coroutine::cancel($cid)); }); + $retval = System::wait(); echo "Done\n"; + Process::kill($proc->pid, SIGKILL); Assert::eq($retval, false); Assert::eq(swoole_last_error(), SWOOLE_ERROR_CO_CANCELED); }); diff --git a/tests/swoole_coroutine/check.phpt b/tests/swoole_coroutine/check.phpt index 0c4e0b3432..ab166aaee9 100644 --- a/tests/swoole_coroutine/check.phpt +++ b/tests/swoole_coroutine/check.phpt @@ -29,18 +29,6 @@ $map = [ (new Chan)->pop(); Assert::assert(0); // never here }, - function () { - Co::fread(STDIN); - Assert::assert(0); // never here - }, - function () { - Co::fgets(fopen(__FILE__, 'r')); - Assert::assert(0); // never here - }, - function () { - Co::fwrite(fopen(TEST_LOG_FILE, 'w+'), 'foo'); - Assert::assert(0); // never here - }, function () { Co::readFile(__FILE__); Assert::assert(0); // never here @@ -80,32 +68,8 @@ $map = [ (new Co\Http\Client('127.0.0.1', 1234))->get('/'); Assert::assert(0); // never here }, - function () { - (new Co\Mysql)->connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - Assert::assert(0); // never here - }, - function () { - (new Co\Redis)->connect('127.0.0.1', 6379); - Assert::assert(0); // never here - }, ]; -function pgsql_test() { - (new Co\Postgresql())->connect('host=127.0.0.1 port=12345 dbname=test user=root password=root'); - Assert::assert(0); // never here -} - -if (class_exists(Co\Postgresql::class)) { - $map[] = function () { - pgsql_test(); - }; -} if (class_exists(Co\Http2\Client::class)) { $map[] = function () { (new Co\Http2\Client('127.0.0.1', 1234))->connect(); @@ -146,9 +110,16 @@ foreach ($map as $i => $f) { $process::wait(); if (Assert::contains($info, 'Swoole\\Error')) { $_info = trim($info); - $_info = preg_replace('/(\#0.+?: )[^\n]+/', '$1%s', $_info, 1); - $_info = preg_replace('/(: )[^\n]+( in )/', '$1%s$2', $_info, 1); - $_info = preg_replace('/\/[^(:]+:?\(?\d+\)?/', '%s:%d', $_info); + if (PHP_VERSION_ID >= 80400) { + $_info = preg_replace('/(\#0.+?: )[^\n]+/', '$1%s', $_info, 1); + $_info = preg_replace('/(: )[^\n]+( in )/', '$1%s$2', $_info, 1); + $_info = preg_replace('/closure:[^(:]+:?\(?\d+\)?/', 'closure', $_info); + $_info = preg_replace('/\/[^(:]+:?\(?\d+\)?/', '%s:%d', $_info); + } else { + $_info = preg_replace('/(\#0.+?: )[^\n]+/', '$1%s', $_info, 1); + $_info = preg_replace('/(: )[^\n]+( in )/', '$1%s$2', $_info, 1); + $_info = preg_replace('/\/[^(:]+:?\(?\d+\)?/', '%s:%d', $_info); + } $info_list[] = $_info; if (!Assert::assert($info_list[0] === $_info)) { var_dump($map[$i]); diff --git a/tests/swoole_coroutine/defer/defer_close.phpt b/tests/swoole_coroutine/defer/defer_close.phpt index 1c2de4c6da..b5eaaf6cff 100644 --- a/tests/swoole_coroutine/defer/defer_close.phpt +++ b/tests/swoole_coroutine/defer/defer_close.phpt @@ -31,9 +31,9 @@ go(function () { Swoole\Event::wait(); ?> --EXPECTF-- -closed Fatal error: Uncaught Exception: something wrong in %s:%d Stack trace: %A thrown in %s on line %d +closed diff --git a/tests/swoole_coroutine/defer/defer_exception.phpt b/tests/swoole_coroutine/defer/defer_exception.phpt index 990ff55e80..74491f485f 100644 --- a/tests/swoole_coroutine/defer/defer_exception.phpt +++ b/tests/swoole_coroutine/defer/defer_exception.phpt @@ -19,7 +19,7 @@ go(function () { defer(function () use ($foo, &$bar) { echo "defer 2\n"; Assert::same($foo, 2); - Assert::assert($foo !== 'gua'); // because of & + Assert::assert($bar !== 'gua'); // because of & }); $foo = 3; $bar = 'zha'; @@ -33,10 +33,10 @@ Swoole\Event::wait(); --EXPECTF-- 3 zha -defer 2 -defer 1 Fatal error: Uncaught Exception: something wrong in %s:%d Stack trace: %A thrown in %s/tests/swoole_coroutine/defer/defer_exception.php on line %d +defer 2 +defer 1 diff --git a/tests/swoole_coroutine/destruct/destruct2.phpt b/tests/swoole_coroutine/destruct/destruct2.phpt index a5ea0ca8c9..52815ea2ff 100644 --- a/tests/swoole_coroutine/destruct/destruct2.phpt +++ b/tests/swoole_coroutine/destruct/destruct2.phpt @@ -37,3 +37,12 @@ call function end Fatal error: go(): can not use coroutine in __destruct after php_request_shutdown %s +--EXPECTF_85-- +call function +end + +Fatal error: go(): can not use coroutine in __destruct after php_request_shutdown %s +Stack trace: +#0 %s(%d): go(Object(Closure)) +#1 [internal function]: T2->__destruct() +#2 {main} diff --git a/tests/swoole_coroutine/destruct/destruct3.phpt b/tests/swoole_coroutine/destruct/destruct3.phpt index 5578f5333d..e3f1624986 100644 --- a/tests/swoole_coroutine/destruct/destruct3.phpt +++ b/tests/swoole_coroutine/destruct/destruct3.phpt @@ -46,6 +46,6 @@ dtor Fatal error: Uncaught Exception: error in %s:%d Stack trace: -#0 [internal function]: class@anonymous->{closure}() +#0 [internal function]: class@anonymous->{closure%S() #1 {main} thrown in %s on line %d diff --git a/tests/swoole_coroutine/exception/core_error.phpt b/tests/swoole_coroutine/exception/core_error.phpt new file mode 100644 index 0000000000..5586f1f961 --- /dev/null +++ b/tests/swoole_coroutine/exception/core_error.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_coroutine/exception: fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected identifier "xde" in %s on line %d +shutdown diff --git a/tests/swoole_coroutine/exception/defer1.phpt b/tests/swoole_coroutine/exception/defer1.phpt new file mode 100644 index 0000000000..65d4173125 --- /dev/null +++ b/tests/swoole_coroutine/exception/defer1.phpt @@ -0,0 +1,50 @@ +--TEST-- +swoole_coroutine/exception: defer 1 +--SKIPIF-- + +--FILE-- +start(); +$status = Process::wait(); +if (Assert::isArray($status)) { + list($pid, $code, $signal) = array_values($status); + Assert::greaterThan($pid, 0); + + $out = $process->read(); + Assert::contains($out, 'co-1 begin'); + Assert::contains($out, 'co-2 begin'); + Assert::contains($out, 'defer task begin'); + Assert::contains($out, 'shutdown'); + Assert::contains($out, 'Fatal error: Uncaught Exception'); + Assert::notContains($out, 'co-1 end'); + Assert::same($code, 255); +} +?> +--EXPECTF-- diff --git a/tests/swoole_coroutine/exception/defer2.phpt b/tests/swoole_coroutine/exception/defer2.phpt new file mode 100644 index 0000000000..865f9cf31b --- /dev/null +++ b/tests/swoole_coroutine/exception/defer2.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_coroutine/exception: defer 2 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +co-1 begin +co-2 begin +co-2 end +defer task begin + +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 [internal function]: {closure%S}(NULL) +#1 {main} + thrown in %s on line %d +shutdown diff --git a/tests/swoole_coroutine/exception/dtor.phpt b/tests/swoole_coroutine/exception/dtor.phpt new file mode 100644 index 0000000000..24a99b35de --- /dev/null +++ b/tests/swoole_coroutine/exception/dtor.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_coroutine/exception: throw in destructor +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +co-1 begin +test + +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 %s(%d): class@anonymous->__destruct() +%A + thrown in %s on line %d +shutdown diff --git a/tests/swoole_coroutine/exception/error.phpt b/tests/swoole_coroutine/exception/error.phpt index 05509aaa62..2411dd70f1 100644 --- a/tests/swoole_coroutine/exception/error.phpt +++ b/tests/swoole_coroutine/exception/error.phpt @@ -5,10 +5,13 @@ swoole_coroutine/exception: error --FILE-- +--FILE-- + 1, + 'location' => $wsdl, + 'features' => 1, + 'exceptions' => true, + ], []); + $client = new \SoapClient($wsdl, $option); + } catch (\Exception $e) { + echo $e->getMessage(); + } +}); +echo "end\n"; + +?> +--EXPECTF-- +start +SOAP-ERROR: Parsing WSDL: Couldn't load from '%s' : failed to load external entity "%s" +end diff --git a/tests/swoole_coroutine/exception/error3.phpt b/tests/swoole_coroutine/exception/error3.phpt new file mode 100644 index 0000000000..af9c99b19e --- /dev/null +++ b/tests/swoole_coroutine/exception/error3.phpt @@ -0,0 +1,26 @@ +--TEST-- +swoole_coroutine/exception: internal_function error +--SKIPIF-- + +--FILE-- + +--FILE-- +start(); +$status = Process::wait(); +if (Assert::isArray($status)) { + list($pid, $code, $signal) = array_values($status); + Assert::greaterThan($pid, 0); + + $out = $process->read(); + Assert::contains($out, 'Uncaught Error: Call to undefined function call_func_not_exists()'); + Assert::contains($out, 'shutdown'); + Assert::notContains($out, 'co end'); + Assert::same($code, 255); +} +?> +--EXPECT-- diff --git a/tests/swoole_coroutine/exit.phpt b/tests/swoole_coroutine/exit.phpt index 10051a1815..50d710f449 100644 --- a/tests/swoole_coroutine/exit.phpt +++ b/tests/swoole_coroutine/exit.phpt @@ -1,75 +1,13 @@ --TEST-- swoole_coroutine: exit --SKIPIF-- - + --FILE-- 'ok'], - (object)['exit' => 'ok'], - STDIN, - 0 -]; - -function route() -{ - controller(); -} - -function controller() -{ - your_code(); -} - -function your_code() -{ - global $exit_status_list; - co::sleep(.001); - $exit_status = array_shift($exit_status_list); - if ($exit_status === 'undef') { - exit; - } else { - exit($exit_status); - } -} - -$chan = new Swoole\Coroutine\Channel; - -go(function () use ($chan, $exit_status_list) { - foreach ($exit_status_list as $val) { - $chan->push($val); - } -}); - -for ($i = 0; $i < count($exit_status_list); $i++) { - go(function () use ($exit_status_list, $chan) { - try { - // in coroutine - route(); - } catch (\Swoole\ExitException $e) { - Assert::assert($e->getFlags() & SWOOLE_EXIT_IN_COROUTINE); - $exit_status = $chan->pop(); - $exit_status = $exit_status === 'undef' ? null : $exit_status; - Assert::same($e->getStatus(), $exit_status); - var_dump($e->getStatus()); - // exit coroutine - return; - } - echo "never here\n"; - }); -} - -Swoole\Event::wait(); - +require TESTS_API_PATH . '/exit.php'; ?> --EXPECTF-- NULL diff --git a/tests/swoole_coroutine/exit_84.phpt b/tests/swoole_coroutine/exit_84.phpt new file mode 100644 index 0000000000..9abfb9ea10 --- /dev/null +++ b/tests/swoole_coroutine/exit_84.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_coroutine: exit +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +int(1) +string(4) "exit" +int(0) diff --git a/tests/swoole_coroutine/exit_exception_backtrace.phpt b/tests/swoole_coroutine/exit_exception_backtrace.phpt index c33c2ff1f7..66007ed34d 100644 --- a/tests/swoole_coroutine/exit_exception_backtrace.phpt +++ b/tests/swoole_coroutine/exit_exception_backtrace.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_coroutine: exit exception backtrace --SKIPIF-- - + --FILE-- --EXPECTF-- -Fatal error: Uncaught Swoole\ExitException: swoole exit in %s/tests/swoole_coroutine/exit_exception_backtrace.php:15 +Fatal error: Uncaught Swoole\ExitException: swoole exit in %s/tests/swoole_coroutine/exit_exception_backtrace.php:%d Stack trace: -#0 %s/tests/swoole_coroutine/exit_exception_backtrace.php(10): char(%d) -#1 %s/tests/swoole_coroutine/exit_exception_backtrace.php(5): bar('%s...') -#2 %s/tests/swoole_coroutine/exit_exception_backtrace.php(22): foo() +#0 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): char(%d) +#1 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): bar('%s...') +#2 %s/tests/swoole_coroutine/exit_exception_backtrace.php(%d): foo() %A - thrown in %s/tests/swoole_coroutine/exit_exception_backtrace.php on line 15 + thrown in %s/tests/swoole_coroutine/exit_exception_backtrace.php on line %d diff --git a/tests/swoole_coroutine/exit_exception_backtrace_84.phpt b/tests/swoole_coroutine/exit_exception_backtrace_84.phpt new file mode 100644 index 0000000000..5b8e235f2c --- /dev/null +++ b/tests/swoole_coroutine/exit_exception_backtrace_84.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_coroutine: exit exception backtrace +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Swoole\ExitException: swoole exit in %s:%d +Stack trace: +#0 %s(%d): exit() +#1 %s(%d): char(%d) +#2 %s(%d): bar('%s') +#3 %s(%d): foo() +#4 [internal function]: {closure:%s:%d}() +#5 {main} + thrown in %s on line %d diff --git a/tests/swoole_coroutine/gethostbyname.phpt b/tests/swoole_coroutine/gethostbyname.phpt index 3cddd73a03..655804d02f 100644 --- a/tests/swoole_coroutine/gethostbyname.phpt +++ b/tests/swoole_coroutine/gethostbyname.phpt @@ -15,7 +15,7 @@ use Swoole\Coroutine; use function Swoole\Coroutine\run; run(function () { - $map = IS_IN_TRAVIS ? [ + $map = IS_IN_CI ? [ 'www.google.com' => null, 'www.youtube.com' => null, 'www.facebook.com' => null, @@ -69,7 +69,7 @@ run(function () { $no_cache_multi_time = microtime(true) - $no_cache_multi_time; phpt_var_dump($first_time, $cache_time, $no_cache_time, $no_cache_multi_time); - if (!IS_IN_TRAVIS) { + if (!IS_IN_CI) { Assert::assert($cache_time < 0.01); Assert::assert($cache_time < $first_time); Assert::assert($cache_time < $no_cache_time); diff --git a/tests/swoole_coroutine/join/5.phpt b/tests/swoole_coroutine/join/5.phpt index 8896cae9b8..930a7c0bc0 100644 --- a/tests/swoole_coroutine/join/5.phpt +++ b/tests/swoole_coroutine/join/5.phpt @@ -21,7 +21,7 @@ run(function () { Swoole\Event::defer(function () use ($cid_list) { go(function () use ($cid_list) { Assert::false(Coroutine::join($cid_list)); - Assert::eq(swoole_last_error(), SWOOLE_ERROR_CO_HAS_BEEN_BOUND); + Assert::eq(swoole_last_error(), SWOOLE_ERROR_WRONG_OPERATION); echo "DONE 2\n"; }); }); diff --git a/tests/swoole_coroutine/new_server.phpt b/tests/swoole_coroutine/new_server.phpt index 926771363f..6abfc09127 100644 --- a/tests/swoole_coroutine/new_server.phpt +++ b/tests/swoole_coroutine/new_server.phpt @@ -13,4 +13,4 @@ go(function () { }); ?> --EXPECTF-- -Warning: Swoole\Server::start(): eventLoop has already been created, unable to start %s in %s on line %d +Warning: Swoole\Server::start(): The event-loop has already been created, unable to start %s in %s on line %d diff --git a/tests/swoole_coroutine/output/concurrency.phpt b/tests/swoole_coroutine/output/concurrency.phpt new file mode 100644 index 0000000000..5a14721bd8 --- /dev/null +++ b/tests/swoole_coroutine/output/concurrency.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_coroutine/output: concurrency +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_coroutine/output/in_nested_co.phpt b/tests/swoole_coroutine/output/in_nested_co.phpt index 9723a3e56a..f43e8cb315 100644 --- a/tests/swoole_coroutine/output/in_nested_co.phpt +++ b/tests/swoole_coroutine/output/in_nested_co.phpt @@ -5,19 +5,18 @@ swoole_coroutine/output: use ob_* in nest co --FILE-- --EXPECT-- 1 diff --git a/tests/swoole_coroutine/output/ob_main.phpt b/tests/swoole_coroutine/output/ob_main.phpt index 10f9380960..c19bb9f22b 100644 --- a/tests/swoole_coroutine/output/ob_main.phpt +++ b/tests/swoole_coroutine/output/ob_main.phpt @@ -7,10 +7,10 @@ swoole_coroutine/output: main output global require __DIR__ . '/../../include/bootstrap.php'; ob_start(); echo 'aaa'; -go(function () { +Co\run(function () { ob_start(); echo 'bbb'; - co::fgets(fopen(__FILE__, 'r')); + fgets(fopen(__FILE__, 'r')); Assert::same(ob_get_clean(), 'bbb'); }); Assert::same(ob_get_clean(), 'aaa'); diff --git a/tests/swoole_coroutine/private_access.phpt b/tests/swoole_coroutine/private_access.phpt index 143fcdd3fc..ddccf4776a 100644 --- a/tests/swoole_coroutine/private_access.phpt +++ b/tests/swoole_coroutine/private_access.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_coroutine: $this private access in PHP70 (EG(scope)) --SKIPIF-- - + --FILE-- private); var_dump($this->protect); var_dump($this->public); - $mysql = new Swoole\Coroutine\MySQL; - $res = $mysql->connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); + $mysql = new mysqli(); + $res = $mysql->connect( + MYSQL_SERVER_HOST, + MYSQL_SERVER_USER, + MYSQL_SERVER_PWD, + MYSQL_SERVER_DB, + MYSQL_SERVER_PORT, + ); Assert::assert($res); - $ret = $mysql->query('show tables', 1); + $ret = $mysql->query('show tables', 1)->fetch_all(); Assert::assert(is_array($ret)); Assert::assert(count($ret) > 0); var_dump(self::$s_private); diff --git a/tests/swoole_coroutine/signal_listener.phpt b/tests/swoole_coroutine/signal_listener.phpt index 3e124e2073..567b17664c 100644 --- a/tests/swoole_coroutine/signal_listener.phpt +++ b/tests/swoole_coroutine/signal_listener.phpt @@ -9,7 +9,7 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine; use Swoole\Process; -ini_set('swoole.enable_coroutine', 'off'); +swoole_async_set(['enable_coroutine' => false]); $pm = new ProcessManager; $pm->parentFunc = function () use ($pm) { diff --git a/tests/swoole_coroutine/timeout.phpt b/tests/swoole_coroutine/timeout.phpt new file mode 100644 index 0000000000..0e78f85093 --- /dev/null +++ b/tests/swoole_coroutine/timeout.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_coroutine: set timeout +--SKIPIF-- + +--FILE-- +add(); + Assert::true(Coroutine::setTimeLimit(2.5)); + sleep(1); + $waitGroup->done(); + }); + + go(function() use ($waitGroup) { + try { + $waitGroup->add(); + Assert::true(Coroutine::setTimeLimit(1.5)); + sleep(2); + } catch (TimeoutException $e) { + echo "timeout"; + } finally { + $waitGroup->done(); + } + }); + $waitGroup->wait(); +}); +?> +--EXPECT-- +timeout diff --git a/tests/swoole_coroutine/use_process.phpt b/tests/swoole_coroutine/use_process.phpt index dbde4373fc..92b98cd0a3 100644 --- a/tests/swoole_coroutine/use_process.phpt +++ b/tests/swoole_coroutine/use_process.phpt @@ -1,28 +1,35 @@ --TEST-- swoole_coroutine: user process --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { - $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $client->set([ - "open_eof_check" => true, - "package_eof" => "\r\n\r\n" + 'open_eof_check' => true, + 'package_eof' => "\r\n\r\n", ]); - $r = $client->connect('127.0.0.1', $pm->getFreePort(), - 1); + $r = $client->connect('127.0.0.1', $pm->getFreePort(), -1); if ($r === false) { - echo "ERROR"; - exit(); + echo 'ERROR'; + exit; } - $client->send("SUCCESS"); - for ($i = 0; $i < TIMES; $i ++) { + $client->send('SUCCESS'); + for ($i = 0; $i < TIMES; $i++) { $ret = $client->recv(); Assert::same(strlen($ret), SIZE + 4); } @@ -31,40 +38,40 @@ $pm->parentFunc = function () use ($pm) { }; $pm->childFunc = function () use ($pm) { - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set([ - "worker_num" => 1, - 'log_file' => '/dev/null' + 'worker_num' => 1, + 'log_file' => '/dev/null', ]); - $proc = new Swoole\Process(function ($process) use ($serv) { - $data = json_decode($process->read(), true); - for ($i = 0; $i < TIMES/2; $i ++) { - go (function() use ($serv,$data, $i){ - //echo "user sleep start\n"; + $proc = new Process(function ($process) use ($serv) { + $data = json_decode($process->read(), true); + for ($i = 0; $i < TIMES / 2; $i++) { + go(function () use ($serv, $data, $i) { + // echo "user sleep start\n"; co::sleep(0.01); - //echo "user sleep end\n"; + // echo "user sleep end\n"; $serv->send($data['fd'], str_repeat('A', SIZE) . "\r\n\r\n"); - //echo "user process $i send ok\n"; + // echo "user process $i send ok\n"; }); } }, false, true); $serv->addProcess($proc); - $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) { + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on("Receive", function (Swoole\Server $serv, $fd, $reactorId, $data) use ($proc) { + $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) use ($proc) { $proc->write(json_encode([ - 'fd' => $fd + 'fd' => $fd, ])); - for ($i = 0; $i < TIMES/2; $i ++) { - go (function() use ($serv,$fd, $i){ - //echo "worker sleep start\n"; + for ($i = 0; $i < TIMES / 2; $i++) { + go(function () use ($serv, $fd, $i) { + // echo "worker sleep start\n"; co::sleep(0.01); - //echo "worker sleep end\n"; + // echo "worker sleep end\n"; $serv->send($fd, str_repeat('A', SIZE) . "\r\n\r\n"); - //echo "worker send $i ok\n"; + // echo "worker send $i ok\n"; }); } }); diff --git a/tests/swoole_coroutine/user_coroutine.phpt b/tests/swoole_coroutine/user_coroutine.phpt index dedc1be2ee..9ce089174b 100644 --- a/tests/swoole_coroutine/user_coroutine.phpt +++ b/tests/swoole_coroutine/user_coroutine.phpt @@ -3,7 +3,7 @@ swoole_coroutine: user coroutine --SKIPIF-- --FILE-- diff --git a/tests/swoole_coroutine_lock/lock.phpt b/tests/swoole_coroutine_lock/lock.phpt new file mode 100644 index 0000000000..a5b96ded9e --- /dev/null +++ b/tests/swoole_coroutine_lock/lock.phpt @@ -0,0 +1,108 @@ +--TEST-- +swoole_coroutine_lock: lock +--SKIPIF-- + +--FILE-- + 32, + 'iouring_entries' => 20000, + 'iouring_flag' => SWOOLE_IOURING_SQPOLL + ]); +} + +$pm = new SwooleTest\ProcessManager; +$pm->parentFunc = function ($pid) use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + run(function () use ($pm) { + $waitGroup = new WaitGroup(); + go(function () use ($pm, $waitGroup) { + $waitGroup->add(); + $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=1", []); + $respData = json_decode($resp, true); + var_dump($respData); + $waitGroup->done(); + }); + go(function () use ($pm, $waitGroup) { + $waitGroup->add(); + $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=2", []); + $respData = json_decode($resp, true); + var_dump($respData); + $waitGroup->done(); + }); + go(function () use ($pm, $waitGroup) { + $waitGroup->add(); + $resp = httpPost("http://127.0.0.1:{$pm->getFreePort()}?value=3", []); + $respData = json_decode($resp, true); + var_dump($respData); + $waitGroup->done(); + }); + + $waitGroup->wait(); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + swoole_async_set([ + 'log_file' => '/dev/null', + ]); + $lock = new Lock(true); + Assert::false($lock->lock()); + Assert::false($lock->unlock()); + Assert::eq($lock->errCode, SWOOLE_ERROR_CO_OUT_OF_COROUTINE); + $serv = new Server('127.0.0.1', $pm->getFreePort()); + $serv->set([ + 'log_file' => '/dev/null', + 'worker_num' => 4, + 'enable_coroutine' => true, + 'hook_flags' => SWOOLE_HOOK_ALL + ]); + + $serv->on("workerStart", function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('request', function ($req, $resp) use ($lock) { + $resp->header('Content-Type', 'text/plain'); + if ($req->get['value'] == 1 || $req->get['value'] == 2) { + $lock->lock(); + if ($req->get['value'] == 1) { + sleep(1); + } + $resp->end(json_encode(['result' => 'lock' . $req->get['value']]) . PHP_EOL); + $lock->unlock(); + } else { + $resp->end(json_encode(['result' => 'value 3']) . PHP_EOL); + } + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +array(1) { + ["result"]=> + string(7) "value 3" +} +array(1) { + ["result"]=> + string(5) "lock1" +} +array(1) { + ["result"]=> + string(5) "lock2" +} +DONE diff --git a/tests/swoole_coroutine_lock/trylock.phpt b/tests/swoole_coroutine_lock/trylock.phpt new file mode 100644 index 0000000000..ef6bef38b4 --- /dev/null +++ b/tests/swoole_coroutine_lock/trylock.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_coroutine_lock: trylock +--SKIPIF-- + +--FILE-- + 32, + 'iouring_entries' => 20000, + 'iouring_flag' => SWOOLE_IOURING_SQPOLL + ]); +} + +Runtime::enableCoroutine(SWOOLE_HOOK_ALL); +run(function () { + $lock = new Lock(false); + Assert::eq($lock->lock(LOCK_NB), true); + go(function () use ($lock) { + Assert::eq($lock->lock(LOCK_NB), false); + $s = microtime(true); + Assert::eq($lock->lock(), true); + Assert::assert(microtime(true) - $s >= 0.05); + echo "co2 end\n"; + }); + + System::sleep(0.05); + Assert::eq($lock->unlock(), true); + echo "co1 end\n"; +}); +echo "DONE\n"; +?> +--EXPECT-- +co1 end +co2 end +DONE diff --git a/tests/swoole_coroutine_lock/trylock2.phpt b/tests/swoole_coroutine_lock/trylock2.phpt new file mode 100644 index 0000000000..91f64512b8 --- /dev/null +++ b/tests/swoole_coroutine_lock/trylock2.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_coroutine_lock: coroutine try lock +--SKIPIF-- + +--FILE-- + 32, + 'iouring_entries' => 20000, + 'iouring_flag' => SWOOLE_IOURING_SQPOLL + ]); +} + +$lock = new Lock(false); + +run(function () use ($argv, $lock) { + $waitGroup = new WaitGroup(); + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + $lock->lock(); + usleep(100000); + var_dump(1); + $lock->unlock(); + $waitGroup->done(); + }); + + go(function () use ($waitGroup, $lock) { + $waitGroup->add(); + if (!$lock->lock(LOCK_NB)) { + var_dump('lock failed'); + } + $waitGroup->done(); + }); + + $waitGroup->wait(); +}); +?> +--EXPECTF-- +string(11) "lock failed" +int(1) diff --git a/tests/swoole_coroutine_scheduler/preemptive/disable.phpt b/tests/swoole_coroutine_scheduler/preemptive/disable.phpt index 3f69a95b73..91d2a8a9d1 100644 --- a/tests/swoole_coroutine_scheduler/preemptive/disable.phpt +++ b/tests/swoole_coroutine_scheduler/preemptive/disable.phpt @@ -3,6 +3,7 @@ swoole_coroutine_scheduler/preemptive: swoole_coroutine_scheduler/disable --SKIPIF-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- --FILE-- true]); -$default = 10; +$default = TEST_MAX_CPU_EXEC_DURATION; $start = microtime(1); echo "start\n"; $flag = 1; diff --git a/tests/swoole_coroutine_scheduler/preemptive/while.phpt b/tests/swoole_coroutine_scheduler/preemptive/while.phpt index da7f7cf27a..efb07939b0 100644 --- a/tests/swoole_coroutine_scheduler/preemptive/while.phpt +++ b/tests/swoole_coroutine_scheduler/preemptive/while.phpt @@ -3,12 +3,13 @@ swoole_coroutine_scheduler/preemptive: while with opcache enable --SKIPIF-- --FILE-- true]); diff --git a/tests/swoole_coroutine_scheduler/preemptive/while2.phpt b/tests/swoole_coroutine_scheduler/preemptive/while2.phpt index 842e84dfab..150e072d08 100644 --- a/tests/swoole_coroutine_scheduler/preemptive/while2.phpt +++ b/tests/swoole_coroutine_scheduler/preemptive/while2.phpt @@ -3,6 +3,7 @@ swoole_coroutine_scheduler/preemptive: while without opcache enable --SKIPIF-- --FILE-- --FILE-- +--FILE-- + +--EXPECT-- +scheduler 1: begin +scheduler 1: end +sleep: begin +sleep: end +scheduler 2: begin +scheduler 2: end +DONE diff --git a/tests/swoole_coroutine_system/fread.phpt b/tests/swoole_coroutine_system/fread.phpt deleted file mode 100644 index d73105aa21..0000000000 --- a/tests/swoole_coroutine_system/fread.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -swoole_coroutine_system: fread ---SKIPIF-- - ---FILE-- - ---EXPECT-- diff --git a/tests/swoole_coroutine_system/fwrite.phpt b/tests/swoole_coroutine_system/fwrite.phpt deleted file mode 100644 index 1b25da5463..0000000000 --- a/tests/swoole_coroutine_system/fwrite.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -swoole_coroutine_system: fwrite ---SKIPIF-- - ---FILE-- - ---EXPECT-- diff --git a/tests/swoole_coroutine_system/getaddrinfo.phpt b/tests/swoole_coroutine_system/getaddrinfo.phpt index 5fda59a59b..87e9185160 100644 --- a/tests/swoole_coroutine_system/getaddrinfo.phpt +++ b/tests/swoole_coroutine_system/getaddrinfo.phpt @@ -7,7 +7,7 @@ skip_if_offline(); --FILE-- +--FILE-- + +--EXPECT-- +DONE diff --git a/tests/swoole_coroutine_system/waitSignal.phpt b/tests/swoole_coroutine_system/waitSignal.phpt index 9f424775b3..44758850e3 100644 --- a/tests/swoole_coroutine_system/waitSignal.phpt +++ b/tests/swoole_coroutine_system/waitSignal.phpt @@ -32,13 +32,13 @@ Coroutine\run(function () use ($atomic) { switch_process(); $atomic->wakeup(); echo "1\n"; - Assert::true(System::waitSignal(SIGUSR1)); + Assert::eq(System::waitSignal(SIGUSR1), SIGUSR1); echo "3\n"; Assert::false(System::waitSignal(SIGUSR2, 0.01)); echo "4\n"; $atomic->wakeup(); echo "5\n"; - Assert::true(System::waitSignal(SIGUSR2)); + Assert::eq(System::waitSignal(SIGUSR2), SIGUSR2); echo "7\n"; System::wait(); echo "9\n"; diff --git a/tests/swoole_coroutine_system/waitSignal_2.phpt b/tests/swoole_coroutine_system/waitSignal_2.phpt new file mode 100644 index 0000000000..09d238d23f --- /dev/null +++ b/tests/swoole_coroutine_system/waitSignal_2.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_coroutine_system: waitSignal 2 +--SKIPIF-- + +--FILE-- +wait(); + echo "2\n"; + switch_process(); + Process::kill($pid, SIGUSR1); + $atomic->wait(); + echo "6\n"; + switch_process(); + Process::kill($pid, SIGUSR2); + echo "8\n"; +}); +$killer->start(); + +Coroutine\run(function () use ($atomic) { + Coroutine::sleep(0.001); + switch_process(); + $atomic->wakeup(); + echo "1\n"; + $list = [SIGUSR1, SIGUSR2, SIGIO]; + Assert::eq(System::waitSignal($list), SIGUSR1); + echo "3\n"; + Assert::false(System::waitSignal($list, 0.01)); + echo "4\n"; + $atomic->wakeup(); + echo "5\n"; + Assert::eq(System::waitSignal($list), SIGUSR2); + echo "7\n"; + System::wait(); + echo "9\n"; +}); + +?> +--EXPECT-- +1 +2 +3 +4 +5 +6 +8 +7 +9 diff --git a/tests/swoole_coroutine_util/dns_lookup.phpt b/tests/swoole_coroutine_util/dns_lookup.phpt index 0866550f21..0acb01aeb5 100644 --- a/tests/swoole_coroutine_util/dns_lookup.phpt +++ b/tests/swoole_coroutine_util/dns_lookup.phpt @@ -3,44 +3,38 @@ swoole_coroutine_util: dns lookup --SKIPIF-- --FILE-- parentFunc = function () use ($pm) -{ +$pm->parentFunc = function () use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set(array( 'log_file' => '/dev/null' )); - $http->on("WorkerStart", function (Swoole\Server $serv) - { + $http->on("WorkerStart", function (Swoole\Server $serv) { /** * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) - { - $host = swoole_async_dns_lookup_coro('www.baidu.com'); - if ($host) - { + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $host = swoole_async_dns_lookup_coro(TEST_DOMAIN_3); + if ($host) { $response->end("OK\n"); - } - else - { - $response->end("ERROR\n"); + } else { + $response->end("ERROR: " . swoole_last_error() . "\n"); } }); $http->start(); diff --git a/tests/swoole_coroutine_util/exec_sleep.phpt b/tests/swoole_coroutine_util/exec_sleep.phpt index 827b93730b..492165d4a3 100644 --- a/tests/swoole_coroutine_util/exec_sleep.phpt +++ b/tests/swoole_coroutine_util/exec_sleep.phpt @@ -14,7 +14,7 @@ for ($i = MAX_PROCESS_NUM; $i--;) { } Swoole\Event::wait(); $s = microtime(true) - $s; -time_approximate(1, $s); +time_approximate(1, $s, 0.5); echo "DONE\n"; ?> --EXPECT-- diff --git a/tests/swoole_coroutine_util/fgets.phpt b/tests/swoole_coroutine_util/fgets.phpt index fff3f62a64..5e45518ef6 100644 --- a/tests/swoole_coroutine_util/fgets.phpt +++ b/tests/swoole_coroutine_util/fgets.phpt @@ -6,33 +6,24 @@ swoole_coroutine_util: fgets 0]); - Co\run(function () { - $file = __DIR__ . '/../../examples/server.php'; - - $coroutine = ''; - $fp = fopen($file, "r"); - while (!feof($fp)) { - $coroutine .= co::fgets($fp); - } + $file = __DIR__ . '/../../examples/server/mixed.php'; - $standard = ''; + Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + $coroutine = []; $fp = fopen($file, "r"); while (!feof($fp)) { - $standard .= fgets($fp); + $coroutine [] = fgets($fp); } - Swoole\Runtime::enableCoroutine(); - $runtime = ''; + Swoole\Runtime::enableCoroutine(false); + $standard = []; $fp = fopen($file, "r"); while (!feof($fp)) { - $runtime .= fgets($fp); + $standard [] = fgets($fp); } Assert::same($standard, $coroutine); - Assert::same($standard, $runtime); - echo "DONE\n"; }); ?> diff --git a/tests/swoole_coroutine_util/fread.phpt b/tests/swoole_coroutine_util/fread.phpt index 2f0d35153a..ff069f7485 100644 --- a/tests/swoole_coroutine_util/fread.phpt +++ b/tests/swoole_coroutine_util/fread.phpt @@ -5,21 +5,14 @@ swoole_coroutine_util: fread --FILE-- --EXPECT-- diff --git a/tests/swoole_coroutine_util/fread_seek.phpt b/tests/swoole_coroutine_util/fread_seek.phpt deleted file mode 100644 index 8e7b1640b8..0000000000 --- a/tests/swoole_coroutine_util/fread_seek.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -swoole_coroutine_util: fread and fseek ---SKIPIF-- - ---FILE-- - ---EXPECT-- diff --git a/tests/swoole_coroutine_util/fwrite.phpt b/tests/swoole_coroutine_util/fwrite.phpt index b6eff6dfe1..ef7ce07b29 100644 --- a/tests/swoole_coroutine_util/fwrite.phpt +++ b/tests/swoole_coroutine_util/fwrite.phpt @@ -5,27 +5,20 @@ swoole_coroutine_util: fwrite --FILE-- --EXPECT-- diff --git a/tests/swoole_coroutine_wait_group/base.phpt b/tests/swoole_coroutine_wait_group/base.phpt index b18be62845..7d0041aca9 100644 --- a/tests/swoole_coroutine_wait_group/base.phpt +++ b/tests/swoole_coroutine_wait_group/base.phpt @@ -36,7 +36,7 @@ Co\run(function () use ($wg) { $wg->wait(); }); ?> ---EXPECT-- -TASK[1] DONE -TASK[2] DONE -TASK[3] DONE +--EXPECTF-- +TASK[%d] DONE +TASK[%d] DONE +TASK[%d] DONE diff --git a/tests/swoole_coroutine_wait_group/defer.phpt b/tests/swoole_coroutine_wait_group/defer.phpt new file mode 100644 index 0000000000..d40dc92a14 --- /dev/null +++ b/tests/swoole_coroutine_wait_group/defer.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_coroutine_wait_group: run in defer +--SKIPIF-- + +--FILE-- +add(); + Co\go(function () use ($wg, $i) { + var_dump("start $i"); + defer(function () use ($wg, $i) { + var_dump("defer $i"); + $wg->done(); + var_dump("done $i"); + }); + var_dump("end $i"); + }); + } + + var_dump("wait"); + $wg->wait(); + var_dump("finish"); +}); + +?> +--EXPECT-- +string(5) "add 1" +string(7) "start 1" +string(5) "end 1" +string(7) "defer 1" +string(6) "done 1" +string(5) "add 2" +string(7) "start 2" +string(5) "end 2" +string(7) "defer 2" +string(6) "done 2" +string(4) "wait" +string(6) "finish" diff --git a/tests/swoole_curl/abnormal_response/1.phpt b/tests/swoole_curl/abnormal_response/1.phpt new file mode 100644 index 0000000000..0649f8d9c4 --- /dev/null +++ b/tests/swoole_curl/abnormal_response/1.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_curl/abnormal_response: 1 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $ch = curl_init(); + $code = uniqid('swoole_'); + $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + $output = curl_exec($ch); + Assert::isEmpty($output); + Assert::oneOf(curl_errno($ch), [CURLE_RECV_ERROR, CURLE_GOT_NOTHING]); + curl_close($ch); + }); + $pm->kill(); + echo "Done\n"; +}; +$pm->childFunc = function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort()); + $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); + $server->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + $server->on('Connect', function ($serv, $fd, $wid) { + $serv->close($fd); + }); + $server->on('Receive', function ($serv, $fd, $wid, $data) { + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +Done diff --git a/tests/swoole_curl/abnormal_response/2.phpt b/tests/swoole_curl/abnormal_response/2.phpt new file mode 100644 index 0000000000..62802a1bb1 --- /dev/null +++ b/tests/swoole_curl/abnormal_response/2.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_curl/abnormal_response: 2 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $ch = curl_init(); + $code = uniqid('swoole_'); + $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + $output = curl_exec($ch); + Assert::isEmpty($output); + Assert::eq(curl_errno($ch), CURLE_PARTIAL_FILE); + curl_close($ch); + }); + $pm->kill(); + echo "Done\n"; +}; +$pm->childFunc = function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort()); + $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); + $server->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + $server->on('Receive', function ($serv, $fd, $wid, $data) { + usleep(100); + $serv->send($fd, "HTTP/1.1 200 OK\r\n" . + "Content-Type: text/html; charset=UTF-8\r\n" . + "Content-Length: 1256\r\n"); + usleep(10000); + $serv->close($fd); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +Done diff --git a/tests/swoole_curl/basic/22.phpt b/tests/swoole_curl/basic/22.phpt index 2aa6ab5c3b..3fd2f216d6 100644 --- a/tests/swoole_curl/basic/22.phpt +++ b/tests/swoole_curl/basic/22.phpt @@ -3,14 +3,16 @@ swoole_curl/basic: Test curl_setopt() function with CURLOPT_FOLLOWLOCATION param --CREDITS-- Jean-Marc Fontaine --SKIPIF-- - + --FILE-- run(function ($host) { - + // co::set(['log_level' => 0, 'trace_flags' => SWOOLE_TRACE_ALL]); // CURLOPT_FOLLOWLOCATION = true $urls = [ "{$host}/get.php?test=redirect_301", @@ -18,7 +20,7 @@ $cm->run(function ($host) { "{$host}/get.php?test=redirect_307", "{$host}/get.php?test=redirect_308", ]; - foreach($urls as $url) { + foreach ($urls as $url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); @@ -27,8 +29,8 @@ $cm->run(function ($host) { curl_exec($ch); $info = curl_getinfo($ch); - Assert::assert(1 === $info['redirect_count']); - + Assert::eq($info['redirect_count'], 1); + curl_close($ch); } @@ -39,13 +41,11 @@ $cm->run(function ($host) { curl_exec($ch); $info = curl_getinfo($ch); - Assert::assert(0 === $info['redirect_count']); + Assert::eq($info['redirect_count'], 0); Assert::assert("http://{$host}/get.php?test=getpost" === $info['redirect_url']); curl_close($ch); - }); - ?> ===DONE=== --EXPECTF-- diff --git a/tests/swoole_curl/basic/7.phpt b/tests/swoole_curl/basic/7.phpt index 53f14f4513..d97f803f29 100644 --- a/tests/swoole_curl/basic/7.phpt +++ b/tests/swoole_curl/basic/7.phpt @@ -19,13 +19,13 @@ $cm->run(function ($host) { $ch = curl_init(); curl_exec($ch); - var_dump(curl_error($ch)); - var_dump(curl_errno($ch)); + Assert::contains(curl_error($ch), 'No URL set'); + Assert::eq(curl_errno($ch), CURLE_URL_MALFORMAT); curl_close($ch); + echo "Done\n"; }, false); ?> --EXPECTF-- -%string(%d) "No URL set%s" -int(3) +Done diff --git a/tests/swoole_curl/close_before_resume.phpt b/tests/swoole_curl/close_before_resume.phpt index 73e670c248..723ddcb41b 100644 --- a/tests/swoole_curl/close_before_resume.phpt +++ b/tests/swoole_curl/close_before_resume.phpt @@ -71,8 +71,13 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -Fatal error: Uncaught Swoole\Error: cURL is executing, cannot be operated in %s:%d +Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d Stack trace: #0 %s(%d): curl_close(%s) %A thrown in %s on line %d + + [Coroutine-%d] Stack trace: + ------------------------------------------------------------------- +#0 %s(%d): curl_exec(Object(CurlHandle)) +#1 [internal function]: {%s}() diff --git a/tests/swoole_curl/coro_read.phpt b/tests/swoole_curl/coro_read.phpt new file mode 100644 index 0000000000..06ac43df27 --- /dev/null +++ b/tests/swoole_curl/coro_read.phpt @@ -0,0 +1,63 @@ +--TEST-- +swoole_curl: coroutine read +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $fileHandle = fopen('download.txt', 'r'); + + $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_UPLOAD, true); + curl_setopt($ch, CURLOPT_INFILE, $fileHandle); + curl_setopt($ch, CURLOPT_INFILESIZE, filesize('download.txt')); + $result = curl_exec($ch); + curl_close($ch); + fclose($fileHandle); + }); + $pm->kill(); + echo "Done\n"; +}; + +$pm->childFunc = function () use ($pm, $data) { + $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); + $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); + + $http->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function (Request $request, Response $response) use ($data) { + var_dump($request->getContent() == $data); + $response->end('Hello World'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +bool(true) +Done +--CLEAN-- + diff --git a/tests/swoole_curl/coro_write.phpt b/tests/swoole_curl/coro_write.phpt new file mode 100644 index 0000000000..9b43ca964a --- /dev/null +++ b/tests/swoole_curl/coro_write.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_curl: coroutine write +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $data) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm, $data) { + $fileHandle = fopen('download.txt', 'wb'); + + $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_FILE, $fileHandle); + $result = curl_exec($ch); + curl_close($ch); + fclose($fileHandle); + Assert::true(file_get_contents('download.txt') == $data); + }); + $pm->kill(); + echo "Done\n"; +}; + +$pm->childFunc = function () use ($pm, $data) { + $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); + $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); + + $http->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function (Request $request, Response $response) use ($data) { + $response->end($data); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +Done +--CLEAN-- + diff --git a/tests/swoole_curl/coro_write_header.phpt b/tests/swoole_curl/coro_write_header.phpt new file mode 100644 index 0000000000..93ef95d28c --- /dev/null +++ b/tests/swoole_curl/coro_write_header.phpt @@ -0,0 +1,64 @@ +--TEST-- +swoole_curl: coroutine write +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $fileHandle = fopen('download.txt', 'wb'); + + $url = $url = "http://127.0.0.1:" . $pm->getFreePort(); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_WRITEHEADER, $fileHandle); + $result = curl_exec($ch); + curl_close($ch); + fclose($fileHandle); + echo file_get_contents('download.txt'); + }); + $pm->kill(); + echo "Done\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); + $http->set(['worker_num' => 2, 'log_file' => '/dev/null', 'package_max_length' => 110 * 1024 * 1024]); + + $http->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function (Request $request, Response $response) { + $response->end('Hello World'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +HTTP/1.1 200 OK +Server: swoole-http-server +Date: %s +Connection: keep-alive +Content-Type: text/html +Content-Length: 11 + +Done +--CLEAN-- + diff --git a/tests/swoole_curl/error.phpt b/tests/swoole_curl/error.phpt index d543910b3c..810908a9c0 100644 --- a/tests/swoole_curl/error.phpt +++ b/tests/swoole_curl/error.phpt @@ -17,7 +17,7 @@ $s = microtime(true); run(function () { $ch = curl_init(); $code = uniqid('swoole_'); - $url = "http://127.0.0.1:49494/?code=".urlencode($code); + $url = 'http://127.0.0.1:49494/?code=' . urlencode($code); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @@ -29,7 +29,8 @@ run(function () { $output = curl_exec($ch); Assert::isEmpty($output); Assert::eq(curl_errno($ch), CURLE_COULDNT_CONNECT); - Assert::contains(curl_error($ch), 'Connection refused'); + Assert::eq(preg_match('#Failed to connect to 127.0.0.1 port \d+#i', curl_error($ch)), 1); + $info = curl_getinfo($ch); Assert::isArray($info); Assert::eq($info['http_code'], 0); diff --git a/tests/swoole_curl/exec_twice.phpt b/tests/swoole_curl/exec_twice.phpt index 012b195750..da3030a6d3 100644 --- a/tests/swoole_curl/exec_twice.phpt +++ b/tests/swoole_curl/exec_twice.phpt @@ -72,8 +72,13 @@ $pm->run(); co 1 exec co 2 exec -Fatal error: Uncaught Swoole\Error: cURL is executing, cannot be operated in %s:%d +Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d Stack trace: #0 %s(%d): curl_exec(%s) %A thrown in %s on line %d + + [Coroutine-%d] Stack trace: + ------------------------------------------------------------------- +#0 %s(%d): curl_exec(Object(CurlHandle)) +#1 [internal function]: {%s}() diff --git a/tests/swoole_curl/fatal_error_in_callback.phpt b/tests/swoole_curl/fatal_error_in_callback.phpt new file mode 100644 index 0000000000..af87ba0c7b --- /dev/null +++ b/tests/swoole_curl/fatal_error_in_callback.phpt @@ -0,0 +1,62 @@ +--TEST-- +swoole_curl: error +--SKIPIF-- + +--FILE-- +getMessage(), E_USER_WARNING); + } + }); + + curl_exec($ch); + echo "Exec\n"; + curl_close($ch); +}); +echo "Done\n"; +?> +--EXPECTF-- +Fatal error: test in %s on line %d + +Warning: curl_close(): Attempt to close cURL handle from a callback in %s on line %d +--EXPECTF_85-- +Fatal error: test in %s on line %d +Stack trace: +#0 %s(%d): trigger_error('test', %d) +#1 [internal function]: {closure:{closure:%s:%d}:%d}(Object(CurlHandle), '%s...') +#2 %s(%d): curl_exec(Object(CurlHandle)) +#3 [internal function]: {closure:%s:%d}() +#4 {main} + +Warning: curl_close(): Attempt to close cURL handle from a callback in %s on line %d diff --git a/tests/swoole_curl/ftp.phpt b/tests/swoole_curl/ftp.phpt new file mode 100644 index 0000000000..fa17a95e0c --- /dev/null +++ b/tests/swoole_curl/ftp.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_curl: ftp +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done diff --git a/tests/swoole_curl/guzzle.phpt b/tests/swoole_curl/guzzle.phpt index 94ca0fc7d0..cc25f8c30d 100644 --- a/tests/swoole_curl/guzzle.phpt +++ b/tests/swoole_curl/guzzle.phpt @@ -19,6 +19,10 @@ use function Swoole\Coroutine\go; Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); +register_shutdown_function(function (){ + phpt_show_usage(); +}); + const N = 4; run(function () { @@ -29,17 +33,17 @@ run(function () { $promises = [ 'baidu' => $client->getAsync('http://www.baidu.com/'), 'qq' => $client->getAsync('https://www.qq.com/'), - 'gov' => $client->getAsync('http://www.gov.cn/') + 'zhihu' => $client->getAsync('http://www.zhihu.com/') ]; $responses = Promise\Utils::unwrap($promises); Assert::contains($responses['baidu']->getBody(), '百度'); - Assert::contains(iconv('gbk', 'utf-8', $responses['qq']->getBody()), '腾讯'); - Assert::contains($responses['gov']->getBody(), '中华人民共和国'); + Assert::contains($responses['qq']->getBody(), '腾讯'); + Assert::contains($responses['zhihu']->getBody(), '知乎'); $result['task_1'] = 'OK'; }); go(function () use ($barrier, &$result) { - $client = new Client(['base_uri' => 'http://httpbin.org/']); + $client = new Client(['base_uri' => 'https://httpbin.org/']); $n = N; $data = $promises = []; while ($n--) { diff --git a/tests/swoole_curl/guzzle/promise.phpt b/tests/swoole_curl/guzzle/promise.phpt index 5230f56e15..37a5bcfc5c 100644 --- a/tests/swoole_curl/guzzle/promise.phpt +++ b/tests/swoole_curl/guzzle/promise.phpt @@ -18,17 +18,17 @@ use function Swoole\Coroutine\run; Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () { - $client = new Client(['base_uri' => 'http://httpbin.org']); + $client = new Client(['base_uri' => 'https://httpbin.org']); // Initiate each request but do not block $promises = [ 'a' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test1!']]), 'b' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test2!']]), - 'b' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test3!']]), + 'c' => $client->requestAsync('POST', '/post', ['json' => ['data' => 'hello test3!']]), ]; // Wait on all of the requests to complete. - $results = Promise\unwrap($promises); + $results = GuzzleHttp\Promise\Utils::unwrap($promises); // You can access each result using the key provided to the unwrap // function. diff --git a/tests/swoole_curl/guzzle/send_async.phpt b/tests/swoole_curl/guzzle/send_async.phpt index 495efee9e4..33bf8b4b6b 100644 --- a/tests/swoole_curl/guzzle/send_async.phpt +++ b/tests/swoole_curl/guzzle/send_async.phpt @@ -19,10 +19,10 @@ Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); run(function () { $client = new Client(); - $response = $client->request('GET', 'https://api.github.com/repos/swoole/swoole-src'); + $response = $client->request('GET', 'https://httpbin.org'); echo $response->getStatusCode(), PHP_EOL; // 200 - echo $response->getHeaderLine('content-type'), PHP_EOL; // 'application/json; charset=utf8' + echo $response->getHeaderLine('content-type'), PHP_EOL; // Send an asynchronous request. $request = new Request('GET', 'http://httpbin.org'); @@ -36,6 +36,6 @@ run(function () { ?> --EXPECT-- 200 -application/json; charset=utf-8 +text/html; charset=utf-8 I completed! 200 Done diff --git a/tests/swoole_curl/https.phpt b/tests/swoole_curl/https.phpt index 324ccd9ee3..6b6cb60512 100644 --- a/tests/swoole_curl/https.phpt +++ b/tests/swoole_curl/https.phpt @@ -22,7 +22,7 @@ run(function () { go(function() { $ch = curl_init(); $code = uniqid('swoole_'); - if (IS_IN_TRAVIS) { + if (IS_IN_CI) { $domain = 'www.google.com'; } else { $domain = 'www.baidu.com'; diff --git a/tests/swoole_curl/keepalive.phpt b/tests/swoole_curl/keepalive.phpt new file mode 100644 index 0000000000..b8d3951ba8 --- /dev/null +++ b/tests/swoole_curl/keepalive.phpt @@ -0,0 +1,75 @@ +--TEST-- +swoole_curl: keepalive +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $ch = curl_init(); + $code = uniqid('swoole_'); + + $execFn = function () use ($ch, $code, $pm) { + $url = "http://127.0.0.1:" . $pm->getFreePort() . "/?code=" . urlencode($code); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($ch, $strHeader) { + return strlen($strHeader); + }); + $output = curl_exec($ch); + $info = curl_getinfo($ch); + Assert::eq($output, "Hello World\n" . $code); + if ($output === false) { + exit("CURL Error:" . curl_error($ch)); + } + return $info; + }; + + echo "co 1 exec\n"; + $info1 = $execFn(); + + Co::sleep(0.1); + + echo "co 2 exec\n"; + $info2 = $execFn(); + + Assert::eq($info1['local_port'], $info2['local_port']); + + curl_close($ch); + }); + $pm->kill(); + echo "Done\n"; +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_PROCESS); + $http->set(['worker_num' => 2, 'log_file' => '/dev/null']); + + $http->on("start", function ($server) use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function (Request $request, Response $response) { + $response->end("Hello World\n" . $request->get['code']); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +co 1 exec +co 2 exec +Done diff --git a/tests/swoole_curl/multi/1.phpt b/tests/swoole_curl/multi/1.phpt new file mode 100644 index 0000000000..b2da703514 --- /dev/null +++ b/tests/swoole_curl/multi/1.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_curl/multi: 1 +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done diff --git a/tests/swoole_curl/multi/2.phpt b/tests/swoole_curl/multi/2.phpt new file mode 100644 index 0000000000..12bd3b65af --- /dev/null +++ b/tests/swoole_curl/multi/2.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_curl/multi: 2 +--SKIPIF-- + +--FILE-- + 0.2]); + echo "Done\n"; +}); +?> +--EXPECT-- +Done diff --git a/tests/swoole_curl/multi/3.phpt b/tests/swoole_curl/multi/3.phpt new file mode 100644 index 0000000000..4f8d4c2dc6 --- /dev/null +++ b/tests/swoole_curl/multi/3.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_curl/multi: 3 +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done +Done +Done +Done diff --git a/tests/swoole_curl/multi/4.phpt b/tests/swoole_curl/multi/4.phpt new file mode 100644 index 0000000000..febd08f1b8 --- /dev/null +++ b/tests/swoole_curl/multi/4.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_curl/multi: 4 +--SKIPIF-- + +--FILE-- + 0.2]); + echo "Done\n"; + }); + } +}); +?> +--EXPECT-- +Done +Done +Done +Done diff --git a/tests/swoole_curl/multi/5.phpt b/tests/swoole_curl/multi/5.phpt new file mode 100644 index 0000000000..a056a63951 --- /dev/null +++ b/tests/swoole_curl/multi/5.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_curl/multi: 5 +--SKIPIF-- + +--FILE-- + +--EXPECT-- +DONE diff --git a/tests/swoole_curl/multi/6.phpt b/tests/swoole_curl/multi/6.phpt new file mode 100644 index 0000000000..c9b2c76473 --- /dev/null +++ b/tests/swoole_curl/multi/6.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_curl/multi: 6 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Done diff --git a/tests/swoole_curl/multi/add_after_easy_exec.phpt b/tests/swoole_curl/multi/add_after_easy_exec.phpt new file mode 100644 index 0000000000..44080478c1 --- /dev/null +++ b/tests/swoole_curl/multi/add_after_easy_exec.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_curl/multi: add handle after easy exec +--SKIPIF-- + + +--FILE-- +run(function ($host) { + $ch1 = curl_init(); + curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=get"); + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1); + $rs = curl_exec($ch1); + Assert::eq($rs, "Hello World!\nHello World!"); + + $mh1 = curl_multi_init(); + Assert::eq(curl_multi_add_handle($mh1, $ch1), 0); +}); +?> +--EXPECT-- diff --git a/tests/swoole_curl/multi/bug4393.phpt b/tests/swoole_curl/multi/bug4393.phpt index 4e734b7f89..6ea973d2e4 100644 --- a/tests/swoole_curl/multi/bug4393.phpt +++ b/tests/swoole_curl/multi/bug4393.phpt @@ -9,13 +9,11 @@ require __DIR__ . '/../../include/skipif.inc'; require __DIR__ . '/../../include/bootstrap.php'; require_once TESTS_LIB_PATH . '/vendor/autoload.php'; -use Swoole\Coroutine\Barrier; use Swoole\Runtime; use GuzzleHttp\Client; use GuzzleHttp\Promise; use function Swoole\Coroutine\run; -use function Swoole\Coroutine\go; Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); @@ -23,25 +21,37 @@ run(function () { $guzzle = new Client(); $test = function () use ($guzzle) { - $promises = [ - 'qq' => $guzzle->getAsync('https://www.qq.com/'), - 'baidu' => $guzzle->getAsync('http://www.baidu.com/'), - ]; + if (IS_IN_CI) { + $promises = [ + 'qq' => $guzzle->getAsync('https://www.qq.com/'), + 'baidu' => $guzzle->getAsync('http://www.baidu.com/'), + ]; + } else { + $promises = [ + 'httpbin' => $guzzle->getAsync('https://www.httpbin.org/'), + 'nghttp2' => $guzzle->getAsync('https://nghttp2.org/'), + ]; + } $responses = []; foreach (Promise\Utils::settle($promises)->wait() as $k => $v) { $responses[$k] = $v['value']; } - Assert::contains($responses['baidu']->getBody(), '百度'); - Assert::contains(iconv('gbk', 'utf-8', $responses['qq']->getBody()), '腾讯'); + if (IS_IN_CI) { + Assert::contains($responses['baidu']->getBody(), '百度'); + Assert::contains($responses['qq']->getBody(), '腾讯'); + } else { + Assert::contains($responses['httpbin']->getBody(), 'httpbin'); + Assert::contains($responses['nghttp2']->getBody(), 'nghttp2'); + } }; $n = 2; while ($n--) { $s = microtime(true); $test(); - Assert::lessThan(microtime(true) - $s, 1.5); + Assert::lessThan(microtime(true) - $s, 3.0); } echo 'Done' . PHP_EOL; diff --git a/tests/swoole_curl/multi/bug76675.phpt b/tests/swoole_curl/multi/bug76675.phpt index eaed125970..76fa84b3ac 100644 --- a/tests/swoole_curl/multi/bug76675.phpt +++ b/tests/swoole_curl/multi/bug76675.phpt @@ -1,14 +1,15 @@ --TEST-- swoole_curl/multi: Bug #76675 (Segfault with H2 server push write/writeheader handlers) --SKIPIF-- - --FILE-- @@ -18,11 +19,11 @@ use Swoole\Runtime; use function Swoole\Coroutine\run; -$fn = function() { +$fn = function () { $transfers = 1; - $callback = function($parent, $passed) use (&$transfers) { + $callback = function ($parent, $passed) use (&$transfers) { curl_setopt($passed, CURLOPT_WRITEFUNCTION, function ($ch, $data) { - echo "Received ".strlen($data); + echo 'Received ' . strlen($data); return strlen($data); }); $transfers++; @@ -41,10 +42,11 @@ $fn = function() { $active = null; do { $status = curl_multi_exec($mh, $active); - // echo "active=$active, status=$status\n"; + phpt_echo("active={$active}, status={$status}\n"); do { $info = curl_multi_info_read($mh); - if (false !== $info && $info['msg'] == CURLMSG_DONE) { + phpt_echo($info); + if ($info !== false && $info['msg'] == CURLMSG_DONE) { $handle = $info['handle']; if ($handle !== null) { $transfers--; @@ -53,6 +55,7 @@ $fn = function() { } } } while ($info); + curl_multi_select($mh); } while ($transfers); curl_multi_close($mh); }; diff --git a/tests/swoole_curl/multi/bug77946.phpt b/tests/swoole_curl/multi/bug77946.phpt index 29ab6be4dd..83176474ab 100644 --- a/tests/swoole_curl/multi/bug77946.phpt +++ b/tests/swoole_curl/multi/bug77946.phpt @@ -33,9 +33,9 @@ run(function () { $status = curl_multi_exec($mh, $active); $info = curl_multi_info_read($mh); if (false !== $info) { - var_dump($info['result']); - var_dump(curl_errno($info['handle'])); - var_dump(curl_error($info['handle'])); + Assert::eq($info['result'], 1); + Assert::eq(curl_errno($info['handle']), CURLE_UNSUPPORTED_PROTOCOL); + Assert::contains(curl_error($info['handle']), 'Protocol "unknown" not supported'); } } while ($status === CURLM_CALL_MULTI_PERFORM || $active); @@ -46,7 +46,4 @@ run(function () { curl_multi_close($mh); }); ?> ---EXPECTF-- -int(1) -int(1) -string(%d) "Protocol %Sunknown%S not supported or disabled in libcurl" +--EXPECT-- diff --git a/tests/swoole_curl/multi/close.phpt b/tests/swoole_curl/multi/close.phpt new file mode 100644 index 0000000000..05e864a36a --- /dev/null +++ b/tests/swoole_curl/multi/close.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_curl/multi: clean handle +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Done diff --git a/tests/swoole_curl/multi/curl_copy_handle_variation4.phpt b/tests/swoole_curl/multi/curl_copy_handle_variation4.phpt index 8757b5689d..114e2d3c4e 100644 --- a/tests/swoole_curl/multi/curl_copy_handle_variation4.phpt +++ b/tests/swoole_curl/multi/curl_copy_handle_variation4.phpt @@ -38,11 +38,13 @@ $cm->run(function ($host) { curl_multi_remove_handle($mh, $ch3); curl_multi_close($mh); }); +echo PHP_EOL; ?> ===DONE=== --EXPECT-- bool(true) -АБВ.txt|application/octet-stream|5АБВ.txt|application/octet-stream|5===DONE=== +АБВ.txt|application/octet-stream|5АБВ.txt|application/octet-stream|5 +===DONE=== --CLEAN-- +--FILE-- + +--EXPECT-- +Done diff --git a/tests/swoole_curl/multi/multiple_binding.phpt b/tests/swoole_curl/multi/multiple_binding.phpt new file mode 100644 index 0000000000..34f23b9a5d --- /dev/null +++ b/tests/swoole_curl/multi/multiple_binding.phpt @@ -0,0 +1,30 @@ +--TEST-- +swoole_curl/multi: multiple binding +--SKIPIF-- + + +--FILE-- +run(function ($host) { + $ch1 = curl_init(); + curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=get"); + + $mh1 = curl_multi_init(); + Assert::eq(curl_multi_add_handle($mh1, $ch1), 0); + + $mh2 = curl_multi_init(); + Assert::eq(curl_multi_add_handle($mh2, $ch1), CURLM_ADDED_ALREADY); +}); +?> +--EXPECT-- diff --git a/tests/swoole_curl/select_cancel.phpt b/tests/swoole_curl/multi/select_cancel.phpt similarity index 93% rename from tests/swoole_curl/select_cancel.phpt rename to tests/swoole_curl/multi/select_cancel.phpt index 9e1b8352a7..5b717f7803 100644 --- a/tests/swoole_curl/select_cancel.phpt +++ b/tests/swoole_curl/multi/select_cancel.phpt @@ -1,13 +1,13 @@ --TEST-- -swoole_curl: select twice +swoole_curl/multi: select twice --SKIPIF-- --FILE-- --FILE-- parentFunc = function () use ($pm) { while ($active && $mrc == CURLM_OK) { $tm = microtime(true); $n = curl_multi_select($mh, TIMEOUT); - Assert::lessThan(microtime(true) - $tm, TIMEOUT + 0.01); + Assert::lessThan(microtime(true) - $tm, TIMEOUT + 0.05); $error = swoole_last_error(); phpt_var_dump('select return value: '.$n); diff --git a/tests/swoole_curl/multi/select_twice.phpt b/tests/swoole_curl/multi/select_twice.phpt new file mode 100644 index 0000000000..483c5c0757 --- /dev/null +++ b/tests/swoole_curl/multi/select_twice.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_curl/multi: select twice +--SKIPIF-- + +--FILE-- + true]); + echo "Done\n"; + }); + } +}); +?> +--EXPECTF-- +Fatal error: Uncaught Swoole\Error: This cURL handle is currently executing in coroutine#%d, cannot be operated in %s:%d +Stack trace: +#0 %s(%d): curl_multi_select(%s) +%A + thrown in %s on line %d + + [Coroutine-%d] Stack trace: + ------------------------------------------------------------------- +#0 %s(%d): curl_multi_select(Object(CurlMultiHandle)) +#1 %s(%d): swoole_test_curl_multi_ex(Object(CurlMultiHandle), Array) +#2 %s(%d): swoole_test_curl_multi(Array) +#3 [internal function]: {%s}() diff --git a/tests/swoole_curl/multi_1.phpt b/tests/swoole_curl/multi_1.phpt deleted file mode 100644 index c025f1c59c..0000000000 --- a/tests/swoole_curl/multi_1.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_curl: multi 1 ---SKIPIF-- - ---FILE-- - ---EXPECT-- -Done diff --git a/tests/swoole_curl/multi_2.phpt b/tests/swoole_curl/multi_2.phpt deleted file mode 100644 index 76814b3072..0000000000 --- a/tests/swoole_curl/multi_2.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_curl: multi 2 ---SKIPIF-- - ---FILE-- - 0.2]); - echo "Done\n"; -}); -?> ---EXPECT-- -Done diff --git a/tests/swoole_curl/multi_3.phpt b/tests/swoole_curl/multi_3.phpt deleted file mode 100644 index dd723c5746..0000000000 --- a/tests/swoole_curl/multi_3.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -swoole_curl: multi 3 ---SKIPIF-- - ---FILE-- - ---EXPECT-- -Done -Done -Done -Done diff --git a/tests/swoole_curl/multi_4.phpt b/tests/swoole_curl/multi_4.phpt deleted file mode 100644 index 145191f090..0000000000 --- a/tests/swoole_curl/multi_4.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -swoole_curl: multi 4 ---SKIPIF-- - ---FILE-- - 0.2]); - echo "Done\n"; - }); - } -}); -?> ---EXPECT-- -Done -Done -Done -Done diff --git a/tests/swoole_curl/multi_close.phpt b/tests/swoole_curl/multi_close.phpt deleted file mode 100644 index ff7546c7e6..0000000000 --- a/tests/swoole_curl/multi_close.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -swoole_curl: clean handle ---SKIPIF-- - ---FILE-- - ---EXPECT-- -Done diff --git a/tests/swoole_curl/multi_dtor.phpt b/tests/swoole_curl/multi_dtor.phpt deleted file mode 100644 index cdd708f766..0000000000 --- a/tests/swoole_curl/multi_dtor.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -swoole_curl: multi dtor ---SKIPIF-- - ---FILE-- - ---EXPECT-- -Done diff --git a/tests/swoole_curl/select_twice.phpt b/tests/swoole_curl/select_twice.phpt deleted file mode 100644 index 35a4a9ecdc..0000000000 --- a/tests/swoole_curl/select_twice.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -swoole_curl: select twice ---SKIPIF-- - ---FILE-- - true]); - echo "Done\n"; - }); - } -}); -?> ---EXPECTF-- -Fatal error: Uncaught Swoole\Error: cURL is executing, cannot be operated in %s:%d -Stack trace: -#0 %s(%d): curl_multi_select(%s) -%A - thrown in %s on line %d diff --git a/tests/swoole_curl/setopt/filetime_1.phpt b/tests/swoole_curl/setopt/filetime_1.phpt index a2089fa32c..8382fa8fe9 100644 --- a/tests/swoole_curl/setopt/filetime_1.phpt +++ b/tests/swoole_curl/setopt/filetime_1.phpt @@ -12,7 +12,7 @@ $cm->run(function ($host) { $ch = curl_init(); $options = array( - CURLOPT_URL => 'http://www.gov.cn/govweb/xhtml/libs/jQuery/jquery-e.min.js', + CURLOPT_URL => 'https://static.zhihu.com/heifetz/chunks/5946.4600cc0c1b3dcecac17c.js', CURLOPT_RETURNTRANSFER => 1, CURLOPT_FILETIME => true, CURLOPT_NOBODY => true, diff --git a/tests/swoole_curl/ssl/version.phpt b/tests/swoole_curl/ssl/version.phpt index 5647a6a0bb..fdeb18c6e3 100644 --- a/tests/swoole_curl/ssl/version.phpt +++ b/tests/swoole_curl/ssl/version.phpt @@ -16,10 +16,10 @@ $cm->run(function ($host) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_URL, "https://www.qq.com/"); + curl_setopt($ch, CURLOPT_URL, "https://www.baidu.com/"); $result = curl_exec($ch); Assert::assert($result); - Assert::contains($result, 'tencent'); + Assert::contains($result, '百度'); curl_close($ch); }, false); diff --git a/tests/swoole_curl/undefined_behavior/8.phpt b/tests/swoole_curl/undefined_behavior/8.phpt new file mode 100644 index 0000000000..5c4ce628a8 --- /dev/null +++ b/tests/swoole_curl/undefined_behavior/8.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_curl/undefined_behavior: 8 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: curl_exec(): The given handle is not initialized in coroutine in %s on line %d diff --git a/tests/swoole_curl/upload/1.phpt b/tests/swoole_curl/upload/1.phpt index 20cc1b2057..1c44fb9544 100644 --- a/tests/swoole_curl/upload/1.phpt +++ b/tests/swoole_curl/upload/1.phpt @@ -2,11 +2,18 @@ swoole_curl/upload: CURL file uploading --INI-- --SKIPIF-- - + --FILE-- true, + // 'enable_kqueue' => true, +]); + $cm = new \SwooleTest\CurlManager(); $cm->run(function ($host) { @@ -32,7 +39,11 @@ $cm->run(function ($host) { curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file)); var_dump(curl_exec($ch)); - curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 0); + try { + curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 0); + } catch (throwable $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + } $params = array('file' => '@' . __DIR__ . '/curl_testdata1.txt'); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); var_dump(curl_exec($ch)); diff --git a/tests/swoole_event/add_after_server_start.phpt b/tests/swoole_event/add_after_server_start.phpt index 82263e3b5d..3bf4756d50 100644 --- a/tests/swoole_event/add_after_server_start.phpt +++ b/tests/swoole_event/add_after_server_start.phpt @@ -10,7 +10,7 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Server; -const FILE = __DIR__.'/tmp_result.txt'; +const FILE = __DIR__ . '/tmp_result.txt'; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { @@ -22,12 +22,12 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( - "worker_num" => 1, + 'worker_num' => 1, 'log_file' => '/dev/null', )); $serv->on("start", function (Server $serv) use ($pm) { - $fp = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30); - fwrite($fp, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); + $fp = stream_socket_client("tcp://" . TEST_DOMAIN_3 . ":80", $errno, $errstr, 30); + fwrite($fp, "GET / HTTP/1.1\r\nHost: " . TEST_DOMAIN_3 . "\r\n\r\n"); Swoole\Event::add($fp, function ($fp) use ($pm) { $resp = fread($fp, 8192); diff --git a/tests/swoole_event/cycle.phpt b/tests/swoole_event/cycle.phpt new file mode 100644 index 0000000000..64851a1c9b --- /dev/null +++ b/tests/swoole_event/cycle.phpt @@ -0,0 +1,35 @@ +--TEST-- +swoole_event: cycle +--SKIPIF-- + +--FILE-- + +--EXPECT-- +cycle [0] +timer [1] +defer [2] +cycle [1] +timer [2] +cycle [2] diff --git a/tests/swoole_event/swoole_event_del.phpt b/tests/swoole_event/del.phpt similarity index 100% rename from tests/swoole_event/swoole_event_del.phpt rename to tests/swoole_event/del.phpt diff --git a/tests/swoole_event/del_after_close.phpt b/tests/swoole_event/del_after_close.phpt index 3ca6333c47..c2e2e4ea76 100644 --- a/tests/swoole_event/del_after_close.phpt +++ b/tests/swoole_event/del_after_close.phpt @@ -18,10 +18,10 @@ $cli->connect("www.qq.com", 80); $fd = $cli->sock; -Event::add($fd, function($fd) use($cli) { - $resp = fread($fp, 8192); - Swoole\Event::del($fp); - fclose($fp); +Event::add($fd, function ($fd) use ($cli) { + $resp = $cli->recv(8192); + Swoole\Event::del($fd); + $cli->close(); }); Event::write($fd, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); @@ -30,7 +30,7 @@ $cli->close(); if (Event::isset($fd)) { if (!Event::del($fd)) { - echo "Unable to release fd {$fd} from EventLoop\n"; + echo "Unable to release fd {$fd} from EventLoop\n"; } else { echo "FD {$fd} released from EventLoop\n"; } diff --git a/tests/swoole_event/swoole_event_isset.phpt b/tests/swoole_event/isset.phpt similarity index 100% rename from tests/swoole_event/swoole_event_isset.phpt rename to tests/swoole_event/isset.phpt diff --git a/tests/swoole_event/kqueue.phpt b/tests/swoole_event/kqueue.phpt new file mode 100644 index 0000000000..145be8ff20 --- /dev/null +++ b/tests/swoole_event/kqueue.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_event: write() +--SKIPIF-- + +--FILE-- + true, +]); + +$fp = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30); + +Swoole\Event::add($fp, function($fp) { + $resp = fread($fp, 8192); + + Swoole\Event::del($fp); + fclose($fp); + + echo "SUCCESS\n"; + + Swoole\Timer::after(100, function () { + posix_kill(posix_getpid(), SIGIO); + Swoole\Timer::after(100, function () { + echo "Done\n"; + }); + }); +}); + +Swoole\Event::write($fp, "GET / HTTP/1.1\r\nHost: www.qq.com\r\n\r\n"); + +Swoole\Process::signal(SIGIO, function () { + echo "SIGIO received\n"; + Swoole\Process::signal(SIGIO, null); +}); + +echo "Finish\n"; +Swoole\Event::wait(); +?> +--EXPECT-- +Finish +SUCCESS +SIGIO received +Done diff --git a/tests/swoole_event/rshutdown.phpt b/tests/swoole_event/rshutdown.phpt index c93adab300..42bbddc201 100644 --- a/tests/swoole_event/rshutdown.phpt +++ b/tests/swoole_event/rshutdown.phpt @@ -20,5 +20,5 @@ Swoole\Timer::after(100, function () { ?> --EXPECTF-- -Deprecated: Swoole\Event::rshutdown(): Event::wait() in shutdown function is deprecated in Unknown on line 0 +Deprecated: swoole_event_rshutdown(): Event::wait() in shutdown function is deprecated in Unknown on line 0 string(0) "" diff --git a/tests/swoole_event/set.phpt b/tests/swoole_event/set.phpt new file mode 100644 index 0000000000..961720839a --- /dev/null +++ b/tests/swoole_event/set.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_event: Swoole\Event::set +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Finish +write_callback: SUCCESS diff --git a/tests/swoole_event/swoole_event.phpt b/tests/swoole_event/simple.phpt similarity index 100% rename from tests/swoole_event/swoole_event.phpt rename to tests/swoole_event/simple.phpt diff --git a/tests/swoole_event/sockets.phpt b/tests/swoole_event/sockets.phpt new file mode 100644 index 0000000000..d8eb3466bd --- /dev/null +++ b/tests/swoole_event/sockets.phpt @@ -0,0 +1,86 @@ +--TEST-- +swoole_event: add event after server start +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Unable to create socket\n"); + socket_set_nonblock($socket) or die("Unable to set nonblock on socket\n"); + + function socket_onRead($socket) + { + static $i = 0; + $line = socket_read($socket, 8192); + if (!$line) { + exit("ERROR\n"); + } + Assert::eq($line, "Swoole: " . GREETING_MESSAGE); + if ($i > 10) { + echo "DONE\n"; + Event::del($socket); + socket_close($socket); + } else { + usleep(10000); + $i++; + Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + } + } + + function socket_onWrite($socket) + { + socket_write($socket, GREETING_MESSAGE); + Event::set($socket, null, null, SWOOLE_EVENT_READ); + } + + function socket_onConnect($socket) + { + $err = socket_get_option($socket, SOL_SOCKET, SO_ERROR); + if ($err == 0) { + echo "CONNECTED\n"; + Event::set($socket, null, 'socket_onWrite', SWOOLE_EVENT_READ); + socket_write($socket, GREETING_MESSAGE); + } else { + echo "connect server failed\n"; + Event::del($socket); + socket_close($socket); + } + } + + Event::add($socket, 'socket_onRead', 'socket_onConnect', SWOOLE_EVENT_WRITE); + socket_connect($socket, '127.0.0.1', $pm->getFreePort()); + Event::wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set(array( + 'log_file' => '/dev/null', + )); + $serv->on("start", function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + $serv->send($fd, "Swoole: $data"); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +CONNECTED +DONE diff --git a/tests/swoole_event/swoole_event_set.phpt b/tests/swoole_event/swoole_event_set.phpt deleted file mode 100644 index a7f209ab2e..0000000000 --- a/tests/swoole_event/swoole_event_set.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -swoole_event: Swoole\Event::set ---SKIPIF-- - ---FILE-- - ---EXPECT-- -Finish -read_callback:SUCCESS diff --git a/tests/swoole_event/swoole_event_wait.phpt b/tests/swoole_event/wait.phpt similarity index 100% rename from tests/swoole_event/swoole_event_wait.phpt rename to tests/swoole_event/wait.phpt diff --git a/tests/swoole_event/swoole_event_write.phpt b/tests/swoole_event/write.phpt similarity index 100% rename from tests/swoole_event/swoole_event_write.phpt rename to tests/swoole_event/write.phpt diff --git a/tests/swoole_feature/cross_close/client.phpt b/tests/swoole_feature/cross_close/client.phpt index 75cdbe9f96..f480f9fb54 100644 --- a/tests/swoole_feature/cross_close/client.phpt +++ b/tests/swoole_feature/cross_close/client.phpt @@ -19,10 +19,8 @@ $pm->parentFunc = function () use ($pm) { $pm->kill(); echo "DONE\n"; }); - Assert::assert(!($ret = @$cli->recv(-1))); - if ($ret === false) { - Assert::same($cli->errCode, SOCKET_ECONNRESET); - } + Assert::false(@$cli->recv(-1)); + Assert::same($cli->errCode, SOCKET_ECANCELED); echo "CLOSED\n"; Assert::assert(!$cli->connected); }); diff --git a/tests/swoole_feature/cross_close/client_by_server.phpt b/tests/swoole_feature/cross_close/client_by_server.phpt index 437b32ca33..fbb2575dd3 100644 --- a/tests/swoole_feature/cross_close/client_by_server.phpt +++ b/tests/swoole_feature/cross_close/client_by_server.phpt @@ -1,40 +1,45 @@ --TEST-- swoole_feature/cross_close: client closed by server --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { - $cli = new Co\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); echo "RECV\n"; Assert::same($cli->recv(-1), ''); echo "CLOSED\n"; - while (($ret = @$cli->send(get_safe_random()))) { + while ($ret = @$cli->send(get_safe_random())) { continue; } if ($cli->errCode) { Assert::same($cli->errCode, SOCKET_EPIPE); } - while (($ret = @$cli->recv(-1))) { + while ($ret = @$cli->recv(-1)) { continue; } if ($ret === false) { - Assert::same($cli->errCode, SOCKET_ECONNRESET); + Assert::same($cli->errCode, IS_MAC_OS ? SOCKET_EPIPE : SOCKET_ECONNRESET); } }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { - $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + $server = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { - if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { + if (Assert::assert(($conn = $server->accept()) && $conn instanceof Socket)) { switch_process(); echo "CLOSE\n"; $conn->close(); diff --git a/tests/swoole_feature/cross_close/full_duplex.phpt b/tests/swoole_feature/cross_close/full_duplex.phpt index 882ee0a479..662630d11c 100644 --- a/tests/swoole_feature/cross_close/full_duplex.phpt +++ b/tests/swoole_feature/cross_close/full_duplex.phpt @@ -22,14 +22,16 @@ $pm->parentFunc = function () use ($pm) { go(function () use ($cli) { echo "SEND\n"; $size = 16 * 1024 * 1024; - Assert::assert($cli->send(str_repeat('S', $size)) < $size); + Assert::lessThan($cli->send(str_repeat('S', $size)), $size); Assert::assert(!$cli->connected); + Assert::eq($cli->errCode, SOCKET_ECANCELED); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; - Assert::assert(!$cli->recv(-1)); + Assert::false($cli->recv(-1)); Assert::assert(!$cli->connected); + Assert::eq($cli->errCode, SOCKET_ECANCELED); echo "RECV CLOSED\n"; }); }); diff --git a/tests/swoole_feature/cross_close/full_duplex_by_server.phpt b/tests/swoole_feature/cross_close/full_duplex_by_server.phpt index 46acdca72a..743d1b2c00 100644 --- a/tests/swoole_feature/cross_close/full_duplex_by_server.phpt +++ b/tests/swoole_feature/cross_close/full_duplex_by_server.phpt @@ -1,42 +1,51 @@ --TEST-- swoole_feature/cross_close: full duplex and close by server --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { go(function () use ($pm) { - $cli = new Co\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); Assert::assert($cli->connect('127.0.0.1', $pm->getFreePort())); Assert::assert($cli->connected); set_socket_coro_buffer_size($cli->exportSocket(), 65536); go(function () use ($cli) { echo "SEND\n"; $size = 16 * 1024 * 1024; - Assert::assert($cli->send(str_repeat('S', $size)) < $size); + $str = str_repeat('S', $size); + Assert::assert($cli->send($str) < $size); + usleep(1000); + Assert::assert($cli->send($str) < $size); Assert::same($cli->errCode, SOCKET_EPIPE); echo "SEND CLOSED\n"; }); go(function () use ($cli) { echo "RECV\n"; - Assert::eq($cli->recv(-1), ""); + Assert::eq($cli->recv(-1), ''); // Assert::same($cli->errCode, SOCKET_ECONNRESET); echo "RECV CLOSED\n"; }); $pm->wakeup(); }); - Swoole\Event::wait(); + Event::wait(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { - $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + $server = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { - if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { + if (Assert::assert(($conn = $server->accept()) && $conn instanceof Socket)) { $pm->wait(); echo "CLOSE\n"; $conn->close(); diff --git a/tests/swoole_feature/cross_close/http.phpt b/tests/swoole_feature/cross_close/http.phpt index 09307ab0fc..a7d1573ea9 100644 --- a/tests/swoole_feature/cross_close/http.phpt +++ b/tests/swoole_feature/cross_close/http.phpt @@ -21,7 +21,7 @@ $pm->parentFunc = function () use ($pm) { Assert::assert(!$http->get('/')); echo "CLOSED\n"; Assert::same($http->statusCode, SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); - Assert::same($http->errCode, SOCKET_ECONNRESET); + Assert::same($http->errCode, SOCKET_ECANCELED); Assert::assert(empty($http->body)); }); Swoole\Event::wait(); diff --git a/tests/swoole_feature/cross_close/redis.phpt b/tests/swoole_feature/cross_close/redis.phpt index 139f4257bf..b48d371cbb 100644 --- a/tests/swoole_feature/cross_close/redis.phpt +++ b/tests/swoole_feature/cross_close/redis.phpt @@ -8,7 +8,8 @@ require __DIR__ . '/../../include/bootstrap.php'; $pm = new ProcessManager(); $pm->initRandomData(1); $pm->parentFunc = function () use ($pm) { - $redis = new Co\Redis; + Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); + $redis = new \redis; go(function () use ($pm, $redis) { $redis->connect('127.0.0.1', $pm->getFreePort()); go(function () use ($pm, $redis) { @@ -20,14 +21,13 @@ $pm->parentFunc = function () use ($pm) { echo "DONE\n"; $pm->kill(); }); - $ret = $redis->get($pm->getRandomData()); - echo "CLOSED\n"; - Assert::assert(!$ret); - Assert::assert(!$redis->connected); - Assert::assert(in_array($redis->errType, [SWOOLE_REDIS_ERR_IO, SWOOLE_REDIS_ERR_EOF], true)); - if ($redis->errType === SWOOLE_REDIS_ERR_IO) { - Assert::same($redis->errCode, SOCKET_ECONNRESET); + try { + $ret = $redis->get($pm->getRandomData()); + } catch (\RedisException $e) { + $ret = false; + echo "CLOSED\n"; } + Assert::assert(!$ret); }); }); }; @@ -37,6 +37,7 @@ $pm->childFunc = function () use ($pm) { Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); go(function () use ($pm, $server) { + $pm->wakeup(); if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { switch_process(); $data = $conn->recv(); @@ -49,7 +50,6 @@ $pm->childFunc = function () use ($pm) { } $server->close(); }); - $pm->wakeup(); }); }; $pm->childFirst(); diff --git a/tests/swoole_feature/cross_close/redis_by_server.phpt b/tests/swoole_feature/cross_close/redis_by_server.phpt index ebe728b7d7..a4436cfe99 100644 --- a/tests/swoole_feature/cross_close/redis_by_server.phpt +++ b/tests/swoole_feature/cross_close/redis_by_server.phpt @@ -8,14 +8,16 @@ require __DIR__ . '/../../include/bootstrap.php'; $pm = new ProcessManager(); $pm->initRandomData(1); $pm->parentFunc = function () use ($pm) { + Swoole\Runtime::setHookFlags(SWOOLE_HOOK_ALL); go(function () use ($pm) { - $redis = new Co\Redis; + $redis = new \redis; Assert::assert($redis->connect('127.0.0.1', $pm->getFreePort())); echo "GET\n"; - Assert::assert(!$redis->get($pm->getRandomData())); - echo "CLOSED\n"; - Assert::same($redis->errType, SWOOLE_REDIS_ERR_EOF); - Assert::same($redis->errCode, SOCKET_ECONNRESET); + try { + $redis->get($pm->getRandomData()); + } catch (\RedisException $e) { + echo "CLOSED\n"; + } $pm->kill(); echo "DONE\n"; }); diff --git a/tests/swoole_feature/cross_close/stream.phpt b/tests/swoole_feature/cross_close/stream.phpt index e815b9d760..d355fbf38d 100644 --- a/tests/swoole_feature/cross_close/stream.phpt +++ b/tests/swoole_feature/cross_close/stream.phpt @@ -34,11 +34,10 @@ $pm = ProcessManager::exec(function ($pm) { $output = $pm->getChildOutput(); Assert::contains($output, "READ\nCLOSE\nCLOSED\n"); -if (PHP_VERSION_ID < 80000) { - Assert::contains($output, "fclose(): supplied resource is not a valid stream resource"); -} else { +if (PHP_VERSION_ID < 80500) { Assert::contains($output, "Fatal error: Uncaught TypeError: fclose(): supplied resource is not a valid stream resource"); +} else { + Assert::contains($output, 'Fatal error: Uncaught TypeError: fclose(): Argument #1 ($stream) must be an open stream resource'); } - ?> --EXPECT-- diff --git a/tests/swoole_ftp/001.phpt b/tests/swoole_ftp/001.phpt new file mode 100644 index 0000000000..b2b9994d66 --- /dev/null +++ b/tests/swoole_ftp/001.phpt @@ -0,0 +1,34 @@ +--TEST-- +FTP login +--FILE-- + +--EXPECT-- +bool(true) +array(4) { + [0]=> + string(55) "214-There is help available for the following commands:" + [1]=> + string(5) " USER" + [2]=> + string(5) " HELP" + [3]=> + string(15) "214 end of list" +} +array(1) { + [0]=> + string(39) "214 Syntax: HELP [ ] " +} +bool(true) diff --git a/tests/swoole_ftp/002.phpt b/tests/swoole_ftp/002.phpt new file mode 100644 index 0000000000..435274814f --- /dev/null +++ b/tests/swoole_ftp/002.phpt @@ -0,0 +1,38 @@ +--TEST-- +FTP login (SSL) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +array(4) { + [0]=> + string(55) "214-There is help available for the following commands:" + [1]=> + string(5) " USER" + [2]=> + string(5) " HELP" + [3]=> + string(15) "214 end of list" +} +array(1) { + [0]=> + string(39) "214 Syntax: HELP [ ] " +} +bool(true) diff --git a/tests/swoole_ftp/003.phpt b/tests/swoole_ftp/003.phpt new file mode 100644 index 0000000000..6f63357368 --- /dev/null +++ b/tests/swoole_ftp/003.phpt @@ -0,0 +1,40 @@ +--TEST-- +FTP cwd +--FILE-- + +--EXPECT-- +bool(true) +string(1) "/" +bool(true) +string(6) "/mydir" +bool(true) +string(11) "/xpto/mydir" +bool(true) +string(5) "/xpto" +bool(true) +string(1) "/" +bool(true) diff --git a/tests/swoole_ftp/004.phpt b/tests/swoole_ftp/004.phpt new file mode 100644 index 0000000000..ad8370b7d2 --- /dev/null +++ b/tests/swoole_ftp/004.phpt @@ -0,0 +1,29 @@ +--TEST-- +FTP with bogus parameters +--FILE-- +getMessage() . "\n"; + } + + $ftp = ftp_connect('127.0.0.1', $fn()); + if (!$ftp) die("Couldn't connect to the server"); + + var_dump(ftp_login($ftp, 'user', 'pass')); + var_dump(ftp_login($ftp, 'user', 'bogus')); + + var_dump(ftp_quit($ftp)); +}); +?> +--EXPECTF-- +ftp_connect(): Argument #3 ($timeout) must be greater than 0 +bool(true) + +Warning: ftp_login(): Not logged in. in %s on line %d +bool(false) +bool(false) diff --git a/tests/swoole_ftp/005.phpt b/tests/swoole_ftp/005.phpt new file mode 100644 index 0000000000..261227eb05 --- /dev/null +++ b/tests/swoole_ftp/005.phpt @@ -0,0 +1,100 @@ +--TEST-- +FTP with bogus server responses +--FILE-- +getMessage() . "\n"; + } + + try { + ftp_fput($ftp, 'x', fopen(__FILE__, 'r'), 0); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } + + try { + ftp_get($ftp, 'x', 'y', 0); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } + + var_dump(ftp_mdtm($ftp, 'x')); + var_dump(ftp_mkdir($ftp, 'x')); + var_dump(ftp_nb_continue($ftp)); + + try { + ftp_nb_fget($ftp, STDOUT, 'x', 0); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } + + try { + ftp_nb_fput($ftp, 'x', fopen(__FILE__, 'r'), 0); + } catch (ValueError $exception) { + echo $exception->getMessage() . "\n"; + } + + var_dump(ftp_systype($ftp)); + var_dump(ftp_pwd($ftp)); + var_dump(ftp_size($ftp, '')); + var_dump(ftp_rmdir($ftp, '')); +}); +?> +--EXPECTF-- +bool(true) +bool(false) + +Warning: ftp_cdup(): Command not implemented (1). in %s005.php on line %d +bool(false) + +Warning: ftp_chdir(): Command not implemented (2). in %s005.php on line %d +bool(false) + +Warning: ftp_chmod(): Command not implemented (3). in %s005.php on line %d +bool(false) + +Warning: ftp_delete(): Command not implemented (4). in %s005.php on line %d +bool(false) + +Warning: ftp_exec(): Command not implemented (5). in %s005.php on line %d +bool(false) +ftp_fget(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY +ftp_fput(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY +ftp_get(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY +int(-1) + +Warning: ftp_mkdir(): Command not implemented (7). in %s005.php on line %d +bool(false) + +Warning: ftp_nb_continue(): No nbronous transfer to continue in %s005.php on line %d +int(0) +ftp_nb_fget(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY +ftp_nb_fput(): Argument #4 ($mode) must be either FTP_ASCII or FTP_BINARY + +Warning: ftp_systype(): Command not implemented (8). in %s005.php on line %d +bool(false) + +Warning: ftp_pwd(): Command not implemented (9). in %s005.php on line %d +bool(false) +int(-1) + +Warning: ftp_rmdir(): Command not implemented (11). in %s005.php on line %d +bool(false) diff --git a/tests/swoole_ftp/007.phpt b/tests/swoole_ftp/007.phpt new file mode 100644 index 0000000000..1a4da9cbc5 --- /dev/null +++ b/tests/swoole_ftp/007.phpt @@ -0,0 +1,213 @@ +--TEST-- +FTP with bogus resource +--CREDITS-- +Michael Paul da Rosa +PHP TestFest Dublin 2017 +--FILE-- +getMessage(), "\n"; + } + try { + var_dump(ftp_pwd($ftp)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_cdup($ftp)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_chdir($ftp, '~')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_exec($ftp, 'x')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_raw($ftp, 'x')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_mkdir($ftp, '/')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_rmdir($ftp, '/')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_chmod($ftp, 7777, '/')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_alloc($ftp, 7777)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nlist($ftp, '/')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_rawlist($ftp, '~')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_mlsd($ftp, '~')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_systype($ftp)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_fget($ftp, $ftp, 'remote', 7777)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nb_fget($ftp, $ftp, 'remote', 7777)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_pasv($ftp, false)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_get($ftp, 'local', 'remote', 7777)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nb_get($ftp, 'local', 'remote', 7777)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nb_continue($ftp)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_fput($ftp, 'remote', $ftp, 9999)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nb_fput($ftp, 'remote', $ftp, 9999)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_put($ftp, 'remote', 'local', 9999)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_append($ftp, 'remote', 'local', 9999)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_nb_put($ftp, 'remote', 'local', 9999)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_size($ftp, '~')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_mdtm($ftp, '~')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_rename($ftp, 'old', 'new')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_delete($ftp, 'gone')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_site($ftp, 'localhost')); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_close($ftp)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_set_option($ftp, 1, 2)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump(ftp_get_option($ftp, 1)); + } catch (TypeError $e) { + echo $e->getMessage(), "\n"; + } + + fclose($ftp); +}); +?> +--EXPECT-- +ftp_login(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_pwd(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_cdup(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_chdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_exec(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_raw(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_mkdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_rmdir(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_chmod(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_alloc(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nlist(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_rawlist(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_mlsd(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_systype(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_fget(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nb_fget(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_pasv(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_get(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nb_get(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nb_continue(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_fput(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nb_fput(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_put(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_append(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_nb_put(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_size(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_mdtm(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_rename(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_delete(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_site(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_close(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_set_option(): Argument #1 ($ftp) must be of type FTP\Connection, resource given +ftp_get_option(): Argument #1 ($ftp) must be of type FTP\Connection, resource given diff --git a/tests/swoole_ftp/bug27809.phpt b/tests/swoole_ftp/bug27809.phpt new file mode 100644 index 0000000000..d7e07f1204 --- /dev/null +++ b/tests/swoole_ftp/bug27809.phpt @@ -0,0 +1,18 @@ +--TEST-- +Bug #27809 (ftp_systype returns null) +--FILE-- + +--EXPECT-- +bool(true) +string(6) "OS/400" diff --git a/tests/swoole_ftp/bug37799.phpt b/tests/swoole_ftp/bug37799.phpt new file mode 100644 index 0000000000..f73bbee971 --- /dev/null +++ b/tests/swoole_ftp/bug37799.phpt @@ -0,0 +1,25 @@ +--TEST-- +Bug #37799 (ftp_ssl_connect() falls back to non-ssl connection) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: ftp_login(): %rdummy|bogus msg%r in %sbug37799.php on line %d +bool(false) diff --git a/tests/swoole_ftp/bug39458-2.phpt b/tests/swoole_ftp/bug39458-2.phpt new file mode 100644 index 0000000000..9fe3e47c5e --- /dev/null +++ b/tests/swoole_ftp/bug39458-2.phpt @@ -0,0 +1,36 @@ +--TEST-- +Bug #39458 (ftp_nlist() returns false on empty directories (other server behaviour)) +--FILE-- + +--EXPECT-- +bool(true) +array(3) { + [0]=> + string(5) "file1" + [1]=> + string(5) "file1" + [2]=> + string(9) "file +b0rk" +} +array(0) { +} +bool(false) diff --git a/tests/swoole_ftp/bug39458.phpt b/tests/swoole_ftp/bug39458.phpt new file mode 100644 index 0000000000..7fdd418257 --- /dev/null +++ b/tests/swoole_ftp/bug39458.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #39458 (ftp_nlist() returns false on empty directories) +--FILE-- + +--EXPECT-- +bool(true) +array(3) { + [0]=> + string(5) "file1" + [1]=> + string(5) "file1" + [2]=> + string(9) "file +b0rk" +} +array(0) { +} +bool(false) diff --git a/tests/swoole_ftp/bug39583-2.phpt b/tests/swoole_ftp/bug39583-2.phpt new file mode 100644 index 0000000000..0abf5a0189 --- /dev/null +++ b/tests/swoole_ftp/bug39583-2.phpt @@ -0,0 +1,34 @@ +--TEST-- +Bug #39583 (FTP always transfers in binary mode) +--FILE-- + +--EXPECTF-- +bool(true) +Uploaded %sbug39583-2.php as bug39583-2.php diff --git a/tests/swoole_ftp/bug39583.phpt b/tests/swoole_ftp/bug39583.phpt new file mode 100644 index 0000000000..86ded77d81 --- /dev/null +++ b/tests/swoole_ftp/bug39583.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #39583 (FTP always transfers in binary mode) +--FILE-- + +--EXPECTF-- +bool(true) +Uploaded %sbug39583.php as bug39583.php diff --git a/tests/swoole_ftp/bug7216-2.phpt b/tests/swoole_ftp/bug7216-2.phpt new file mode 100644 index 0000000000..1406615ee5 --- /dev/null +++ b/tests/swoole_ftp/bug7216-2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Bug #7216 (ftp_mkdir returns nothing (2)) +--FILE-- + +--EXPECT-- +bool(true) +string(3) "CVS" diff --git a/tests/swoole_ftp/bug79100.phpt b/tests/swoole_ftp/bug79100.phpt new file mode 100644 index 0000000000..e7f71a69e9 --- /dev/null +++ b/tests/swoole_ftp/bug79100.phpt @@ -0,0 +1,28 @@ +--TEST-- +Bug #79100 (Wrong FTP error messages) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) + +Warning: ftp_systype(): %rConnection|Operation%r timed out in %s on line %d diff --git a/tests/swoole_ftp/bug80901.phpt b/tests/swoole_ftp/bug80901.phpt new file mode 100644 index 0000000000..131278da68 --- /dev/null +++ b/tests/swoole_ftp/bug80901.phpt @@ -0,0 +1,32 @@ +--TEST-- +Bug #80901 (Info leak in ftp extension) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: ftp_systype(): **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** in %s on line %d diff --git a/tests/swoole_ftp/cert.pem b/tests/swoole_ftp/cert.pem new file mode 100644 index 0000000000..2bb30614ed --- /dev/null +++ b/tests/swoole_ftp/cert.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAN75FFz+owOAMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xNjA3MjUxMjM3 +MTJaFw0yNjA3MjMxMjM3MTJaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALtG +AIrNUDItISfpAqztL2TFEWEHLGTzCEh5Ag2sdMD7UYbqIPHLOE4EINv+dqEMM0Nz +LYnw7ChtVegXT907xCaQcmeDFSdhqze4L8zawDfnn4syB8XAwGYJfpstYwe3nO6+ +0WvLSb1A5TYNeyoXjwlAUKElxkeWAo51uhR41GDhDQ9GgpqX1ccAhmSoUhgIRSzf +6f4KE3WTdzl1p12ZtkYHB8Jo2jB/JXnwGOz6isLnpRvkex4B7sUX+7u1MqK/e1X7 +Hi1G/VkaAfC2SOfjTePtGBDBXrQ1arYXDPRA04sgFzSh55l7lC/4HasQ/jAb3h95 +dcEIqyc69iioaN1c1NcCAwEAAaNQME4wHQYDVR0OBBYEFNv3kefb1H+6/6CpjiBi ++I2s9E90MB8GA1UdIwQYMBaAFNv3kefb1H+6/6CpjiBi+I2s9E90MAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIzSEWpHSaBs7KduBRXX5+qFxBN6OCPl +7ID0rxAOYfw7ruzbmwgOpBgMIHGn+KqA6CmQI0jh9bZbv5TV2aFpFsUihugPc2lW +5EshCozxlEPmIJNsO8jDqPE4w3m4KiVTscRWjBa5cco+lwLDqboerm2l7vvrtr6B +pgLaZct1c73MouvoJSCGK5EOGW7jsgaxjxJ3UZug+24Ko1wulO2cgBLhda9Ilrnx +CIKI9h8Z2WVWuVQfyCyO1g7XkJgkBec77OhxD+m4onzPY6waqnnhmFOBcS+gKgBV +jHeK9DCvZ9zet3EyEp6fyQOOtsC+gU0piYgfsQL7aCp5oLe+fjTiuUY= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7RgCKzVAyLSEn +6QKs7S9kxRFhByxk8whIeQINrHTA+1GG6iDxyzhOBCDb/nahDDNDcy2J8OwobVXo +F0/dO8QmkHJngxUnYas3uC/M2sA355+LMgfFwMBmCX6bLWMHt5zuvtFry0m9QOU2 +DXsqF48JQFChJcZHlgKOdboUeNRg4Q0PRoKal9XHAIZkqFIYCEUs3+n+ChN1k3c5 +daddmbZGBwfCaNowfyV58Bjs+orC56Ub5HseAe7FF/u7tTKiv3tV+x4tRv1ZGgHw +tkjn403j7RgQwV60NWq2Fwz0QNOLIBc0oeeZe5Qv+B2rEP4wG94feXXBCKsnOvYo +qGjdXNTXAgMBAAECggEAD7yDISa9fWnjZlojGmrX16zjl/alWVo+sPBSJtn9+ZVk +tWSJHihIc+3O4Q2R5FiFGj7cbcHr5j3BwT3sPRfflKoAowgVx/hiDc2RXrJnAouZ +EXZDxu86e5iCpgF7V9OrATjRmjA74wZH/HHHjrLqFwnrfI8TCULmthfYag35Mqax +qrIEzvSuYdaGxblNe+ZfnVEDW2F9DLBGcma0ffUlJp8AvV7bpo8Rj/JovPxit/VS +UdwSRxwSAugctFpmcGlFkoQfxUx1WdEy8hjopLrayMjCCJvRUL4+C4zT9r9PBHOj +fCSbJ+ajQIoRrgaL9bURk8BFMHY8+yMUsWEYVSmFAQKBgQDtOvPkhvZsNocUB5nJ +ti3SXyDQ6OZQuKKHPSeDV/EvmZKeNlrQ1ZnwXLP3vkcedDOkt1nEVq/hUewpjt08 +2MvmMwJBQEnmbTzMf43DtlXsStdP1lhYaFbU4iMM5zRfyBHDu1GPZEPXvKKpJUk0 +M+jYIyTAP3mcZhqDKn0mPVP7VwKBgQDKFy9DtWFKxCvhFQvHx7YeZiVWJbIst/O4 +ZyuPVAErni0hzSeCkmm7+F9hgEdPSLRcSaeTWP4L0u1cixECKboIhoNs38aft7o3 +MdnI2RDSEKtKX2uVuhvpGDNuGpBAc8Qu8iCiv5INSC36ZhD1h++O/TiiUdgRJ3yX +yeG7ej+CgQKBgCR5F95e0aw5hfMSaBaXJ9xcO9Niu2ZVvMdGI7kR4EcNOXmRqczJ +ym0mE5VXb9/Cxd3hQq/pFAl0avbIvEMKoe62kPYvSC1hRiO6yLT6Z6N4rjncHqEZ +CaCZVAI72dWQEQsi1ZtSMwwMOIYA8YxRHs98N75HBA+DszfPZIZoj2zpAoGBAJHp +B3ElhmeLF/tdTLIj2bQ9H/wBH5H2Bvw/UU4c4vNxMzjSfRJjUAAtpgAptFLkNYTk +kR9sA5DZ7BMDPXaIVg9Nv5peP3SWHNc5IPtI7kIdUu9R0cW7J+e2V3vJphlC/ITA +wRuAoZ0BXmEKTHhae3aMEdXwrcZE8kpNsrO/4hcBAoGBAMISiPJPuxAX1UtqcxTa +mDJfnQ2gxRu6AK9VmXqo0X4IBxDSnTjcL0huUlS849wgsE5oTXgdYb2hn+TXM5JJ +NsEXLhV09X1mrk4M4LV1npd0mYxvFsO4+p+IX5YLiahInmQtq0gx3DWE8wouVFER +4yzfp27z8MZT8Qvr/ZI9lzWd +-----END PRIVATE KEY----- diff --git a/tests/swoole_ftp/dead-resource.phpt b/tests/swoole_ftp/dead-resource.phpt new file mode 100644 index 0000000000..3f9969e76b --- /dev/null +++ b/tests/swoole_ftp/dead-resource.phpt @@ -0,0 +1,24 @@ +--TEST-- +Attempt to use a closed FTP\Connection +--FILE-- +getMessage(), "\n"; + } +}); +?> +--EXPECT-- +bool(true) +bool(true) +Exception: FTP\Connection is already closed \ No newline at end of file diff --git a/tests/swoole_ftp/filesize_large.phpt b/tests/swoole_ftp/filesize_large.phpt new file mode 100644 index 0000000000..dfd96ea514 --- /dev/null +++ b/tests/swoole_ftp/filesize_large.phpt @@ -0,0 +1,24 @@ +--TEST-- +Verify php can handle filesizes >32bit +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(5368709120) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_alloc_basic1.phpt b/tests/swoole_ftp/ftp_alloc_basic1.phpt new file mode 100644 index 0000000000..aa24413651 --- /dev/null +++ b/tests/swoole_ftp/ftp_alloc_basic1.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing ftp_alloc returns true +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_alloc_basic2.phpt b/tests/swoole_ftp/ftp_alloc_basic2.phpt new file mode 100644 index 0000000000..646930240d --- /dev/null +++ b/tests/swoole_ftp/ftp_alloc_basic2.phpt @@ -0,0 +1,21 @@ +--TEST-- +Testing ftp_alloc returns true +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +bool(true) +string(20) "1024 bytes allocated" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_append.phpt b/tests/swoole_ftp/ftp_append.phpt new file mode 100644 index 0000000000..b1a1a30e49 --- /dev/null +++ b/tests/swoole_ftp/ftp_append.phpt @@ -0,0 +1,40 @@ +--TEST-- +ftp_append() create new file and append something +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +string(6) "foobar" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_chmod_basic.phpt b/tests/swoole_ftp/ftp_chmod_basic.phpt new file mode 100644 index 0000000000..ed32f9edff --- /dev/null +++ b/tests/swoole_ftp/ftp_chmod_basic.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing ftp_chmod returns file mode +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +int(420) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_connect_001.phpt b/tests/swoole_ftp/ftp_connect_001.phpt new file mode 100644 index 0000000000..1829847c90 --- /dev/null +++ b/tests/swoole_ftp/ftp_connect_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +ftp_connect - return FALSE if connection fails and Waning is generated +--FILE-- + +--EXPECTF-- +Warning: ftp_connect(): getaddrinfo for 'dummy-host-name' failed, error: %s in %s on line %d +bool(false) diff --git a/tests/swoole_ftp/ftp_constructor.phpt b/tests/swoole_ftp/ftp_constructor.phpt new file mode 100644 index 0000000000..3d124910c0 --- /dev/null +++ b/tests/swoole_ftp/ftp_constructor.phpt @@ -0,0 +1,14 @@ +--TEST-- +Attempt to instantiate an FTP\Connection directly +--FILE-- +getMessage(), "\n"; + } +}); +?> +--EXPECT-- +Exception: Cannot directly construct FTP\Connection, use ftp_connect() or ftp_ssl_connect() instead \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_delete.phpt b/tests/swoole_ftp/ftp_delete.phpt new file mode 100644 index 0000000000..6c499d95a5 --- /dev/null +++ b/tests/swoole_ftp/ftp_delete.phpt @@ -0,0 +1,31 @@ +--TEST-- +Testing ftp_delete basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +Contributed by Ward Cappelle +User Group: PHP-WVL & PHPGent #PHPTestFest +--FILE-- + +--EXPECTF-- +Test case #1: removal of existing file from FTP, should return true: +bool(true) +Test case #2: removal of non-existent file from FTP, should return false: + +Warning: ftp_delete(): No such file or directory in %s on line %d +bool(false) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_exec_basic.phpt b/tests/swoole_ftp/ftp_exec_basic.phpt new file mode 100644 index 0000000000..6763a72b5e --- /dev/null +++ b/tests/swoole_ftp/ftp_exec_basic.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing ftp_exec returns true +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fget_basic.phpt b/tests/swoole_ftp/ftp_fget_basic.phpt new file mode 100644 index 0000000000..03a8e1210d --- /dev/null +++ b/tests/swoole_ftp/ftp_fget_basic.phpt @@ -0,0 +1,41 @@ +--TEST-- +FTP ftp_fget file for both binary and ASCII transfer modes +--CREDITS-- +Nathaniel McHugh +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +For sale: baby shoes, never worn. +bool(true) +"BINARYFoo\u0000Bar\r\n" + +Warning: ftp_fget(): a warning: No such file or directory in %sftp_fget_basic.php on line %d \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fget_basic1.phpt b/tests/swoole_ftp/ftp_fget_basic1.phpt new file mode 100644 index 0000000000..3643b5929f --- /dev/null +++ b/tests/swoole_ftp/ftp_fget_basic1.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_fget ignore autoresume if autoseek is switched off +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fget_basic2.phpt b/tests/swoole_ftp/ftp_fget_basic2.phpt new file mode 100644 index 0000000000..2dd3d13db1 --- /dev/null +++ b/tests/swoole_ftp/ftp_fget_basic2.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_fget autoresume +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fget_basic3.phpt b/tests/swoole_ftp/ftp_fget_basic3.phpt new file mode 100644 index 0000000000..7bd70e1be9 --- /dev/null +++ b/tests/swoole_ftp/ftp_fget_basic3.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_fget resume parameter +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fput.phpt b/tests/swoole_ftp/ftp_fput.phpt new file mode 100644 index 0000000000..20579bd65b --- /dev/null +++ b/tests/swoole_ftp/ftp_fput.phpt @@ -0,0 +1,21 @@ +--TEST-- +Testing ftp_fput basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_fput_ascii_over_4_kib.phpt b/tests/swoole_ftp/ftp_fput_ascii_over_4_kib.phpt new file mode 100644 index 0000000000..d2ebf174c9 --- /dev/null +++ b/tests/swoole_ftp/ftp_fput_ascii_over_4_kib.phpt @@ -0,0 +1,31 @@ +--TEST-- +Testing ftp_fput basic functionality +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_get_basic.phpt b/tests/swoole_ftp/ftp_get_basic.phpt new file mode 100644 index 0000000000..2b550b3956 --- /dev/null +++ b/tests/swoole_ftp/ftp_get_basic.phpt @@ -0,0 +1,39 @@ +--TEST-- +FTP ftp_get file for both binary and ASCII transfer modes +--CREDITS-- +Nathaniel McHugh +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +For sale: baby shoes, never worn. +bool(true) +"BINARYFoo\u0000Bar\r\n" + +Warning: ftp_get(): a warning: No such file or directory in %sftp_get_basic.php on line %d \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_get_option.phpt b/tests/swoole_ftp/ftp_get_option.phpt new file mode 100644 index 0000000000..ef2ac76813 --- /dev/null +++ b/tests/swoole_ftp/ftp_get_option.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_get_option basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- +getMessage() . "\n"; + } +}); +?> +--EXPECTF-- +int(%d) +bool(true) +bool(true) +ftp_get_option(): Argument #2 ($option) must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_mdtm_basic.phpt b/tests/swoole_ftp/ftp_mdtm_basic.phpt new file mode 100644 index 0000000000..173312c6b1 --- /dev/null +++ b/tests/swoole_ftp/ftp_mdtm_basic.phpt @@ -0,0 +1,44 @@ +--TEST-- +Test the File Modification Time as described in http://tools.ietf.org/html/rfc3659#section-3.1 +--CREDITS-- +Nathaniel McHugh +--FILE-- + +--EXPECT-- +bool(true) +June 15 1998 10:00:45 000000 +June 15 1998 10:00:45 000000 +July 05 1998 13:23:16 000000 +int(-1) +October 05 1999 21:31:02 000000 +int(-1) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_mlsd.phpt b/tests/swoole_ftp/ftp_mlsd.phpt new file mode 100644 index 0000000000..d0e2095c8c --- /dev/null +++ b/tests/swoole_ftp/ftp_mlsd.phpt @@ -0,0 +1,95 @@ +--TEST-- +ftp_mlsd() return parsed lines +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: ftp_mlsd(): Missing pathname in MLSD response in %s on line %d + +Warning: ftp_mlsd(): Malformed fact in MLSD response in %s on line %d + +Warning: ftp_mlsd(): Malformed fact in MLSD response in %s on line %d +array(4) { + [0]=> + array(8) { + ["name"]=> + string(1) "." + ["modify"]=> + string(14) "20170127230002" + ["perm"]=> + string(7) "flcdmpe" + ["type"]=> + string(4) "cdir" + ["unique"]=> + string(11) "811U4340002" + ["UNIX.group"]=> + string(2) "33" + ["UNIX.mode"]=> + string(4) "0755" + ["UNIX.owner"]=> + string(2) "33" + } + [1]=> + array(8) { + ["name"]=> + string(2) ".." + ["modify"]=> + string(14) "20170127230002" + ["perm"]=> + string(7) "flcdmpe" + ["type"]=> + string(4) "pdir" + ["unique"]=> + string(11) "811U4340002" + ["UNIX.group"]=> + string(2) "33" + ["UNIX.mode"]=> + string(4) "0755" + ["UNIX.owner"]=> + string(2) "33" + } + [2]=> + array(9) { + ["name"]=> + string(6) "foobar" + ["modify"]=> + string(14) "20170126121225" + ["perm"]=> + string(5) "adfrw" + ["size"]=> + string(4) "4729" + ["type"]=> + string(4) "file" + ["unique"]=> + string(11) "811U4340CB9" + ["UNIX.group"]=> + string(2) "33" + ["UNIX.mode"]=> + string(4) "0644" + ["UNIX.owner"]=> + string(2) "33" + } + [3]=> + array(3) { + ["name"]=> + string(9) "path;name" + ["fact"]=> + string(6) "val=ue" + ["empty"]=> + string(0) "" + } +} \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_mlsd_empty_directory.phpt b/tests/swoole_ftp/ftp_mlsd_empty_directory.phpt new file mode 100644 index 0000000000..988fc118b8 --- /dev/null +++ b/tests/swoole_ftp/ftp_mlsd_empty_directory.phpt @@ -0,0 +1,23 @@ +--TEST-- +ftp_mlsd() must not return false on empty directories +--FILE-- + +--EXPECT-- +bool(true) +array(0) { +} +bool(false) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_mlsd_missing_directory.phpt b/tests/swoole_ftp/ftp_mlsd_missing_directory.phpt new file mode 100644 index 0000000000..04472901c6 --- /dev/null +++ b/tests/swoole_ftp/ftp_mlsd_missing_directory.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testing ftp_mlsd returns false on server error +--CREDITS-- +Andreas Treichel +--FILE-- + +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_continue.phpt b/tests/swoole_ftp/ftp_nb_continue.phpt new file mode 100644 index 0000000000..7a794697a6 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_continue.phpt @@ -0,0 +1,180 @@ +--TEST-- +Testing whether ftp_nb_continue() fetches more data +--FILE-- + +--CLEAN-- + +--EXPECT-- +This is line 0 of the test data. +This is line 1 of the test data. +This is line 2 of the test data. +This is line 3 of the test data. +This is line 4 of the test data. +This is line 5 of the test data. +This is line 6 of the test data. +This is line 7 of the test data. +This is line 8 of the test data. +This is line 9 of the test data. +This is line 10 of the test data. +This is line 11 of the test data. +This is line 12 of the test data. +This is line 13 of the test data. +This is line 14 of the test data. +This is line 15 of the test data. +This is line 16 of the test data. +This is line 17 of the test data. +This is line 18 of the test data. +This is line 19 of the test data. +This is line 20 of the test data. +This is line 21 of the test data. +This is line 22 of the test data. +This is line 23 of the test data. +This is line 24 of the test data. +This is line 25 of the test data. +This is line 26 of the test data. +This is line 27 of the test data. +This is line 28 of the test data. +This is line 29 of the test data. +This is line 30 of the test data. +This is line 31 of the test data. +This is line 32 of the test data. +This is line 33 of the test data. +This is line 34 of the test data. +This is line 35 of the test data. +This is line 36 of the test data. +This is line 37 of the test data. +This is line 38 of the test data. +This is line 39 of the test data. +This is line 40 of the test data. +This is line 41 of the test data. +This is line 42 of the test data. +This is line 43 of the test data. +This is line 44 of the test data. +This is line 45 of the test data. +This is line 46 of the test data. +This is line 47 of the test data. +This is line 48 of the test data. +This is line 49 of the test data. +This is line 50 of the test data. +This is line 51 of the test data. +This is line 52 of the test data. +This is line 53 of the test data. +This is line 54 of the test data. +This is line 55 of the test data. +This is line 56 of the test data. +This is line 57 of the test data. +This is line 58 of the test data. +This is line 59 of the test data. +This is line 60 of the test data. +This is line 61 of the test data. +This is line 62 of the test data. +This is line 63 of the test data. +This is line 64 of the test data. +This is line 65 of the test data. +This is line 66 of the test data. +This is line 67 of the test data. +This is line 68 of the test data. +This is line 69 of the test data. +This is line 70 of the test data. +This is line 71 of the test data. +This is line 72 of the test data. +This is line 73 of the test data. +This is line 74 of the test data. +This is line 75 of the test data. +This is line 76 of the test data. +This is line 77 of the test data. +This is line 78 of the test data. +This is line 79 of the test data. +This is line 80 of the test data. +This is line 81 of the test data. +This is line 82 of the test data. +This is line 83 of the test data. +This is line 84 of the test data. +This is line 85 of the test data. +This is line 86 of the test data. +This is line 87 of the test data. +This is line 88 of the test data. +This is line 89 of the test data. +This is line 90 of the test data. +This is line 91 of the test data. +This is line 92 of the test data. +This is line 93 of the test data. +This is line 94 of the test data. +This is line 95 of the test data. +This is line 96 of the test data. +This is line 97 of the test data. +This is line 98 of the test data. +This is line 99 of the test data. +This is line 100 of the test data. +This is line 101 of the test data. +This is line 102 of the test data. +This is line 103 of the test data. +This is line 104 of the test data. +This is line 105 of the test data. +This is line 106 of the test data. +This is line 107 of the test data. +This is line 108 of the test data. +This is line 109 of the test data. +This is line 110 of the test data. +This is line 111 of the test data. +This is line 112 of the test data. +This is line 113 of the test data. +This is line 114 of the test data. +This is line 115 of the test data. +This is line 116 of the test data. +This is line 117 of the test data. +This is line 118 of the test data. +This is line 119 of the test data. +This is line 120 of the test data. +This is line 121 of the test data. +This is line 122 of the test data. +This is line 123 of the test data. +This is line 124 of the test data. +This is line 125 of the test data. +This is line 126 of the test data. +This is line 127 of the test data. +This is line 128 of the test data. +This is line 129 of the test data. +This is line 130 of the test data. +This is line 131 of the test data. +This is line 132 of the test data. +This is line 133 of the test data. +This is line 134 of the test data. +This is line 135 of the test data. +This is line 136 of the test data. +This is line 137 of the test data. +This is line 138 of the test data. +This is line 139 of the test data. +This is line 140 of the test data. +This is line 141 of the test data. +This is line 142 of the test data. +This is line 143 of the test data. +This is line 144 of the test data. +This is line 145 of the test data. +This is line 146 of the test data. +This is line 147 of the test data. +This is line 148 of the test data. +This is line 149 of the test data. \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_fget_basic1.phpt b/tests/swoole_ftp/ftp_nb_fget_basic1.phpt new file mode 100644 index 0000000000..0d917725ea --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_fget_basic1.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_nb_fget ignore autoresume if autoseek is switched off +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +int(2) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_fget_basic2.phpt b/tests/swoole_ftp/ftp_nb_fget_basic2.phpt new file mode 100644 index 0000000000..5a0b902ec6 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_fget_basic2.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_nb_fget autoresume +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +int(2) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_fget_basic3.phpt b/tests/swoole_ftp/ftp_nb_fget_basic3.phpt new file mode 100644 index 0000000000..f102aab749 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_fget_basic3.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_nb_fget resume parameter +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--CLEAN-- + +--EXPECT-- +int(2) +string(12) "ASCIIFooBar +" \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_fput.phpt b/tests/swoole_ftp/ftp_nb_fput.phpt new file mode 100644 index 0000000000..bbb607cee5 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_fput.phpt @@ -0,0 +1,21 @@ +--TEST-- +Testing ftp_nb_fput basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +int(1) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_get_large.phpt b/tests/swoole_ftp/ftp_nb_get_large.phpt new file mode 100644 index 0000000000..eb0af8aea7 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_get_large.phpt @@ -0,0 +1,37 @@ +--TEST-- +Testing ftp_nb_fget can handle large files incl. resume +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECT-- +string(1) "X" +int(5368709120) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_nb_put.phpt b/tests/swoole_ftp/ftp_nb_put.phpt new file mode 100644 index 0000000000..0ebf4a4194 --- /dev/null +++ b/tests/swoole_ftp/ftp_nb_put.phpt @@ -0,0 +1,21 @@ +--TEST-- +Testing ftp_nb_put basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +int(1) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_pasv.phpt b/tests/swoole_ftp/ftp_pasv.phpt new file mode 100644 index 0000000000..5b62b84563 --- /dev/null +++ b/tests/swoole_ftp/ftp_pasv.phpt @@ -0,0 +1,18 @@ +--TEST-- +Testing ftp_pasv basic funcionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_rawlist_basic1.phpt b/tests/swoole_ftp/ftp_rawlist_basic1.phpt new file mode 100644 index 0000000000..313e8c604a --- /dev/null +++ b/tests/swoole_ftp/ftp_rawlist_basic1.phpt @@ -0,0 +1,30 @@ +--TEST-- +Testing ftp_rawlist basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +bool(true) +array(3) { + [0]=> + string(5) "file1" + [1]=> + string(5) "file1" + [2]=> + string(9) "file +b0rk" +} \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_rawlist_basic2.phpt b/tests/swoole_ftp/ftp_rawlist_basic2.phpt new file mode 100644 index 0000000000..6d220c6414 --- /dev/null +++ b/tests/swoole_ftp/ftp_rawlist_basic2.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing ftp_rawlist returns false on server error +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_rename_basic1.phpt b/tests/swoole_ftp/ftp_rename_basic1.phpt new file mode 100644 index 0000000000..70af1a0939 --- /dev/null +++ b/tests/swoole_ftp/ftp_rename_basic1.phpt @@ -0,0 +1,21 @@ +--TEST-- +FTP basic ftp_rename calls +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: ftp_rename(): No such file or directory in %sftp_rename_basic1.php on line %d +bool(false) diff --git a/tests/swoole_ftp/ftp_rmdir_basic.phpt b/tests/swoole_ftp/ftp_rmdir_basic.phpt new file mode 100644 index 0000000000..c611f3a0da --- /dev/null +++ b/tests/swoole_ftp/ftp_rmdir_basic.phpt @@ -0,0 +1,19 @@ +--TEST-- +Testing ftp_rmdir returns true +--CREDITS-- +Rodrigo Moyle +#testfest PHPSP on 2009-06-20 +--FILE-- + +--EXPECT-- +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_set_option.phpt b/tests/swoole_ftp/ftp_set_option.phpt new file mode 100644 index 0000000000..26612b895e --- /dev/null +++ b/tests/swoole_ftp/ftp_set_option.phpt @@ -0,0 +1,22 @@ +--TEST-- +Testing ftp_set_option basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_set_option_errors.phpt b/tests/swoole_ftp/ftp_set_option_errors.phpt new file mode 100644 index 0000000000..b5368456aa --- /dev/null +++ b/tests/swoole_ftp/ftp_set_option_errors.phpt @@ -0,0 +1,34 @@ +--TEST-- +Testing ftp_set_option errors while setting up +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + FTP_TIMEOUT_SEC, 'value' => 0 ], + [ 'option' => FTP_TIMEOUT_SEC, 'value' => '0' ], + [ 'option' => FTP_USEPASVADDRESS, 'value' => ['1'] ], + [ 'option' => FTP_AUTOSEEK, 'value' => 'true' ], + [ 'option' => FOO_BAR, 'value' => 1 ], + ]; + foreach ($options as $option) try { + var_dump(ftp_set_option($ftp, $option['option'], $option['value'])); + } catch (\Throwable $ex) { + echo "Exception: ", $ex->getMessage(), "\n"; + } +}); +?> +--EXPECT-- +Exception: ftp_set_option(): Argument #3 ($value) must be greater than 0 for the FTP_TIMEOUT_SEC option +Exception: ftp_set_option(): Argument #3 ($value) must be of type int for the FTP_TIMEOUT_SEC option, string given +Exception: ftp_set_option(): Argument #3 ($value) must be of type bool for the FTP_USEPASVADDRESS option, array given +Exception: ftp_set_option(): Argument #3 ($value) must be of type bool for the FTP_AUTOSEEK option, string given +Exception: ftp_set_option(): Argument #2 ($option) must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_site_basic.phpt b/tests/swoole_ftp/ftp_site_basic.phpt new file mode 100644 index 0000000000..24c332af1f --- /dev/null +++ b/tests/swoole_ftp/ftp_site_basic.phpt @@ -0,0 +1,23 @@ +--TEST-- +ftp_site function basic functionality +--CREDITS-- +Gabriel Caruso (carusogabriel34@gmail.com) +--FILE-- + +--EXPECTF-- +bool(true) + +Warning: ftp_site(): Syntax error, command unrecognized. in %s on line %d +bool(false) \ No newline at end of file diff --git a/tests/swoole_ftp/ftp_ssl_connect_error.phpt b/tests/swoole_ftp/ftp_ssl_connect_error.phpt new file mode 100644 index 0000000000..36b37d5a99 --- /dev/null +++ b/tests/swoole_ftp/ftp_ssl_connect_error.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test ftp_ssl_connect() function : error conditions +--SKIPIF-- + +--FILE-- +getMessage() . "\n"; + } + + echo "===DONE===\n"; +}); +?> +--EXPECTF-- +*** Testing ftp_ssl_connect() function : error conditions *** + +-- Testing ftp_ssl_connect() function on failure -- + +Warning: ftp_ssl_connect(): getaddrinfo for '%s' failed, error: %s in %s on line %d +bool(false) + +-- Testing ftp_ssl_connect() function timeout exception for value 0 -- +ftp_ssl_connect(): Argument #3 ($timeout) must be greater than 0 +===DONE=== diff --git a/tests/swoole_ftp/gh10521.phpt b/tests/swoole_ftp/gh10521.phpt new file mode 100644 index 0000000000..6dcf1a965c --- /dev/null +++ b/tests/swoole_ftp/gh10521.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-10521 (ftp_get/ftp_nb_get resumepos offset is maximum 10GB) +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +bool(true) + +%s: ftp_fget(): Can't open data connection (12345678910). in %s on line %d + +%s: ftp_fget(): Can't open data connection (9223372036854775807). in %s on line %d \ No newline at end of file diff --git a/tests/swoole_ftp/gh10562.phpt b/tests/swoole_ftp/gh10562.phpt new file mode 100644 index 0000000000..30d0764d5c --- /dev/null +++ b/tests/swoole_ftp/gh10562.phpt @@ -0,0 +1,39 @@ +--TEST-- +GH-10562 (Memory leak with consecutive ftp_nb_fget) +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +BINARYFooBar +For sale: baby shoes, never worn. \ No newline at end of file diff --git a/tests/swoole_ftp/server.inc b/tests/swoole_ftp/server.inc new file mode 100644 index 0000000000..6771a3045c --- /dev/null +++ b/tests/swoole_ftp/server.inc @@ -0,0 +1,521 @@ + ['local_cert' => dirname(__FILE__) . '/cert.pem']]); + +$socket = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context); +if (!$socket) { + echo "{$errstr} ({$errno})\n"; + exit("could not start/bind the ftp server\n"); +} + +$socket_name = stream_socket_get_name($socket, false); +$port = (int) substr($socket_name, strrpos($socket_name, ':') + 1); + +$pid = pcntl_fork(); + +// server +if ($pid == 0) { + function dump_and_exit($buf) + { + var_dump($buf); + exit; + } + + function anonymous() + { + return $GLOBALS['user'] === 'anonymous'; + } + + /* quick&dirty realpath() like function */ + function change_dir($dir) + { + global $cwd; + + if ($dir[0] == '/') { + $cwd = $dir; + return; + } + + $cwd = "{$cwd}/{$dir}"; + + do { + $old = $cwd; + $cwd = preg_replace('@/?[^/]+/\.\.@', '', $cwd); + } while ($old != $cwd); + + $cwd = strtr($cwd, ['//' => '/']); + if (!$cwd) { + $cwd = '/'; + } + } + + $s = stream_socket_accept($socket); + if (!$s) { + exit("Error accepting a new connection\n"); + } + + fputs($s, "220----- PHP FTP server 0.3 -----\r\n220 Service ready\r\n"); + $buf = fread($s, 2048); + + function user_auth($buf) + { + global $user, $s, $ssl, $bug37799; + + if (!empty($ssl)) { + if ($buf !== "AUTH TLS\r\n") { + fputs($s, "500 Syntax error, command unrecognized.\r\n"); + dump_and_exit($buf); + } + + if (empty($bug37799)) { + fputs($s, "234 auth type accepted\r\n"); + } else { + fputs($s, "666 dummy\r\n"); + sleep(1); + fputs($s, "666 bogus msg\r\n"); + exit; + } + + if (!stream_socket_enable_crypto($s, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER)) { + exit("SSLv23 handshake failed.\n"); + } + + if (!preg_match('/^PBSZ \d+\r\n$/', $buf = fread($s, 2048))) { + fputs($s, "501 bogus data\r\n"); + dump_and_exit($buf); + } + + fputs($s, "200 OK\r\n"); + $buf = fread($s, 2048); + + if ($buf !== "PROT P\r\n") { + fputs($s, "504 Wrong protection.\r\n"); + dump_and_exit($buf); + } + + fputs($s, "200 OK\r\n"); + + $buf = fread($s, 2048); + } + + if ($buf == "AUTH TLS\r\n") { + fputs($s, "500 not supported.\r\n"); + return; + } + if (!preg_match('/^USER (\w+)\r\n$/', $buf, $m)) { + fputs($s, "500 Syntax error, command unrecognized.\r\n"); + dump_and_exit($buf); + } + $user = $m[1]; + if ($user !== 'user' && $user !== 'anonymous') { + fputs($s, "530 Not logged in.\r\n"); + exit; + } + + if (anonymous()) { + fputs($s, "230 Anonymous user logged in\r\n"); + } else { + fputs($s, "331 User name ok, need password\r\n"); + + if (!preg_match('/^PASS (\w+)\r\n$/', $buf = fread($s, 100), $m)) { + fputs($s, "500 Syntax error, command unrecognized.\r\n"); + dump_and_exit($buf); + } + + $pass = $m[1]; + if ($pass === 'pass') { + fputs($s, "230 User logged in\r\n"); + } else { + fputs($s, "530 Not logged in.\r\n"); + exit; + } + } + } + + user_auth($buf); + + $cwd = '/'; + $num_bogus_cmds = 0; + + while ($buf = fread($s, 4098)) { + if (!empty($bogus)) { + fputs($s, '502 Command not implemented (' . $num_bogus_cmds++ . ").\r\n"); + } elseif ($buf === "HELP\r\n") { + fputs($s, "214-There is help available for the following commands:\r\n"); + fputs($s, " USER\r\n"); + fputs($s, " HELP\r\n"); + fputs($s, "214 end of list\r\n"); + } elseif ($buf === "HELP HELP\r\n") { + fputs($s, "214 Syntax: HELP [ ] \r\n"); + } elseif ($buf === "PWD\r\n") { + fputs($s, "257 \"{$cwd}\" is current directory.\r\n"); + } elseif ($buf === "CDUP\r\n") { + change_dir('..'); + fputs($s, "250 CDUP command successful.\r\n"); + } elseif ($buf === "SYST\r\n") { + if (isset($bug27809)) { + fputs($s, "215 OS/400 is the remote operating system. The TCP/IP version is \"V5R2M0\"\r\n"); + } elseif (isset($bug79100)) { + // do nothing so test hits timeout + } elseif (isset($bug80901)) { + fputs($s, "\r\n" . str_repeat('*', 4096) . "\r\n"); + } else { + fputs($s, "215 UNIX Type: L8.\r\n"); + } + } elseif ($buf === "TYPE A\r\n") { + $ascii = true; + fputs($s, "200 OK\r\n"); + } elseif ($buf === "AUTH SSL\r\n") { + $ascii = true; + fputs($s, "500 not supported\r\n"); + } elseif ($buf === "TYPE I\r\n") { + $ascii = false; + fputs($s, "200 OK\r\n"); + } elseif ($buf === "QUIT\r\n") { + fputs($s, "221 Bye\r\n"); + break; + } elseif (preg_match("~^PORT (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)\r\n$~", $buf, $m)) { + $host = "{$m[1]}.{$m[2]}.{$m[3]}.{$m[4]}"; + $port = ((int) $m[5] << 8) + (int) $m[6]; + fputs($s, "200 OK.\r\n"); + } elseif (preg_match("~^STOR ([\\w/.-]+)\r\n$~", $buf, $m)) { + fputs($s, "150 File status okay; about to open data connection\r\n"); + + if (empty($pasv)) { + if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + + $data = stream_get_contents($fs); + $orig = file_get_contents(dirname(__FILE__) . '/' . $m[1]); + + if (isset($ascii) && !$ascii && $orig === $data) { + fputs($s, "226 Closing data Connection.\r\n"); + } elseif ((!empty($ascii) || isset($bug39583)) && $data === strtr($orig, ["\r\n" => "\n", "\r" => "\n", "\n" => "\r\n"])) { + fputs($s, "226 Closing data Connection.\r\n"); + } else { + var_dump($data); + var_dump($orig); + fputs($s, "552 Requested file action aborted.\r\n"); + } + fclose($fs); + } else { + $data = file_get_contents('nm2.php'); + $orig = file_get_contents(dirname(__FILE__) . '/' . $m[1]); + if ($orig === $data) { + fputs($s, "226 Closing data Connection.\r\n"); + } else { + var_dump($data); + var_dump($orig); + fputs($s, "552 Requested file action aborted.\r\n"); + } + } + } elseif (preg_match("~^APPE ([\\w/.-]+)\r\n$~", $buf, $m)) { + fputs($s, "150 File status okay; about to open data connection\r\n"); + + if (empty($pasv)) { + if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + + $data = stream_get_contents($fs); + file_put_contents(__DIR__ . '/' . $m[1], $data, FILE_APPEND); + fputs($s, "226 Closing data Connection.\r\n"); + fclose($fs); + } else { + $data = stream_get_contents($fs); + file_put_contents(__DIR__ . '/' . $m[1], $data, FILE_APPEND); + fputs($s, "226 Closing data Connection.\r\n"); + fclose($fs); + } + } elseif (preg_match("~^CWD ([A-Za-z./]+)\r\n$~", $buf, $m)) { + if (isset($bug77680)) { + fputs($s, "550 Directory change to {$m[1]} failed: file does not exist\r\n"); + var_dump($buf); + } else { + change_dir($m[1]); + fputs($s, "250 CWD command successful.\r\n"); + } + } elseif (preg_match("~^NLST(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) { + if (isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) { + fputs($s, "250 {$m[1]}: No such file or directory\r\n"); + continue; + } + + // there are some servers that don't open the ftp-data socket if there's nothing to send + if (isset($bug39458, $m[1]) && $m[1] === 'emptydir') { + fputs($s, "226 Transfer complete.\r\n"); + continue; + } + + if (empty($pasv)) { + fputs($s, "150 File status okay; about to open data connection\r\n"); + if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + } else { + fputs($s, "125 Data connection already open; transfer starting.\r\n"); + $fs = $pasvs; + } + + if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { + exit("SSLv23 handshake failed.\n"); + } + + if (empty($m[1]) || $m[1] !== 'emptydir') { + fputs($fs, "file1\r\nfile1\r\nfile\nb0rk\r\n"); + } + + fputs($s, "226 Closing data Connection.\r\n"); + fclose($fs); + } elseif (preg_match("~^MKD ([A-Za-z./]+)\r\n$~", $buf, $m)) { + if (isset($bug7216)) { + fputs($s, "257 OK.\r\n"); + } else { + if (isset($bug77680)) { + var_dump($buf); + } + fputs($s, "257 \"/path/to/ftproot{$cwd}{$m[1]}\" created.\r\n"); + } + } elseif (preg_match('/^USER /', $buf)) { + user_auth($buf); + } elseif (preg_match('/^MDTM ([\w\h]+)/', $buf, $matches)) { + switch ($matches[1]) { + case 'A': + fputs($s, "213 19980615100045.014\r\n"); + break; + case 'B': + fputs($s, "213 19980615100045.014\r\n"); + break; + case 'C': + fputs($s, "213 19980705132316\r\n"); + break; + case '19990929043300 File6': + fputs($s, "213 19991005213102\r\n"); + break; + default: + fputs($s, "550 No file named \"{$matches[1]}\"\r\n"); + break; + } + } elseif (preg_match('/^RETR ([\/]*[\w\h]+)/', $buf, $matches)) { + if (!empty($pasv)) { + } elseif (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + + switch ($matches[1]) { + case 'pasv': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + // the data connection is handled in another forked process + // called from outside this while loop + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'a story': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + fputs($fs, "For sale: baby shoes, never worn.\r\n"); + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'binary data': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + $transfer_type = $ascii ? 'ASCII' : 'BINARY'; + fputs($fs, $transfer_type . "Foo\0Bar\r\n"); + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'fget': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + $transfer_type = $ascii ? 'ASCII' : 'BINARY'; + fputs($fs, $transfer_type . "FooBar\r\n"); + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'fgetresume': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + $transfer_type = $ascii ? 'ASCII' : 'BINARY'; + fputs($fs, "Bar\r\n"); + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'fget_large': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + $transfer_type = $ascii ? 'ASCII' : 'BINARY'; + if ($GLOBALS['rest_pos'] == '5368709119') { + fputs($fs, 'X'); + } else { + fputs($fs, 'Y'); + } + fputs($s, "226 Closing data Connection.\r\n"); + break; + case 'mediumfile': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + for ($i = 0; $i < 150; $i++) { + fputs($fs, "This is line {$i} of the test data.\n"); + } + fputs($s, "226 Closing data Connection.\r\n"); + break; + case '/bug73457': + fputs($s, "150 File status okay; about to open data connection.\r\n"); + break; + case 'gh10521': + // Just a side channel for getting the received file size. + fputs($s, "425 Can't open data connection (" . $GLOBALS['rest_pos'] . ").\r\n"); + break; + default: + fputs($s, "550 {$matches[1]}: No such file or directory \r\n"); + break; + } + if (isset($fs)) { + fclose($fs); + } + } elseif (preg_match('/^PASV/', $buf, $matches)) { + $pasv = true; + $host = '127.0.0.1'; + $i = 0; + + if (empty($bug73457)) { + if (!empty($ssl)) { + $soc = stream_socket_server('tcp://127.0.0.1:0', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context); + } else { + $soc = stream_socket_server('tcp://127.0.0.1:0'); + } + if (!$soc) { + echo "{$errstr} ({$errno})\n"; + exit("could not bind passive port\n"); + } + + $soc_name = stream_socket_get_name($soc, false); + $pasv_port = (int) substr($soc_name, strrpos($soc_name, ':') + 1); + } else { + $pasv_port = 1234; + } + + $p2 = $pasv_port % ((int) 1 << 8); + $p1 = ($pasv_port - $p2) / ((int) 1 << 8); + fputs($s, "227 Entering Passive Mode. (127,0,0,1,{$p1},{$p2})\r\n"); + + if (empty($bug73457)) { + $pasvs = stream_socket_accept($soc, 10); + } + } elseif (preg_match('/^EPSV/', $buf, $matches)) { + fputs($s, "550 Extended passsive mode not supported.\r\n"); + } elseif (preg_match('/^SITE EXEC/', $buf, $matches)) { + fputs($s, "200 OK\r\n"); + } elseif (preg_match('/^RMD/', $buf, $matches)) { + fputs($s, "250 OK\r\n"); + } elseif (preg_match('/^SITE CHMOD/', $buf, $matches)) { + fputs($s, "200 OK\r\n"); + } elseif (preg_match('/^DELE ([\w\h]+)/', $buf, $matches)) { + if (isset($matches[1]) && in_array($matches[1], ['file1', "file\nb0rk"])) { + fputs($s, "250 Delete successful\r\n"); + } else { + fputs($s, "550 No such file or directory\r\n"); + } + } elseif (preg_match('/^ALLO (\d+)/', $buf, $matches)) { + fputs($s, '200 ' . $matches[1] . " bytes allocated\r\n"); + } elseif (preg_match('/^LIST www\//', $buf, $matches)) { + if (empty($pasv)) { + fputs($s, "150 File status okay; about to open data connection\r\n"); + if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + } else { + fputs($s, "125 Data connection already open; transfer starting.\r\n"); + $fs = $pasvs; + } + + if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { + exit("SSLv23 handshake failed.\n"); + } + + fputs($fs, "file1\r\nfile1\r\nfile\nb0rk\r\n"); + fputs($s, "226 Closing data Connection.\r\n"); + fclose($fs); + + fputs($s, "226 Transfer complete\r\n"); + } elseif (preg_match('/^LIST no_exists\//', $buf, $matches)) { + fputs($s, "425 Error establishing connection\r\n"); + } elseif (preg_match('/^REST (\d+)/', $buf, $matches)) { + $GLOBALS['rest_pos'] = $matches[1]; + fputs($s, "350 OK\r\n"); + } elseif (preg_match('/^SIZE largefile/', $buf)) { + fputs($s, "213 5368709120\r\n"); + } elseif (preg_match('/^RNFR existing_file/', $buf, $matches)) { + fputs($s, "350 File or directory exists, ready for destination name\r\n"); + } elseif (preg_match('/^RNFR nonexisting_file/', $buf, $matches)) { + fputs($s, "550 No such file or directory\r\n"); + } elseif (preg_match('/^RNTO nonexisting_file/', $buf, $matches)) { + fputs($s, "250 Rename successful\r\n"); + } elseif (preg_match('/^MLSD no_exists\//', $buf, $matches)) { + fputs($s, "425 Error establishing connection\r\n"); + } elseif (preg_match("~^MLSD(?: ([A-Za-z./]+))?\r\n$~", $buf, $m)) { + if (isset($m[1]) && (($m[1] === 'bogusdir') || ($m[1] === '/bogusdir'))) { + fputs($s, "250 {$m[1]}: No such file or directory\r\n"); + continue; + } + + // there are some servers that don't open the ftp-data socket if there's nothing to send + if (isset($bug39458, $m[1]) && $m[1] === 'emptydir') { + fputs($s, "226 Transfer complete.\r\n"); + continue; + } + + if (empty($pasv)) { + fputs($s, "150 File status okay; about to open data connection\r\n"); + if (!$fs = stream_socket_client("tcp://{$host}:{$port}")) { + fputs($s, "425 Can't open data connection\r\n"); + continue; + } + } else { + fputs($s, "125 Data connection already open; transfer starting.\r\n"); + $fs = $pasvs; + } + + if ((!empty($ssl)) && (!stream_socket_enable_crypto($pasvs, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER))) { + exit("SSLv23 handshake failed.\n"); + } + + if (empty($m[1]) || $m[1] !== 'emptydir') { + fputs($fs, "modify=20170127230002;perm=flcdmpe;type=cdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; .\r\n"); + fputs($fs, "modify=20170127230002;perm=flcdmpe;type=pdir;unique=811U4340002;UNIX.group=33;UNIX.mode=0755;UNIX.owner=33; ..\r\n"); + fputs($fs, "modify=20170126121225;perm=adfrw;size=4729;type=file;unique=811U4340CB9;UNIX.group=33;UNIX.mode=0644;UNIX.owner=33; foobar\r\n"); + fputs($fs, "fact=val=ue;empty=; path;name\r\n"); + fputs($fs, "no_space\r\n"); + fputs($fs, "no_semi pathname\r\n"); + fputs($fs, "no_eq; pathname\r\n"); + } + + fputs($s, "226 Closing data Connection.\r\n"); + fclose($fs); + } elseif (preg_match('/^SIZE \/bug73457/', $buf)) { + fputs($s, "213 10\r\n"); + } elseif (preg_match('/^SITE/', $buf)) { + fputs($s, "500 Syntax error, command unrecognized.\r\n"); + } else { + dump_and_exit($buf); + } + } + exit; +} + +fclose($socket); + +return function () use ($pid, $port) { + Co\defer(function () use ($pid, $port) { + $out = Co\System::waitpid($pid); + assert($out['code'] == 0); + }); + return $port; +}; diff --git a/tests/swoole_function/substr_unserialize.phpt b/tests/swoole_function/substr_unserialize.phpt index 7ee9d98483..efdd032477 100644 --- a/tests/swoole_function/substr_unserialize.phpt +++ b/tests/swoole_function/substr_unserialize.phpt @@ -12,11 +12,11 @@ $a['int'] = rand(1, 999999); $a['list'] = ['a,', 'b', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']; $val = serialize($a); -$str = pack('N', strlen($val)).$val."\r\n"; +$str = pack('N', strlen($val)) . $val . "\r\n"; $l = strlen($str) - 6; Assert::eq(swoole_substr_unserialize($str, 4, $l), $a); -Assert::eq(swoole_substr_unserialize($str, 4), $a); +Assert::eq(@swoole_substr_unserialize($str, 4), $a); Assert::eq(@swoole_substr_unserialize($str, 0), false); Assert::eq(@swoole_substr_unserialize($str, 6), false); Assert::eq(@swoole_substr_unserialize($str, 4, $l - 4), false); diff --git a/tests/swoole_function/swoole_set_process_name.phpt b/tests/swoole_function/swoole_set_process_name.phpt index 1f2ced0b10..14b72461f5 100644 --- a/tests/swoole_function/swoole_set_process_name.phpt +++ b/tests/swoole_function/swoole_set_process_name.phpt @@ -12,7 +12,7 @@ require __DIR__ . '/../include/bootstrap.php'; $name = "SWOOLE_PROCESS_TEST_" . rand(1, 100); swoole_set_process_name($name); -$count = (int)trim(`ps aux|grep $name|grep -v grep|wc -l`); +$count = (int) trim(shell_exec("ps aux|grep $name|grep -v grep|wc -l")); Assert::same($count, 1); echo "SUCCESS"; diff --git a/tests/swoole_global/channel_construct_check.phpt b/tests/swoole_global/channel_construct_check.phpt index c33fd6461d..a796002c3a 100644 --- a/tests/swoole_global/channel_construct_check.phpt +++ b/tests/swoole_global/channel_construct_check.phpt @@ -5,16 +5,18 @@ swoole_global: socket construct check --FILE-- push('123'); + +$pm = ProcessManager::exec(function () { + go(function () { + $chan = new class () extends Co\Channel { + public function __construct($size = 1) + { + // parent::__construct($size); // without parent call + } + }; + $chan->push('123'); + }); }); +Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> ---EXPECTF-- -Fatal error: Swoole\Coroutine\Channel::push(): you must call Channel constructor first in %s on line %d +--EXPECT-- diff --git a/tests/swoole_global/closed_stdout.phpt b/tests/swoole_global/closed_stdout.phpt new file mode 100644 index 0000000000..10fb1bb245 --- /dev/null +++ b/tests/swoole_global/closed_stdout.phpt @@ -0,0 +1,9 @@ +--TEST-- +swoole_global: handle closed STDOUT/STDERR without exception +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_global/serialize_deny.phpt b/tests/swoole_global/serialize_deny.phpt index 82a24e8ec4..6d9154d7f3 100644 --- a/tests/swoole_global/serialize_deny.phpt +++ b/tests/swoole_global/serialize_deny.phpt @@ -34,22 +34,6 @@ go(function () { } catch (\Exception $exception) { Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); } - try { - $hcc = new \Swoole\Coroutine\Mysql(); - serialize($hcc); - Assert::true(false, 'never here'); - } catch (\Exception $exception) { - Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); - } - if (HAS_ASYNC_REDIS) { - try { - $hcc = new \Swoole\Coroutine\Redis(); - serialize($hcc); - Assert::true(false, 'never here'); - } catch (\Exception $exception) { - Assert::same(strpos($exception->getMessage(), 'Serialization'), 0); - } - } try { $hcc = new \Swoole\Table(1); serialize($hcc); diff --git a/tests/swoole_global/socket_construct_check.phpt b/tests/swoole_global/socket_construct_check.phpt index b9c97b54c4..1a18346ffe 100644 --- a/tests/swoole_global/socket_construct_check.phpt +++ b/tests/swoole_global/socket_construct_check.phpt @@ -5,16 +5,17 @@ swoole_global: socket construct check --FILE-- connect('127.0.0.1', 12345); +$pm = ProcessManager::exec(function () { + go(function () { + $socket = new class (1, 2, 3) extends Co\Socket { + public function __construct($domain, $type, $protocol) + { + // parent::__construct($domain, $type, $protocol); // without parent call + } + }; + $socket->connect('127.0.0.1', 12345); + }); }); +Assert::contains($pm->getChildOutput(), "must call constructor first"); ?> --EXPECTF-- -Fatal error: Swoole\Coroutine\Socket::connect(): you must call Socket constructor first in %s on line %d diff --git a/tests/swoole_http2_client_coro/bug_5127.phpt b/tests/swoole_http2_client_coro/bug_5127.phpt new file mode 100644 index 0000000000..6636433d60 --- /dev/null +++ b/tests/swoole_http2_client_coro/bug_5127.phpt @@ -0,0 +1,14 @@ +--TEST-- +swoole_http2_client_coro: Github #5127 When use swoole in php 8.2,Swoole\Http2\Request may throw ErrorException:Creation of dynamic property $usePipelineRead +--SKIPIF-- + +--FILE-- +usePipelineRead = true; +echo 'DONE'; +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http2_client_coro/connect_twice.phpt b/tests/swoole_http2_client_coro/connect_twice.phpt index 798ca5f724..106b6d0c51 100644 --- a/tests/swoole_http2_client_coro/connect_twice.phpt +++ b/tests/swoole_http2_client_coro/connect_twice.phpt @@ -6,29 +6,35 @@ swoole_http2_client_coro: connect twice connect(); - $req = new \Swoole\Http2\Request(); + $req = new Request(); + $uuid = uniqid(); $req->method = 'GET'; - $req->path = '/io?io=' . str_repeat('xxx', 1000); + $req->path = '/base64/' . base64_encode($uuid); $client->send($req); $chan->push(true); $resp = $client->recv(); + Assert::notNull($resp); Assert::eq($resp->statusCode, 200); - Assert::contains($resp->data, '知乎'); + Assert::eq($resp->data, $uuid); $chan->pop(); }); go(function () use ($client, $chan) { Assert::eq($client->connect(), false); - $req = new \Swoole\Http2\Request(); + $uuid = uniqid(); + $req = new Request(); $req->method = 'GET'; - $req->path = '/io?io=xxx'; + $req->path = '/base64/' . base64_encode($uuid); $client->send($req); $chan->push(true); Assert::eq($client->recv(), false); diff --git a/tests/swoole_http2_client_coro/max-frame-size.phpt b/tests/swoole_http2_client_coro/max-frame-size.phpt new file mode 100644 index 0000000000..951469cfea --- /dev/null +++ b/tests/swoole_http2_client_coro/max-frame-size.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_http2_client_coro: max frame size +--SKIPIF-- +&1"), 'nghttp2') === false) { + skip('no nghttpd'); +} +?> +--FILE-- +parentFunc = function ($pid) use ($pm) { + Co\run(function () use ($pm) { + co::sleep(0.1); + $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); + $cli->connect(); + + $req = new Swoole\Http2\Request; + $req->path = "/test.jpg"; + Assert::greaterThanEq($cli->send($req), 1); + $resp = $cli->recv(); + Assert::contains($resp->headers['server'], 'nghttpd'); + Assert::eq($resp->data, file_get_contents(TEST_IMAGE)); + + shell_exec("ps -A | grep nghttpd | awk '{print $1}' | xargs kill -9 > /dev/null 2>&1"); + echo "DONE\n"; + $pm->kill(); + }); +}; +$pm->childFunc = function () use ($pm) { + $root = ROOT_DIR . '/examples'; + $pm->wakeup(); + shell_exec("nghttpd -v -d {$root}/ -a 0.0.0.0 {$pm->getFreePort()} --no-tls&"); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http2_client_coro/multi.phpt b/tests/swoole_http2_client_coro/multi.phpt index 6f35cfaeb5..7d823977d7 100644 --- a/tests/swoole_http2_client_coro/multi.phpt +++ b/tests/swoole_http2_client_coro/multi.phpt @@ -8,7 +8,7 @@ skip_if_offline(); set([ 'timeout' => -1, @@ -18,14 +18,14 @@ go(function () { Assert::true($cli->connected); $req = new Swoole\Http2\Request; - $req->path = '/terms/privacy'; + $req->path = '/'; $req->headers = [ 'Host' => $domain, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-encoding' => 'gzip', ]; - /**@var $response swoole_http2_response */ + /**@var $response swoole\http2\response */ $i = 4; while ($i--) { Assert::assert($cli->send($req)); @@ -34,11 +34,12 @@ go(function () { $responses_headers_count_map = []; $i = 0; while ($cli->connected) { - $response = $cli->recv(0.1); // it's for the test, you should make timeout bigger + // it's for the test, you should make timeout bigger + $response = $cli->recv(0.1); if ($response) { echo "$response->statusCode\n"; $responses_headers_count_map[] = count($response->headers); - Assert::contains($response->data, 'Cookie'); + Assert::contains($response->data, 'nghttp2.org'); $stream_map[] = $response->streamId; if (++$i === 4) { break; diff --git a/tests/swoole_http2_client_coro/no-gzip.phpt b/tests/swoole_http2_client_coro/no-gzip.phpt index ec7a583f22..ce5e93ecdc 100644 --- a/tests/swoole_http2_client_coro/no-gzip.phpt +++ b/tests/swoole_http2_client_coro/no-gzip.phpt @@ -3,7 +3,7 @@ swoole_http2_client_coro: http2 without gzip and recv big data (window-update) --SKIPIF-- --FILE-- diff --git a/tests/swoole_http2_client_coro/post.phpt b/tests/swoole_http2_client_coro/post.phpt index 19dc7cc61b..d789b3d4af 100644 --- a/tests/swoole_http2_client_coro/post.phpt +++ b/tests/swoole_http2_client_coro/post.phpt @@ -29,7 +29,7 @@ go(function () { $req->data = '{"type":"up"}'; $cli->send($req); $response = $cli->recv(); - Assert::true(in_array(json_decode($response->data)->error->code, [602, 10002], true)); + Assert::true(in_array(json_decode($response->data)->error->code, [602, 10002, 100], true)); }); ?> --EXPECT-- diff --git a/tests/swoole_http2_server/413.phpt b/tests/swoole_http2_server/413.phpt new file mode 100644 index 0000000000..6861247b7e --- /dev/null +++ b/tests/swoole_http2_server/413.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_http2_server: 413 +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $file = tempnam('/tmp', 'swoole_http2_'); + file_put_contents($file, str_repeat('A', 2 * 1024 * 1024)); + Assert::assert(empty($res = `nghttp -d {$file} https://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null`)); + $pm->kill(); + unlink($file); +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'package_max_length' => 1024 * 1024, + 'open_http2_protocol' => true, + 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/server.key' + ]); + $http->on("WorkerStart", function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $response->end($request->getContent()); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http2_server/big_data.phpt b/tests/swoole_http2_server/big_data.phpt index e0b1a49890..9270dc54a4 100644 --- a/tests/swoole_http2_server/big_data.phpt +++ b/tests/swoole_http2_server/big_data.phpt @@ -12,8 +12,8 @@ $pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => 10, - 'ssl_cert_file' => SSL_FILE_DIR2 . '/client-cert.pem', - 'ssl_key_file' => SSL_FILE_DIR2 . '/client-key.pem' + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); Assert::assert($cli->connect()); @@ -45,10 +45,10 @@ $pm->childFunc = function () use ($pm) { 'open_http2_protocol' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' - ] + (IS_IN_TRAVIS ? [] : [ + ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, - 'ssl_client_cert_file' => SSL_FILE_DIR2 . '/ca-cert.pem' + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) use ($pm) { diff --git a/tests/swoole_http2_server/compression.phpt b/tests/swoole_http2_server/compression.phpt index 2a8b6a2d66..744e4163fb 100644 --- a/tests/swoole_http2_server/compression.phpt +++ b/tests/swoole_http2_server/compression.phpt @@ -12,8 +12,8 @@ $pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => -1, - 'ssl_cert_file' => SSL_FILE_DIR2 . '/client-cert.pem', - 'ssl_key_file' => SSL_FILE_DIR2 . '/client-key.pem' + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); $cli->connect(); @@ -44,10 +44,10 @@ $pm->childFunc = function () use ($pm) { 'http_compression' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' - ] + (IS_IN_TRAVIS ? [] : [ + ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, - 'ssl_client_cert_file' => SSL_FILE_DIR2 . '/ca-cert.pem' + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) { diff --git a/tests/swoole_http2_server/getMethod.phpt b/tests/swoole_http2_server/getMethod.phpt new file mode 100644 index 0000000000..7a20f89d2e --- /dev/null +++ b/tests/swoole_http2_server/getMethod.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_http2_server: getMethod +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + go(function () use ($pm) { + $cli = new Swoole\Coroutine\Http2\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($cli->connect()); + $req = new Swoole\Http2\Request(); + $req->method = 'POST'; + $req->path = '/api'; + $req->headers = [ + 'user-agent' => 'Chrome/49.0.2587.3', + 'accept' => 'text/html,application/xhtml+xml,application/xml', + 'accept-encoding' => 'gzip' + ]; + $req->data = '{"type":"up"}'; + $cli->send($req); + $response = $cli->recv(); + $json = json_decode($response->data); + Assert::same($json->request_method, 'POST'); + Assert::same($json->getMethod, 'POST'); + $pm->kill(); + }); + Swoole\Event::wait(); +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('::', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP6); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_http2_protocol' => true + ]); + $http->on('workerStart', function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) { + $request_method = $request->server['request_method']; + $getMethod = $request->getMethod(); + $response->end(json_encode(compact('request_method', 'getMethod'), JSON_PRETTY_PRINT) . "\n"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http2_server/http2_headers.phpt b/tests/swoole_http2_server/http2_headers.phpt index 4f7ff14e5c..d2ba2eae6a 100644 --- a/tests/swoole_http2_server/http2_headers.phpt +++ b/tests/swoole_http2_server/http2_headers.phpt @@ -1,13 +1,18 @@ --TEST-- swoole_http2_server: array headers --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm) { - $output = `curl --http2-prior-knowledge --silent -I http://127.0.0.1:{$pm->getFreePort()}`; + $output = shell_exec("curl --http2-prior-knowledge --silent -I http://127.0.0.1:{$pm->getFreePort()}"); Assert::contains($output, 'HTTP/2 200'); Assert::contains($output, 'test-value: a'); Assert::contains($output, 'test-value: d5678'); @@ -17,16 +22,16 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm) { - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ 'worker_num' => 1, 'log_file' => '/dev/null', - 'open_http2_protocol' => true + 'open_http2_protocol' => true, ]); $http->on('workerStart', function ($serv, $wid) use ($pm) { $pm->wakeup(); }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $http->on('request', function (Request $request, Response $response) { $response->header('test-value', [ "a\r\n", 'd5678', @@ -35,7 +40,7 @@ $pm->childFunc = function () use ($pm) { 5678, 3.1415926, ]); - $response->end("

    Hello Swoole.

    "); + $response->end('

    Hello Swoole.

    '); }); $http->start(); }; diff --git a/tests/swoole_http2_server/issue_4365.phpt b/tests/swoole_http2_server/issue_4365.phpt index 34f2581283..81740997f0 100644 --- a/tests/swoole_http2_server/issue_4365.phpt +++ b/tests/swoole_http2_server/issue_4365.phpt @@ -3,23 +3,27 @@ swoole_http2_server: github issue#4365 --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { - if (Assert::assert(!empty($res = `curl -s --http2-prior-knowledge http://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null`))) { + if (Assert::assert(!empty($res = shell_exec("curl -s --http2-prior-knowledge http://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null")))) { Assert::length($res, N); echo "DONE\n"; } $pm->kill(); }; $pm->childFunc = function () use ($pm) { - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $http = new Server('0.0.0.0', $pm->getFreePort(), SERVER_MODE_RANDOM); $http->set([ 'open_http2_protocol' => true, 'enable_reuse_port' => true, @@ -28,7 +32,7 @@ $pm->childFunc = function () use ($pm) { 'log_file' => TEST_LOG_FILE, ]); $http->on('request', function ($request, $response) { - $response->end(str_repeat('x', N)); + $response->end(str_repeat('x', N - 4) . "\r\n\r\n"); }); $http->start(); }; diff --git a/tests/swoole_http2_server/max_concurrency.phpt b/tests/swoole_http2_server/max_concurrency.phpt index a8be63affd..388bd115c1 100644 --- a/tests/swoole_http2_server/max_concurrency.phpt +++ b/tests/swoole_http2_server/max_concurrency.phpt @@ -20,7 +20,7 @@ $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $n = SERVER_MAX_CONCURRENCY; - // 200 + // 200, low concurrency $cid_list = []; while ($n--) { $cid_list[] = go(function () use ($pm) { @@ -36,7 +36,7 @@ $pm->parentFunc = function ($pid) use ($pm) { System::sleep(0.005); - // 403, high concurrency + // 503, high concurrency $n = SERVER_MAX_CONCURRENCY; while ($n--) { $cid_list[] = go(function () use ($pm) { diff --git a/tests/swoole_http2_server/nghttp2_big_data.phpt b/tests/swoole_http2_server/nghttp2_big_data.phpt index b24cb2a0ee..5563c9ea0a 100644 --- a/tests/swoole_http2_server/nghttp2_big_data.phpt +++ b/tests/swoole_http2_server/nghttp2_big_data.phpt @@ -3,16 +3,14 @@ swoole_http2_server: nghttp2 big data with ssl --SKIPIF-- &1`, 'nghttp2') === false) { - skip('no nghttp'); -} +skip_if_no_nghttp(); ?> --FILE-- parentFunc = function ($pid) use ($pm) { - $file = __DIR__ . '/../../benchmark/post.big.data'; + $file = TEST_IMAGE; if (Assert::assert(!empty($res = `nghttp -d {$file} https://127.0.0.1:{$pm->getFreePort()}/ > /dev/stdout 2>/dev/null`))) { Assert::same(md5($res), md5_file($file)); } @@ -31,7 +29,7 @@ $pm->childFunc = function () use ($pm) { $pm->wakeup(); }); $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { - $response->end($request->rawcontent()); + $response->end($request->getContent()); }); $http->start(); }; diff --git a/tests/swoole_http2_server/no_compression.phpt b/tests/swoole_http2_server/no_compression.phpt index d46b0b1b3a..bca15f142c 100644 --- a/tests/swoole_http2_server/no_compression.phpt +++ b/tests/swoole_http2_server/no_compression.phpt @@ -12,8 +12,8 @@ $pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); $cli->set([ 'timeout' => -1, - 'ssl_cert_file' => SSL_FILE_DIR2 . '/client-cert.pem', - 'ssl_key_file' => SSL_FILE_DIR2 . '/client-key.pem' + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' ]); $cli->connect(); @@ -42,10 +42,10 @@ $pm->childFunc = function () use ($pm) { 'http_compression' => false, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key' - ] + (IS_IN_TRAVIS ? [] : [ + ] + (IS_IN_CI ? [] : [ 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, - 'ssl_client_cert_file' => SSL_FILE_DIR2 . '/ca-cert.pem' + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' ]) ); $http->on("WorkerStart", function ($serv, $wid) { diff --git a/tests/swoole_http2_server/server_addr.phpt b/tests/swoole_http2_server/server_addr.phpt new file mode 100644 index 0000000000..55245c75bf --- /dev/null +++ b/tests/swoole_http2_server/server_addr.phpt @@ -0,0 +1,74 @@ +--TEST-- +swoole_http2_server: add server addr for http2 server +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $ips) { + go(function () use ($pm, $ips) { + $domain = $ips[1]; + $cli = new Swoole\Coroutine\Http2\Client($domain, $pm->getFreePort(), true); + $cli->set([ + 'timeout' => 10, + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem' + ]); + Assert::assert($cli->connect()); + + $req = new Swoole\Http2\Request; + $req->method = 'POST'; + $req->path = '/'; + $req->headers = [ + 'Host' => $domain, + "User-Agent" => 'Chrome/49.0.2587.3', + 'Accept' => 'text/html,application/xhtml+xml,application/xml', + 'Accept-encoding' => 'gzip' + ]; + for ($n = MAX_REQUESTS; $n--;) { + $req->data = get_safe_random(65535 + mt_rand(0, 65535)); + Assert::assert($cli->send($req)); + $res = $cli->recv(); + Assert::same($res->statusCode, 200); + Assert::same(md5($req->data), md5($res->data)); + } + $pm->kill(); + }); + Swoole\Event::wait(); +}; +$pm->childFunc = function () use ($pm, $ips) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/server.key' + ] + (IS_IN_CI ? [] : [ + 'ssl_verify_peer' => true, + 'ssl_allow_self_signed' => true, + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem' + ]) + ); + $http->on("WorkerStart", function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($ips) { + $server = $request->server; + Assert::eq($server['server_addr'], $ips[1]); + Assert::eq($server['remote_addr'], $ips[1]); + Assert::true($server['server_port'] != $server['remote_port']); + $response->end($request->rawcontent()); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http2_server/streaming.phpt b/tests/swoole_http2_server/streaming.phpt new file mode 100644 index 0000000000..0475e3de4c --- /dev/null +++ b/tests/swoole_http2_server/streaming.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_http2_server: streaming +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $proc = ChildProcess::exec("nghttp http://127.0.0.1:{$pm->getFreePort()}/"); + $out = ''; + while($line = $proc->read()) { + $out .= $line; + if (str_contains($line, 'hello world, end')) { + break; + } + } + echo $out; + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + ]); + $http->on('WorkerStart', function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + $http->on('Request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $n = 5; + while ($n--) { + $response->write("hello world, #$n\n"); + Co\System::sleep(0.1); + } + $response->end("hello world, end\n"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +hello world, #4 +hello world, #3 +hello world, #2 +hello world, #1 +hello world, #0 +hello world, end diff --git a/tests/swoole_http2_server/streaming_2.phpt b/tests/swoole_http2_server/streaming_2.phpt new file mode 100644 index 0000000000..66e6e56862 --- /dev/null +++ b/tests/swoole_http2_server/streaming_2.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_http2_server: streaming 2 +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $proc = ChildProcess::exec("nghttp http://127.0.0.1:{$pm->getFreePort()}/"); + $out = ''; + while($line = $proc->read()) { + $out .= $line; + if (str_contains($line, 'hello world, #0')) { + break; + } + } + echo $out; + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + ]); + $http->on('WorkerStart', function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + $http->on('Request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $n = 5; + while ($n--) { + $response->write("hello world, #$n\n"); + Co\System::sleep(0.1); + } + $response->end(); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +hello world, #4 +hello world, #3 +hello world, #2 +hello world, #1 +hello world, #0 diff --git a/tests/swoole_http_client_coro/204.phpt b/tests/swoole_http_client_coro/204.phpt index 6625fad097..253da87b68 100644 --- a/tests/swoole_http_client_coro/204.phpt +++ b/tests/swoole_http_client_coro/204.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: http 204 no content --SKIPIF-- --FILE-- diff --git a/tests/swoole_http_client_coro/bind_address.phpt b/tests/swoole_http_client_coro/bind_address.phpt index 02dd382628..9357435ea3 100644 --- a/tests/swoole_http_client_coro/bind_address.phpt +++ b/tests/swoole_http_client_coro/bind_address.phpt @@ -100,7 +100,7 @@ $pm->parentFunc = function () use ($pm) { }); $wg->wait(); - + $client = new Client('127.0.0.1', $pm->getFreePort()); $client->get('/stop?hello=1'); echo $client->body . PHP_EOL; @@ -117,7 +117,7 @@ $pm->childFunc = function () use ($pm) { $server->handle('/invalidaddress', function (Request $request, Response $response) { Assert::eq($request->post['bind_address'], '11111'); Assert::eq($request->server['remote_addr'], '127.0.0.1'); - Assert::eq($request->server['remote_port'], $request->post['bind_port']); + Assert::notEq($request->server['remote_port'], $request->post['bind_port']); }); $server->handle('/invalidport', function (Request $request, Response $response) { Assert::eq($request->post['bind_port'], '-1'); diff --git a/tests/swoole_http_client_coro/bug_2661.phpt b/tests/swoole_http_client_coro/bug_2661.phpt index 0ceb61e082..6e22a7929f 100644 --- a/tests/swoole_http_client_coro/bug_2661.phpt +++ b/tests/swoole_http_client_coro/bug_2661.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: #2611 bound error with dns resolve and cross close --SKIPIF-- --FILE-- diff --git a/tests/swoole_http_client_coro/connection_close.phpt b/tests/swoole_http_client_coro/connection_close.phpt new file mode 100644 index 0000000000..917ae15b29 --- /dev/null +++ b/tests/swoole_http_client_coro/connection_close.phpt @@ -0,0 +1,77 @@ +--TEST-- +swoole_http_client_coro: connection close +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + run(function () use ($pm) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + var_dump($client->get('/close')); + var_dump($client->getHeaders()); + + var_dump($client->get('/keep_alive')); + var_dump($client->getHeaders()); + $client->close(); + + var_dump($client->errMsg); + echo "DONE\n"; + $pm->kill(); + }); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort()); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('request', function($request, $response) { + if ($request->server['request_uri'] == '/close') { + $response->header('connection', 'close'); + } + $response->end(); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +bool(true) +array(5) { + ["connection"]=> + string(5) "close" + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) "%s" + ["content-type"]=> + string(9) "text/html" + ["content-length"]=> + string(1) "0" +} +bool(true) +array(5) { + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) "%s" + ["connection"]=> + string(10) "keep-alive" + ["content-type"]=> + string(9) "text/html" + ["content-length"]=> + string(1) "0" +} +string(0) "" +DONE diff --git a/tests/swoole_http_client_coro/cookies_set_bug.phpt b/tests/swoole_http_client_coro/cookies_set_bug.phpt index 34d88ea54f..f8f9700d37 100644 --- a/tests/swoole_http_client_coro/cookies_set_bug.phpt +++ b/tests/swoole_http_client_coro/cookies_set_bug.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: cookies set bug --SKIPIF-- --FILE-- diff --git a/tests/swoole_http_client_coro/defer.phpt b/tests/swoole_http_client_coro/defer.phpt index 6b4adeca13..bf8c30e42b 100644 --- a/tests/swoole_http_client_coro/defer.phpt +++ b/tests/swoole_http_client_coro/defer.phpt @@ -29,7 +29,7 @@ $pm->parentFunc = function () use ($pm) { $retry_time = microtime(true) - $retry_time; $pm->kill(); - usleep(1000); + $pm->wait(); // failed when connect $failed_time = microtime(true); @@ -66,6 +66,7 @@ $pm->childFunc = function () use ($pm) { } }); $server->start(); + $pm->wakeup(); }; $pm->childFirst(); $pm->run(); diff --git a/tests/swoole_http_client_coro/disable_keep_alive.phpt b/tests/swoole_http_client_coro/disable_keep_alive.phpt index 12c3083163..6a809d9b98 100644 --- a/tests/swoole_http_client_coro/disable_keep_alive.phpt +++ b/tests/swoole_http_client_coro/disable_keep_alive.phpt @@ -7,8 +7,8 @@ skip_if_offline(); --FILE-- set([ 'timeout' => 10, @@ -18,14 +18,14 @@ go(function () { $cli->get('/'); Assert::same($cli->statusCode, 200); - Assert::true($cli->get('/contract.shtml')); + Assert::true($cli->get('/ch/tech/')); Assert::same($cli->statusCode, 200); // failed clear $cli->set([ 'timeout' => 0.001 ]); - Assert::false($cli->get('/contract.shtml')); + Assert::false($cli->get('/ch/tech/')); Assert::assert(empty($cli->headers)); Assert::assert(empty($cli->body)); }); diff --git a/tests/swoole_http_client_coro/download.phpt b/tests/swoole_http_client_coro/download.phpt index 7a1572fd3e..a3abee1e74 100644 --- a/tests/swoole_http_client_coro/download.phpt +++ b/tests/swoole_http_client_coro/download.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: download file and download offset --SKIPIF-- --FILE-- set(['timeout' => 5]); $client->download('/', $fileName); + Assert::true(file_get_contents(TEST_IMAGE) == file_get_contents($fileName)); } $pm = new SwooleTest\ProcessManager; diff --git a/tests/swoole_http_client_coro/duplicate_header.phpt b/tests/swoole_http_client_coro/duplicate_header.phpt new file mode 100644 index 0000000000..2a99b5ce4b --- /dev/null +++ b/tests/swoole_http_client_coro/duplicate_header.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_http_client_coro: duplicate header +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $uuid) { + Co\run(function () use ($pm, $uuid) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($client->get('/')); + Assert::eq($client->headers['values-1'], ['hello', 'swoole', $uuid]); + Assert::eq($client->headers['values-2'], ['hello', $uuid]); + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $uuid) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->on("WorkerStart", function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on("request", function ($request, Swoole\Http\Response $response) use ($uuid) { + $response->header('values-1', ['hello', 'swoole', $uuid]); + $response->header('values-2', ['hello', $uuid]); + $response->end('OK'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_client_coro/error_handler.phpt b/tests/swoole_http_client_coro/error_handler.phpt index 66be953b3c..22145d22ce 100644 --- a/tests/swoole_http_client_coro/error_handler.phpt +++ b/tests/swoole_http_client_coro/error_handler.phpt @@ -6,23 +6,33 @@ swoole_http_client_coro: error handler initRandomData(MAX_CONCURRENCY_MID); -$pm->parentFunc = function () use ($pm) { - run(function () use ($pm) { + +$rdata = []; +for ($c = MAX_CONCURRENCY_MID; $c--;) { + $rdata[] = $pm->getRandomData(); +} + +$pm->parentFunc = function () use ($pm, $rdata) { + run(function () use ($pm, $rdata) { $cli_map = []; + $data_map = []; for ($c = MAX_CONCURRENCY_MID; $c--;) { - $cli_map[] = $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli_map[$c] = $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->setDefer(true); - $cli->get('/'); + $cli->get('/?c=' . $c); + $data_map[$cli->socket->fd] = $rdata[$c]; } foreach ($cli_map as $cli) { Assert::assert($cli->recv()); - Assert::same($cli->body, $pm->getRandomData()); + Assert::same($cli->body, $data_map[$cli->socket->fd]); } $pm->kill(); @@ -40,7 +50,7 @@ $pm->parentFunc = function () use ($pm) { }); echo "OK\n"; }; -$pm->childFunc = function () use ($pm) { +$pm->childFunc = function () use ($pm, $rdata) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, @@ -49,11 +59,11 @@ $pm->childFunc = function () use ($pm) { $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); - $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $server) { + $server->on('request', function (Request $request, Response $response) use ($pm, $server, $rdata) { static $i = 0; $i++; if ($i <= MAX_CONCURRENCY_MID) { - $response->end($pm->getRandomData()); + $response->end($rdata[$request->get['c']]); } else { $server->close($request->fd); } diff --git a/tests/swoole_http_client_coro/get.phpt b/tests/swoole_http_client_coro/get.phpt index 3d048bedca..42792caeaf 100644 --- a/tests/swoole_http_client_coro/get.phpt +++ b/tests/swoole_http_client_coro/get.phpt @@ -1,48 +1,58 @@ --TEST-- swoole_http_client_coro: http client --SKIPIF-- - --FILE-- parentFunc = function ($pid) use ($pm) { - go(function () use ($pm) { + Co\run(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - $pm->kill(); }); + $pm->kill(); }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $http->set([ - 'log_file' => '/dev/null' + 'log_file' => '/dev/null', ]); - $http->on('WorkerStart', function (Swoole\Server $serv) { - /** + $http->on('WorkerStart', function (Server $serv) { + /* * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { - $cli = new Swoole\Coroutine\Http\Client('www.qq.com', 443, true); + $http->on('request', function (Request $request, Response $response) { + $domain = TEST_DOMAIN_3; + $cli = new Client($domain, 443, true); $cli->set(['timeout' => 10]); $cli->setHeaders([ - 'Host' => 'www.qq.com', - 'User-Agent' => 'Chrome/49.0.2587.3', + 'Host' => $domain, + 'User-Agent' => TEST_USER_AGENT, 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); - $ret = ($cli->get('/')); + $ret = $cli->get('/'); + Assert::assert($cli->socket instanceof Socket); if (!$ret) { - $response->end("ERROR\n"); + $response->end('ERROR:' . $cli->errCode . "\n"); return; - } else { - $response->end("OK\n"); - $cli->close(); } + $response->end("OK\n"); + $cli->close(); }); $http->start(); }; diff --git a/tests/swoole_http_client_coro/h2c_upgrade.phpt b/tests/swoole_http_client_coro/h2c_upgrade.phpt index 14e93dbd93..3afcb8c8f9 100644 --- a/tests/swoole_http_client_coro/h2c_upgrade.phpt +++ b/tests/swoole_http_client_coro/h2c_upgrade.phpt @@ -8,7 +8,7 @@ skip_if_offline(); set(['timeout' => 10]); $cli->setHeaders([ @@ -20,7 +20,7 @@ go(function () { ]); $ret = $cli->get('/'); Assert::assert($ret); - Assert::assert(strpos($cli->body, 'Swoole') !== false); + Assert::assert(str_contains($cli->body, 'httpbin.org')); }); ?> --EXPECT-- diff --git a/tests/swoole_http_client_coro/http_chunk.phpt b/tests/swoole_http_client_coro/http_chunk.phpt new file mode 100644 index 0000000000..d9a75f9fc7 --- /dev/null +++ b/tests/swoole_http_client_coro/http_chunk.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_client_coro: http chunk +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $chunks, $body) { + Co\run(function () use ($pm, $chunks, $body) { + $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::assert($cli->get('/')); + Assert::eq($cli->getBody(), $body); + }); + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm, $chunks) { + Co\run(function () use ($pm, $chunks) { + Event::defer(function () use ($pm) { + $pm->wakeup(); + }); + $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); + $server->handle('/', function ($req, $resp) use ($server, $chunks) { + foreach ($chunks as $chunk) { + $resp->write($chunk); + usleep(mt_rand(10, 50) * 100); + } + }); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_client_coro/http_proxy.phpt b/tests/swoole_http_client_coro/http_proxy.phpt index 21158bd5fc..af8d9c3961 100644 --- a/tests/swoole_http_client_coro/http_proxy.phpt +++ b/tests/swoole_http_client_coro/http_proxy.phpt @@ -10,7 +10,7 @@ skip_if_offline(); setHeaders(['Host' => $domain]); // without host header it can also work well $cli->set([ @@ -20,7 +20,7 @@ go(function () { ]); $result = $cli->get('/'); Assert::assert($result); - Assert::assert(stripos($cli->body, 'tencent') !== false); + Assert::assert(stripos($cli->body, '百度') !== false); echo "DONE\n"; }); ?> diff --git a/tests/swoole_http_client_coro/issue_2664.phpt b/tests/swoole_http_client_coro/issue_2664.phpt index d3e8b2323a..4131610d1c 100644 --- a/tests/swoole_http_client_coro/issue_2664.phpt +++ b/tests/swoole_http_client_coro/issue_2664.phpt @@ -34,11 +34,11 @@ $pm->run(); --EXPECTF-- array(4) { [0]=> - string(76) "key1=val1; expires=%s; path=/; domain=id.test.com" + string(91) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com" [1]=> - string(76) "key1=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=test.com" + string(87) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=test.com" [2]=> - string(76) "key2=val2; %s; path=/; domain=id.test.com" + string(91) "key2=val2; expires=%s; Max-Age=84600; path=/; domain=id.test.com" [3]=> - string(76) "key2=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/; domain=test.com" + string(87) "key2=deleted; expires=%s; Max-Age=0; path=/; domain=test.com" } diff --git a/tests/swoole_http_client_coro/long_domain.phpt b/tests/swoole_http_client_coro/long_domain.phpt index b339178405..206251ded5 100644 --- a/tests/swoole_http_client_coro/long_domain.phpt +++ b/tests/swoole_http_client_coro/long_domain.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: long domain --SKIPIF-- --FILE-- diff --git a/tests/swoole_http_client_coro/multi_and_reuse.phpt b/tests/swoole_http_client_coro/multi_and_reuse.phpt index 16bdabbc24..31f2afbd97 100644 --- a/tests/swoole_http_client_coro/multi_and_reuse.phpt +++ b/tests/swoole_http_client_coro/multi_and_reuse.phpt @@ -24,7 +24,7 @@ go(function () { } $baidu = createDeferCli('www.baidu.com', true); - $qq = createDeferCli('www.qq.com', true); + $qq = createDeferCli('news.qq.com', true); //first $baidu->get('/'); @@ -38,7 +38,7 @@ go(function () { //reuse $baidu->get('/duty/'); - $qq->get('/contract.shtml'); + $qq->get('/ch/tech/'); $baidu->recv(10); $qq->recv(10); Assert::same($baidu->statusCode, 200); diff --git a/tests/swoole_http_client_coro/parser.phpt b/tests/swoole_http_client_coro/parser.phpt index 6ceb748308..85e13e72b5 100644 --- a/tests/swoole_http_client_coro/parser.phpt +++ b/tests/swoole_http_client_coro/parser.phpt @@ -53,25 +53,14 @@ require __DIR__ . '/../include/bootstrap.php'; static $normal_chars = [ '!', - '"', '$', '%', '&', - '\'', - - // not allowed as a start in http parser - 'x(', - 'x)', - '*', '+', - - // not allowed as a start in http parser - 'x,', - '-', '.', - '/', + '\'', // not support numeric header name in swoole // '0', @@ -88,13 +77,6 @@ static $normal_chars = [ // will be split and not allowed as a start in http parser // ':', - // not allowed as a start in http parser - 'x;', - 'x<', - 'x=', - 'x>', - 'x@', - // case insensitive // 'A', // 'B', @@ -123,11 +105,6 @@ static $normal_chars = [ // 'Y', // 'Z', - // not allowed as a start in http parser - 'x[', - 'x\\', - 'x]', - '^', '_', '`', @@ -159,17 +136,32 @@ static $normal_chars = [ 'y', 'z', + '|', + '~', + // not allowed as a start in http parser + '"', // start at 40 + 'x(', + 'x)', + 'x,', + '/', + 'x;', + 'x<', + 'x=', + 'x>', + 'x@', + 'x[', + 'x\\', + 'x]', 'x{', - - '|', '}', - '~', ]; +$start = 40; + $pm = new ProcessManager; -$pm->initRandomData((count($normal_chars) + 1) * 2); -$pm->parentFunc = function () use ($pm, &$normal_chars) { +$pm->initRandomData((count($normal_chars) + 1) * 4); +$pm->parentFunc = function () use ($pm, &$normal_chars, $start) { // use curl $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); @@ -193,16 +185,24 @@ $pm->parentFunc = function () use ($pm, &$normal_chars) { Assert::same($body, $pm->getRandomData()); // use swoole http client - go(function () use ($pm, &$normal_chars) { + go(function () use ($pm, &$normal_chars, $start) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); - Assert::assert($cli->get('/')); + Assert::assert($cli->get('/valid')); foreach ($cli->headers as $name => $value) { if (in_array($name, $normal_chars)) { Assert::same($value, $pm->getRandomData()); } } Assert::same($cli->body, $pm->getRandomData()); + + for ($i = $start; $i < sizeof($normal_chars); $i++) { + $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli->set(['timeout' => 1]); + Assert::false($cli->get('/invalid?key=' . $i)); + Assert::same($cli->errMsg, "Http invalid protocol"); + $cli->close(); + } }); Swoole\Event::wait(); @@ -217,9 +217,19 @@ $pm->childFunc = function () use ($pm) { }); $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { global $normal_chars; - foreach ($normal_chars as $char) { - $response->header($char, $pm->getRandomData()); + global $start; + if ($request->server['request_uri'] == '/') { + foreach ($normal_chars as $char) { + $response->header($char, $pm->getRandomData()); + } + } else if ($request->server['request_uri'] == '/valid') { + for ($i = 0; $i < $start; $i++) { + $response->header($normal_chars[$i], $pm->getRandomData()); + } + } else { + $response->header($normal_chars[$request->get['key']], $pm->getRandomData()); } + $response->end($pm->getRandomData()); }); $server->start(); diff --git a/tests/swoole_http_client_coro/reconnect_but_failed.phpt b/tests/swoole_http_client_coro/reconnect_but_failed.phpt index e8512e1d26..e23bf4a46c 100644 --- a/tests/swoole_http_client_coro/reconnect_but_failed.phpt +++ b/tests/swoole_http_client_coro/reconnect_but_failed.phpt @@ -21,7 +21,7 @@ $pm->parentFunc = function () use ($pm) { } $pm->kill(); - usleep(10000); + usleep(100000); Assert::assert(!$cli->get('/')); Assert::same($cli->errCode, SOCKET_ECONNREFUSED); @@ -39,7 +39,8 @@ $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->set([ 'worker_num' => 1, - 'log_file' => '/dev/null' + 'log_file' => '/dev/null', + 'enable_coroutine' => false, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); diff --git a/tests/swoole_http_client_coro/recv_slow_timeout.phpt b/tests/swoole_http_client_coro/recv_slow_timeout.phpt index d453059a53..81605dbe26 100644 --- a/tests/swoole_http_client_coro/recv_slow_timeout.phpt +++ b/tests/swoole_http_client_coro/recv_slow_timeout.phpt @@ -1,16 +1,21 @@ --TEST-- swoole_http_client_coro: recv_all data from slow server --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm) { - $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 1]); $s = microtime(true); $ret = $cli->get('/'); @@ -24,30 +29,31 @@ $pm->parentFunc = function ($pid) use ($pm) { $cli->close(); }); } - Swoole\Event::wait(); + Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { - $server = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); + $server = new Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($server->listen()); + $pm->wakeup(); while ($client = $server->accept()) { go(function () use ($server, $client) { - Assert::assert($client instanceof Swoole\Coroutine\Socket); - $data = - "HTTP/1.1 200 OK\r\n" . - "Connection: keep-alive\r\n" . - "Server: gunicorn/19.9.0\r\n" . - "Date: Wed, 26 Dec 2018 23:56:51 GMT\r\n" . - "Content-Type: text/html; charset=utf-8\r\n" . - "Content-Length: 10122\r\n" . - "Access-Control-Allow-Origin: *\r\n" . - "Access-Control-Allow-Credentials: true\r\n" . - "Via: 1.1 vegur\r\n" . - "\r\n"; + Assert::assert($client instanceof Socket); + $data + = "HTTP/1.1 200 OK\r\n" + . "Connection: keep-alive\r\n" + . "Server: gunicorn/19.9.0\r\n" + . "Date: Wed, 26 Dec 2018 23:56:51 GMT\r\n" + . "Content-Type: text/html; charset=utf-8\r\n" + . "Content-Length: 10122\r\n" + . "Access-Control-Allow-Origin: *\r\n" + . "Access-Control-Allow-Credentials: true\r\n" + . "Via: 1.1 vegur\r\n" + . "\r\n"; for ($n = 0; $n < strlen($data); $n++) { var_dump_return("send {$n}\n"); $client->send($data[$n]); diff --git a/tests/swoole_http_client_coro/socks5_proxy_ipv6.phpt b/tests/swoole_http_client_coro/socks5_proxy_ipv6.phpt new file mode 100644 index 0000000000..2e9f45f19f --- /dev/null +++ b/tests/swoole_http_client_coro/socks5_proxy_ipv6.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_http_client_coro: socks5 proxy with IPv6 +--SKIPIF-- + +--FILE-- +setHeaders([ + 'Host' => $domain, + ]); + + $client->set([ + 'ssl_host_name' => $domain, + 'socks5_host' => SOCKS5_PROXY_HOST, + 'socks5_port' => SOCKS5_PROXY_PORT, + ]); + + Assert::true($client->get('/')); + $json = json_decode($client->body); + Assert::eq($json->code, 403); + $client->close(); +}); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/timeout_before_connect.phpt b/tests/swoole_http_client_coro/timeout_before_connect.phpt index 367ef00d13..940ff3342c 100644 --- a/tests/swoole_http_client_coro/timeout_before_connect.phpt +++ b/tests/swoole_http_client_coro/timeout_before_connect.phpt @@ -3,7 +3,7 @@ swoole_http_client_coro: use timeout and timeout before connect --SKIPIF-- --FILE-- diff --git a/tests/swoole_http_client_coro/upload_huge.phpt b/tests/swoole_http_client_coro/upload_huge.phpt index 5694a6166e..f25078ea25 100644 --- a/tests/swoole_http_client_coro/upload_huge.phpt +++ b/tests/swoole_http_client_coro/upload_huge.phpt @@ -11,7 +11,7 @@ require __DIR__ . '/../include/bootstrap.php'; go(function () { $cli = new Swoole\Coroutine\Http\Client(HTTPBIN_SERVER_HOST, HTTPBIN_SERVER_PORT); $cli->set(['timeout' => 10]); - $content = str_repeat(get_safe_random(IS_IN_TRAVIS ? 16 : 64), 1024 * 1024); // 64M + $content = str_repeat(get_safe_random(IS_IN_CI ? 16 : 64), 1024 * 1024); // 64M file_put_contents('/tmp/test.jpg', $content); $cli->addFile('/tmp/test.jpg', 'test.jpg'); $ret = $cli->post('/post', ['name' => 'twosee']); diff --git a/tests/swoole_http_client_coro/websocket/1.phpt b/tests/swoole_http_client_coro/websocket/1.phpt index 2c8f8701c0..4620d8d02a 100644 --- a/tests/swoole_http_client_coro/websocket/1.phpt +++ b/tests/swoole_http_client_coro/websocket/1.phpt @@ -1,58 +1,63 @@ --TEST-- swoole_http_client_coro/websocket: client & server --SKIPIF-- - --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { - $cli = new Co\http\Client('127.0.0.1', $pm->getFreePort()); + $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => -1]); $cli->setHeaders([]); $ret = $cli->upgrade('/'); - if (!$ret) - { + if (!$ret) { echo "ERROR\n"; return; } echo $cli->recv()->data; - for ($i = 0; $i < 5; $i++) - { + for ($i = 0; $i < 5; $i++) { $cli->push('hello server'); - echo ($cli->recv())->data; + echo $cli->recv()->data; co::sleep(0.1); } }); - Swoole\Event::wait(); + Event::wait(); $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ - $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $ws->set(array( - 'log_file' => '/dev/null' - )); - $ws->on('WorkerStart', function (Swoole\Server $serv) { - /** +$pm->childFunc = function () use ($pm) { + $ws = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $ws->set([ + 'log_file' => '/dev/null', + ]); + $ws->on('WorkerStart', function (Server $serv) { + /* * @var $pm ProcessManager */ global $pm; $pm->wakeup(); }); - $ws->on('open', function ($serv, Swoole\Http\Request $request) { - $ip = co::gethostbyname('www.baidu.com'); - if ($ip) - { + $ws->on('open', function (Server $serv, Request $request) { + $ip = Co::gethostbyname(TEST_DOMAIN_1); + if ($ip) { $serv->push($request->fd, "start\n"); + } else { + $serv->push($request->fd, 'error: ' . swoole_last_error() . "\n"); } }); diff --git a/tests/swoole_http_client_coro/websocket/auto_pong.phpt b/tests/swoole_http_client_coro/websocket/auto_pong.phpt new file mode 100644 index 0000000000..53c06b0fd5 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/auto_pong.phpt @@ -0,0 +1,66 @@ +--TEST-- +swoole_http_client_coro/websocket: auto pong +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + $client->push('hello world', SWOOLE_WEBSOCKET_OPCODE_TEXT); + while ($client->recv()) { + $client->push('hello world', SWOOLE_WEBSOCKET_OPCODE_TEXT); + } + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + 'open_websocket_pong_frame' => true + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $data = []; + $count = 0; + $server->on('message', function (Server $server, Frame $frame) use ($pm, &$data, &$count) { + if ($count == 1000) { + $server->disconnect($frame->fd); + for ($i = 0; $i < 1000; $i++) { + Assert::true($data[$i]->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); + } + return; + } + + if ($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG) { + $count++; + $data[] = $frame; + } + + $server->push($frame->fd, "hello world", SWOOLE_WEBSOCKET_OPCODE_TEXT); + $ping = new Frame(); + $ping->data = $frame->data; + $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; + $server->push($frame->fd, $ping); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/close_socket.phpt b/tests/swoole_http_client_coro/websocket/close_socket.phpt new file mode 100644 index 0000000000..a5ca32ffa3 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/close_socket.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_http_client_coro/websocket: close socket +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + go(function () use ($pm) { + $cli = new Co\http\Client('127.0.0.1', $pm->getFreePort()); + $cli->set(['timeout' => -1]); + $cli->setHeaders([]); + $ret = $cli->upgrade('/'); + if (!$ret) { + echo "ERROR\n"; + return; + } + Assert::assert($cli->socket->close()); + Assert::false($cli->recv()); + Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); + Assert::false($cli->push('hello server')); + Assert::eq($cli->errCode, SWOOLE_ERROR_CLIENT_NO_CONNECTION); + }); + Swoole\Event::wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $ws->set(array( + 'log_file' => '/dev/null' + )); + $ws->on('WorkerStart', function (Swoole\Server $serv) { + global $pm; + $pm->wakeup(); + }); + $ws->on('open', function ($serv, Swoole\Http\Request $request) { + + }); + $ws->on('message', function ($serv, $frame) { + $serv->push($frame->fd, "hello client\n"); + }); + $ws->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag.phpt b/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag.phpt new file mode 100644 index 0000000000..94a1e349b0 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_http_client_coro/websocket: continue frame finish flag +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); + Assert::eq($frame->finish, true); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == $data1 . $data2 . $data3); + Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); + Assert::eq($frame->finish, true); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag2.phpt b/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag2.phpt new file mode 100644 index 0000000000..54a5abedb0 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frame_finish_flag2.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_http_client_coro/websocket: continue frame finish flag - 2 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); + Assert::eq($frame->finish, true); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + Assert::eq($frame->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); + Assert::eq($frame->finish, true); + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/continue_frames.phpt b/tests/swoole_http_client_coro/websocket/continue_frames.phpt new file mode 100644 index 0000000000..061ea036f4 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frames.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_http_client_coro/websocket: client continue frames - 1 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == $data1 . $data2 . $data3); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/continue_frames2.phpt b/tests/swoole_http_client_coro/websocket/continue_frames2.phpt new file mode 100644 index 0000000000..23ed6df189 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frames2.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_http_client_coro/websocket: client continue frames - 2 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == $data1 . $data2 . $data3); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s All fragments of a message, except for the initial frame, must use the continuation frame opcode(0). diff --git a/tests/swoole_http_client_coro/websocket/continue_frames3.phpt b/tests/swoole_http_client_coro/websocket/continue_frames3.phpt new file mode 100644 index 0000000000..b885c77126 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frames3.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_client_coro/websocket: client continue frames - 3 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == $data1 . $data2 . $data3); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s A continuation frame cannot stand alone and MUST be preceded by an initial frame whose opcode indicates either text or binary data. diff --git a/tests/swoole_http_client_coro/websocket/continue_frames4.phpt b/tests/swoole_http_client_coro/websocket/continue_frames4.phpt new file mode 100644 index 0000000000..3aaaba5daf --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/continue_frames4.phpt @@ -0,0 +1,63 @@ +--TEST-- +swoole_http_client_coro/websocket: client continue frames - 4 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $results = []; + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $results[] = $client->recv(); + Assert::true($results[0]->data == $data1 . $data2 . $data3); + $client->push('222', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $results[] = $client->recv(); + Assert::true($results[1]->data == $data3 . $data2 . $data1); + Assert::true($results[0]->data != $results[1]->data); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + if ($frame->data == '111') { + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + } else { + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + } + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/control_frame_compress.phpt b/tests/swoole_http_client_coro/websocket/control_frame_compress.phpt new file mode 100644 index 0000000000..ebe4a785db --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/control_frame_compress.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_http_client_coro/websocket: control frame can not compress +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['websocket_compression' => true]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + + $closeFrame = new CloseFrame(); + $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $closeFrame->reason = 'hahahahaha'; + $closeFrame->flags = 0; + $client->push($closeFrame, SWOOLE_WEBSOCKET_FLAG_RSV1 | SWOOLE_WEBSOCKET_FLAG_COMPRESS); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'websocket_compression' => true, + 'open_websocket_close_frame' => true, + 'package_max_length' => 300 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm) { + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_FIN) == 1); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/control_frame_fragmented.phpt b/tests/swoole_http_client_coro/websocket/control_frame_fragmented.phpt new file mode 100644 index 0000000000..954c7956af --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/control_frame_fragmented.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_client_coro/websocket: Control frames must not be fragmented +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'websocket_compression' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + + $closeFrame = new CloseFrame(); + $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $closeFrame->reason = 'hahahahaha'; + $closeFrame->flags = 0; + $client->push($closeFrame, 0); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'websocket_compression' => true, + 'open_websocket_close_frame' => true, + 'package_max_length' => 300 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm) { + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_FIN) == 1); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/disconnect.phpt b/tests/swoole_http_client_coro/websocket/disconnect.phpt new file mode 100644 index 0000000000..ed74f73b05 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/disconnect.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_http_client_coro/websocket: test disconnect function +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::true($client->disconnect(SWOOLE_WEBSOCKET_CLOSE_NORMAL, 'close it')); + Assert::true($client->connected == 0); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + 'open_websocket_close_frame' => true + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) { + var_dump($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + var_dump($frame->reason == 'close it'); + }); + + $server->on('close', function (Swoole\Server $server, int $fd, int $reactorId) { + var_dump($reactorId); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +bool(true) +bool(true) +int(0) diff --git a/tests/swoole_http_client_coro/websocket/open_websocket_frame.phpt b/tests/swoole_http_client_coro/websocket/open_websocket_frame.phpt new file mode 100644 index 0000000000..7ab303d951 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/open_websocket_frame.phpt @@ -0,0 +1,69 @@ +--TEST-- +swoole_http_client_coro/websocket: open websocket frame +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('Hello World!!!'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + $frame = $client->recv(); + Assert::true($frame == ''); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == 'Hello World!!!'); + $ping = new Frame(); + $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; + $server->push($frame->fd, $ping); + + $pong = new Frame(); + $pong->opcode = SWOOLE_WEBSOCKET_OPCODE_PONG; + $server->push($frame->fd, $pong); + + $server->disconnect($frame->fd); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/ping.phpt b/tests/swoole_http_client_coro/websocket/ping.phpt new file mode 100644 index 0000000000..dd2849237d --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/ping.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_http_client_coro/websocket: test ping function +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['open_websocket_pong_frame' => true]); + $ret = $client->upgrade('/'); + Assert::true($client->ping('Hello World')); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); + Assert::true($frame->data == 'Hello World'); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) { + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/priority.phpt b/tests/swoole_http_client_coro/websocket/priority.phpt new file mode 100644 index 0000000000..559fd3b755 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/priority.phpt @@ -0,0 +1,66 @@ +--TEST-- +swoole_http_client_coro/websocket: control frame priority - 1 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'open_websocket_ping_frame' => true + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('Hello World!!!'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data2 . $data3); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == 'Hello World!!!'); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + + $ping = new Frame(); + $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; + $ping->data = 'PING'; + $server->push($frame->fd, $ping); + + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/priority1.phpt b/tests/swoole_http_client_coro/websocket/priority1.phpt new file mode 100644 index 0000000000..1b0410be00 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/priority1.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_client_coro/websocket: control frame priority - 2 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'open_websocket_close_frame' => true + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('Hello World!!!'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + $frame = $client->recv(); + Assert::true($frame == ''); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $server->set([ + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + Assert::true($frame->data == 'Hello World!!!'); + $server->push($frame->fd, $data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, $data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + + $server->disconnect($frame->fd); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/send_more_continue_frame.phpt b/tests/swoole_http_client_coro/websocket/send_more_continue_frame.phpt new file mode 100644 index 0000000000..21fd06c18d --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/send_more_continue_frame.phpt @@ -0,0 +1,70 @@ +--TEST-- +swoole_http_client_coro/websocket: send more continue frames - websocket server +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $results = []; + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['websocket_compression' => true]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3); + $frame = $client->recv(); + Assert::true($frame->data == $data2 . $data1 . $data3); + $frame = $client->recv(); + Assert::true($frame->data == $data3 . $data2 . $data1); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + 'package_max_length' => 100 * 1024 * 1024, + 'websocket_compression' => true + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm, $data1, $data2, $data3) { + $context = deflate_init(ZLIB_ENCODING_RAW); + $server->push($frame->fd, deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + + $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $server->push($frame->fd, deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + + $server->push($frame->fd, deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $server->push($frame->fd, deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $server->push($frame->fd, deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/send_more_continue_frame2.phpt b/tests/swoole_http_client_coro/websocket/send_more_continue_frame2.phpt new file mode 100644 index 0000000000..fc01ff0926 --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/send_more_continue_frame2.phpt @@ -0,0 +1,65 @@ +--TEST-- +swoole_http_client_coro/websocket: send more continue frames - coroutine websocket server +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $results = []; + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['websocket_compression' => true]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data2 . $data2 . $data3); + $frame = $client->recv(); + Assert::true($frame->data == $data2 . $data1 . $data3); + $frame = $client->recv(); + Assert::true($frame->data == $data3 . $data2 . $data1); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->set(['websocket_compression' => true]); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + $context = deflate_init(ZLIB_ENCODING_RAW); + $response->push(deflate_add($context, $data1, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data2, ZLIB_SYNC_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + + $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + + $response->push(deflate_add($context, $data3, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data1, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_client_coro/websocket/server_push_first.phpt b/tests/swoole_http_client_coro/websocket/server_push_first.phpt index 7981a579e1..4b404dab1d 100644 --- a/tests/swoole_http_client_coro/websocket/server_push_first.phpt +++ b/tests/swoole_http_client_coro/websocket/server_push_first.phpt @@ -36,8 +36,9 @@ Co\run(function () use ($pm) { go(function () use ($pm, $server) { $wr = WaitRef::create(); + $childs = []; for ($c = MAX_CONCURRENCY_LOW; $c--;) { - go(function () use ($pm, $wr) { + $childs[] = go(function () use ($pm, $wr) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cli->set(['timeout' => 5]); $ret = $cli->upgrade('/websocket'); @@ -49,9 +50,11 @@ Co\run(function () use ($pm) { }); } WaitRef::wait($wr); + echo "DONE\n"; $server->shutdown(); }); }); ?> --EXPECT-- +DONE diff --git a/tests/swoole_http_client_coro/websocket/upgrade_after_get.phpt b/tests/swoole_http_client_coro/websocket/upgrade_after_get.phpt new file mode 100644 index 0000000000..48bba417de --- /dev/null +++ b/tests/swoole_http_client_coro/websocket/upgrade_after_get.phpt @@ -0,0 +1,72 @@ +--TEST-- +swoole_http_client_coro/websocket: client & server +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort(), false); + $client->setHeaders([ + "User-Agent" => 'Chrome/49.0.2587.3', + 'Accept' => 'text/html,application/xhtml+xml,application/xml', + 'Accept-Encoding' => 'gzip', + ]); + + Assert::assert($client->get('/')); + echo $client->getBody(); + + Assert::assert($client->upgrade('/')); + + echo $client->recv(2)->data; + $client->push("hello"); + echo $client->recv(2)->data; + + $client->close(); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) +{ + $ws = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $ws->set(array( + 'log_file' => '/dev/null' + )); + $ws->on('WorkerStart', function (Swoole\Server $serv) { + /** + * @var $pm ProcessManager + */ + global $pm; + $pm->wakeup(); + }); + + $ws->on('open', function ($serv, Swoole\Http\Request $request) { + $serv->push($request->fd, "msg 1\n"); + }); + + $ws->on('message', function ($serv, $frame) { + co::sleep(0.1); + $serv->push($frame->fd, "msg 2\n"); + }); + + $ws->on('request', function ($req, $resp) { + $resp->end("OK\n"); + }); + + $ws->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +OK +msg 1 +msg 2 diff --git a/tests/swoole_http_client_coro/write_func_1.phpt b/tests/swoole_http_client_coro/write_func_1.phpt new file mode 100644 index 0000000000..51f668b9a8 --- /dev/null +++ b/tests/swoole_http_client_coro/write_func_1.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_http_client_coro: write func 1 +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $chunks) { + Co\run(function () use ($pm, $chunks) { + $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $index = 0; + $cli->set(['write_func' => function ($client, $data) use ($chunks, &$index) { + Assert::eq($chunks[$index], $data); + $index++; + }]); + Assert::assert($cli->get('/')); + }); + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm, $chunks) { + Co\run(function () use ($pm, $chunks) { + Event::defer(function () use ($pm) { + $pm->wakeup(); + }); + $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); + $server->handle('/', function ($req, $resp) use ($server, $chunks) { + foreach ($chunks as $chunk) { + $resp->write($chunk); + usleep(mt_rand(10, 50) * 1000); + } + }); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_client_coro/write_func_2.phpt b/tests/swoole_http_client_coro/write_func_2.phpt new file mode 100644 index 0000000000..e2da0b38d8 --- /dev/null +++ b/tests/swoole_http_client_coro/write_func_2.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_client_coro: write func 1 +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $chunks) { + Co\run(function () use ($pm, $chunks) { + $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $index = 0; + $cli->set(['write_func' => function ($client, $data) use ($chunks, &$index) { + Assert::eq($chunks[$index], $data); + $index++; + if ($index == N / 2) { + // reset connection + $client->close(); + } + }]); + Assert::false($cli->get('/')); + Assert::eq($cli->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); + }); + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm, $chunks) { + Co\run(function () use ($pm, $chunks) { + Event::defer(function () use ($pm) { + $pm->wakeup(); + }); + $server = new Swoole\Coroutine\Http\Server('127.0.0.1', $pm->getFreePort()); + $server->handle('/', function ($req, $resp) use ($server, $chunks) { + foreach ($chunks as $chunk) { + $resp->write($chunk); + usleep(mt_rand(10, 50) * 1000); + } + $resp->end(); + }); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/100-continue.phpt b/tests/swoole_http_server/100-continue.phpt new file mode 100644 index 0000000000..d2f4a79800 --- /dev/null +++ b/tests/swoole_http_server/100-continue.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_http_server: 100-continue +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}"); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect: 100-continue']); + + $file = TEST_IMAGE; + $post_data = array('test' => str_repeat('a', 80)); + if (function_exists("curl_file_create")) { + $cfile = curl_file_create($file); + $post_data['file'] = $cfile; + } else { + $post_data['file'] = '@' . $file; + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data); //POST数据 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $res = curl_exec($ch); + Assert::assert(!empty($res)); + Assert::same($res, md5_file($file)); + curl_close($ch); + + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + + $http->set([ + 'log_file' => '/dev/null', + ]); + + $http->on("WorkerStart", function () use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $response->end(md5_file($request->files['file']['tmp_name'])); + }); + + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server/If_Modified_Since.phpt b/tests/swoole_http_server/If_Modified_Since.phpt new file mode 100644 index 0000000000..9b13f92e4f --- /dev/null +++ b/tests/swoole_http_server/If_Modified_Since.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_server: If-Modified-Since +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + foreach ([false, true] as $http2) { + Swoole\Coroutine\run(function () use ($pm, $http2) { + $data2 = file_get_contents(TEST_IMAGE); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg"); + $lastModified = $response['headers']['last-modified']; + Assert::same($response['statusCode'], 200); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['headers' => ['-If-Modified-Since' => 'aaaa', 'If-Modified-Since' => $lastModified]]); + Assert::same($response['statusCode'], 304); + }); + } + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => dirname(dirname(__DIR__)) . '/examples/', + 'static_handler_locations' => ['/static', '/'] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end('hello world'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/accept_encoding.phpt b/tests/swoole_http_server/accept_encoding.phpt new file mode 100644 index 0000000000..da396e4e54 --- /dev/null +++ b/tests/swoole_http_server/accept_encoding.phpt @@ -0,0 +1,63 @@ +--TEST-- +swoole_http_server: accept encoding type +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) +{ + Runtime::enableCoroutine(SWOOLE_HOOK_NATIVE_CURL); + run(function () use ($pm) { + $url = "http://127.0.0.1:".$pm->getFreePort(); + curl_request('br', $url); + curl_request('gzip', $url); + curl_request('deflate', $url); + curl_request('zstd', $url); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) +{ + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->set([ + 'http_compression' => true, + ]); + $http->on("WorkerStart", function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on("request", function (Request $request, Response $response) { + $response->end(co::readFile(__DIR__ . '/../../README.md')); + }); + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server/bug_2444.phpt b/tests/swoole_http_server/bug_2444.phpt index ff6357a7f2..5d994e0f0e 100644 --- a/tests/swoole_http_server/bug_2444.phpt +++ b/tests/swoole_http_server/bug_2444.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_http_server: bug #2444 --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { }; $pm->childFunc = function () use ($pm) { $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); - $server->set(['log_file' => '/dev/null']); + $server->set(['log_file' => '/dev/null', 'hook_flags' => SWOOLE_HOOK_ALL]); $server->on('start', function () use ($pm) { $pm->wakeup(); }); @@ -30,18 +32,16 @@ $pm->childFunc = function () use ($pm) { return; } $cli->close(); - $db = new Swoole\Coroutine\Mysql(); - if (!Assert::assert($db->connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]))) { + $db = new mysqli(); + $db->set_opt(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1); + if (!Assert::assert($db->connect(MYSQL_SERVER_HOST, + MYSQL_SERVER_USER, + MYSQL_SERVER_PWD, + MYSQL_SERVER_DB, + MYSQL_SERVER_PORT))) { goto _error; } - if (!Assert::assert($db->query('select 1')[0][1] === 1)) { + if (!Assert::assert($db->query('select 1')->fetch_all()[0][0] === 1)) { goto _error; } $db->close(); diff --git a/tests/swoole_http_server/bug_4857.phpt b/tests/swoole_http_server/bug_4857.phpt new file mode 100644 index 0000000000..6abded5af2 --- /dev/null +++ b/tests/swoole_http_server/bug_4857.phpt @@ -0,0 +1,104 @@ +--TEST-- +swoole_http_server: bug Github#4857 Invalid "Transfer-Encoding: chunked" header appended +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + + // without special content-length + $headers = httpGetHeaders( + "http://127.0.0.1:{$pm->getFreePort()}?encoding=1", + [ + 'headers' => ['Accept-Encoding' => 'gzip, br'], + ] + ); + var_dump($headers); + + // without content-length + $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); + var_dump($headers); + + // with content-length + $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}?normal=1"); + var_dump($headers); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $data = '宛如繁星般,宛如皎月般'; + if (isset($request->get['normal'])) { + $response->header('Content-Length', mb_strlen($data)); + $response->end($data); + } elseif (isset($request->get['encoding'])) { + $response->header('Content-Length', 1000); + $response->end($data); + } else { + $response->header('Content-Length', 100); + $response->write($data); + $response->end(); + } + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s +array(6) { + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) %s + ["connection"]=> + string(10) "keep-alive" + ["content-type"]=> + string(9) "text/html" + ["content-encoding"]=> + string(%d) %s + ["content-length"]=> + string(%d) %s +} +%s +array(5) { + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) %s + ["connection"]=> + string(10) "keep-alive" + ["content-type"]=> + string(9) "text/html" + ["transfer-encoding"]=> + string(7) "chunked" +} +%s +array(6) { + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) %s + ["connection"]=> + string(10) "keep-alive" + ["content-type"]=> + string(9) "text/html" + ["content-encoding"]=> + string(%d) %s + ["content-length"]=> + string(%d) %s +} +DONE diff --git a/tests/swoole_http_server/bug_5107.phpt b/tests/swoole_http_server/bug_5107.phpt new file mode 100644 index 0000000000..5e071f9607 --- /dev/null +++ b/tests/swoole_http_server/bug_5107.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_http_server: bug Github#5107 Error response status +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); + var_dump($headers); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $response->status(200, "status"); + $response->end("Hello World"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +array(5) { + ["server"]=> + string(18) "swoole-http-server" + ["date"]=> + string(%d) %s + ["connection"]=> + string(10) "keep-alive" + ["content-type"]=> + string(9) "text/html" + ["content-length"]=> + string(2) "11" +} +DONE diff --git a/tests/swoole_http_server/bug_5114.phpt b/tests/swoole_http_server/bug_5114.phpt new file mode 100644 index 0000000000..43d1d1fbf9 --- /dev/null +++ b/tests/swoole_http_server/bug_5114.phpt @@ -0,0 +1,51 @@ +--TEST-- +swoole_http_server: bug #5114 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Swoole\Coroutine\run(function () use ($pm) { + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/UPPER.TXT"); + Assert::same($response['statusCode'], 200); + Assert::same($response['headers']['content-type'], 'text/plain'); + Assert::same($response['body'], "HELLO WORLD!\n"); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/http/test.txt"); + Assert::same($response['statusCode'], 200); + Assert::same($response['headers']['content-type'], 'text/plain'); + Assert::same($response['body'], "hello world!\n"); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => dirname(dirname(__DIR__)) . '/examples/', + 'static_handler_locations' => ['/static', '/'] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end('hello world'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/bug_5146.phpt b/tests/swoole_http_server/bug_5146.phpt new file mode 100644 index 0000000000..2efbbc39dc --- /dev/null +++ b/tests/swoole_http_server/bug_5146.phpt @@ -0,0 +1,68 @@ +--TEST-- +swoole_http_server: Github#5146 HTTP服务器,添加响应cookie时,如果设置了过期时间会内存泄漏 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Swoole\Coroutine\run(function () use ($pm) { + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $previous = memory_get_usage(); + $response->cookie( + 'test_cookie', + 'hello', + time() + (24 * 60 * 60) + ); + + global $previous; + global $item; + $current = memory_get_usage(); + $stats = [ + 'id' => $http->getWorkerId(), + 'item' => $item++, + 'prev_mem' => $previous, + 'curr_mem' => $current, + 'diff_mem' => $current - $previous, + ]; + $previous = $current; + + echo json_encode($stats), PHP_EOL; + $response->end('test response'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +{"id":%d,"item":null,"prev_mem":null,"curr_mem":%d,"diff_mem":%d} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":%d} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +DONE diff --git a/tests/swoole_http_server/bug_5186.phpt b/tests/swoole_http_server/bug_5186.phpt new file mode 100644 index 0000000000..b6672c216e --- /dev/null +++ b/tests/swoole_http_server/bug_5186.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_http_server: GitHub issue #5186 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + run(function () use ($pm) { + $content = "POST / HTTP/1.1\r\nHost: 127.0.0.1:{$pm->getFreePort()}\r\nConnection: keep-alive\r\nContent-Length: 44\r\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryOldDnwBESVoBBtI5\r\nAccept-Encoding: gzip, deflate\r\n\r\n------WebKitFormBoundaryOldDnwBESVoBBtI5--\r\n\r\n"; + $client = new Client(SWOOLE_SOCK_TCP); + $client->connect('127.0.0.1', $pm->getFreePort(), 0.5); + $client->send($content); + $client->close(); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + var_dump($request->files); + $response->end('Hello World'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE +NULL diff --git a/tests/swoole_http_server/bug_6007.phpt b/tests/swoole_http_server/bug_6007.phpt new file mode 100644 index 0000000000..00afe0dde7 --- /dev/null +++ b/tests/swoole_http_server/bug_6007.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_http_server: Github bug #6007 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli->get('/'); + Assert::assert($cli->set_cookie_headers === + [ + 'userId=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; domain=my.web.site; HttpOnly; Partitioned', + ] + ); + }); + Swoole\Event::wait(); + echo "SUCCESS\n"; + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $cookie = new Swoole\Http\Cookie(); + $cookie->withName('userId') + ->withValue('') // <-- + ->withExpires(time() - 84600) + ->withPath('/') + ->withDomain('my.web.site') + ->withHttpOnly(true) + ->withPartitioned(true); + $response->setCookie($cookie); + $response->end(); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_http_server/callback_with_internal_function.phpt b/tests/swoole_http_server/callback_with_internal_function.phpt index ddf723da48..8dbc704522 100644 --- a/tests/swoole_http_server/callback_with_internal_function.phpt +++ b/tests/swoole_http_server/callback_with_internal_function.phpt @@ -41,7 +41,7 @@ object(Swoole\Http\Request)#%d (%d) { string(%d) "%s" } ["server"]=> - array(10) { + array(11) { ["request_method"]=> string(3) "GET" ["request_uri"]=> @@ -58,6 +58,8 @@ object(Swoole\Http\Request)#%d (%d) { int(%d) ["remote_port"]=> int(%d) + ["server_addr"]=> + string(9) "127.0.0.1" ["remote_addr"]=> string(9) "127.0.0.1" ["master_time"]=> diff --git a/tests/swoole_http_server/callback_with_private.phpt b/tests/swoole_http_server/callback_with_private.phpt index d5b5ecae0d..e436119df0 100644 --- a/tests/swoole_http_server/callback_with_private.phpt +++ b/tests/swoole_http_server/callback_with_private.phpt @@ -38,10 +38,6 @@ $pm->run(true); //Fatal Error $pm->expectExitCode(255); $output = $pm->getChildOutput(); -if (PHP_VERSION_ID < 80000) { - Assert::contains($output, 'Swoole\Server::on() must be callable'); -} else { - Assert::contains($output, 'Swoole\Server::on(): function \'TestCo_9::foo\' is not callable'); -} +Assert::contains($output, "Swoole\Server\Port::on(): function 'TestCo_9::foo' is not callable"); ?> --EXPECT-- diff --git a/tests/swoole_http_server/callback_with_protected.phpt b/tests/swoole_http_server/callback_with_protected.phpt index 0af346d91d..bf645080af 100644 --- a/tests/swoole_http_server/callback_with_protected.phpt +++ b/tests/swoole_http_server/callback_with_protected.phpt @@ -34,10 +34,6 @@ $pm = ProcessManager::exec(function ($pm) { //Fatal Error $pm->expectExitCode(255); $output = $pm->getChildOutput(); -if (PHP_VERSION_ID < 80000) { - Assert::contains($output, 'Swoole\Server::on() must be callable'); -} else { - Assert::contains($output, 'Swoole\Server::on(): function \'TestCo::foo\' is not callable'); -} +Assert::contains($output, 'Swoole\Server\Port::on(): function \'TestCo::foo\' is not callable'); ?> --EXPECT-- diff --git a/tests/swoole_http_server/chunk.phpt b/tests/swoole_http_server/chunk.phpt index be33665b4e..c85da9073c 100644 --- a/tests/swoole_http_server/chunk.phpt +++ b/tests/swoole_http_server/chunk.phpt @@ -12,7 +12,7 @@ $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); Assert::assert(!empty($data)); - Assert::assert(md5($data) === md5_file(TEST_IMAGE)); + Assert::eq(md5($data), md5_file(TEST_IMAGE)); $pm->kill(); }); Swoole\Event::wait(); @@ -34,9 +34,9 @@ $pm->childFunc = function () use ($pm) { $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $data = str_split(file_get_contents(TEST_IMAGE), 8192); foreach ($data as $chunk) { - $response->write($chunk); + Assert::true($response->write($chunk)); } - $response->end(); + Assert::true($response->end()); }); $http->start(); diff --git a/tests/swoole_http_server/chunk_with_end_data.phpt b/tests/swoole_http_server/chunk_with_end_data.phpt new file mode 100644 index 0000000000..369d02bc89 --- /dev/null +++ b/tests/swoole_http_server/chunk_with_end_data.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_server: send data in the end method with chunked encoding +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); + Assert::assert(!empty($data)); + Assert::eq(md5($data), md5_file(TEST_IMAGE)); + $pm->kill(); + }); + Swoole\Event::wait(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + + $http->set([ + //'log_file' => '/dev/null', + ]); + + $http->on("WorkerStart", function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + + $http->on("request", function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $data = str_split(file_get_contents(TEST_IMAGE), 8192); + foreach ($data as $k => $chunk) { + if ($k == count($data) - 1) { + break; + } + $response->write($chunk); + } + $response->end($data[count($data) - 1]); + }); + + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/client_ca.phpt b/tests/swoole_http_server/client_ca.phpt new file mode 100644 index 0000000000..5770929e48 --- /dev/null +++ b/tests/swoole_http_server/client_ca.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_http_server: ssl client ca +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $html) { + go(function () use ($pm, $html) { + $commnd = "curl https://127.0.0.1:" . $pm->getFreePort() . " --cert " . SSL_FILE_DIR. "/client.crt" . + " --key ". SSL_FILE_DIR. "/client.key -k -vvv --stderr /tmp/client_ca.txt"; + $out = shell_exec($commnd); + Assert::eq($out, $html); + $pm->kill(); + }); + Swoole\Event::wait(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm, $html) { + $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM, SWOOLE_SOCK_TCP | SWOOLE_SSL); + $serv->set([ + 'log_file' => '/dev/null', + 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/server.key', + 'ssl_verify_peer' => true, + 'ssl_verify_depth' => 10, + 'ssl_cafile' => SSL_FILE_DIR . '/ca.crt', + ]); + $serv->on("workerStart", function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('request', function ($req, $resp) use ($html) { + $resp->end($html); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/client_compress.phpt b/tests/swoole_http_server/client_compress.phpt new file mode 100644 index 0000000000..7bbea5ada8 --- /dev/null +++ b/tests/swoole_http_server/client_compress.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_http_server: compress response by http client +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + $data = httpRequest("http://127.0.0.1:{$pm->getFreePort()}", ['headers' => ['accept-encoding' => 'br']]); + $headers = $data['headers']; + Assert::assert(!empty($headers)); + Assert::eq($headers['content-encoding'], 'gzip'); + Assert::eq($data['body'], file_get_contents(TEST_IMAGE)); + $pm->kill(); + }); + Swoole\Event::wait(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on("WorkerStart", function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + + $http->on("request", function ($request, $response) { + $gzipEncoded = gzencode(file_get_contents(TEST_IMAGE), 9, FORCE_GZIP); + $response->setHeader('Content-Encoding', 'gzip'); + $response->end($gzipEncoded); + }); + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/cookieAlias.phpt b/tests/swoole_http_server/cookieAlias.phpt new file mode 100644 index 0000000000..21050d873d --- /dev/null +++ b/tests/swoole_http_server/cookieAlias.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_http_server: cookie alias +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\Run(function () use ($pm) { + var_dump(httpRequest("http://127.0.0.1:{$pm->getFreePort()}")['set_cookie_headers']); + }); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $server->set(['log_file' => '/dev/null']); + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $cookie = new Swoole\Http\Cookie(); + $cookie->withName('key1') + ->withValue('val1') + ->withExpires(time() + 84600) + ->withPath('/') + ->withDomain('id.test.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite('None') + ->withPriority('High') + ->withPartitioned(true); + $response->setCookie($cookie); + $response->setCookie('key1', 'val1', time() + 84600, '/', 'id.test.com', true, true, 'None', 'High', true); + $response->setRawCookie('key1', 'val1', time() + 84600, '/', 'id.test.com', true, true, 'None', 'High', true); + + $cookie->withValue(''); + $response->setCookie($cookie); + $response->end("

    Hello Swoole. #" . rand(1000, 9999) . "

    "); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +array(4) { + [0]=> + string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" + [1]=> + string(152) "key1=val1; expires=%s; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" + [2]=> + string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" + [3]=> + string(151) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" +} diff --git a/tests/swoole_http_server/cookie_delete.phpt b/tests/swoole_http_server/cookie_delete.phpt index a4d3f0ed7f..59421cc3eb 100644 --- a/tests/swoole_http_server/cookie_delete.phpt +++ b/tests/swoole_http_server/cookie_delete.phpt @@ -14,14 +14,14 @@ $pm->parentFunc = function () use ($pm) { Assert::same($cli->statusCode, 200); Assert::assert($cli->set_cookie_headers === [ - 'cookie1=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT', - 'cookie2=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT', + 'cookie1=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', + 'cookie2=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', 'cookie3=cookie3', 'cookie4=cookie4', - 'cookie5=cookie5; expires=Thu, 01-Jan-1970 00:00:01 GMT', - 'cookie6=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT', - 'cookie7=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT', - 'cookie8=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT', + 'cookie5=cookie5; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', + 'cookie6=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', + 'cookie7=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', + 'cookie8=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0', ] ); }); diff --git a/tests/swoole_http_server/cookie_samesite.phpt b/tests/swoole_http_server/cookie_samesite.phpt index 939ec2cf9a..17b6707ee0 100644 --- a/tests/swoole_http_server/cookie_samesite.phpt +++ b/tests/swoole_http_server/cookie_samesite.phpt @@ -12,7 +12,7 @@ $pm->parentFunc = function () use ($pm) { $cli->get('/'); Assert::assert($cli->set_cookie_headers === [ - 'a=123; samesite=Lax', + 'a=123; SameSite=Lax', ] ); }); diff --git a/tests/swoole_http_server/cookie_vs_rawcookie.phpt b/tests/swoole_http_server/cookie_vs_rawcookie.phpt index 172f32815e..3767d19846 100644 --- a/tests/swoole_http_server/cookie_vs_rawcookie.phpt +++ b/tests/swoole_http_server/cookie_vs_rawcookie.phpt @@ -10,14 +10,13 @@ $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); $cookie = '123_,; abc'; - Assert::assert($cli->get('/?cookie=' . urlencode($cookie))); + $cookie_encoded = urlencode($cookie); + Assert::assert($cli->get('/?cookie=' . $cookie_encoded)); Assert::same($cli->statusCode, 200); - Assert::assert($cli->set_cookie_headers === - [ - 'cookie=' . urlencode($cookie), - 'rawcookie=' . $cookie, - ] - ); + Assert::eq($cli->set_cookie_headers, [ + 'cookie=' . $cookie_encoded, + 'rawcookie=' . $cookie_encoded, + ]); }); for ($i = MAX_CONCURRENCY_LOW; $i--;) { go(function () use ($pm) { @@ -41,9 +40,9 @@ $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); $http->set(['worker_num' => 1, 'log_file' => '/dev/null']); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { - $request->get['cookie'] = urldecode($request->get['cookie']); - $response->cookie('cookie', $request->get['cookie']); - $response->rawcookie('rawcookie', $request->get['cookie']); + $cookie = $request->get['cookie']; + $response->cookie('cookie', $cookie); + $response->rawcookie('rawcookie', urlencode($cookie)); $response->end(); }); $http->start(); @@ -51,5 +50,5 @@ $pm->childFunc = function () use ($pm) { $pm->childFirst(); $pm->run(); ?> ---EXPECT-- +--EXPECTF-- SUCCESS diff --git a/tests/swoole_http_server/disable_compression.phpt b/tests/swoole_http_server/disable_compression.phpt new file mode 100644 index 0000000000..8bd47e68bc --- /dev/null +++ b/tests/swoole_http_server/disable_compression.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_http_server: disable compression +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($client->get('/')); + Assert::eq(md5_file(__DIR__ . '/../../README.md'), md5($client->getBody())); + Assert::keyNotExists($client->headers, 'content-encoding'); + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->on("WorkerStart", function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on("request", function ($request, Swoole\Http\Response $response) { + // Set Content-Encoding header to empty to disable compression + $response->header('Content-Encoding', ''); + $response->end(co::readFile(__DIR__ . '/../../README.md')); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/duplicate_header.phpt b/tests/swoole_http_server/duplicate_header.phpt index 37743d6715..129220808e 100644 --- a/tests/swoole_http_server/duplicate_header.phpt +++ b/tests/swoole_http_server/duplicate_header.phpt @@ -12,7 +12,15 @@ $pm->parentFunc = function () use ($pm) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://127.0.0.1:{$pm->getFreePort()}/"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_HEADER, TRUE); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'X-Test-Header1: value1', + 'X-Test-Header2: value2', + 'X-Test-Header2: value3', + 'X-Test-Header3: value4', + 'X-Test-Header3: value5', + 'X-Test-Header3: value6', + ]); + curl_setopt($ch, CURLOPT_HEADER, true); echo curl_exec($ch); curl_close($ch); $pm->kill(); @@ -30,6 +38,9 @@ $pm->childFunc = function () use ($pm) { }); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $msg = "hello world"; + Assert::eq($request->header['x-test-header1'], 'value1'); + Assert::eq($request->header['x-test-header2'], ['value2', 'value3']); + Assert::eq($request->header['x-test-header3'], ['value4', 'value5', 'value6']); $response->header("content-length", strlen($msg) . " "); $response->header("Test-Value", [ "a\r\n", diff --git a/tests/swoole_http_server/event_stream.phpt b/tests/swoole_http_server/event_stream.phpt new file mode 100644 index 0000000000..948d0c400c --- /dev/null +++ b/tests/swoole_http_server/event_stream.phpt @@ -0,0 +1,76 @@ +--TEST-- +swoole_http_server: event stream +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $data) { + Co\run(function () use ($pm, $data) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + + $index = 0; + $buffer = ''; + $client->set([ + 'timeout' => 60, + 'write_func' => function($client, $chunk) use (&$buffer, &$index, $data) { + $buffer .= $chunk; + while(true) { + $position = mb_strpos($buffer, "\n\n"); + if ($position === false) { + break; + } + + Assert::eq(mb_substr($buffer, 0, $position) . "\n\n", $data[$index]); + $buffer = mb_substr($buffer, $position + 2); + $index++; + } + + return true; + } + ]); + Assert::true($client->get('/')); + Assert::isEmpty($client->getBody()); + Assert::keyNotExists($client->headers, 'content-length'); + Assert::eq($client->headers['content-type'], "text/event-stream"); + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $data) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('WorkerStart', function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on('request', function ($req, Swoole\Http\Response $resp) use ($http, $data) { + $resp->header("Content-Type", "text/event-stream"); + $resp->header("Cache-Control", "no-cache"); + $resp->header("Connection", "keep-alive"); + $resp->header("X-Accel-Buffering", "no"); + + for ($i = 0; $i < N; $i++) { + Co::sleep(0.01); + $resp->write($data[$i]); + } + + $resp->write("data: [DONE]\n\n"); + $resp->end(); + Co::sleep(0.05); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/event_stream2.phpt b/tests/swoole_http_server/event_stream2.phpt new file mode 100644 index 0000000000..cd84eafde6 --- /dev/null +++ b/tests/swoole_http_server/event_stream2.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_server: event stream 2 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $data) { + Co\run(function () use ($pm, $data) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($client->get('/')); + Assert::isEmpty($client->getBody()); + Assert::keyNotExists($client->headers, 'content-length'); + Assert::eq($client->headers['content-type'], "text/event-stream"); + for ($i = 0; $i < N; $i++) { + Co::sleep(0.01); + $line1 = $client->socket->recvLine(); + $line2 = $client->socket->recvLine(); + Assert::eq($line1 . $line2, $data[$i]); + } + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $data) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('WorkerStart', function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on('request', function ($req, Swoole\Http\Response $resp) use ($http, $data) { + $resp->header("Content-Type", "text/event-stream"); + $resp->header("Cache-Control", "no-cache"); + $resp->header("Connection", "keep-alive"); + $resp->header("X-Accel-Buffering", "no"); + $resp->header('Content-Encoding', ''); + $resp->header("Content-Length", ''); + $resp->end(); + Co::sleep(0.05); + for ($i = 0; $i < N; $i++) { + Co::sleep(0.01); + $http->send($resp->fd, $data[$i]); + } + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/form_data_1.phpt b/tests/swoole_http_server/form_data_1.phpt index 03445b3d10..88493f4fc6 100644 --- a/tests/swoole_http_server/form_data_1.phpt +++ b/tests/swoole_http_server/form_data_1.phpt @@ -11,13 +11,11 @@ use Swoole\Http\Server; use Swoole\Http\Request; use Swoole\Http\Response; -const OFFSET = 250; - $pm = new ProcessManager; $pm->initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { - form_data_test_1($pm); + form_data_test($pm, [250]); }; $pm->childFunc = function () use ($pm) { diff --git a/tests/swoole_http_server/issue_2360.phpt b/tests/swoole_http_server/issue_2360.phpt index ccd5998784..5638c3b494 100644 --- a/tests/swoole_http_server/issue_2360.phpt +++ b/tests/swoole_http_server/issue_2360.phpt @@ -1,12 +1,21 @@ --TEST-- swoole_http_server: issue 2360 (Swoole\Http\Server silently fails to read requests) --SKIPIF-- - + --FILE-- setRandomFunc(function () { $size = mt_rand(SOCKET_BUFFER_SIZE, SOCKET_BUFFER_SIZE << 3); // 1024 ~ 65536 @@ -19,31 +28,31 @@ $pm->setRandomFunc(function () { $pm->initRandomDataEx(1, MAX_REQUESTS); $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { - $cli = new Co\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set(['socket_buffer_size' => SOCKET_BUFFER_SIZE]); for ($n = MAX_REQUESTS; $n--;) { $data = $pm->getRandomData(); Assert::true($cli->post('/', $data)); Assert::same($cli->statusCode, 200); Assert::same($cli->body, $data); - phpt_echo("posting " . strlen($data) . " bytes\n"); + phpt_echo('posting ' . strlen($data) . " bytes\n"); } $cli->close(); }); - Swoole\Event::wait(); + Event::wait(); $pm->kill(); echo "DONE\n"; }; $pm->childFunc = function () use ($pm) { - $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', - 'socket_buffer_size' => SOCKET_BUFFER_SIZE + 'socket_buffer_size' => SOCKET_BUFFER_SIZE, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); - $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $server->on('request', function (Request $request, Response $response) use ($pm) { phpt_echo("received {$request->header['content-length']} bytes\n"); if (Assert::assert($request->rawContent() === $pm->getRandomData())) { $response->end($request->rawContent()); diff --git a/tests/swoole_http_server/json_encode.phpt b/tests/swoole_http_server/json_encode.phpt new file mode 100644 index 0000000000..787ebb024f --- /dev/null +++ b/tests/swoole_http_server/json_encode.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_http_server: json_encode or serialize Swoole\Http\Request::class OR Swoole\Http\Response::class +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test") . PHP_EOL; + $pm->kill(); + }); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $server->set(['log_file' => '/dev/null']); + $server->on('start', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + Assert::true($request->fd > 0); + Assert::true($response->fd > 0); + + $result = json_decode(json_encode($request), true); + Assert::true($result['fd'] > 0); + + $result = json_decode(json_encode($response), true); + Assert::true($result['fd'] > 0); + + $response->end('OK'); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +OK diff --git a/tests/swoole_http_server/json_encode2.phpt b/tests/swoole_http_server/json_encode2.phpt new file mode 100644 index 0000000000..89ca75f6e0 --- /dev/null +++ b/tests/swoole_http_server/json_encode2.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_http_server: json_encode or serialize Swoole\Http\Request::class OR Swoole\Http\Response::class +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/test") . PHP_EOL; + $pm->kill(); + }); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $server->set(['log_file' => '/dev/null']); + $server->on('start', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $result = json_decode(json_encode($request), true); + Assert::true($result['fd'] > 0); + + $result = json_decode(json_encode($response), true); + Assert::true($result['fd'] > 0); + + Assert::true($request->fd > 0); + Assert::true($response->fd > 0); + + $response->end('OK'); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +OK diff --git a/tests/swoole_http_server/max-age.phpt b/tests/swoole_http_server/max-age.phpt new file mode 100644 index 0000000000..610f1faa2d --- /dev/null +++ b/tests/swoole_http_server/max-age.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_http_server: cookies (max-age) +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $uri = "http://127.0.0.1:{$pm->getFreePort()}"; + $cookies = httpRequest($uri)['set_cookie_headers']; + + var_dump(strpos($cookies[0], 'test=123456789') !== false); + var_dump(strpos($cookies[0], 'expires='.date('D, d-M-Y H:i:s \G\M\T', time() + 3600)) !== false); + var_dump(strpos($cookies[0], 'Max-Age=3600') !== false); + var_dump(strpos($cookies[0], 'path=/') !== false); + var_dump(strpos($cookies[0], 'domain=example.com') !== false); + var_dump(strpos($cookies[0], 'secure') !== false); + var_dump(strpos($cookies[0], 'HttpOnly') !== false); + var_dump(strpos($cookies[0], 'SameSite=None') !== false); + var_dump(strpos($cookies[1], 'test=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT') !== false); + var_dump(strpos($cookies[1], 'Max-Age=0') !== false); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $response->cookie('test', '123456789', time() + 3600, '/', 'example.com', true, true, 'None'); + $response->cookie('test', ''); + $response->end(); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +DONE diff --git a/tests/swoole_http_server/max_execution_time.phpt b/tests/swoole_http_server/max_execution_time.phpt new file mode 100644 index 0000000000..48ce5fa9df --- /dev/null +++ b/tests/swoole_http_server/max_execution_time.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server: max execution time +--SKIPIF-- + +--FILE-- +initRandomData(2); +$pm->parentFunc = function ($pid) use ($pm) { + go(function () use ($pm) { + $result = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); + var_dump($result); + $pm->kill(); + }); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + + $http->set( + [ + 'enable_coroutine' => true, + 'hook_flags' => SWOOLE_HOOK_ALL, + ] + ); + + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + try { + Swoole\Coroutine::setTimeLimit(1); + sleep(5); + $response->header('Content-Type', 'text/plain'); + $response->end('Hello World'); + } catch (\Throwable $e) { + Assert::true($e instanceof \Swoole\Coroutine\TimeoutException); + $response->end('execution timeout'); + } + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +string(17) "execution timeout" diff --git a/tests/swoole_http_server/max_input_vars.phpt b/tests/swoole_http_server/max_input_vars.phpt new file mode 100644 index 0000000000..fd5aae3498 --- /dev/null +++ b/tests/swoole_http_server/max_input_vars.phpt @@ -0,0 +1,67 @@ +--TEST-- +swoole_http_server: max_input_vars +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Swoole\Coroutine\run(function () use ($pm) { + $maxInputVars = ini_get('max_input_vars') + 10; + $data = []; + $cookies = []; + $temp = 'max_input_vars'; + for ($i = 0; $i < $maxInputVars; $i++) { + $data[$temp . $i] = $temp; + $cookies[] = $temp . $i.'='.$temp; + } + + // post method + httpRequest("http://127.0.0.1:{$pm->getFreePort()}", ['data' => $data, 'headers' => ['Cookie' => implode(';', $cookies)]]); + + // get method + httpRequest("http://127.0.0.1:{$pm->getFreePort()}?".http_build_query($data)); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response){ + $maxInputVars = ini_get('max_input_vars'); + if ($request->get) { + var_dump(count($request->get) == $maxInputVars); + } + + if ($request->post) { + var_dump(count($request->post) == $maxInputVars); + } + + if ($request->cookie) { + var_dump(count($request->cookie) == $maxInputVars); + } + $response->end(); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s To increase the limit change max_input_vars in php.ini. +%s To increase the limit change max_input_vars in php.ini. +bool(true) +bool(true) +%s To increase the limit change max_input_vars in php.ini. +bool(true) +DONE diff --git a/tests/swoole_http_server/new_cookie.phpt b/tests/swoole_http_server/new_cookie.phpt new file mode 100644 index 0000000000..17fc34117c --- /dev/null +++ b/tests/swoole_http_server/new_cookie.phpt @@ -0,0 +1,74 @@ +--TEST-- +swoole_http_server: new cookie +--SKIPIF-- + +--FILE-- +withName('test') + ->withValue('123456789') + ->withExpires(time() + 3600) + ->withPath('/path') + ->withDomain('example.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite('None'); + +var_dump($cookie->toArray()); +$cookie->reset(); +var_dump($cookie->toArray()); +?> +--EXPECTF-- +array(11) { + ["name"]=> + string(4) "test" + ["value"]=> + string(9) "123456789" + ["path"]=> + string(5) "/path" + ["domain"]=> + string(11) "example.com" + ["sameSite"]=> + string(4) "None" + ["priority"]=> + string(0) "" + ["encode"]=> + bool(true) + ["expires"]=> + int(%d) + ["secure"]=> + bool(true) + ["httpOnly"]=> + bool(true) + ["partitioned"]=> + bool(false) +} +array(11) { + ["name"]=> + string(0) "" + ["value"]=> + string(0) "" + ["path"]=> + string(0) "" + ["domain"]=> + string(0) "" + ["sameSite"]=> + string(0) "" + ["priority"]=> + string(0) "" + ["encode"]=> + bool(true) + ["expires"]=> + int(0) + ["secure"]=> + bool(false) + ["httpOnly"]=> + bool(false) + ["partitioned"]=> + bool(false) +} diff --git a/tests/swoole_http_server/numeric_header_name.phpt b/tests/swoole_http_server/numeric_header_name.phpt new file mode 100644 index 0000000000..001dc1300d --- /dev/null +++ b/tests/swoole_http_server/numeric_header_name.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_http_server: numeric header name +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($client->get('/')); + Assert::eq($client->headers['12345'], 'hello'); + Assert::eq($client->headers['12345.678'], 'world'); + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP); + $http->on("WorkerStart", function ($serv, $wid) { + global $pm; + $pm->wakeup(); + }); + $http->on("request", function ($request, Swoole\Http\Response $response) { + $response->header(12345, 'hello'); + $response->header(12345.678, 'world'); + $response->end('OK'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/objectCookie.phpt b/tests/swoole_http_server/objectCookie.phpt new file mode 100644 index 0000000000..4926279ab7 --- /dev/null +++ b/tests/swoole_http_server/objectCookie.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server: new cookie +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\Run(function () use ($pm) { + var_dump(httpRequest("http://127.0.0.1:{$pm->getFreePort()}")['set_cookie_headers']); + }); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $server->set(['log_file' => '/dev/null']); + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $cookie = new Swoole\Http\Cookie(); + $cookie->withName('key1') + ->withValue('val1') + ->withExpires(time() + 84600) + ->withPath('/') + ->withDomain('id.test.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite('None') + ->withPriority('High') + ->withPartitioned(true); + $response->setCookie($cookie); + $cookie->withValue(''); + $response->setCookie($cookie); + $response->end("

    Hello Swoole. #" . rand(1000, 9999) . "

    "); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +array(2) { + [0]=> + string(152) "key1=val1; expires=%s; Max-Age=84600; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" + [1]=> + string(151) "key1=deleted; expires=%s; Max-Age=0; path=/; domain=id.test.com; secure; HttpOnly; SameSite=None; Priority=High; Partitioned" +} diff --git a/tests/swoole_http_server/objectCookieMemory.phpt b/tests/swoole_http_server/objectCookieMemory.phpt new file mode 100644 index 0000000000..32d05d4cd5 --- /dev/null +++ b/tests/swoole_http_server/objectCookieMemory.phpt @@ -0,0 +1,77 @@ +--TEST-- +swoole_http_server: new cookie memory +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Swoole\Coroutine\run(function () use ($pm) { + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + httpRequest("http://127.0.0.1:{$pm->getFreePort()}"); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $previous = memory_get_usage(); + $cookie = new Swoole\Http\Cookie(); + $i = 10000; + while($i--) { + $cookie->withName('key1') + ->withValue('val1') + ->withExpires(time() + 84600) + ->withPath('/') + ->withDomain('id.test.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite('None') + ->withPriority('High') + ->withPartitioned(true); + } + + global $previous; + global $item; + $current = memory_get_usage(); + $stats = [ + 'id' => $http->getWorkerId(), + 'item' => $item++, + 'prev_mem' => $previous, + 'curr_mem' => $current, + 'diff_mem' => $current - $previous, + ]; + $previous = $current; + + echo json_encode($stats), PHP_EOL; + $response->end('test response'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +{"id":%d,"item":null,"prev_mem":null,"curr_mem":%d,"diff_mem":%d} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":%d} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +{"id":%d,"item":%d,"prev_mem":%d,"curr_mem":%d,"diff_mem":0} +DONE diff --git a/tests/swoole_http_server/octane_bug_651.phpt b/tests/swoole_http_server/octane_bug_651.phpt new file mode 100644 index 0000000000..5b6af6fe5b --- /dev/null +++ b/tests/swoole_http_server/octane_bug_651.phpt @@ -0,0 +1,68 @@ +--TEST-- +swoole_http_server: Octane bug 651 https://github.com/laravel/octane/issues/651 +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $client = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + Assert::true($client->get('/')); + Assert::eq($client->getBody(), 'timeout'); + $pm->kill(); + }); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); + $http->set(['log_file' => '/dev/null']); + + $timerTable = new Swoole\Table(250); + $timerTable->column('worker_pid', Swoole\Table::TYPE_INT); + $timerTable->column('time', Swoole\Table::TYPE_INT); + $timerTable->column('fd', Swoole\Table::TYPE_INT); + $timerTable->create(); + + $http->on("WorkerStart", function ($serv, $wid) use ($pm) { + $pm->wakeup(); + }); + + $http->on('start', function ($server) use ($timerTable) { + Swoole\Timer::tick(500, function ($id) use ($timerTable, $server) { + foreach ($timerTable as $workerId => $row) { + if ((time() - $row['time']) > 3) { + $timerTable->del($workerId); + $newRes = Swoole\Http\Response::create($server, $row['fd']);; + if ($newRes) { + Swoole\Timer::clear($id); + $newRes->status(408); + $newRes->end('timeout'); + Swoole\Process::kill($row['worker_pid'], 9); + return; + } + } + } + }); + }); + + $http->on('Request', function ($request, $response) use ($http, $timerTable) { + $timerTable->set($http->getWorkerId(), [ + 'worker_pid' => $http->getWorkerPid(), + 'time' => time(), + 'fd' => $request->fd, + ]); + sleep(10); + $response->end('Hello'); + $timerTable->del($http->getWorkerId()); + }); + + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_http_server/range.phpt b/tests/swoole_http_server/range.phpt new file mode 100644 index 0000000000..5891fc03c4 --- /dev/null +++ b/tests/swoole_http_server/range.phpt @@ -0,0 +1,158 @@ +--TEST-- +swoole_http_server: range +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + foreach ([false, true] as $http2) { + Swoole\Coroutine\run(function () use ($pm, $http2) { + $data2 = file_get_contents(TEST_IMAGE); + + // range + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15']]); + Assert::same($response['statusCode'], 206); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); + Assert::same('bytes 0-15/218787', $response['headers']['content-range']); + $lastModified = $response['headers']['last-modified'] ?? null; + Assert::notNull($lastModified); + Assert::null($response['headers']['accept-ranges'] ?? null); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-']]); + Assert::same($response['statusCode'], 206); + Assert::same('bytes 0-218786/218787', $response['headers']['content-range']); + Assert::same(bin2hex($response['body']), bin2hex($data2)); + // exit; + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=16-31']]); + Assert::same($response['statusCode'], 206); + Assert::same('bytes 16-31/218787', $response['headers']['content-range']); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 16, 16))); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=-16']]); + Assert::same($response['statusCode'], 206); + Assert::same('bytes 218771-218786/218787', $response['headers']['content-range']); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, -16))); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=128-']]); + Assert::same($response['statusCode'], 206); + Assert::same('bytes 128-218786/218787', $response['headers']['content-range']); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 128))); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-0,-1']]); + Assert::same($response['statusCode'], 206); + Assert::isEmpty($response['headers']['content-range'] ?? null); + Assert::notEq(preg_match('/multipart\/byteranges; boundary=(.+)/', $response['headers']['content-type'] ?? '', $matches), false); + $boundary = $matches[1]; + $expect = sprintf(<<getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15,32-63']]); + Assert::same($response['statusCode'], 206); + Assert::notEq(preg_match('/multipart\/byteranges; boundary=(.+)/', $response['headers']['content-type'] ?? '', $matches), false); + $boundary = $matches[1]; + $expect = sprintf(<<getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => $lastModified]]); + Assert::same($response['statusCode'], 206); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => 'test']]); + Assert::same($response['statusCode'], 206); + Assert::same(bin2hex($response['body']), bin2hex(substr($data2, 0, 16))); + + $lastModifiedTime = strtotime($lastModified); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => date(DATE_RFC7231, $lastModifiedTime - 1)]]); + Assert::same($response['statusCode'], 200); + Assert::same(bin2hex($response['body']), bin2hex($data2)); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=0-15', 'If-Range' => date(DATE_RFC7231, $lastModifiedTime + 1)]]); + Assert::same($response['statusCode'], 200); + Assert::same(bin2hex($response['body']), bin2hex($data2)); + + // head + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'method' => 'HEAD']); + Assert::same($response['statusCode'], 200); + Assert::isEmpty($response['body']); + Assert::same($response['headers']['accept-ranges'], 'bytes'); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'method' => 'HEAD', 'headers' => ['Range' => 'bytes=0-15']]); + Assert::same($response['statusCode'], 206); + Assert::same('bytes 0-15/218787', $response['headers']['content-range']); + Assert::isEmpty($response['body']); + Assert::null($response['headers']['accept-ranges'] ?? null); + + // data boundary + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'abc']]); + Assert::same($response['statusCode'], 200); + Assert::same(bin2hex($response['body']), bin2hex($data2)); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=abc']]); + Assert::same($response['statusCode'], 416); + Assert::isEmpty($response['body']); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=-999999']]); + Assert::same($response['statusCode'], 206); + Assert::same(bin2hex($response['body']), bin2hex($data2)); + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['Range' => 'bytes=999999']]); + Assert::same($response['statusCode'], 416); + Assert::isEmpty($response['body']); + }); + } + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => dirname(dirname(__DIR__)) . '/examples/', + 'static_handler_locations' => ['/static', '/'] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end('hello world'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/range2.phpt b/tests/swoole_http_server/range2.phpt new file mode 100644 index 0000000000..37cbfe90d6 --- /dev/null +++ b/tests/swoole_http_server/range2.phpt @@ -0,0 +1,50 @@ +--TEST-- +swoole_http_server: range - confusing header +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + foreach ([false, true] as $http2) { + Swoole\Coroutine\run(function () use ($pm, $http2) { + $data2 = file_get_contents(TEST_IMAGE); + + // range + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/test.jpg", ['http2' => $http2, 'headers' => ['-Range' => 'none', 'Range' => 'bytes=0-15']]); + Assert::same($response['statusCode'], 206); + }); + } + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Assert::true(swoole_mime_type_add('moc', 'application/x-mocha')); + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => dirname(dirname(__DIR__)) . '/examples/', + 'static_handler_locations' => ['/static', '/'] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end('hello world'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/rawCookie.phpt b/tests/swoole_http_server/rawCookie.phpt index ed2be373e8..59a0e2e33b 100644 --- a/tests/swoole_http_server/rawCookie.phpt +++ b/tests/swoole_http_server/rawCookie.phpt @@ -43,7 +43,7 @@ $pm->childFunc = function () use ($pm, $simple_http_server) { $httpOnly = true; // string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false ]]]]]] $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); - $expect = "name=value; path=/; httponly"; + $expect = "name=value; path=/; HttpOnly"; Assert::assert(in_array($expect, $response->cookie, true)); $response->cookie($name, $value, $expire, $path, $domain, $secure, $httpOnly); $response->rawcookie("rawcontent", $request->rawcontent()); diff --git a/tests/swoole_http_server/reset_concurrency_with_base.phpt b/tests/swoole_http_server/reset_concurrency_with_base.phpt new file mode 100644 index 0000000000..1c3b12016a --- /dev/null +++ b/tests/swoole_http_server/reset_concurrency_with_base.phpt @@ -0,0 +1,90 @@ +--TEST-- +swoole_http_server: reset concurrency [SWOOLE_BASE] +--SKIPIF-- + +--FILE-- +column('pid', Table::TYPE_INT); +$table->create(); + +$pm = new SwooleTest\ProcessManager; +$pm->parentFunc = function () use ($pm) { + run(function () use ($pm) { + $n = N; + $coroutines = []; + while ($n--) { + $coroutines[] = go(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['timeout' => 10]); + Assert::eq($client->get('/'), false); + Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); + }); + } + + Co::join($coroutines); + Co::sleep(0.1); + $client = new Client('127.0.0.1', $pm->getFreePort()); + Assert::assert($client->get('/')); + $stats = json_decode($client->getBody()); + Assert::eq($stats->concurrency, 1); + $pm->kill(); + echo "DONE\n"; + }); +}; + +$pm->childFunc = function () use ($pm, $counter, $table) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'worker_num' => 4, + 'max_concurrency' => 160, + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function ($server, $wid) use ($pm, $table) { + if ($wid === 0) { + $pm->wakeup(); + } + $pid = posix_getpid(); + $table->set('worker_' . $wid, ['pid' => $pid]); + // echo "Worker #{$wid}(pid=$pid) is started\n"; + }); + $http->on('request', function (Request $request, Response $response) use ($http, $counter, $table) { + $c = $counter->add(); + if ($c < N) { + Co::sleep(100); + } elseif ($c == N) { + $stats = $http->stats(); + Assert::eq($stats['concurrency'], N); + $pid = posix_getpid(); + foreach ($table as $val) { + if ($val['pid'] !== $pid) { + posix_kill($val['pid'], SIGKILL); + } + } + posix_kill($pid, SIGKILL); + } else { + $stats = $http->stats(); + Assert::eq($stats['concurrency'], 1); + $response->end(json_encode($stats)); + } + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/reset_concurrency_with_process.phpt b/tests/swoole_http_server/reset_concurrency_with_process.phpt new file mode 100644 index 0000000000..97cbf5432a --- /dev/null +++ b/tests/swoole_http_server/reset_concurrency_with_process.phpt @@ -0,0 +1,97 @@ +--TEST-- +swoole_http_server: reset concurrency [SWOOLE_PROCESS] +--SKIPIF-- + +--FILE-- +column('pid', Table::TYPE_INT); +$table->create(); + +$pm = new SwooleTest\ProcessManager; +$pm->parentFunc = function () use ($pm) { + run(function () use ($pm) { + $n = N; + $coroutines = []; + while ($n--) { + $coroutines[] = go(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['timeout' => 10]); + Assert::eq($client->get('/'), false); + Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); + }); + } + + Co::sleep(0.1); + $pm->wait(); + + $client = new Client('127.0.0.1', $pm->getFreePort()); + Assert::assert($client->get('/')); + $stats = json_decode($client->getBody()); + Assert::eq($stats->concurrency, 1); + + /** + * PROCESS 模式下 Worker 进程退出时连接不会被关闭,这与 BASE 模式不同,因此需要先关闭服务器,其他正在运行的协程才会获得返回值 + */ + $pm->kill(); + + Co::join($coroutines); + echo "DONE\n"; + }); +}; + +$pm->childFunc = function () use ($pm, $counter, $table) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $http->set([ + 'worker_num' => 4, + 'max_concurrency' => 160, + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function ($server, $wid) use ($pm, $table) { + if ($wid === 0) { + $pm->wakeup(); + } + $pid = posix_getpid(); + $table->set('worker_' . $wid, ['pid' => $pid]); + // echo "Worker #{$wid}(pid=$pid) is started\n"; + }); + $http->on('request', function (Request $request, Response $response) use ($http, $counter, $table) { + $c = $counter->add(); + if ($c < N) { + Co::sleep(100); + } elseif ($c == N) { + $stats = $http->stats(); + Assert::eq($stats['concurrency'], N); + $pid = posix_getpid(); + foreach ($table as $val) { + if ($val['pid'] !== $pid) { + posix_kill($val['pid'], SIGKILL); + } + } + posix_kill($pid, SIGKILL); + } else { + $stats = $http->stats(); + Assert::eq($stats['concurrency'], 1); + $response->end(json_encode($stats)); + } + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/send_500_to_client.phpt b/tests/swoole_http_server/send_500_to_client.phpt new file mode 100644 index 0000000000..6f16989148 --- /dev/null +++ b/tests/swoole_http_server/send_500_to_client.phpt @@ -0,0 +1,88 @@ +--TEST-- +swoole_http_server: When the process restarts, send a 500 status code to the clients waiting in the queue +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::setHookFlags(SWOOLE_HOOK_ALL); + $statusCodes = []; + run(function () use ($pm, &$statusCodes) { + $waitGroup = new WaitGroup(); + $index = 0; + go(function () use ($waitGroup, $pm, &$index, &$statusCodes) { + $waitGroup->add(); + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['timeout' => 15]); + $client->get('/?id=' . $index++); + if (!isset($statusCodes[$client->statusCode])) { + $statusCodes[$client->statusCode] = 0; + } + $statusCodes[$client->statusCode]++; + $waitGroup->done(); + }); + + sleep(1); + + for ($i = 0; $i < 10; $i++) { + go(function () use ($waitGroup, $pm, &$index, &$statusCodes) { + $waitGroup->add(); + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['timeout' => 15]); + $client->get('/?id=' . $index++); + if (!isset($statusCodes[$client->statusCode])) { + $statusCodes[$client->statusCode] = 0; + } + $statusCodes[$client->statusCode]++; + $waitGroup->done(); + }); + } + + $waitGroup->wait(); + $pm->kill(); + }); + Assert::greaterThanEq($statusCodes[503], 8); + Assert::lessThanEq($statusCodes[200], 3); + echo 'DONE'; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $http->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'enable_coroutine' => true, + 'worker_max_concurrency' => 1, + 'max_wait_time' => 10, + 'reload_async' => true, + 'hook_flags' => SWOOLE_HOOK_ALL, + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $http->reload(); + sleep(2); + $response->end("id=" . $request->get['id']); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/server_addr.phpt b/tests/swoole_http_server/server_addr.phpt new file mode 100644 index 0000000000..e0809edf66 --- /dev/null +++ b/tests/swoole_http_server/server_addr.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_http_server: add server addr +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm, $ips) { + Co\run(function () use ($pm, $ips) { + $body = httpGetBody("http://{$ips[1]}:{$pm->getFreePort()}"); + Assert::eq($body, 'Hello World'); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm, $ips) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm, $ips) { + $server = $request->server; + Assert::eq($server['server_addr'], $ips[1]); + Assert::eq($server['remote_addr'], $ips[1]); + Assert::true($server['server_port'] != $server['remote_port']); + $response->status(200, "status"); + $response->end("Hello World"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_http_server/server_addr2.phpt b/tests/swoole_http_server/server_addr2.phpt new file mode 100644 index 0000000000..c92263197e --- /dev/null +++ b/tests/swoole_http_server/server_addr2.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_http_server: add server addr2 +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $body = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}"); + Assert::eq($body, 'Hello World'); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_BASE); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $server = $request->server; + Assert::eq($server['server_addr'], '127.0.0.1'); + Assert::eq($server['remote_addr'], '127.0.0.1'); + Assert::true($server['server_port'] != $server['remote_port']); + $response->status(200, "status"); + $response->end("Hello World"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_http_server/shutdown_in_event_worker.phpt b/tests/swoole_http_server/shutdown_in_event_worker.phpt new file mode 100644 index 0000000000..d555a71729 --- /dev/null +++ b/tests/swoole_http_server/shutdown_in_event_worker.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server: shutdown in worker process +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); + }); + usleep(100000); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set([ + 'worker_num' => 2, + 'log_file' => '/dev/null', + ]); + + $serv->on('ManagerStart', function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + + $serv->on('workerStop', function ($server) { + echo "worker exit\n"; + }); + + $serv->on('Request', function (Request $request, Response $response) use ($serv, $pm) { + $response->end('Hello Swoole'); + $serv->shutdown(); + $pm->wakeup(); + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +worker exit +worker exit diff --git a/tests/swoole_http_server/shutdown_in_task_worker.phpt b/tests/swoole_http_server/shutdown_in_task_worker.phpt new file mode 100644 index 0000000000..bebdaa8284 --- /dev/null +++ b/tests/swoole_http_server/shutdown_in_task_worker.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_http_server: shutdown in task process +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); + }); + usleep(100000); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set([ + 'worker_num' => 2, + 'task_worker_num' => 1, + 'log_file' => '/dev/null', + ]); + + $serv->on('ManagerStart', function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + + $serv->on('workerStop', function ($server) { + echo "worker exit\n"; + }); + + $serv->on('Task', function ($server, $taskId, $workerId, $data) use ($pm) { + $server->shutdown(); + $pm->wakeup(); + }); + + $serv->on('Request', function (Request $request, Response $response) use ($serv) { + $response->end('Hello Swoole'); + $serv->task('a'); + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +worker exit +worker exit +worker exit diff --git a/tests/swoole_http_server/static_handler/read_link_2.phpt b/tests/swoole_http_server/static_handler/read_link_2.phpt new file mode 100644 index 0000000000..6ce49e3866 --- /dev/null +++ b/tests/swoole_http_server/static_handler/read_link_2.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_server/static_handler: link to a file outside the document root +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $doc_root, $image_dir, $image_link) { + Swoole\Coroutine\run(function () use ($pm, $doc_root, $image_dir, $image_link) { + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/{$image_dir}/image.jpg"); + Assert::assert(md5($data) === md5_file(TEST_IMAGE)); + }); + $pm->kill(); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $doc_root, $image_dir, $image_link) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => $doc_root, + 'static_handler_locations' => ['/image'] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) { + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +$cleanup_fn(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/static_handler/read_link_file.phpt b/tests/swoole_http_server/static_handler/read_link_file.phpt index 14046892a4..f7bc7b2893 100644 --- a/tests/swoole_http_server/static_handler/read_link_file.phpt +++ b/tests/swoole_http_server/static_handler/read_link_file.phpt @@ -29,7 +29,7 @@ $pm->childFunc = function () use ($pm) { 'log_file' => '/dev/null', 'open_http2_protocol' => true, 'enable_static_handler' => true, - 'document_root' => dirname(dirname(dirname(__DIR__))) . '/', + 'document_root' => dirname(__DIR__, 3) . '/', 'static_handler_locations' => ['/examples'] ]); $http->on('workerStart', function () use ($pm) { diff --git a/tests/swoole_http_server/static_handler/relative_path_2.phpt b/tests/swoole_http_server/static_handler/relative_path_2.phpt new file mode 100644 index 0000000000..26c06d14c2 --- /dev/null +++ b/tests/swoole_http_server/static_handler/relative_path_2.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server/static_handler: static handler with relative path [2] +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + foreach ([false, true] as $http2) { + Swoole\Coroutine\run(function () use ($pm, $http2) { + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../examples/test.jpg", ['http2' => $http2]); + Assert::notEmpty($data); + Assert::same(md5($data), md5_file(TEST_IMAGE)); + + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docs/swoole-logo.svg", ['http2' => $http2]); + Assert::eq("hello world", $data); + }); + } + echo "DONE\n"; + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => SOURCE_ROOT_PATH . '/examples', + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end("hello world"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/static_handler/relative_path_3.phpt b/tests/swoole_http_server/static_handler/relative_path_3.phpt new file mode 100644 index 0000000000..a174c88cd4 --- /dev/null +++ b/tests/swoole_http_server/static_handler/relative_path_3.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_http_server/static_handler: doc root with same prefix +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $doc1_root, $doc2_root) { + Swoole\Coroutine\run(function () use ($pm, $doc1_root, $doc2_root) { + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot/image.jpg"); + Assert::assert(md5($data) === md5_file(TEST_IMAGE)); + + $data = httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/../docroot2/uuid.txt"); + Assert::isEmpty($data); + }); + $pm->kill(); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $doc1_root, $doc2_root) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + 'open_http2_protocol' => true, + 'enable_static_handler' => true, + 'document_root' => $doc1_root, + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) { + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +$cleanup_fn(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/task/enable_coroutine_with_wrong_usage.phpt b/tests/swoole_http_server/task/enable_coroutine_with_wrong_usage.phpt index 266a44e8be..cceb2cfc03 100644 --- a/tests/swoole_http_server/task/enable_coroutine_with_wrong_usage.phpt +++ b/tests/swoole_http_server/task/enable_coroutine_with_wrong_usage.phpt @@ -36,3 +36,9 @@ $pm->run(); ?> --EXPECTF-- Fatal error: Swoole\Server::finish(): please use Swoole\Server\Task->finish instead when task_enable_coroutine is enable in %s/task/enable_coroutine_with_wrong_usage.php on line %d +--EXPECTF_85-- +Fatal error: Swoole\Server::finish(): please use Swoole\Server\Task->finish instead when task_enable_coroutine is enable in %s/task/enable_coroutine_with_wrong_usage.php on line %d +Stack trace: +#0 %s(%d): Swoole\Server->finish('bar') +#1 [internal function]: {closure:{closure:%s:%d}:%d}(Object(Swoole\Http\Server), Object(Swoole\Server\Task)) +#2 {main} diff --git a/tests/swoole_http_server/task/use_object.phpt b/tests/swoole_http_server/task/use_object.phpt index 6a64fee608..0fc9f58661 100644 --- a/tests/swoole_http_server/task/use_object.phpt +++ b/tests/swoole_http_server/task/use_object.phpt @@ -6,22 +6,29 @@ require __DIR__ . '/../../include/skipif.inc'; ?> --FILE-- set([ 'log_file' => '/dev/null', 'task_worker_num' => 1, - 'task_use_object' => true + 'task_use_object' => true, ]); -$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { +$server->on('request', function (Request $request, Response $response) use ($server) { $response->end("Hello Swoole\n"); }); -$server->on('managerStart', function (Swoole\Http\Server $server) { +$server->on('managerStart', function (Server $server) { $server->task(''); }); -$server->on('task', function ($_, Swoole\Server\Task $task) use ($server) { +$server->on('task', function ($_, Task $task) use ($server) { var_dump(func_num_args()); var_dump(func_get_args()[1]); Assert::same($task->flags & SWOOLE_TASK_NOREPLY, SWOOLE_TASK_NOREPLY); + usleep(100000); $server->shutdown(); }); $server->start(); diff --git a/tests/swoole_http_server/tmp-content-type.phpt b/tests/swoole_http_server/tmp-content-type.phpt new file mode 100644 index 0000000000..ed8631d05b --- /dev/null +++ b/tests/swoole_http_server/tmp-content-type.phpt @@ -0,0 +1,70 @@ +--TEST-- +swoole_http_server: tmp content-type +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + run(function () use ($pm) { + $client = new GuzzleHttpClient(); + $baseUrl = 'http://127.0.0.1:' . $pm->getFreePort(); + $res = $client->post($baseUrl . '/', [ + 'multipart' => [ + [ + 'name' => 'file', + 'contents' => fopen(__FILE__, 'r'), + 'filename' => basename(__FILE__), + 'headers' => ['Content-Type' => 'application/php-script'] + ], + ], + ]); + + $status = $res->getStatusCode(); + $body = $res->getBody()->getContents(); + Assert::eq($status, 200); + $result = json_decode($body, true); + Assert::eq($result['file']['name'], basename(__FILE__)); + Assert::eq($result['file']['type'], 'application/php-script'); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'log_file' => '/dev/null', + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) use ($http) { + $response->end(json_encode($request->files)); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/trailer.phpt b/tests/swoole_http_server/trailer.phpt index 96ce29b7ec..fa6710ccb1 100644 --- a/tests/swoole_http_server/trailer.phpt +++ b/tests/swoole_http_server/trailer.phpt @@ -30,9 +30,9 @@ $pm->childFunc = function () use ($pm) { $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { $response->header('trailer', 'Content-MD5'); $data = 'hello world'; - $response->write($data); - $response->trailer('Content-MD5', md5($data)); - $response->end(); + Assert::true($response->write($data)); + Assert::true($response->trailer('Content-MD5', md5($data))); + Assert::true($response->end()); }); $http->start(); }; diff --git a/tests/swoole_http_server/upload_file_array_default.phpt b/tests/swoole_http_server/upload_file_array_default.phpt index e379160e46..a21b6baf96 100644 --- a/tests/swoole_http_server/upload_file_array_default.phpt +++ b/tests/swoole_http_server/upload_file_array_default.phpt @@ -31,9 +31,9 @@ $pm->parentFunc = function () use ($pm) { $json = json_decode($result, true); - assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', 218787, 0); - assert_upload_file($json['form']['file'], '/tmp/swoole.upfile.fixture2', 'photo.jpg', 'image/jpeg', 218787, 0); - assert_upload_file($json['form']['group']['file'], '/tmp/swoole.upfile.fixture3', 'swoole-logo.svg', 'image/svg+xml', 7424, 0); + assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', filesize(TEST_IMAGE), 0); + assert_upload_file($json['form']['file'], '/tmp/swoole.upfile.fixture2', 'photo.jpg', 'image/jpeg', filesize(TEST_IMAGE), 0); + assert_upload_file($json['form']['group']['file'], '/tmp/swoole.upfile.fixture3', 'swoole-logo.svg', 'image/svg+xml', filesize(TEST_IMAGE2), 0); $pm->kill(); }; diff --git a/tests/swoole_http_server/upload_file_array_parsed.phpt b/tests/swoole_http_server/upload_file_array_parsed.phpt index 30206c8c25..cb2f15fea7 100644 --- a/tests/swoole_http_server/upload_file_array_parsed.phpt +++ b/tests/swoole_http_server/upload_file_array_parsed.phpt @@ -31,7 +31,7 @@ $pm->parentFunc = function () use ($pm) { $json = json_decode($result, true); - assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', 218787, 0); + assert_upload_file($json['file'], '/tmp/swoole.upfile.fixture1', 'image.jpg', 'application/octet-stream', filesize(TEST_IMAGE), 0); assert_upload_file($json['form'], [ 'file' => '/tmp/swoole.upfile.fixture2', 'group' => [ @@ -48,9 +48,9 @@ $pm->parentFunc = function () use ($pm) { 'file' => 'image/svg+xml', ], ], [ - 'file' => 218787, + 'file' => filesize(TEST_IMAGE), 'group' => [ - 'file' => 7424, + 'file' => filesize(TEST_IMAGE2), ], ], [ 'file' => 0, diff --git a/tests/swoole_http_server/url_rewrite.phpt b/tests/swoole_http_server/url_rewrite.phpt new file mode 100644 index 0000000000..8a95225278 --- /dev/null +++ b/tests/swoole_http_server/url_rewrite.phpt @@ -0,0 +1,79 @@ +--TEST-- +swoole_http_server: url rewrite +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm, $file1, $file2) { + foreach ([false, true] as $http2) { + Swoole\Coroutine\run(function () use ($pm, $http2, $file1, $file2) { + $options = ['http2' => $http2]; + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/view/post/3", $options); + Assert::same($response['statusCode'], 200); + Assert::same($response['body'], 'user_john_action_settings'); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/view/post/99", $options); + Assert::same($response['statusCode'], 404); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/article/settings.html", $options); + Assert::same($response['statusCode'], 200); + Assert::same($response['body'], 'post_99_query_foo=bar'); + + $response = httpRequest("http://127.0.0.1:{$pm->getFreePort()}/article/not_exists.html", $options); + Assert::same($response['statusCode'], 404); + }); + } + echo "DONE\n"; + $pm->kill(); + unlink($file1); + unlink($file2); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Server('127.0.0.1', $pm->getFreePort()); + $http->set([ + 'log_file' => '/dev/null', + 'document_root' => __DIR__, + 'enable_static_handler' => true, + 'open_http2_protocol' => true, + 'static_handler_locations' => ['/static'], + 'url_rewrite_rules' => [ + '~^/view/post/(\d+)$~' => '/static/$1.html', + '/article/' => '/static/article/' + ] + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Request $request, Response $response) { + $response->end('dynamic_response'); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server/zstd.phpt b/tests/swoole_http_server/zstd.phpt new file mode 100644 index 0000000000..79d9d1753c --- /dev/null +++ b/tests/swoole_http_server/zstd.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_http_server: support zstd compress +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $data) { + run(function () use ($pm, $data) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->setHeaders(['Accept-Encoding' => 'zstd']); + $client->get('/'); + Assert::true($client->body == $data); + Assert::true($client->headers['content-encoding'] == 'zstd'); + Assert::true($client->headers['content-length'] != strlen($client->body)); + }); + echo "DONE\n"; + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data) { + $serv = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort()); + $serv->set([ + 'compression_level' => 20 + ]); + $serv->on("workerStart", function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('request', function ($req, $resp) use ($data) { + $resp->end($data); + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server_coro/buffer_clear.phpt b/tests/swoole_http_server_coro/buffer_clear.phpt new file mode 100644 index 0000000000..f448330484 --- /dev/null +++ b/tests/swoole_http_server_coro/buffer_clear.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_server_coro: websocket buffer clear +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function() use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + $client->set(['open_websocket_pong_frame' => true]); + $client->push('hello world'); + $client->push('ping', SWOOLE_WEBSOCKET_OPCODE_PING); + $frame1 = $client->recv(); + $frame2 = $client->recv(); + Assert::eq($frame1->data, 'received: hello world'); + Assert::eq($frame1->opcode, SWOOLE_WEBSOCKET_OPCODE_TEXT); + Assert::eq($frame2->data, 'ping'); + Assert::eq($frame2->opcode, SWOOLE_WEBSOCKET_OPCODE_PONG); + }); + + Event::wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + go(function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), false); + $server->handle('/', function (Request $request, Response $response) { + $response->upgrade(); + while ($frame = $response->recv()) { + $response->push('received: ' . $frame->data); + } + }); + $pm->wakeup(); + $server->start(); + }); + Event::wait(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/check_cookie_crlf.phpt b/tests/swoole_http_server_coro/check_cookie_crlf.phpt index c114a4632e..5a6a7d2640 100644 --- a/tests/swoole_http_server_coro/check_cookie_crlf.phpt +++ b/tests/swoole_http_server_coro/check_cookie_crlf.phpt @@ -54,5 +54,5 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -Warning: Swoole\Http\Response::rawcookie(): Header may not contain more than a single header, new line detected in %s +Warning: Swoole\Http\Response::rawcookie(): The value cannot contain ",", ";", " ", "\t", "\r", "\n", "\013", or "\014" in %s DONE diff --git a/tests/swoole_http_server_coro/close_socket.phpt b/tests/swoole_http_server_coro/close_socket.phpt index f0e937175a..a7448f8d2f 100644 --- a/tests/swoole_http_server_coro/close_socket.phpt +++ b/tests/swoole_http_server_coro/close_socket.phpt @@ -20,7 +20,7 @@ $pm->parentFunc = function () use ($pm) { for ($i = 0; $i < 2; $i++) { $cli = new Client('127.0.0.1', $pm->getFreePort()); Assert::assert($cli->get('/')); - Assert::contains($cli->headers['server'], 'BWS'); + Assert::assert(str_contains($cli->headers['server'], 'BWS') or str_contains($cli->headers['server'], 'bfe')); } }); echo "DONE\n"; @@ -71,7 +71,7 @@ $pm->childFunc = function () use ($pm) { foreach (array_merge([ 'Content-Type' => 'application/octet-stream', 'X-Accel-Buffering' => 'no', - 'server' => 'webserver/1.0' + 'X-Server' => 'webserver/1.0' ], $headers) as $k => $v) { $socket->send("{$k}: {$v}\r\n"); } diff --git a/tests/swoole_http_server_coro/compress_continue_frames.phpt b/tests/swoole_http_server_coro/compress_continue_frames.phpt new file mode 100644 index 0000000000..a09ad9b320 --- /dev/null +++ b/tests/swoole_http_server_coro/compress_continue_frames.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_http_server_coro: compress continue frames +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'websocket_compression' => true + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $context = deflate_init(ZLIB_ENCODING_RAW); + $client->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $client->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->set(['websocket_compression' => true]); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + $context = deflate_init(ZLIB_ENCODING_RAW); + $response->push(deflate_add($context, $data1, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_COMPRESS | SWOOLE_WEBSOCKET_FLAG_RSV1); + $response->push(deflate_add($context, $data2, ZLIB_NO_FLUSH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push(deflate_add($context, $data3, ZLIB_FINISH), SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/continue_frames.phpt b/tests/swoole_http_server_coro/continue_frames.phpt new file mode 100644 index 0000000000..4bdd707ca0 --- /dev/null +++ b/tests/swoole_http_server_coro/continue_frames.phpt @@ -0,0 +1,51 @@ +--TEST-- +swoole_http_server_coro: client continue frames - 1 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/continue_frames2.phpt b/tests/swoole_http_server_coro/continue_frames2.phpt new file mode 100644 index 0000000000..1b90b6a862 --- /dev/null +++ b/tests/swoole_http_server_coro/continue_frames2.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_http_server_coro: client continue frames - 2 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s All fragments of a message, except for the initial frame, must use the continuation frame opcode(0). diff --git a/tests/swoole_http_server_coro/continue_frames3.phpt b/tests/swoole_http_server_coro/continue_frames3.phpt new file mode 100644 index 0000000000..93b4873227 --- /dev/null +++ b/tests/swoole_http_server_coro/continue_frames3.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server_coro: client continue frames - 3 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $client->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $client->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + $frame = $client->recv(); + }); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + $frame = $response->recv(); + Assert::true($frame->data == $data1 . $data2 . $data3); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + }); + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +%s A continuation frame cannot stand alone and MUST be preceded by an initial frame whose opcode indicates either text or binary data. diff --git a/tests/swoole_http_server_coro/continue_frames4.phpt b/tests/swoole_http_server_coro/continue_frames4.phpt new file mode 100644 index 0000000000..a3f7c2de00 --- /dev/null +++ b/tests/swoole_http_server_coro/continue_frames4.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_http_server_coro: client continue frames - 4 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $results = []; + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + Assert::assert($ret); + $client->push('111', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $results[] = $client->recv(); + Assert::true($results[0]->data == $data1 . $data2 . $data3); + $client->push('222', SWOOLE_WEBSOCKET_OPCODE_TEXT, SWOOLE_WEBSOCKET_FLAG_FIN); + $results[] = $client->recv(); + Assert::true($results[1]->data == $data3 . $data2 . $data1); + Assert::true($results[0]->data != $results[1]->data); + }); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm, $data1, $data2, $data3) { + Co\run(function () use ($pm, $data1, $data2, $data3) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) use ($data1, $data2, $data3) { + $response->upgrade(); + while ($frame = $response->recv()) { + if ($frame->data == '111') { + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + } else { + $response->push($data3, SWOOLE_WEBSOCKET_OPCODE_TEXT, 0); + $response->push($data2, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, 0); + $response->push($data1, SWOOLE_WEBSOCKET_OPCODE_CONTINUATION, SWOOLE_WEBSOCKET_FLAG_FIN); + } + } + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/control_frame_compress.phpt b/tests/swoole_http_server_coro/control_frame_compress.phpt new file mode 100644 index 0000000000..72f766589c --- /dev/null +++ b/tests/swoole_http_server_coro/control_frame_compress.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_http_server_coro: control frame can not compress +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'websocket_compression' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + + $client->push('A'); + $frame = $client->recv(); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); + Assert::true($frame->flags == SWOOLE_WEBSOCKET_FLAG_FIN); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->set(['websocket_compression' => true]); + $server->handle('/', function ($request, $response) { + $response->upgrade(); + $closeFrame = new CloseFrame(); + $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $closeFrame->reason = 'hahahahaha'; + $response->push($closeFrame, SWOOLE_WEBSOCKET_FLAG_RSV1 | SWOOLE_WEBSOCKET_FLAG_COMPRESS); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/control_frame_fragmented.phpt b/tests/swoole_http_server_coro/control_frame_fragmented.phpt new file mode 100644 index 0000000000..7c08e3e3e6 --- /dev/null +++ b/tests/swoole_http_server_coro/control_frame_fragmented.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_http_server_coro: Control frames must not be fragmented +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'websocket_compression' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $client->upgrade('/'); + Assert::assert($ret); + + $client->push('A'); + $frame = $client->recv(); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_RSV1) == 0); + Assert::true(($frame->flags & SWOOLE_WEBSOCKET_FLAG_COMPRESS) == 0); + Assert::true($frame->flags == SWOOLE_WEBSOCKET_FLAG_FIN); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->set(['websocket_compression' => true]); + $server->handle('/', function ($request, $response) { + $response->upgrade(); + $closeFrame = new CloseFrame(); + $closeFrame->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $closeFrame->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $closeFrame->reason = 'hahahahaha'; + $closeFrame->flags = 0; + $response->push($closeFrame, 0); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/crash-bad-return-type.phpt b/tests/swoole_http_server_coro/crash-bad-return-type.phpt new file mode 100644 index 0000000000..e89d92685d --- /dev/null +++ b/tests/swoole_http_server_coro/crash-bad-return-type.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_http_server_coro: crash - bad return type +--SKIPIF-- + +--FILE-- + '/dev/null']); + +go(function () use ($port) { + $server = new Co\Http\Server("127.0.0.1", $port, true); + $server->set([ + 'open_tcp_nodelay' => true, + 'ssl_cert_file' => SSL_FILE_DIR.'/server.crt', + 'ssl_key_file' => SSL_FILE_DIR.'/server.key', + ]); + $server->handle('/', function ($request, $response) { + $response->end("

    Index

    "); + }); + $server->handle('/stop', function ($request, $response) use ($server) { + $response->end("

    Stop

    "); + $server->shutdown(); + }); + $server->start(); +}); + +go(function () use ($port) { + try { + echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; + } catch (Throwable $e) { + Assert::contains($e->getMessage(), 'Connection reset by peer'); + echo "Bad Client\n"; + } + echo httpGetBody("https://127.0.0.1:{$port}/") . PHP_EOL; + echo httpGetBody("https://127.0.0.1:{$port}/stop?hello=1") . PHP_EOL; +}); +Swoole\Event::wait(); + +?> +--EXPECT-- +Bad Client +

    Index

    +

    Stop

    diff --git a/tests/swoole_http_server_coro/disconnect.phpt b/tests/swoole_http_server_coro/disconnect.phpt new file mode 100644 index 0000000000..a4cb20b5ab --- /dev/null +++ b/tests/swoole_http_server_coro/disconnect.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server_coro: test disconnect function +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'open_websocket_close_frame' => true + ]); + $ret = $client->upgrade('/'); + $client->push('Hello World'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + Assert::true($frame->reason == 'close it'); + Assert::true($client->recv() == ''); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + run(function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), false); + $server->handle('/', function (Request $request, Response $response) { + $response->upgrade(); + $response->recv(); + Assert::true($response->disconnect(SWOOLE_WEBSOCKET_CLOSE_NORMAL, 'close it')); + }); + + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/form_data_1.phpt b/tests/swoole_http_server_coro/form_data_1.phpt index d14da250dc..8202c0a2a6 100644 --- a/tests/swoole_http_server_coro/form_data_1.phpt +++ b/tests/swoole_http_server_coro/form_data_1.phpt @@ -13,13 +13,11 @@ use Swoole\Http\Response; use Swoole\Process; use function Swoole\Coroutine\run; -const OFFSET = 250; - $pm = new ProcessManager; $pm->initFreePorts(); $pm->parentFunc = function ($pid) use ($pm) { - form_data_test_1($pm); + form_data_test($pm, [250]); }; $pm->childFunc = function () use ($pm) { diff --git a/tests/swoole_http_server_coro/form_data_2.phpt b/tests/swoole_http_server_coro/form_data_2.phpt new file mode 100644 index 0000000000..2073d3ddd1 --- /dev/null +++ b/tests/swoole_http_server_coro/form_data_2.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_http_server_coro: form data 2 +--SKIPIF-- + +--FILE-- +initFreePorts(); + +$pm->parentFunc = function ($pid) use ($pm) { + form_data_test($pm, [225]); +}; + +$pm->childFunc = function () use ($pm) { + run(function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), false); + $server->handle('/', function (Request $request, Response $response) { + $response->end(json_encode($request->post)); + }); + Process::signal(SIGTERM, function () use ($server) { + $server->shutdown(); + }); + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_http_server_coro/graceful_shutdown.phpt b/tests/swoole_http_server_coro/graceful_shutdown.phpt new file mode 100644 index 0000000000..eb6e52f789 --- /dev/null +++ b/tests/swoole_http_server_coro/graceful_shutdown.phpt @@ -0,0 +1,66 @@ +--TEST-- +swoole_http_server_coro: graceful shutdown +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + $errors = ''; + Runtime::setHookFlags(SWOOLE_HOOK_ALL); + Co\run(function () use ($pm, &$errors) { + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; + + go(function () use ($pm, &$errors) { + try { + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/sleep") . PHP_EOL; + } catch (Throwable $e) { + $errors .= $e->getMessage() . PHP_EOL; + } + }); + + go(function () use ($pm, &$errors) { + usleep(5000); + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/shutdown") . PHP_EOL; + try { + echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; + } catch (Throwable $e) { + $errors .= $e->getMessage() . PHP_EOL; + echo "done\n"; + } + }); + }); + + Assert::contains($errors, 'Connection reset by peer'); +}; +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) { + $response->end("index"); + }); + $server->handle('/sleep', function ($request, $response) { + Co::sleep(0.2); + $response->end("sleep"); + }); + $server->handle('/shutdown', function ($request, $response) use ($server) { + $response->end("shutdown"); + $server->shutdown(); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +index +shutdown +sleep +done diff --git a/tests/swoole_http_server_coro/http2_ssl.phpt b/tests/swoole_http_server_coro/http2_ssl.phpt new file mode 100644 index 0000000000..72a8cf94aa --- /dev/null +++ b/tests/swoole_http_server_coro/http2_ssl.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_http_server_coro: http2 + SSL +--SKIPIF-- + +--FILE-- +set([ + 'open_http2_protocol' => true, + 'open_tcp_nodelay' => true, + 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/server.key', + ]); + $server->handle('/', function ($request, $response) { + $response->end("

    Index

    "); + }); + $server->handle('/stop', function ($request, $response) use ($server) { + $response->end("

    Stop

    "); + $server->shutdown(); + }); + $server->start(); +}); + +go(function () use ($port) { + echo shell_exec("curl --no-progress-meter --http2 -k https://127.0.0.1:$port/") . PHP_EOL; + echo shell_exec("curl --no-progress-meter --http2 -k https://127.0.0.1:$port/stop") . PHP_EOL; +}); +Swoole\Event::wait(); +?> +--EXPECT-- +

    Index

    +

    Stop

    diff --git a/tests/swoole_http_server_coro/open_frame.phpt b/tests/swoole_http_server_coro/open_frame.phpt new file mode 100644 index 0000000000..89837eaf1f --- /dev/null +++ b/tests/swoole_http_server_coro/open_frame.phpt @@ -0,0 +1,76 @@ +--TEST-- +swoole_http_server_coro: handle frame by user +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $client->upgrade('/'); + $client->push('Hello World'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PONG); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_CLOSE); + Assert::true($frame->code == SWOOLE_WEBSOCKET_CLOSE_NORMAL); + Assert::true($frame->reason == "lalalala FUMEI"); + $frame = $client->recv(); + Assert::true($frame == ''); + $client->disconnect(); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + run(function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), false); + $server->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); + $server->handle('/', function (Request $request, Response $response) { + $response->upgrade(); + $ping = new Frame(); + $ping->opcode = SWOOLE_WEBSOCKET_OPCODE_PING; + $response->push($ping); + + $pong = new Frame(); + $pong->opcode = SWOOLE_WEBSOCKET_OPCODE_PONG; + $response->push($pong); + + $close = new CloseFrame(); + $close->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $close->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $close->reason = "lalalala FUMEI"; + $response->push($close); + }); + + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/ping.phpt b/tests/swoole_http_server_coro/ping.phpt new file mode 100644 index 0000000000..3a5a76e202 --- /dev/null +++ b/tests/swoole_http_server_coro/ping.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_http_server_coro: test ping function +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['open_websocket_ping_frame' => true]); + Assert::true($client->upgrade('/')); + Assert::true($client->push('Hello World')); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + Assert::true($frame->data == 'Hello World'); + Assert::true($client->ping()); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + Assert::true($frame->data == ''); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) { + $response->upgrade(); + $response->recv(); + $response->ping('Hello World'); + $response->ping(); + }); + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/restart.phpt b/tests/swoole_http_server_coro/restart.phpt new file mode 100644 index 0000000000..845f920beb --- /dev/null +++ b/tests/swoole_http_server_coro/restart.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_http_server_coro: graceful shutdown +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $n = 2; + while ($n--) { + echo "[$n]", httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/") . PHP_EOL; + echo "[$n]", httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/shutdown") . PHP_EOL; + usleep(150000); + } + }); +}; +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function ($request, $response) { + $response->end("index"); + }); + $server->handle('/shutdown', function ($request, $response) use ($server) { + $response->end("shutdown"); + $server->shutdown(); + }); + $pm->wakeup(); + + $n = 2; + while ($n--) { + $server->start(); + usleep(100000); + } + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +[1]index +[1]shutdown +[0]index +[0]shutdown diff --git a/tests/swoole_http_server_coro/reuse_port.phpt b/tests/swoole_http_server_coro/reuse_port.phpt index 00c0bd5b2d..8c797f242b 100644 --- a/tests/swoole_http_server_coro/reuse_port.phpt +++ b/tests/swoole_http_server_coro/reuse_port.phpt @@ -7,19 +7,22 @@ swoole_http_server_coro: reuse port parentFunc = function ($pid) use ($pm) { - $sch = new Swoole\Coroutine\Scheduler(); + $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { - $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli = new Client('127.0.0.1', $pm->getFreePort()); $ret = $cli->get('/hello'); if (!$ret) { echo "ERROR [3]\n"; @@ -33,18 +36,17 @@ $pm->parentFunc = function ($pid) use ($pm) { $pids[$result['wid']] = 1; }); $sch->start(); - Assert::eq(count($pids), 2); + Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { - - $atomic = new \Swoole\Atomic(); - $pool = new Swoole\Process\Pool(2); + $atomic = new Atomic(); + $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { - $server = new Swoole\Coroutine\Http\Server("127.0.0.1", $pm->getFreePort(), false, true); + $server = new Server('127.0.0.1', $pm->getFreePort(), false, true); $server->handle('/', function ($request, $response) { $response->end(serialize(['wid' => posix_getpid()])); }); diff --git a/tests/swoole_http_server_coro/server_addr.phpt b/tests/swoole_http_server_coro/server_addr.phpt new file mode 100644 index 0000000000..fe4024e503 --- /dev/null +++ b/tests/swoole_http_server_coro/server_addr.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_http_server_coro: add server addr +--SKIPIF-- + +--FILE-- +initFreePorts(); + +$port = $pm->getFreePort(); + +$output = shell_exec('ip addr show'); +preg_match_all('/inet (\d+\.\d+\.\d+\.\d+)\//', $output, $matches); +$ips = $matches[1]; + +$pm->parentFunc = function ($pid) use ($pm, $port, $ips) { + run(function () use ($pm, $port, $ips) { + $client = new Client($ips[1], $port); + $client->get('/'); + $client->close(); + $pm->kill(); + }); +}; + +$pm->childFunc = function () use ($pm, $port, $ips) { + run(function () use ($pm, $port, $ips) { + $server = new Server('0.0.0.0', $port, false); + $server->handle('/', function (Request $request, Response $response) use ($ips){ + $server = $request->server; + Assert::eq($server['server_addr'], $ips[1]); + Assert::eq($server['remote_addr'], $ips[1]); + Assert::true($server['server_port'] != $server['remote_port']); + }); + + Swoole\Process::signal(SIGTERM, function () use ($server) { + $server->shutdown(); + }); + $pm->wakeup(); + $server->start(); + }); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/ssl_bad_client.phpt b/tests/swoole_http_server_coro/ssl_bad_client.phpt index bb0a0a008e..2ef4bd925f 100644 --- a/tests/swoole_http_server_coro/ssl_bad_client.phpt +++ b/tests/swoole_http_server_coro/ssl_bad_client.phpt @@ -1,45 +1,55 @@ --TEST-- swoole_http_server_coro: bad client --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { Co\run(function () use ($pm) { $port = $pm->getFreePort(); - $client = new Co\Client(SWOOLE_SOCK_TCP); //同步阻塞 - if (!$client->connect('127.0.0.1', $port)) - { + $client = new Client(SWOOLE_SOCK_TCP); // 同步阻塞 + if (!$client->connect('127.0.0.1', $port)) { exit("connect failed\n"); } $client->send('hello world'); - Assert::same($client->recv(), ''); + while (true) { + $data = $client->recv(); + if (!$data) { + break; + } + } echo httpGetBody("https://127.0.0.1:{$port}/stop?hello=1") . PHP_EOL; }); }; $pm->childFunc = function () use ($pm) { go(function () use ($pm) { - $server = new Co\Http\Server("127.0.0.1", $pm->getFreePort(), true); + $server = new Server('127.0.0.1', $pm->getFreePort(), true); $server->set([ 'open_tcp_nodelay' => true, 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); $server->handle('/', function ($request, $response) { - $response->end("

    Index

    "); + $response->end('

    Index

    '); }); $server->handle('/stop', function ($request, $response) use ($server) { - $response->end("

    Stop

    "); + $response->end('

    Stop

    '); $server->shutdown(); }); $server->start(); }); - Swoole\Event::wait(); + Event::wait(); }; $pm->childFirst(); diff --git a/tests/swoole_http_server_coro/websocket_close.phpt b/tests/swoole_http_server_coro/websocket_close.phpt index 2e9cbbd80d..ef3a2dffe4 100644 --- a/tests/swoole_http_server_coro/websocket_close.phpt +++ b/tests/swoole_http_server_coro/websocket_close.phpt @@ -21,9 +21,10 @@ $pm->parentFunc = function (int $pid) use ($pm, &$count) { $ret = $cli->recv(); Assert::same($ret->data, "Hello {$data}!"); $s = microtime(true); + // An empty string was received, indicating that the connection has been closed by the peer $ret = $cli->recv(); Assert::lessThan(microtime(true) - $s, 0.002); - Assert::same($ret, false); + Assert::same($ret, ""); }); Swoole\Event::wait(); $pm->kill(); diff --git a/tests/swoole_http_server_coro/websocket_ping.phpt b/tests/swoole_http_server_coro/websocket_ping.phpt new file mode 100644 index 0000000000..e8b6701831 --- /dev/null +++ b/tests/swoole_http_server_coro/websocket_ping.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_http_server_coro: websocket ping +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + go(function () use ($pm) { + $cli = new Client('127.0.0.1', $pm->getFreePort()); + $cli->set([ + 'timeout' => 5, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); + $ret = $cli->upgrade('/websocket'); + Assert::assert($ret); + $cli->push('Swoole'); + $ret = $cli->recv(); + Assert::same($ret->opcode, WEBSOCKET_OPCODE_PING); + }); + Swoole\Event::wait(); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + go(function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/websocket', function ($request, $ws) { + $ws->upgrade(); + while (true) { + $frame = $ws->recv(); + if ($frame === false) { + echo "error : " . swoole_last_error() . "\n"; + break; + } else if ($frame === '') { + break; + } else { + usleep(10000); + $ws->ping(); + } + } + }); + $server->start(); + $pm->wakeup(); + }); + Swoole\Event::wait(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_http_server_coro/websocket_ping_pong.phpt b/tests/swoole_http_server_coro/websocket_ping_pong.phpt index 4c68cf64c9..77b4f8f67a 100644 --- a/tests/swoole_http_server_coro/websocket_ping_pong.phpt +++ b/tests/swoole_http_server_coro/websocket_ping_pong.phpt @@ -14,7 +14,12 @@ $pm = new ProcessManager; $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); - $cli->set(['timeout' => 5]); + $cli->set([ + 'timeout' => 5, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $ret = $cli->upgrade('/websocket'); Assert::assert($ret); $cli->push('Swoole'); @@ -35,6 +40,11 @@ $pm->parentFunc = function () use ($pm) { $pm->childFunc = function () use ($pm) { go(function () use ($pm) { $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $server->handle('/websocket', function ($request, $ws) { $ws->upgrade(); while (true) { diff --git a/tests/swoole_iouring/mix.phpt b/tests/swoole_iouring/mix.phpt new file mode 100644 index 0000000000..2179cea38b --- /dev/null +++ b/tests/swoole_iouring/mix.phpt @@ -0,0 +1,201 @@ +--TEST-- +swoole_iouring: support io_uring +--SKIPIF-- + +--FILE-- + 32, + 'iouring_entries' => 30000, +]; + +if (defined('SWOOLE_IOURING_SQPOLL')) { + $setting['iouring_flag'] = SWOOLE_IOURING_SQPOLL; +} + +swoole_async_set($setting); + +$results = []; +for ($i = 1; $i <= 10000; $i++) { + $results[$i] = random_bytes(rand(8192, 8192 * 3)); +} + +run(function() use ($results) { + $filesize = 1048576; + $content = random_bytes($filesize); + $fileName = '/tmp/test_file'; + Assert::eq(file_put_contents($fileName, $content), 1048576); + var_dump(stat($fileName)); + for ($i = 0; $i < 100; $i++) { + Assert::eq(filesize($fileName), 1048576); + Assert::eq(file_get_contents($fileName), $content); + } + unlink($fileName); + Assert::true(!file_exists($fileName)); + + $stream = fopen($fileName, 'w'); + fwrite($stream, $content); + if (PHP_VERSION_ID >= 80100) { + Assert::true(fdatasync($stream)); + Assert::true(fsync($stream)); + } + Assert::eq(file_get_contents($fileName), $content); + var_dump(fstat($stream)); + fclose($stream); + unlink($fileName); + + file_put_contents($fileName, $content); + rename($fileName, $fileName.'aaa'); + Assert::true(!file_exists($fileName)); + Assert::true(file_exists($fileName.'aaa')); + unlink($fileName.'aaa'); + + $directory = '/tmp/a/b/c/d/e/f'; + mkdir($directory, 0755, true); + Assert::true(is_dir($directory)); + rmdir($directory); + Assert::true(!is_dir($directory)); + + $waitGroup = new WaitGroup(); + for ($i = 1; $i <= 10000; $i++) { + go(function() use ($waitGroup, $i, $results){ + $waitGroup->add(); + $filename = '/tmp/file'.$i; + file_put_contents($filename, $results[$i]); + Assert::true($results[$i] == file_get_contents($filename)); + file_put_contents($filename, $results[$i], FILE_APPEND); + file_put_contents($filename, $results[$i], FILE_APPEND); + Assert::true(strlen($results[$i]) * 3 == strlen(file_get_contents($filename))); + + $stream = fopen($filename, 'r+'); + $size = rand(1, filesize($filename)); + Assert::true(ftruncate($stream, $size)); + fclose($stream); + Assert::true($size == strlen(file_get_contents($filename))); + $waitGroup->done(); + }); + } + $waitGroup->wait(); + echo 'SUCCESS'; +}); +?> +--EXPECTF-- +array(26) { + [0]=> + int(%d) + [1]=> + int(%d) + [2]=> + int(%d) + [3]=> + int(%d) + [4]=> + int(%d) + [5]=> + int(%d) + [6]=> + int(%d) + [7]=> + int(%d) + [8]=> + int(%d) + [9]=> + int(%d) + [10]=> + int(%d) + [11]=> + int(%d) + [12]=> + int(%d) + ["dev"]=> + int(%d) + ["ino"]=> + int(%d) + ["mode"]=> + int(%d) + ["nlink"]=> + int(%d) + ["uid"]=> + int(%d) + ["gid"]=> + int(%d) + ["rdev"]=> + int(%d) + ["size"]=> + int(%d) + ["atime"]=> + int(%d) + ["mtime"]=> + int(%d) + ["ctime"]=> + int(%d) + ["blksize"]=> + int(%d) + ["blocks"]=> + int(%d) +} +array(26) { + [0]=> + int(%d) + [1]=> + int(%d) + [2]=> + int(%d) + [3]=> + int(%d) + [4]=> + int(%d) + [5]=> + int(%d) + [6]=> + int(%d) + [7]=> + int(%d) + [8]=> + int(%d) + [9]=> + int(%d) + [10]=> + int(%d) + [11]=> + int(%d) + [12]=> + int(%d) + ["dev"]=> + int(%d) + ["ino"]=> + int(%d) + ["mode"]=> + int(%d) + ["nlink"]=> + int(%d) + ["uid"]=> + int(%d) + ["gid"]=> + int(%d) + ["rdev"]=> + int(%d) + ["size"]=> + int(%d) + ["atime"]=> + int(%d) + ["mtime"]=> + int(%d) + ["ctime"]=> + int(%d) + ["blksize"]=> + int(%d) + ["blocks"]=> + int(%d) +} +SUCCESS diff --git a/tests/swoole_iouring/setting.phpt b/tests/swoole_iouring/setting.phpt new file mode 100644 index 0000000000..a83a3272b5 --- /dev/null +++ b/tests/swoole_iouring/setting.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_iouring: iouring setting test +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $headers = httpGetHeaders("http://127.0.0.1:{$pm->getFreePort()}"); + }); + + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $http->set([ + 'iouring_flag' => SWOOLE_IOURING_SQPOLL, + 'iouring_entries' => 4096, + 'iouring_workers' => 16 + ]); + $http->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($pm) { + $response->status(200, "status"); + $response->end("Hello World"); + }); + $http->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_library/name_resolver/1.phpt b/tests/swoole_library/name_resolver/1.phpt index 191b441960..d1b03c6d0b 100644 --- a/tests/swoole_library/name_resolver/1.phpt +++ b/tests/swoole_library/name_resolver/1.phpt @@ -1,7 +1,10 @@ --TEST-- swoole_library/name_resolver: resolve --SKIPIF-- - + --FILE-- + --FILE-- +--FILE-- + SWOOLE_HOOK_ALL & ~SWOOLE_HOOK_CURL]); +$fileEx = '/tmp/test_ex.txt'; +file_put_contents($fileEx, '0'); + +$lockEx = file_get_contents($fileEx); +Co\run(function () use ($fileEx) { + for ($i = 0; $i < 50; $i++) { + go(function () use ($i, $fileEx) { + $fp = fopen($fileEx, 'r+'); + flock($fp, LOCK_EX); + $val = (int) file_get_contents($fileEx); + usleep(100); + file_put_contents($fileEx, (string) ($val + 1)); + flock($fp, LOCK_UN); + fclose($fp); + }); + } +}); + +$val = (int) file_get_contents($fileEx); +echo $val === 50 ? "✓ 通过 (计数=$val)\n" : "✗ 失败 (计数=$val, 预期50)\n"; +?> +--EXPECT-- +✓ 通过 (计数=50) diff --git a/tests/swoole_lock/lock_nb.phpt b/tests/swoole_lock/lock_nb.phpt new file mode 100644 index 0000000000..dba941cb07 --- /dev/null +++ b/tests/swoole_lock/lock_nb.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_lock: lock nb +--SKIPIF-- + +--FILE-- +lock()); + +$ret = $lock->lock(LOCK_EX | LOCK_NB); +Assert::false($ret); + +$lock->unlock(); +Assert::true($lock->lock(LOCK_EX | LOCK_NB)); +?> +--EXPECT-- diff --git a/tests/swoole_lock/lock_read.phpt b/tests/swoole_lock/lock_read.phpt new file mode 100644 index 0000000000..d63bad444e --- /dev/null +++ b/tests/swoole_lock/lock_read.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_lock: lock read +--SKIPIF-- + +--FILE-- +lock(LOCK_SH); + usleep(200_000); + $lock->unlock(); +}); +$process1->start(); + +$process2 = new Process(function ($p) use ($lock) { + $lock->lock(LOCK_SH); + usleep(200_000); + $lock->unlock(); +}); +$process2->start(); + +Process::wait(); +Process::wait(); + +// Using shared locks, two processes will get locks at the same time and execute them concurrently +Assert::lessThan(microtime(true) - $begin, 0.35); +?> +--EXPECT-- diff --git a/tests/swoole_lock/lock_timeout.phpt b/tests/swoole_lock/lock_timeout.phpt new file mode 100644 index 0000000000..c79925f597 --- /dev/null +++ b/tests/swoole_lock/lock_timeout.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_lock: lock timeout +--SKIPIF-- + +--FILE-- +lock()); + +$start = microtime(true); +$ret = $lock->lock(LOCK_EX, 0.2); +Assert::false($ret); +$end = microtime(true); + +Assert::eq($lock->errCode, SOCKET_ETIMEDOUT); +Assert::greaterThanEq($end - $start, 0.2); + +?> +--EXPECT-- +bool(true) diff --git a/tests/swoole_lock/lockwait_twice.phpt b/tests/swoole_lock/lockwait_twice.phpt deleted file mode 100644 index 89655b78cf..0000000000 --- a/tests/swoole_lock/lockwait_twice.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_lock: test lock twice ---SKIPIF-- - ---FILE-- -lock()); - - -$start = microtime(true); -$ret = $lock->lockwait(0.2); -Assert::false($ret); -$end = microtime(true); - -Assert::eq($lock->errCode, SOCKET_ETIMEDOUT); -Assert::lessThan($end - $start, 0.2); - -?> ---EXPECT-- -bool(true) diff --git a/tests/swoole_lock/mutex_robust.phpt b/tests/swoole_lock/mutex_robust.phpt deleted file mode 100644 index 3781e4f3e1..0000000000 --- a/tests/swoole_lock/mutex_robust.phpt +++ /dev/null @@ -1,56 +0,0 @@ ---TEST-- -swoole_lock: mutex robust ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) { - $pm->kill(); -}; - -$pm->childFunc = function () use ($pm, $fp) { - $lock = new Lock(SWOOLE_MUTEX); - $pid = posix_getpid(); - fwrite($fp, "[Master {$pid}] Create Lock\n"); - $lock->lock(); - $n = 2; - while ($n--) { - $process = new Process(function ($p) use ($lock, $fp) { - fwrite($fp, "[Child {$p->pid}] Wait Lock\n"); - $lock->lock(); - fwrite($fp, "[Child {$p->pid}] Get Lock\n"); - $lock->unlock(); - fwrite($fp, "[Child {$p->pid}] exit\n"); - }); - $process->start(); - } - sleep(30); - $lock->unlock(); -}; - -$pm->childFirst(); -$pm->run(); -fclose($fp); -echo file_get_contents($file); -unlink($file); -?> ---EXPECTF-- -[Master %d] Create Lock -[Child %d] Wait Lock -[Child %d] Wait Lock -[Child %d] Get Lock -[Child %d] exit -[Child %d] Get Lock -[Child %d] exit diff --git a/tests/swoole_lock/spinlock_timeout.phpt b/tests/swoole_lock/spinlock_timeout.phpt new file mode 100644 index 0000000000..0adb61d392 --- /dev/null +++ b/tests/swoole_lock/spinlock_timeout.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_lock: lock timeout +--SKIPIF-- + +--FILE-- +lock()); + +$start = microtime(true); +$ret = $lock->lock(LOCK_EX, 0.2); +Assert::false($ret); +$end = microtime(true); + +Assert::eq($lock->errCode, SOCKET_ETIMEDOUT); +Assert::greaterThanEq($end - $start, 0.2); + +?> +--EXPECT-- +bool(true) diff --git a/tests/swoole_mysql_coro/abandon_prepare_dtor.phpt b/tests/swoole_mysql_coro/abandon_prepare_dtor.phpt deleted file mode 100644 index a965bf6313..0000000000 --- a/tests/swoole_mysql_coro/abandon_prepare_dtor.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare dtor ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - for ($n = MAX_REQUESTS; $n--;) { - $statement = $mysql->prepare('SELECT ?'); - $statement = null; - Co::sleep(0.001); - $result = $mysql->query('show status like \'Prepared_stmt_count\''); - Assert::eq($result[0]['Value'], '0'); - } -}); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/aborted_clients.phpt b/tests/swoole_mysql_coro/aborted_clients.phpt deleted file mode 100644 index 0b1b14f42f..0000000000 --- a/tests/swoole_mysql_coro/aborted_clients.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql-close/reconnect/aborted-client-num ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - Assert::true($db->connect($server)); - $before_num = (int)$db->query('show status like "Aborted_clients"')[0]["Value"]; - Assert::true($db->close()); - Assert::true($db->connect($server)); - $after_num = (int)$db->query('show status like "Aborted_clients"')[0]["Value"]; - Assert::same($after_num - $before_num, 0); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/another_coroutine.phpt b/tests/swoole_mysql_coro/another_coroutine.phpt deleted file mode 100644 index 6927f0cfac..0000000000 --- a/tests/swoole_mysql_coro/another_coroutine.phpt +++ /dev/null @@ -1,65 +0,0 @@ ---TEST-- -swoole_mysql_coro: illegal another coroutine ---SKIPIF-- - ---FILE-- -query('SELECT SLEEP(1)'); - Assert::assert(false, 'never here'); - } - - $cli = new Co\MySQL; - $connected = $cli->connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - if (Assert::true($connected)) { - go(function () use ($cli) { - $cli->query('SELECT SLEEP(1)'); - Assert::assert(false, 'never here'); - }); - go(function () use ($cli) { - (function () use ($cli) { - (function () use ($cli) { - get($cli); - })(); - })(); - }); - } - }); -}, false, null, false); -$process->start(); -Swoole\Process::wait(); -?> ---EXPECTF-- -Fatal error: Uncaught Swoole\Error: Socket#%d has already been bound to another coroutine#%d, reading of the same socket in coroutine#%d at the same time is not allowed in %s:%d -Stack trace: -#0 %s(%d): Swoole\Coroutine\MySQL->query('SELECT SLEEP(%d)') -#1 %s(%d): get(Object(Swoole\Coroutine\MySQL)) -#2 %s(%d): {closure}() -#3 %s(%d): {closure}() -%A - thrown in %s on line %d -DONE diff --git a/tests/swoole_mysql_coro/bc_fetchAll.phpt b/tests/swoole_mysql_coro/bc_fetchAll.phpt deleted file mode 100644 index 02a3d15d1c..0000000000 --- a/tests/swoole_mysql_coro/bc_fetchAll.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql fetchAll should return empty array (#2674) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ]; - - if (Assert::true($client->connect($server))) { - defer(function () use ($server, $client) { - $client->connect($server); - $client->query('DROP TABLE `empty`'); - }); - if (Assert::true($client->query("CREATE TABLE `empty` (`id` int(11))"))) { - // query - Assert::notEmpty($client->query('SELECT * FROM `ckl`')); - Assert::same($client->query('SELECT * FROM `empty`'), []); - Assert::same($client->query('SELECT * FROM `notexist`'), false); - // execute - Assert::notEmpty($client->prepare('SELECT * FROM `ckl`')->execute()); - Assert::same(($statement = $client->prepare('SELECT * FROM `empty`'))->execute(), []); - Assert::same($client->prepare('SELECT * FROM `notexist`'), false); - // closed - Assert::true($client->close()); - Assert::same($client->query('SELECT * FROM `empty`'), false); - Assert::same($client->prepare('SELECT * FROM `empty`'), false); - Assert::same($statement->execute(), false); - - if (Assert::true($client->connect($server + ['fetch_mode' => true]))) { - // query - Assert::true($client->query('SELECT * FROM `ckl` LIMIT 1')); - Assert::notEmpty($client->fetch()); - Assert::null($client->fetch()); - Assert::null($client->fetch()); - Assert::same($client->fetchAll(), []); - Assert::true($client->query('SELECT * FROM `ckl` LIMIT 1')); - Assert::count($client->fetchAll(), 1); - Assert::same($client->fetchAll(), []); - // execute - Assert::isInstanceOf( - $statement = $client->prepare('SELECT * FROM `ckl` LIMIT 1'), - Swoole\Coroutine\MySQL\Statement::class - ); - Assert::same($statement->fetchAll(), []); - Assert::true($statement->execute()); - Assert::notEmpty($statement->fetch()); - Assert::null($statement->fetch()); - Assert::true($statement->execute()); - Assert::notEmpty($statement->fetchAll()); - Assert::same($statement->fetchAll(), []); - // closed - Assert::true($client->close()); - Assert::false($client->query('SELECT * FROM `ckl` LIMIT 1')); - Assert::false($client->fetch()); - Assert::false($client->fetchAll()); - Assert::false($statement->execute()); - Assert::false($statement->fetch()); - Assert::false($statement->fetchAll()); - echo "DONE\n"; - } - } - } -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/bc_sync_properties.phpt b/tests/swoole_mysql_coro/bc_sync_properties.phpt deleted file mode 100644 index a23866ff9d..0000000000 --- a/tests/swoole_mysql_coro/bc_sync_properties.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (insert) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ]; - - if (Assert::true($client->connect($server))) { - /* @var $statement Swoole\Coroutine\MySQL\Statement */ - $statement = $client->prepare('INSERT INTO ckl (`domain`,`path`,`name`) VALUES (?,?,?)'); - if (Assert::isInstanceOf($statement, Swoole\Coroutine\MySQL\Statement::class)) { - if (Assert::true($statement->execute(['www.baidu.com', '/search', 'baidu']))) { - Assert::same($statement->affected_rows, 1); - Assert::greaterThan($statement->insert_id, 0); - Assert::same($client->affected_rows, $statement->affected_rows); - Assert::same($client->insert_id, $statement->insert_id); - if (Assert::false($statement->execute())) { - Assert::same($statement->errno, SWOOLE_MYSQLND_CR_INVALID_PARAMETER_NO); - Assert::same($client->error, $statement->error); - Assert::same($client->errno, $statement->errno); - } - echo "SUCCESS\n"; - } - } - } -}); -?> ---EXPECT-- -SUCCESS diff --git a/tests/swoole_mysql_coro/big_data.phpt b/tests/swoole_mysql_coro/big_data.phpt deleted file mode 100644 index 02f6b3f6f8..0000000000 --- a/tests/swoole_mysql_coro/big_data.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -swoole_mysql_coro: select big data from db ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - Assert::assert($db->connect($server)); - - $table_name = get_safe_random(16); - $createTable = "CREATE TABLE {$table_name} (\nid bigint PRIMARY KEY AUTO_INCREMENT,\n`content` text NOT NULL\n);"; - if (Assert::assert($db->query($createTable))) { - $statement = $db->prepare("INSERT INTO {$table_name} VALUES (?, ?)"); - $random = []; - for ($n = 0; $n < MAX_REQUESTS; $n++) { - $random[$n] = str_repeat(get_safe_random(256), 128); // 32K - $ret = $statement->execute([$n + 1, $random[$n]]); - Assert::assert($ret); - } - $statement = $db->prepare("SELECT * FROM {$table_name}"); - $ret = $statement->execute(); - for ($n = 0; $n < MAX_REQUESTS; $n++) { - Assert::same($ret[$n]['content'], $random[$n]); - } - Assert::assert($db->query("DROP TABLE {$table_name}")); - } -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/bug_0814.phpt b/tests/swoole_mysql_coro/bug_0814.phpt deleted file mode 100644 index a3626e6981..0000000000 --- a/tests/swoole_mysql_coro/bug_0814.phpt +++ /dev/null @@ -1,72 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (select) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ]; - - $ret1 = $db->connect($server); - if (!$ret1) { - echo "CONNECT[1] ERROR\n"; - return; - } - - /** - * 第一次执行prepare - */ - $stmt = $db->prepare('SELECT * FROM userinfo WHERE id=?'); - if (!$stmt) { - echo "PREPARE ERROR\n"; - return; - } - - $ret3 = $stmt->execute([5]); - if (!$ret3) { - echo "EXECUTE ERROR#{$stmt->errno}: {$stmt->error}\n"; - return; - } - Assert::assert(count($ret3) > 0); - - $s = microtime(true); - $ret = $db->query("select sleep(20)", 0.1); - time_approximate(0.1, microtime(true) - $s); - Assert::false($ret); - Assert::same($db->errno, SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR); - $ret1 = $db->connect($server); - if (!$ret1) { - echo "CONNECT[2] ERROR\n"; - return; - } - - /** - * 第二次执行prepare - */ - $stmt = $db->prepare('SELECT * FROM userinfo WHERE id=?'); - if (!$stmt) { - echo "PREPARE ERROR\n"; - return; - } - - $ret3 = $stmt->execute([5]); - if (!$ret3) { - echo "EXECUTE ERROR#{$stmt->errno}: {$stmt->error}\n"; - return; - } - Assert::assert(count($ret3) > 0); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/connect_timeout.phpt b/tests/swoole_mysql_coro/connect_timeout.phpt deleted file mode 100644 index e9f4954dde..0000000000 --- a/tests/swoole_mysql_coro/connect_timeout.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -swoole_mysql_coro: connect timeout ---SKIPIF-- - ---FILE-- -connect([ - 'host' => '192.0.0.1', - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'timeout' => ($timeout = mt_rand(100, 500) / 1000) - ]); - time_approximate($timeout, microtime(true) - $s); - Assert::assert(!$connected); - Assert::assert($mysql->connected === false); - Assert::assert($mysql->connect_errno === SWOOLE_MYSQLND_CR_CONNECTION_ERROR); - // handshake timeout - $s = microtime(true); - $connected = $mysql->connect([ - 'host' => REDIS_SERVER_HOST, - 'port' => REDIS_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'timeout' => ($timeout = mt_rand(100, 500) / 1000) - ]); - time_approximate($timeout, microtime(true) - $s); - Assert::false($connected); - Assert::same($mysql->connected, false); - Assert::same($mysql->connect_errno, SWOOLE_MYSQLND_CR_CONNECTION_ERROR); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/db_destruct.phpt b/tests/swoole_mysql_coro/db_destruct.phpt deleted file mode 100644 index 53d102077f..0000000000 --- a/tests/swoole_mysql_coro/db_destruct.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql db destruct ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $ret = $db->connect($server); - if (Assert::true($ret)) { - $statement = $db->prepare('SELECT 1'); - Assert::isInstanceOf($statement, Co\Mysql\Statement::class); - $ret = $statement->execute(); - Assert::same($ret[0][1], 1); - echo "DONE\n"; - } -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/defer_and_fetch.phpt b/tests/swoole_mysql_coro/defer_and_fetch.phpt deleted file mode 100644 index 758f345982..0000000000 --- a/tests/swoole_mysql_coro/defer_and_fetch.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql defer and fetch ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]); - $mysql->setDefer(true); - for ($n = 0; $n < MAX_REQUESTS; $n++) { - if ($n === 0 || mt_rand(0, 1)) { - $ret = $mysql->prepare('SELECT ?+?'); - Assert::true($ret); - $statement = $mysql->recv(); - Assert::isInstanceOf($statement, Swoole\Coroutine\MySQL\Statement::class); - } - $a = mt_rand(0, 65535); - $b = mt_rand(0, 65535); - /** @var $statement Swoole\Coroutine\MySQL\Statement */ - Assert::true($statement->execute([$a, $b])); - Assert::true($statement->recv()); - $result = $statement->fetchAll(); - if (Assert::isArray($result)) { - Assert::same(reset($result[0]), (float)($a + $b)); - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/err_instead_of_eof.phpt b/tests/swoole_mysql_coro/err_instead_of_eof.phpt deleted file mode 100644 index d453be39b5..0000000000 --- a/tests/swoole_mysql_coro/err_instead_of_eof.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -swoole_mysql_coro: ERR Instead of EOF ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $db->connect($server); - if (!$db->query("EXPLAIN SELECT * FROM dual;")) { - echo $db->errno . PHP_EOL; - echo $db->error . PHP_EOL; - } -}); -?> ---EXPECT-- -1096 -SQLSTATE[HY000] [1096] No tables used diff --git a/tests/swoole_mysql_coro/escape.phpt b/tests/swoole_mysql_coro/escape.phpt deleted file mode 100644 index a58987ae93..0000000000 --- a/tests/swoole_mysql_coro/escape.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql escape ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - Assert::same($mysql->escape(""), ""); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/fetch.phpt b/tests/swoole_mysql_coro/fetch.phpt deleted file mode 100644 index 3c794d133e..0000000000 --- a/tests/swoole_mysql_coro/fetch.phpt +++ /dev/null @@ -1,34 +0,0 @@ ---TEST-- -swoole_mysql_coro: use fetch to get data ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]; - - $db->connect($server); - - // now we can make the responses independent - $stmt = $db->prepare('SELECT `id` FROM `userinfo` LIMIT 2'); - Assert::true($stmt->execute()); - if (!Assert::assert(is_array($ret = $stmt->fetch()) && !empty($ret))) { - echo "FETCH1 ERROR#{$stmt->errno}: {$stmt->error}\n"; - } - if (!Assert::assert(is_array($ret = $stmt->fetch()) && !empty($ret))) { - echo "FETCH2 ERROR#{$stmt->errno}: {$stmt->error}\n"; - } - Assert::same($stmt->fetch(), null); - Assert::same($stmt->fetchAll(), []); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/fetch_mode.phpt b/tests/swoole_mysql_coro/fetch_mode.phpt deleted file mode 100644 index 5000a2a8cb..0000000000 --- a/tests/swoole_mysql_coro/fetch_mode.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -swoole_mysql_coro: use fetch to get data ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]; - - $db->connect($server); - - // now we can make the responses independent - $stmt1 = $db->prepare('SELECT * FROM ckl LIMIT 1'); - Assert::true($stmt1->execute()); - $stmt2 = $db->prepare('SELECT * FROM ckl LIMIT 2'); - Assert::true($stmt2->execute()); - Assert::same(count($stmt1->fetchAll()), 1); - Assert::same(count($stmt2->fetchAll()), 2); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/fetch_mode_twice.phpt b/tests/swoole_mysql_coro/fetch_mode_twice.phpt deleted file mode 100644 index 1a312c4b9e..0000000000 --- a/tests/swoole_mysql_coro/fetch_mode_twice.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -swoole_mysql_coro: call fetch twice ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]; - - $db->connect($server); - - Assert::true($db->query("INSERT INTO ckl (`domain`,`path`,`name`) VALUES ('www.baidu.com', '/search', 'baidu')")); - // now we can make the responses independent - $stmt = $db->prepare('SELECT * FROM ckl LIMIT 1'); - Assert::true($stmt->execute()); - Assert::assert(($ret = $stmt->fetchAll()) && is_array($ret) && count($ret) === 1); - Assert::same($stmt->fetchAll(), []); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/illegal_extends.phpt b/tests/swoole_mysql_coro/illegal_extends.phpt deleted file mode 100644 index 558bade8ab..0000000000 --- a/tests/swoole_mysql_coro/illegal_extends.phpt +++ /dev/null @@ -1,62 +0,0 @@ ---TEST-- -swoole_mysql_coro: illegal child class ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]; - - // invalid connect - Assert::true($db->connect($server)); - Assert::false($db->connected); - Assert::false($db->query('select 1')); - Assert::same($db->errno, SWOOLE_MYSQLND_CR_CONNECTION_ERROR); - - // right implementation - Assert::true($db->connectRaw($server)); - Assert::same($db->query('select 1')[0][1], 1); -}); - -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/invalid_host.phpt b/tests/swoole_mysql_coro/invalid_host.phpt deleted file mode 100644 index 2c5a945fb4..0000000000 --- a/tests/swoole_mysql_coro/invalid_host.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -swoole_mysql_coro: invalid host ---SKIPIF-- - ---FILE-- -connect([ - 'host' => get_safe_random(), - 'port' => MYSQL_SERVER_PORT, - 'database' => MYSQL_SERVER_DB, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'timeout' => 0.5 - ]); - echo 'Connection: ' . ($connected ? 'Connected' : 'Not connected') . PHP_EOL; - Assert::same($mysql->connect_errno, SWOOLE_MYSQLND_CR_CONNECTION_ERROR); - echo $mysql->connect_error . PHP_EOL; -}); -?> ---EXPECTF-- -Connection: Not connected -SQLSTATE[HY000] [2002] %s diff --git a/tests/swoole_mysql_coro/kill_process.phpt b/tests/swoole_mysql_coro/kill_process.phpt deleted file mode 100644 index 025cad9bc9..0000000000 --- a/tests/swoole_mysql_coro/kill_process.phpt +++ /dev/null @@ -1,56 +0,0 @@ ---TEST-- -swoole_mysql_coro: kill process and check liveness ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]; - $mysql = new Swoole\Coroutine\MySQL; - Assert::true($mysql->connect($config)); - Assert::same($mysql->query('SELECT 1')[0][1], 1); - - $killer = new Swoole\Coroutine\MySQL; - Assert::true($killer->connect($config)); - - foreach ( - [ - function () use ($mysql) { - return $mysql->query('SELECT 1'); - }, - function () use ($mysql) { - return $mysql->begin(); - }, - function () use ($mysql) { - return $mysql->prepare('SELECT 1'); - }, - ] as $command - ) { - $processList = $killer->query('show processlist'); - $processList = array_filter($processList, function (array $value) { - return $value['db'] == MYSQL_SERVER_DB && $value['Info'] != 'show processlist'; - }); - foreach ($processList as $process) { - $killer->query("KILL {$process['Id']}"); - } - switch_process(); - Assert::false($command()); - Assert::same($mysql->errno, SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR); - Assert::true($mysql->connect($config)); - } - - echo $mysql->error . PHP_EOL; -}); -echo "DONE\n"; -?> ---EXPECT-- -SQLSTATE[HY000] [2006] MySQL server has gone away -DONE diff --git a/tests/swoole_mysql_coro/many_rows.phpt b/tests/swoole_mysql_coro/many_rows.phpt deleted file mode 100644 index 1742140cd0..0000000000 --- a/tests/swoole_mysql_coro/many_rows.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -swoole_mysql_coro: insert and select many rows ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - Assert::assert($db->connect($server)); - - $table_name = get_safe_random(16); - $createTable = "CREATE TABLE {$table_name} (\nid bigint PRIMARY KEY AUTO_INCREMENT,\n`content` text NOT NULL\n);"; - $row_num = [100, 200, 1000, 3000][PRESSURE_LEVEL]; - if (Assert::assert($db->query($createTable))) { - $sql = "INSERT INTO {$table_name} (`content`) VALUES " . rtrim(str_repeat('(?), ', $row_num), ', '); - $statement = $db->prepare($sql); - $random = []; - for ($n = 0; $n < $row_num; $n++) { - $random[$n] = get_safe_random(64); - } - $statement->execute($random); - $statement = $db->prepare("SELECT * FROM {$table_name}"); - $result = $statement->execute(); - if (Assert::assert(count($result) === $row_num)) { - for ($n = 0; $n < $row_num; $n++) { - Assert::same($result[$n]['content'], $random[$n]); - } - } - Assert::assert($db->query("DROP TABLE {$table_name}")); - echo "DONE\n"; - } -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/multi_packets.phpt b/tests/swoole_mysql_coro/multi_packets.phpt deleted file mode 100644 index 92787d1b94..0000000000 --- a/tests/swoole_mysql_coro/multi_packets.phpt +++ /dev/null @@ -1,125 +0,0 @@ ---TEST-- -swoole_mysql_coro: select and insert huge data from db (10M~64M) ---SKIPIF-- - ---FILE-- - -1 -]); -go(function () { - $mysql = new Swoole\Coroutine\Mysql; - $mysql_server = [ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]; - // set max_allowed_packet - $mysql->connect($mysql_server); - if (!$mysql->query('set global max_allowed_packet = 100 * 1024 * 1024')) { - exit('unable to set max_allowed_packet to 100M.'); - } - // reconnect and we can see changes - $mysql->close(); - $mysql->connect($mysql_server); - @$mysql->query('DROP TABLE `firmware`'); - $ret = $mysql->query(<<error); - } - $max_allowed_packet = $mysql->query('show VARIABLES like \'max_allowed_packet\''); - $max_allowed_packet = $max_allowed_packet[0]['Value'] / 1024 / 1024; - phpt_var_dump("max_allowed_packet: {$max_allowed_packet}M"); - if (IS_IN_TRAVIS) { - $max_allowed_packet = 36; - } else { - $max_allowed_packet = 64; - } - $pdo = new PDO( - "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", - MYSQL_SERVER_USER, MYSQL_SERVER_PWD - ); - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - $mysql_query = new Swoole\Coroutine\Mysql; - $mysql_query->connect($mysql_server); - $mysql_prepare = new Swoole\Coroutine\Mysql; - $mysql_prepare->connect($mysql_server); - for ($fid = 1; $fid <= $max_allowed_packet / 10; $fid++) { - $random_size = 2 << mt_rand(2, 9); - $text_size = min($fid * 10 + mt_rand(1, 9), $max_allowed_packet) * 1024 * 1024; // 1xM ~ 5xM - $firmware = str_repeat(get_safe_random($random_size), $text_size / $random_size); - $f_md5 = md5($firmware); - $f_remark = get_safe_random(); - if (mt_rand(0, 1)) { - $sql = "INSERT INTO `firmware` (`fid`, `firmware`, `f_md5`, `f_remark`) " . - "VALUES ({$fid}, '{$firmware}', '{$f_md5}', '{$f_remark}')"; - $ret = $mysql_query->query($sql); - } else { - $sql = "INSERT INTO `firmware` (`fid`, `firmware`, `f_md5`, `f_remark`) VALUES (?, ?, ?, ?)"; - $pdo_stmt = $mysql_prepare->prepare($sql); - if ($pdo_stmt) { - $ret = $pdo_stmt->execute([$fid, $firmware, $f_md5, $f_remark]); - if (!$ret) { - var_dump($pdo_stmt); - exit; - } - } else { - $ret = false; - } - } - if (Assert::assert($ret)) { - $sql = 'SELECT * FROM `test`.`firmware` WHERE fid='; - $pdo_stmt = $pdo->prepare("{$sql}?"); - $mysql_stmt = $mysql_prepare->prepare("{$sql}?"); - $chan = new Chan(); - go(function () use ($chan, $pdo_stmt, $fid) { - $pdo_stmt->execute([$fid]); - $result = $pdo_stmt->fetch(PDO::FETCH_ASSOC); - $chan->push(['pdo', $result]); - }); - go(function () use ($chan, $mysql_stmt, $fid) { - $result = $mysql_stmt->execute([$fid])[0]; - $chan->push(['mysql_prepare', $result]); - }); - go(function () use ($chan, $mysql_query, $sql, $fid) { - $chan->push(['mysql_query', $mysql_query->query("{$sql}{$fid}")[0]]); - }); - for ($i = 3; $i--;) { - list($from, $result) = $chan->pop(); - if ($result['fid'] === $fid) { - Assert::same($result['firmware'], $firmware); - Assert::same($result['f_md5'], $f_md5); - Assert::same($result['f_remark'], $f_remark); - } else { - Assert::assert(0, 'wrong result from ' . $from); - unset($result['firmware']); // too long to show - phpt_var_dump($result); - } - phpt_var_dump(sprintf('%-16s: %s', $from, (strlen($firmware) / 1024 / 1024) . 'M')); - } - } - } - echo "DONE\n"; -}); -Swoole\Event::wait(); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/not_exist.phpt b/tests/swoole_mysql_coro/not_exist.phpt deleted file mode 100644 index 280b1fad61..0000000000 --- a/tests/swoole_mysql_coro/not_exist.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql connect to wrong database ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => 'not_exist' - ]; - $connected = $db->connect($server); - Assert::assert(!$connected); - Assert::same($db->connect_errno, 1049); // unknown database - Assert::assert(strpos($db->connect_error, 'not_exist')); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/null.phpt b/tests/swoole_mysql_coro/null.phpt deleted file mode 100644 index 9f29d660ca..0000000000 --- a/tests/swoole_mysql_coro/null.phpt +++ /dev/null @@ -1,51 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql null ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]); - Assert::assert($connected); - Assert::assert($mysql->query('INSERT INTO `custom` (`content`) VALUES (NULL)')); - Assert::assert($mysql->query('INSERT INTO `custom` (`content`) VALUES ("")')); - Assert::assert($mysql->query('INSERT INTO `custom` (`content`) VALUES ("NULL")')); - $result = $mysql->query('select `content` from custom'); - var_dump(array_merge_recursive(...$result)['content']); - Assert::assert($mysql->query('TRUNCATE TABLE `custom`')); - - $stmt = $mysql->prepare('INSERT INTO `custom` (`content`) VALUES (?)'); - Assert::assert($stmt->execute([NULL])); - Assert::assert($stmt->execute([''])); - Assert::assert($stmt->execute(['NULL'])); - $result = $mysql->query('select `content` from custom'); - var_dump(array_merge_recursive(...$result)['content']); - Assert::assert($mysql->query('TRUNCATE TABLE `custom`')); -}); -?> ---EXPECT-- -array(3) { - [0]=> - NULL - [1]=> - string(0) "" - [2]=> - string(4) "NULL" -} -array(3) { - [0]=> - NULL - [1]=> - string(0) "" - [2]=> - string(4) "NULL" -} diff --git a/tests/swoole_mysql_coro/null_bit_map.phpt b/tests/swoole_mysql_coro/null_bit_map.phpt deleted file mode 100644 index f8cdec902e..0000000000 --- a/tests/swoole_mysql_coro/null_bit_map.phpt +++ /dev/null @@ -1,103 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql null bit map rand test ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - Assert::assert($connected); - return $mysql; -} - -for ($c = MAX_CONCURRENCY_LOW; $c--;) { - go(function () use ($c) { - // gen table structure - $table_name = 't' . substr(md5(mt_rand()), 0, 15); - $field_size = mt_rand(1, 100); - list($fields, $fields_info) = (function () use ($field_size) { - $fields_info = []; - $fields = ''; - for ($i = $field_size; $i--;) { - $info = $fields_info[] = [ - 'name' => 'f' . substr(md5(mt_rand()), 0, 7), - 'type' => gen_type() - ]; - $fields .= "{$info['name']} {$info['type']} NULL,\n"; - } - return [rtrim($fields, " \n,"), $fields_info]; - })(); - $mysql = mysql(); - // create table - $createTable = <<query($createTable)) { - trigger_error("create table error by query statement [{$createTable}]", E_USER_WARNING); - return; - } - $_insert = "INSERT INTO {$table_name} VALUES (" . rtrim(str_repeat('?, ', $field_size + 1), ', ') . ")"; - $data_list = []; - try { - for ($n = MAX_REQUESTS; $n--;) { - $insert = $mysql->prepare($_insert); - Assert::assert($insert instanceof Co\Mysql\Statement); - $data_list[] = $gen = (function ($id, $fields_info) { - $r = ['id' => $id]; - foreach ($fields_info as $info) { - if (mt_rand(0, 1)) { - $r[$info['name']] = null; - } else { - $r[$info['name']] = gen_data_from_type($info['type']); - } - } - return $r; - })($n + 1, $fields_info); - Assert::assert($insert->execute(array_values($gen))); - } - $result = $mysql->prepare("SELECT * FROM {$table_name}")->execute(); - Assert::same(array_reverse($data_list), $result); - } catch (Throwable $e) { - Assert::assert(0); - } finally { - Assert::assert($mysql->query("DROP TABLE {$table_name}")); - } - }); -} -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/numbers.phpt b/tests/swoole_mysql_coro/numbers.phpt deleted file mode 100644 index 6dfd2c217b..0000000000 --- a/tests/swoole_mysql_coro/numbers.phpt +++ /dev/null @@ -1,143 +0,0 @@ ---TEST-- -swoole_mysql_coro: floating point value precision and unsigned big int overflow ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => (PHP_VERSION_ID >= 80100) - ]; - $db->connect($server); - $r_string1 = $db->query('SELECT * FROM numbers'); - $db->close(); - $server['strict_type'] = true; - $db->connect($server); - $r_strong1 = $db->query('SELECT * FROM numbers'); - $stmt = $db->prepare('SELECT * FROM numbers'); - $r_strong2 = $stmt->execute(); - - try { - $pdo = new PDO( - "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", - MYSQL_SERVER_USER, MYSQL_SERVER_PWD - ); - $r_string2 = $pdo->query('SELECT * FROM numbers')->fetchAll(PDO::FETCH_ASSOC); - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - $stmt = $pdo->prepare('SELECT * FROM numbers'); - $stmt->execute(); - $r_strong3 = $stmt->fetchAll(PDO::FETCH_ASSOC); - Assert::same($r_string1, $r_string2); - Assert::same($r_strong2, $r_strong3); - } catch (\PDOException $e) { - Assert::same($e->getCode(), 2054); // not support auth plugin - } - - if (!is_musl_libc()) { - Assert::same($r_strong1, $r_strong2); - } - var_dump($r_strong2); -}); -?> ---EXPECT-- -array(3) { - [0]=> - array(13) { - ["id"]=> - int(1) - ["tinyint"]=> - int(127) - ["utinyint"]=> - int(255) - ["smallint"]=> - int(32767) - ["usmallint"]=> - int(65535) - ["mediumint"]=> - int(8388607) - ["umediumint"]=> - int(16777215) - ["int"]=> - int(2147483647) - ["uint"]=> - int(4294967294) - ["bigint"]=> - int(9223372036854775807) - ["ubigint"]=> - string(20) "18446744073709551615" - ["float"]=> - float(1.23457) - ["double"]=> - float(1.2345678901234567) - } - [1]=> - array(13) { - ["id"]=> - int(2) - ["tinyint"]=> - int(-128) - ["utinyint"]=> - int(123) - ["smallint"]=> - int(-32768) - ["usmallint"]=> - int(12345) - ["mediumint"]=> - int(-8388608) - ["umediumint"]=> - int(123456) - ["int"]=> - int(-2147483648) - ["uint"]=> - int(123456) - ["bigint"]=> - int(-9223372036854775808) - ["ubigint"]=> - int(123456) - ["float"]=> - float(-1.23457) - ["double"]=> - float(-1.2345678901234567) - } - [2]=> - array(13) { - ["id"]=> - int(3) - ["tinyint"]=> - int(0) - ["utinyint"]=> - int(0) - ["smallint"]=> - int(0) - ["usmallint"]=> - int(0) - ["mediumint"]=> - int(0) - ["umediumint"]=> - int(0) - ["int"]=> - int(0) - ["uint"]=> - int(0) - ["bigint"]=> - int(0) - ["ubigint"]=> - int(0) - ["float"]=> - float(1.23) - ["double"]=> - float(1.23) - } -} diff --git a/tests/swoole_mysql_coro/prepare_field_type.phpt b/tests/swoole_mysql_coro/prepare_field_type.phpt deleted file mode 100644 index 339ef2c6e1..0000000000 --- a/tests/swoole_mysql_coro/prepare_field_type.phpt +++ /dev/null @@ -1,48 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare field type ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true, - ]; - - $ret1 = $db->connect($server); - if (! $ret1) { - echo "CONNECT ERROR\n"; - - return; - } - - $stmt = $db->prepare('SELECT ? as a, ? as b, ? as c, ? as d, ? + ? as e'); - if (! $stmt) { - echo "PREPARE ERROR\n"; - - return; - } - - $ret3 = $stmt->execute([123, 3.14, true, false, 11, 22]); - if (! $ret3) { - echo "EXECUTE ERROR#{$stmt->errno}: {$stmt->error}\n"; - - return; - } - if (Assert::isArray($ret3)) { - Assert::same(reset($ret3), ['a' => 123, 'b' => 3.14, 'c' => 1, 'd' => 0, 'e' => 33]); - } -}); - -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/prepare_insert.phpt b/tests/swoole_mysql_coro/prepare_insert.phpt deleted file mode 100644 index 152a5e2dd8..0000000000 --- a/tests/swoole_mysql_coro/prepare_insert.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (insert) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ); - - $ret1 = $db->connect($server); - if (!$ret1) { - echo "CONNECT ERROR\n"; - return; - } - - $stmt = $db->prepare('INSERT INTO ckl (`domain`,`path`,`name`) VALUES (?,?,?)'); - if (!$stmt) { - echo "PREPARE ERROR\n"; - return; - } - - $ret3 = $stmt->execute(array('www.baidu.com', '/search', 'baidu')); - if (!$ret3) { - echo "EXECUTE ERROR\n"; - return; - } - Assert::assert($stmt->insert_id > 0); - Assert::assert($db->insert_id == $stmt->insert_id); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/prepare_multi.phpt b/tests/swoole_mysql_coro/prepare_multi.phpt deleted file mode 100644 index ca73c5eab1..0000000000 --- a/tests/swoole_mysql_coro/prepare_multi.phpt +++ /dev/null @@ -1,49 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare multi (insert and delete) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ]; - $connected = $db->connect($server); - if (!$connected) { - echo "CONNECT ERROR\n"; - return; - } - for ($n = MAX_REQUESTS_MID; $n--;) { - $statement = $db->prepare('INSERT INTO ckl (`domain`,`path`,`name`) VALUES (?, ?, ?)'); - if (!$statement) { - echo "PREPARE ERROR\n"; - return; - } - $executed = $statement->execute(['www.baidu.com', '/search', 'baidu']); - if (!$executed) { - echo "EXECUTE ERROR\n"; - return; - } - if ($statement->insert_id > 0) { - $deleted = $db->query("DELETE FROM ckl WHERE id={$statement->insert_id}"); - if (!$deleted) { - echo "DELETE ERROR\n"; - } - } else { - echo "INSERT ERROR\n"; - } - } - }); -} -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/prepare_select.phpt b/tests/swoole_mysql_coro/prepare_select.phpt deleted file mode 100644 index 46ebc89112..0000000000 --- a/tests/swoole_mysql_coro/prepare_select.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (select) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ); - - $ret1 = $db->connect($server); - if (!$ret1) { - echo "CONNECT ERROR\n"; - return; - } - - $stmt = $db->prepare('SELECT * FROM userinfo WHERE id=?'); - if (!$stmt) { - echo "PREPARE ERROR\n"; - return; - } - - $ret3 = $stmt->execute([5]); - if (!$ret3) { - echo "EXECUTE ERROR#{$stmt->errno}: {$stmt->error}\n"; - return; - } - Assert::assert(count($ret3) > 0); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/procedure.phpt b/tests/swoole_mysql_coro/procedure.phpt deleted file mode 100644 index b0fe8966c9..0000000000 --- a/tests/swoole_mysql_coro/procedure.phpt +++ /dev/null @@ -1,60 +0,0 @@ ---TEST-- -swoole_mysql_coro: procedure without fetch mode ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - - $clear = <<connect($server); - - if ($db->query($clear) && $db->query($procedure)) { - $stmt = $db->prepare('CALL reply(?)'); - for ($n = MAX_REQUESTS; $n--;) { - //SWOOLE - $_map = $map; - $res = $stmt->execute(['hello mysql!']); - do { - Assert::same(current($res[0]), array_shift($_map)); - } while ($res = $stmt->nextResult()); - Assert::same($stmt->affected_rows, 1, 'get the affected rows failed!'); - Assert::assert(empty($_map), 'there are some results lost!'); - } - } - - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/procedure_by_query.phpt b/tests/swoole_mysql_coro/procedure_by_query.phpt deleted file mode 100644 index 4d1a7470db..0000000000 --- a/tests/swoole_mysql_coro/procedure_by_query.phpt +++ /dev/null @@ -1,63 +0,0 @@ ---TEST-- -swoole_mysql_coro: procedure without fetch mode by query ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => true - ]; - - $clear = <<connect($server); - - if ($db->query($clear) && $db->query($procedure)) { - for ($n = MAX_REQUESTS; $n--;) { - $_map = $map; - $res = $db->query('CALL reply("hello mysql!")'); - do { - if (is_array($res)) { - Assert::same(current($res[0]), array_shift($_map)); - } else { - Assert::true($res); - } - } while ($res = $db->nextResult()); - Assert::same($db->affected_rows, 1); - Assert::assert(empty($_map), 'there are some results lost!'); - } - } - - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/procedure_in_fetch.phpt b/tests/swoole_mysql_coro/procedure_in_fetch.phpt deleted file mode 100644 index baf72fdb7c..0000000000 --- a/tests/swoole_mysql_coro/procedure_in_fetch.phpt +++ /dev/null @@ -1,82 +0,0 @@ ---TEST-- -swoole_mysql_coro: procedure in fetch mode ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]; - - $clear = <<connect($server); - if ($db->query($clear) && $db->query($procedure)) { - - //SWOOLE - $_map = $map; - $stmt = $db->prepare('CALL reply(?)'); - Assert::true($stmt->execute(['hello mysql!'])); - do { - $res = $stmt->fetchAll(); - Assert::same(current($res[0]), array_shift($_map)); - } while ($ret = $stmt->nextResult()); - Assert::same($stmt->affected_rows, 1); - Assert::assert(empty($_map), 'there are some results lost!'); - - //PDO - if (extension_loaded('PDO')) { - $_map = $map; - try { - $pdo = new PDO( - "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", - MYSQL_SERVER_USER, MYSQL_SERVER_PWD - ); - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - $stmt = $pdo->prepare("CALL reply(?)"); - Assert::true($stmt->execute(['hello mysql!'])); - do { - $res = $stmt->fetchAll(); - Assert::same(current($res[0]), array_shift($_map)); - } while ($ret = $stmt->nextRowset() and count($_map) > 0); - Assert::same($stmt->rowCount(), 1, 'get the affected rows failed!'); - Assert::assert(empty($_map), 'there are some results lost!'); - } catch (\PDOException $e) { - Assert::same($e->getCode(), 2054); // not support auth plugin - } - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/procedure_single.phpt b/tests/swoole_mysql_coro/procedure_single.phpt deleted file mode 100644 index cc876712e7..0000000000 --- a/tests/swoole_mysql_coro/procedure_single.phpt +++ /dev/null @@ -1,42 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql procedure single ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - - $clear = <<connect($server); - if ($db->query($clear) && $db->query($procedure)) { - $stmt = $db->prepare('CALL say(?)'); - for ($n = MAX_REQUESTS; $n--;) { - $ret = $stmt->execute(['hello mysql!']); - Assert::same(current($ret[0]), 'You said: "hello mysql!"'); - Assert::null($stmt->nextResult()); - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/procedure_with_query.phpt b/tests/swoole_mysql_coro/procedure_with_query.phpt deleted file mode 100644 index 1fec3a58a6..0000000000 --- a/tests/swoole_mysql_coro/procedure_with_query.phpt +++ /dev/null @@ -1,65 +0,0 @@ ---TEST-- -swoole_mysql_coro: procedure without query (#2117) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - - $clear = <<connect($server); - - if ($db->query($clear) && $db->query($procedure)) { - for ($n = MAX_REQUESTS_LOW; $n--;) { - $res = $db->query('CALL reply("hello mysql!")'); - $_map = $map; - do { - Assert::same(current($res[0]), array_shift($_map)); - } while ($res = $db->nextResult()); - } - for ($n = MAX_REQUESTS_LOW; $n--;) { - $res = $db->query('CALL reply("hello mysql!")'); - $_map = $map; - do { - Assert::same(current($res[0]), array_shift($_map)); - } while ($res = $db->nextResult()); - Assert::same($db->affected_rows, 1, 'get the affected rows failed!'); - Assert::assert(empty($_map), 'there are some results lost!'); - } - } - - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/procedure_with_query_and_prepare.phpt b/tests/swoole_mysql_coro/procedure_with_query_and_prepare.phpt deleted file mode 100644 index 22a92273e5..0000000000 --- a/tests/swoole_mysql_coro/procedure_with_query_and_prepare.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -swoole_mysql_coro: query 'CALL' statement & prepare (#2117) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - - $clear = <<connect($server); - if ($db->query($clear) && $db->query($procedure)) { - $db->query('CALL sp_whoami()'); - Assert::null($db->nextResult()); - $stmt = $db->prepare('CALL sp_whoami()'); - $ret = $stmt->execute(); - Assert::assert(strpos(current($ret[0]), MYSQL_SERVER_USER) !== false); - Assert::null($stmt->nextResult()); - } -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/query.phpt b/tests/swoole_mysql_coro/query.phpt deleted file mode 100644 index d459d979df..0000000000 --- a/tests/swoole_mysql_coro/query.phpt +++ /dev/null @@ -1,63 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql client ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) -{ - go(function () use ($pm) { - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - $pm->kill(); - }); -}; - -$pm->childFunc = function () use ($pm) -{ - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $http->set(array( - 'log_file' => '/dev/null' - )); - $http->on("WorkerStart", function (Swoole\Server $serv) - { - /** - * @var $pm ProcessManager - */ - global $pm; - $pm->wakeup(); - }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) - { - $mysql = new Swoole\Coroutine\MySQL(); - $res = $mysql->connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - if (!$res) - { - fail: - $response->end("ERROR\n"); - return; - } - $ret = $mysql->query('show tables', 2); - if (!$ret) { - goto fail; - } - if (count($ret) > 0) { - $response->end("OK\n"); - } - }); - $http->start(); -}; - -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -OK diff --git a/tests/swoole_mysql_coro/query_multifield.phpt b/tests/swoole_mysql_coro/query_multifield.phpt deleted file mode 100644 index b39c02da27..0000000000 --- a/tests/swoole_mysql_coro/query_multifield.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -swoole_mysql_coro: multi field ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $ret = $db->connect($server); - if (Assert::true($ret)) { - $n = range(0, FIELD_NUM - 1); - $fields = implode(", ", $n); - $result = $db->query("select $fields"); - Assert::assert(count($result[0]) == FIELD_NUM); - echo "DONE\n"; - } -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/query_timeout.phpt b/tests/swoole_mysql_coro/query_timeout.phpt deleted file mode 100644 index 5105153f73..0000000000 --- a/tests/swoole_mysql_coro/query_timeout.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql query timeout ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - if (Assert::true($ret)) { - $s = microtime(true); - $timeout = mt_rand(100, 500) / 1000; - $ret = $mysql->query('select sleep(1)', $timeout); - time_approximate($timeout, microtime(true) - $s); - if (Assert::false($ret)) { - Assert::same($mysql->errno, SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR); - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/readonly.phpt b/tests/swoole_mysql_coro/readonly.phpt deleted file mode 100644 index 69850419b4..0000000000 --- a/tests/swoole_mysql_coro/readonly.phpt +++ /dev/null @@ -1,53 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql use readonly user ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $connected = $root->connect($server); - Assert::assert($connected); - - // create read only user - $create = $root->query('CREATE USER `readonly`@`%` IDENTIFIED BY \'123456\';'); - Assert::assert($create); - $grant = $root->query('GRANT SELECT ON *.* TO `readonly`@`%` WITH GRANT OPTION;'); - Assert::assert($grant); - - // use readonly - $server['user'] = 'readonly'; - $server['password'] = '123456'; - $readonly = new Swoole\Coroutine\MySQL; - $connected = $readonly->connect($server); - Assert::assert($connected); - - // read - $result = $readonly->query('SELECT * FROM userinfo'); - Assert::assert(is_array($result) && count($result) > 5); - $id = $result[0]['id']; - // write - $delete = $readonly->query('DELETE FROM userinfo WHERE id=' . $id); - Assert::assert(!$delete); - echo $readonly->errno . "\n"; - echo $readonly->error . "\n"; - - // drop - Assert::assert($root->query('DROP ROLE readonly')); -}); -Swoole\Event::wait(); -?> ---EXPECTF-- -1142 -SQLSTATE[42000] [1142] DELETE command denied to user 'readonly'@'%s' for table 'userinfo' diff --git a/tests/swoole_mysql_coro/simple_query.phpt b/tests/swoole_mysql_coro/simple_query.phpt deleted file mode 100644 index 484696df28..0000000000 --- a/tests/swoole_mysql_coro/simple_query.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql simple query ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - Assert::assert($res); - $ret = $mysql->query('show tables', 2); - Assert::assert(is_array($ret)); - Assert::assert(count($ret) > 0); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/statement_closed.phpt b/tests/swoole_mysql_coro/statement_closed.phpt deleted file mode 100644 index 2d90837628..0000000000 --- a/tests/swoole_mysql_coro/statement_closed.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (destruct) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $ret = $db->connect($server); - assert($ret); - $statement = $db->prepare('SELECT 1'); - assert($statement instanceof Co\Mysql\Statement); - $ret = $statement->execute(); - assert($ret[0][1] === 1); - $db->close(); - $ret = $db->connect($server); - assert($ret); - $ret = $statement->execute(); - assert(!$ret); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/statement_destruct.phpt b/tests/swoole_mysql_coro/statement_destruct.phpt deleted file mode 100644 index a226edb2ba..0000000000 --- a/tests/swoole_mysql_coro/statement_destruct.phpt +++ /dev/null @@ -1,53 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare (destruct) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - ]; - - $ret1 = $db->connect($server); - - $start_prepared_num = (int)(($db->query('show status like \'Prepared_stmt_count\''))[0]['Value']); - if (!$ret1) { - echo "CONNECT ERROR\n"; - return; - } - $stmt1 = $db->prepare('SELECT * FROM userinfo WHERE id=?'); - if (!$stmt1) { - echo "PREPARE1 ERROR\n"; - return; - } - $stmt2 = $db->prepare('SELECT * FROM `userinfo`'); - if (!$stmt2) { - echo "PREPARE2 ERROR\n"; - return; - } - $stmt3 = $db->prepare('SELECT `id` FROM `userinfo`'); - if (!$stmt3) { - echo "PREPARE3 ERROR\n"; - return; - } - - $prepared_num1 = (int)(($db->query('show status like \'Prepared_stmt_count\''))[0]['Value']); - Assert::same($prepared_num1 - $start_prepared_num, 3); - $stmt1 = null; //destruct - unset($stmt2); //destruct - $prepared_num2 = (int)(($db->query('show status like \'Prepared_stmt_count\''))[0]['Value']); - Assert::same($prepared_num1 - $prepared_num2, 2); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/timeout.phpt b/tests/swoole_mysql_coro/timeout.phpt deleted file mode 100644 index 9c0e78abb9..0000000000 --- a/tests/swoole_mysql_coro/timeout.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -swoole_mysql_coro: timeout ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]); - Assert::assert($connected); - $statement = $mysql->prepare('SELECT SLEEP(1)'); - Assert::assert($statement instanceof Co\Mysql\Statement); - $timeout = ms_random(0.1, 0.5); - $s = microtime(true); - $use_query = !!mt_rand(0, 1); - if ($use_query) { - $ret = $mysql->query('SELECT SLEEP(1)', $timeout); - } else { - $ret = $statement->execute(null, $timeout); - } - time_approximate($timeout, microtime(true) - $s); - Assert::assert(!$ret); - Assert::same($use_query ? $mysql->errno : $statement->errno, SWOOLE_MYSQLND_CR_SERVER_GONE_ERROR); - }); -} -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/transaction.phpt b/tests/swoole_mysql_coro/transaction.phpt deleted file mode 100644 index 34b9ff47f0..0000000000 --- a/tests/swoole_mysql_coro/transaction.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -swoole_mysql_coro: transaction ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - $db->connect($server); - - $random = mt_rand(); - Assert::assert($db->begin()); - Assert::assert($db->query('INSERT INTO ckl (`domain`,`path`,`name`) VALUES ("www.swoole.com", "/", "' . $random . '")')); - Assert::assert(!empty($db->query('SELECT `name` FROM `ckl` WHERE `name`="' . $random . '"'))); - Assert::assert($db->rollback()); - Assert::assert(empty($db->query('SELECT `name` FROM `ckl` WHERE `name`="' . $random . '"'))); - $random = mt_rand(); - Assert::assert($db->begin()); - Assert::assert($db->query('INSERT INTO ckl (`domain`,`path`,`name`) VALUES ("www.swoole.com", "/", "' . $random . '")')); - Assert::assert($db->commit()); - Assert::assert(!empty($db->query('SELECT `name` FROM `ckl` WHERE `name`="' . $random . '"'))); - Assert::assert($db->query('DELETE FROM `ckl` WHERE `name`="' . $random . '"')); - Assert::assert(empty($db->query('SELECT `name` FROM `ckl` WHERE `name`="' . $random . '"'))); - - $db->setDefer(); - Assert::throws(function () use ($db) { $db->begin(); }, Exception::class); - Assert::throws(function () use ($db) { $db->commit(); }, Exception::class); - Assert::throws(function () use ($db) { $db->rollback(); }, Exception::class); - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/unixsocket.phpt b/tests/swoole_mysql_coro/unixsocket.phpt deleted file mode 100644 index 377ee006d0..0000000000 --- a/tests/swoole_mysql_coro/unixsocket.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql connection on unix socket ---SKIPIF-- - ---FILE-- - 'unix:/' . MYSQL_SERVER_PATH, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB - ]; - Assert::assert($db->connect($server)); - Assert::same($db->query('SELECT 1'), [['1' => '1']]); - echo "DONE\n"; -}); -?> ---EXPECTF-- -DONE diff --git a/tests/swoole_mysql_coro/userinfo.phpt b/tests/swoole_mysql_coro/userinfo.phpt deleted file mode 100644 index f4af02765a..0000000000 --- a/tests/swoole_mysql_coro/userinfo.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql prepare dtor ---SKIPIF-- - ---FILE-- -connect([ - 'host' => MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'strict_type' => (PHP_VERSION_ID >= 80100) - ]); - $result = $mysql->query('SELECT * FROM `userinfo`'); - $pdo = new PDO( - "mysql:host=" . MYSQL_SERVER_HOST . ";port=" . MYSQL_SERVER_PORT . ";dbname=" . MYSQL_SERVER_DB . ";charset=utf8", - MYSQL_SERVER_USER, MYSQL_SERVER_PWD - ); - $pdo_result = $pdo->query('SELECT * FROM `userinfo`')->fetchAll(PDO::FETCH_ASSOC); - Assert::same($result, $pdo_result); - - $result = $mysql->prepare('SELECT * FROM `userinfo`')->execute(); - $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); - $pdo_stmt = $pdo->prepare('SELECT * FROM `userinfo`'); - $pdo_stmt->execute(); - $pdo_result =$pdo_stmt->fetchAll(PDO::FETCH_ASSOC); - - Assert::same($result, $pdo_result); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_mysql_coro/without_fetch.phpt b/tests/swoole_mysql_coro/without_fetch.phpt deleted file mode 100644 index 253444f1a3..0000000000 --- a/tests/swoole_mysql_coro/without_fetch.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -swoole_mysql_coro: just execute (test memory leak) ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => MYSQL_SERVER_PWD, - 'database' => MYSQL_SERVER_DB, - 'fetch_mode' => true - ]; - - $db->connect($server); - $stmt = $db->prepare('SELECT * FROM `userinfo` LIMIT 1'); - Assert::true($stmt->execute()); - Assert::true($stmt->execute()); - Assert::true($stmt->execute()); - Assert::assert(is_array($stmt->fetchAll())); -}); -?> ---EXPECT-- diff --git a/tests/swoole_mysql_coro/wrong_password.phpt b/tests/swoole_mysql_coro/wrong_password.phpt deleted file mode 100644 index 8b7e619254..0000000000 --- a/tests/swoole_mysql_coro/wrong_password.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -swoole_mysql_coro: mysql connect with wrong password ---SKIPIF-- - ---FILE-- - MYSQL_SERVER_HOST, - 'port' => MYSQL_SERVER_PORT, - 'user' => MYSQL_SERVER_USER, - 'password' => 'i am hack', - 'database' => MYSQL_SERVER_DB - ]; - $connected = $db->connect($server); - Assert::assert(!$connected); - echo $db->connect_errno . "\n"; - echo $db->connect_error, "\n"; -}); -?> ---EXPECTF-- -1045 -SQLSTATE[28000] [1045] Access denied for user 'root'@'%s' (using password: YES) diff --git a/tests/swoole_mysql_coro/z_reset.phpt b/tests/swoole_mysql_coro/z_reset.phpt deleted file mode 100644 index fd30746921..0000000000 --- a/tests/swoole_mysql_coro/z_reset.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -swoole_mysql_coro: reset test mysql database ---SKIPIF-- - ---FILE-- - /dev/null`; -?> ---EXPECT-- diff --git a/tests/swoole_pdo_firebird/base.phpt b/tests/swoole_pdo_firebird/base.phpt new file mode 100644 index 0000000000..203cbf0cdc --- /dev/null +++ b/tests/swoole_pdo_firebird/base.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_pdo_firebird: test hook pdo_firebird +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_FIREBIRD]); +run(static function (): void { + $db = PdoFirebirdTest::create(); + + $db->exec('CREATE TABLE test_table (id INTEGER PRIMARY KEY, name VARCHAR(50))'); + + $db->exec("INSERT INTO test_table VALUES (1, 'Firebird Test')"); + + $stmt = $db->query('SELECT * FROM test_table'); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + $stmt = null; + var_dump($result); + + $db->exec('DROP TABLE test_table'); +}); +echo "DONE\n"; +?> +--EXPECTF-- +array(2) { + ["ID"]=> + int(1) + ["NAME"]=> + string(13) "Firebird Test" +} +DONE diff --git a/tests/swoole_pdo_firebird/pdo_firebird.inc b/tests/swoole_pdo_firebird/pdo_firebird.inc new file mode 100644 index 0000000000..b0d78982d5 --- /dev/null +++ b/tests/swoole_pdo_firebird/pdo_firebird.inc @@ -0,0 +1,18 @@ +getMessage()); + } + } + + public static function create(): PDO + { + return new PDO(FIREBIRD_DSN, FIREBIRD_USER, FIREBIRD_PASSWORD); + } +} diff --git a/tests/swoole_pdo_firebird/query.phpt b/tests/swoole_pdo_firebird/query.phpt new file mode 100644 index 0000000000..8833182460 --- /dev/null +++ b/tests/swoole_pdo_firebird/query.phpt @@ -0,0 +1,91 @@ +--TEST-- +swoole_pdo_firebird: test query +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_FIREBIRD]); +Co\run(static function (): void { + $db = PdoFirebirdTest::create(); + + // 准备测试表 + try { + $db->exec('DROP TABLE concurrent_test'); + try { + $db->exec('COMMIT'); + } catch (PDOException $e) { + // 忽略COMMIT失败的错误 + } + } catch (PDOException $e) { + // 表不存在时的错误可以忽略 + } + + // 创建测试表 + $db->exec('CREATE TABLE concurrent_test (id INTEGER PRIMARY KEY, name VARCHAR(100), age INTEGER)'); + + $stmt = $db->prepare('INSERT INTO concurrent_test (id, name, age) values (?, ?, ?)'); + + $list = []; + for ($i = 0; $i < N; $i++) { + $id = $i + 1; + $name = base64_encode(random_bytes(8)); + $age = random_int(18, 35); + $stmt->bindValue(1, $id); + $stmt->bindValue(2, $name); + $stmt->bindValue(3, $age); + $stmt->execute(); + + $list[] = [ + 'id' => $id, + 'name' => $name, + 'age' => $age + ]; + } + + // 创建通道用于同步 + $channel = new Co\Channel(N); + + foreach ($list as $rs) { + Co\go(function () use ($rs, $channel) { + $db = PdoFirebirdTest::create(); + try { + $statement = $db->query('SELECT * FROM concurrent_test WHERE id = ' . $rs['id'] . ' ROWS 1'); + $result = $statement->fetch(PDO::FETCH_ASSOC); + Assert::eq($result['ID'], $rs['id']); + Assert::eq($result['NAME'], $rs['name']); + Assert::eq($result['AGE'], $rs['age']); + } catch (PDOException $e) { + echo "Error in coroutine: " . $e->getMessage() . "\n"; + } + // 通知主协程当前协程已完成 + $channel->push(true); + }); + } + + // 等待所有协程完成 + for ($i = 0; $i < N; $i++) { + $channel->pop(); + } + + // 所有协程完成后再清理测试表 + try { + $db->exec('DROP TABLE concurrent_test'); + } catch (PDOException $e) { + // 忽略清理错误 + } +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_firebird/sleep.phpt b/tests/swoole_pdo_firebird/sleep.phpt new file mode 100644 index 0000000000..8110cb81e8 --- /dev/null +++ b/tests/swoole_pdo_firebird/sleep.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_pdo_firebird: test hook firebird sleep +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_FIREBIRD]); +Co\run(static function (): void { + $sleep_count = 0; + Co\go(function () use (&$sleep_count) { + $n = N; + while ($n--) { + Co::sleep(0.002); + $sleep_count++; + } + }); + + $db = PdoFirebirdTest::create(); + + $iterations = 50; + for ($i = 0; $i < $iterations; $i++) { + $statement = $db->query('SELECT COUNT(*) FROM RDB$RELATIONS'); + $statement->fetch(); + } + + Assert::greaterThanEq($sleep_count, 10); +}); +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_firebird/transaction.phpt b/tests/swoole_pdo_firebird/transaction.phpt new file mode 100644 index 0000000000..fa75710cb5 --- /dev/null +++ b/tests/swoole_pdo_firebird/transaction.phpt @@ -0,0 +1,94 @@ +--TEST-- +swoole_pdo_firebird: test transaction +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_FIREBIRD]); +run(static function (): void { + $db = PdoFirebirdTest::create(); + + // 先尝试删除可能存在的表(忽略错误) + try { + $db->exec('DROP TABLE transaction_test'); + // 使用try-catch包围COMMIT + try { + $db->exec('COMMIT'); + } catch (PDOException $e) { + // 忽略COMMIT失败的错误 + } + } catch (PDOException $e) { + // 表不存在时的错误可以忽略 + } + + // 创建测试表 - 将value改为test_value以避免保留关键字冲突 + $db->exec('CREATE TABLE transaction_test (id INTEGER PRIMARY KEY, test_value VARCHAR(50))'); + + try { + // 开始事务 + $db->beginTransaction(); + + // 插入数据 - 更新列名 + $db->exec("INSERT INTO transaction_test VALUES (1, 'Value 1')"); + $db->exec("INSERT INTO transaction_test VALUES (2, 'Value 2')"); + + // 提交事务 + $db->commit(); + + // 验证数据已插入 + $stmt = $db->query('SELECT COUNT(*) FROM transaction_test'); + $count = $stmt->fetchColumn(); + echo "Count after commit: ", $count, "\n"; + + // 再次开始事务 + $db->beginTransaction(); + $db->exec("INSERT INTO transaction_test VALUES (3, 'Value 3')"); + + // 回滚事务 + $db->rollback(); + + // 验证回滚后的数据 + $stmt = $db->query('SELECT COUNT(*) FROM transaction_test'); + $count = $stmt->fetchColumn(); + echo "Count after rollback: ", $count, "\n"; + + } catch (PDOException $e) { + echo "Error: ", $e->getMessage(), "\n"; + } + + // 清理测试表 + try { + $db->exec('DROP TABLE transaction_test'); + } catch (PDOException $e) { + // 如果删除失败,尝试使用更安全的方式处理事务 + try { + // 首先尝试回滚任何活动事务 + $db->rollBack(); + } catch (PDOException $e) { + // 忽略回滚失败的错误 + } + // 再次尝试删除表 + try { + $db->exec('DROP TABLE transaction_test'); + } catch (PDOException $e) { + // 忽略第二次删除失败的错误 + } + } +}); + +echo "DONE\n"; +?> +--EXPECT-- +Count after commit: 2 +Count after rollback: 2 +DONE diff --git a/tests/swoole_pdo_odbc/base.phpt b/tests/swoole_pdo_odbc/base.phpt new file mode 100644 index 0000000000..67875c9b16 --- /dev/null +++ b/tests/swoole_pdo_odbc/base.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_pdo_odbc: test hook pdo_odbc +--SKIPIF-- + +--FILE-- +prepare('show tables'); + $statement->execute(); + Assert::greaterThan(count($statement->fetchAll(PDO::FETCH_COLUMN)), 1); + }); + + Co\go(function () { + $pdo = new PDO(ODBC_DSN); + $statement = $pdo->prepare('show tables'); + $statement->execute(); + Assert::greaterThan(count($statement->fetchAll(PDO::FETCH_COLUMN)), 1); + }); +}); + +echo "DONE\n"; +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_pdo_odbc/blocking.phpt b/tests/swoole_pdo_odbc/blocking.phpt new file mode 100644 index 0000000000..8d1e2acc16 --- /dev/null +++ b/tests/swoole_pdo_odbc/blocking.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_pdo_odbc: test hook pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT sleep(1) ss'); + $statement->execute(); + Assert::eq($sleep_count, 0); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_odbc/query.phpt b/tests/swoole_pdo_odbc/query.phpt new file mode 100644 index 0000000000..c837ae377b --- /dev/null +++ b/tests/swoole_pdo_odbc/query.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_pdo_odbc: test query +--SKIPIF-- + +--FILE-- +prepare('INSERT INTO ckl (name, domain, path) values (?, ?, ?)'); + + $list = []; + for ($i = 0; $i < N; $i++) { + $row = [ + 'name' => base64_encode(random_bytes(8)), + 'domain' => 'domain-' . random_int(10000, 99999), + 'path' => '/' . uniqid() . '/' . $i, + ]; + $list[] = $row; + $stmt->bindValue(1, $row['name']); + $stmt->bindValue(2, $row['domain']); + $stmt->bindValue(3, $row['path']); + $stmt->execute(); + } + + foreach ($list as $rs) { + Co\go(function () use ($rs) { + $pdo = new PDO(ODBC_DSN); + $statement = $pdo->query('select name, domain, path from ckl where path = "' . $rs['path'] . '" limit 1'); + Assert::eq($statement->fetch(PDO::FETCH_ASSOC), $rs); + }); + } +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_odbc/race.phpt b/tests/swoole_pdo_odbc/race.phpt new file mode 100644 index 0000000000..79cd255ff5 --- /dev/null +++ b/tests/swoole_pdo_odbc/race.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_pdo_odbc: race +--SKIPIF-- + +--FILE-- +prepare('SELECT sleep(1) ss'); + try { + $statement->execute(); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); + } catch (\PDOException $e) { + $msg[] = $e->getMessage(); + } + }); + } + Assert::count($msg, 0); +}); + +Assert::greaterThanEq(microtime(true) - $begin, 2); +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_odbc/server_info.phpt b/tests/swoole_pdo_odbc/server_info.phpt new file mode 100644 index 0000000000..c5ddc01d9f --- /dev/null +++ b/tests/swoole_pdo_odbc/server_info.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_pdo_odbc: test hook pgsql +--SKIPIF-- + +--FILE-- +getAttribute(PDO::ATTR_SERVER_INFO); + Assert::eq(strtolower($info), 'mysql'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_odbc/sleep.phpt b/tests/swoole_pdo_odbc/sleep.phpt new file mode 100644 index 0000000000..35776405cc --- /dev/null +++ b/tests/swoole_pdo_odbc/sleep.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_pdo_odbc: test hook pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT sleep(1) ss'); + $statement->execute(); + Assert::eq($sleep_count, N); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'ss'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_odbc/transaction.phpt b/tests/swoole_pdo_odbc/transaction.phpt new file mode 100644 index 0000000000..93f92c556c --- /dev/null +++ b/tests/swoole_pdo_odbc/transaction.phpt @@ -0,0 +1,57 @@ +--TEST-- +swoole_pdo_odbc: test query +--SKIPIF-- + +--FILE-- +prepare('INSERT INTO ckl (name, domain, path) values (?, ?, ?)'); + $row = [ + 'name' => base64_encode(random_bytes(8)), + 'domain' => 'domain-' . random_int(10000, 99999), + 'path' => '/' . uniqid() . '/' . 0, + ]; + $stmt->bindValue(1, $row['name']); + $stmt->bindValue(2, $row['domain']); + $stmt->bindValue(3, $row['path']); + $stmt->execute(); + }; + + $countTable = function ($pdo) { + return $pdo->query('select count(*) c from ckl')->fetch(PDO::FETCH_ASSOC)['c']; + }; + + $insertToTable($pdo); + var_dump('insert'); + + $c1 = $countTable($pdo); + + $pdo->beginTransaction(); + $insertToTable($pdo); + $pdo->rollBack(); + var_dump('rollback'); + + Assert::eq($countTable($pdo), $c1); + + $pdo->beginTransaction(); + $insertToTable($pdo); + $pdo->commit(); + var_dump('commit'); + + Assert::eq($countTable($pdo), $c1 + 1); +}); + +echo "Done\n"; +?> +--EXPECTF-- +string(6) "insert" +string(8) "rollback" +string(6) "commit" +Done diff --git a/tests/swoole_pdo_oracle/bug41996.phpt b/tests/swoole_pdo_oracle/bug41996.phpt new file mode 100644 index 0000000000..dd77f157c4 --- /dev/null +++ b/tests/swoole_pdo_oracle/bug41996.phpt @@ -0,0 +1,26 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI Bug #41996 (Problem accessing Oracle ROWID) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $stmt = $db->prepare('SELECT rowid FROM dual'); + $stmt->execute(); + $row = $stmt->fetch(); + var_dump(strlen($row[0]) > 0); +}); +?> +--EXPECT-- +bool(true) diff --git a/tests/swoole_pdo_oracle/bug44301.phpt b/tests/swoole_pdo_oracle/bug44301.phpt new file mode 100644 index 0000000000..f6bba7afd7 --- /dev/null +++ b/tests/swoole_pdo_oracle/bug44301.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI Bug #44301 (Segfault when an exception is thrown on persistent connections) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_PERSISTENT, true); + try { + $stmt = $db->prepare('SELECT * FROM no_table'); + $stmt->execute(); + } catch (PDOException $e) { + print $e->getMessage(); + } + $db = null; +}); +?> +--EXPECTF-- +SQLSTATE[HY000]: General error: 942 OCIStmtExecute: ORA-00942: table or view %Sdoes not exist + (%soci_statement.c:%d) diff --git a/tests/swoole_pdo_oracle/bug46274.phpt b/tests/swoole_pdo_oracle/bug46274.phpt new file mode 100644 index 0000000000..0442d9571e --- /dev/null +++ b/tests/swoole_pdo_oracle/bug46274.phpt @@ -0,0 +1,75 @@ +--TEST-- +swoole_pdo_oracle:ATTR_STRINGIFY_FETCHES and blob) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + + try { + $db->exec("DROP TABLE test_one_blob"); + } catch (Exception $e) { + } + + $db->beginTransaction(); + + $db->query('CREATE TABLE test_one_blob (id INT NOT NULL, blob1 BLOB)'); + + $stmt = $db->prepare("INSERT INTO test_one_blob (id, blob1) VALUES (:id, EMPTY_BLOB()) RETURNING blob1 INTO :foo"); + + $data = 'foo'; + $blob = fopen('php://memory', 'a'); + fwrite($blob, $data); + rewind($blob); + + $id = 1; + $stmt->bindparam(':id', $id); + $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); + $stmt->execute(); + + $data = ''; + $blob = fopen('php://memory', 'a'); + fwrite($blob, $data); + rewind($blob); + + $id = 1; + $stmt->bindparam(':id', $id); + $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); + $stmt->execute(); + + $res = $db->query("SELECT blob1 from test_one_blob"); + // Resource + var_dump($res->fetch()); + + // Empty string + var_dump($res->fetch()); + + $db->exec("DROP TABLE test_one_blob"); +}); +?> +--EXPECT-- +array(2) { + ["blob1"]=> + string(3) "foo" + [0]=> + string(3) "foo" +} +array(2) { + ["blob1"]=> + string(0) "" + [0]=> + string(0) "" +} diff --git a/tests/swoole_pdo_oracle/bug46274_2.phpt b/tests/swoole_pdo_oracle/bug46274_2.phpt new file mode 100644 index 0000000000..fb6ff2cfd5 --- /dev/null +++ b/tests/swoole_pdo_oracle/bug46274_2.phpt @@ -0,0 +1,80 @@ +--TEST-- +swoole_pdo_oracle:ATTR_STRINGIFY_FETCHES and blob) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + try { + $db->exec("DROP TABLE test_one_blob"); + } catch (Exception $e) { + } + + $db->beginTransaction(); + + $db->query('CREATE TABLE test_one_blob (id INT NOT NULL, blob1 BLOB)'); + + $stmt = $db->prepare("INSERT INTO test_one_blob (id, blob1) VALUES (:id, EMPTY_BLOB()) RETURNING blob1 INTO :foo"); + + $data = 'foo'; + $blob = fopen('php://memory', 'a'); + fwrite($blob, $data); + rewind($blob); + + $id = 1; + $stmt->bindparam(':id', $id); + $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); + $stmt->execute(); + + $data = ''; + $blob = fopen('php://memory', 'a'); + fwrite($blob, $data); + rewind($blob); + + $id = 1; + $stmt->bindparam(':id', $id); + $stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); + $stmt->execute(); + + $res = $db->query("SELECT blob1 from test_one_blob"); + // Resource + var_dump($row = $res->fetch()); + var_dump(fread($row[0], 1024)); + fclose($row[0]); + + // Empty string + var_dump($row = $res->fetch()); + var_dump(fread($row[0], 1024)); + fclose($row[0]); + + $db->exec("DROP TABLE test_one_blob"); +}); +?> +--EXPECTF-- +array(2) { + ["blob1"]=> + resource(%d) of type (stream) + [0]=> + resource(%d) of type (stream) +} +string(3) "foo" +array(2) { + ["blob1"]=> + resource(%d) of type (stream) + [0]=> + resource(%d) of type (stream) +} +string(0) "" diff --git a/tests/swoole_pdo_oracle/bug54379.phpt b/tests/swoole_pdo_oracle/bug54379.phpt new file mode 100644 index 0000000000..e7ce939d6a --- /dev/null +++ b/tests/swoole_pdo_oracle/bug54379.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_pdo_oracle: UTF-8 output gets truncated) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + try { + $db->exec("DROP TABLE test"); + } catch (Exception $e) { + } + $db->exec("CREATE TABLE test (col1 NVARCHAR2(20))"); + $db->exec("INSERT INTO test VALUES('12345678901234567890')"); + $db->exec("INSERT INTO test VALUES('あいうえおかきくけこさしすせそたちつてと')"); + $stmt = $db->prepare("SELECT * FROM test"); + $stmt->execute(); + var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); + $db->exec("DROP TABLE test"); +}); +?> +--EXPECT-- +array(2) { + [0]=> + array(1) { + ["col1"]=> + string(20) "12345678901234567890" + } + [1]=> + array(1) { + ["col1"]=> + string(60) "あいうえおかきくけこさしすせそたちつてと" + } +} diff --git a/tests/swoole_pdo_oracle/bug57702.phpt b/tests/swoole_pdo_oracle/bug57702.phpt new file mode 100644 index 0000000000..f02a5204d6 --- /dev/null +++ b/tests/swoole_pdo_oracle/bug57702.phpt @@ -0,0 +1,190 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI Bug #57702 (Multi-row BLOB fetches) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + // Note the PDO test setup sets PDO::ATTR_STRINGIFY_FETCHES to true + // (and sets PDO::ATTR_CASE to PDO::CASE_LOWER) + + $query = "begin execute immediate 'drop table bug57702'; exception when others then if sqlcode <> -942 then raise; end if; end;"; + $stmt = $db->prepare($query); + $stmt->execute(); + + $query = "create table bug57702 (id number, data1 blob, data2 blob)"; + $stmt = $db->prepare($query); + $stmt->execute(); + + function do_insert($db, $id, $data1, $data2) + { + $db->beginTransaction(); + $stmt = $db->prepare("insert into bug57702 (id, data1, data2) values (:id, empty_blob(), empty_blob()) returning data1, data2 into :blob1, :blob2"); + $stmt->bindParam(':id', $id); + $stmt->bindParam(':blob1', $blob1, PDO::PARAM_LOB); + $stmt->bindParam(':blob2', $blob2, PDO::PARAM_LOB); + $blob1 = null; + $blob2 = null; + $stmt->execute(); + + fwrite($blob1, $data1); + fclose($blob1); + fwrite($blob2, $data2); + fclose($blob2); + $db->commit(); + } + + do_insert($db, 1, "row 1 col 1", "row 1 col 2"); + do_insert($db, 2, "row 2 col 1", "row 2 col 2"); + + //////////////////// + + echo "First Query\n"; + + // Fetch it back + $stmt = $db->prepare('select data1, data2 from bug57702 order by id'); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + var_dump($row['data1']); + var_dump($row['data2']); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + var_dump($row['data1']); + var_dump($row['data2']); + + //////////////////// + + echo "\nSecond Query\n"; + + foreach($db->query("select data1 as d1, data2 as d2 from bug57702 order by id") as $row) { + var_dump($row['d1']); + var_dump($row['d2']); + } + + //////////////////// + + echo "\nThird Query\n"; + + $stmt = $db->prepare('select data1 as d3_1, data2 as d3_2 from bug57702 order by id'); + + $rs = $stmt->execute(); + $stmt->bindColumn('d3_1' , $clob1, PDO::PARAM_LOB); + $stmt->bindColumn('d3_2' , $clob2, PDO::PARAM_LOB); + + while ($stmt->fetch(PDO::FETCH_BOUND)) { + var_dump($clob1); + var_dump($clob2); + } + + //////////////////// + + echo "\nFourth Query\n"; + + $a = array(); + $i = 0; + foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { + $a[$i][0] = $row['d4_1']; + $a[$i][1] = $row['d4_2']; + $i++; + } + + for ($i = 0; $i < count($a); $i++) { + var_dump($a[$i][0]); + var_dump($a[$i][1]); + } + + //////////////////// + + echo "\nFifth Query\n"; + + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams + + // Since each column only has one lob descriptor, the last row is + // shown twice because the lob descriptor for each column is reused in + // the stream + + $a = array(); + $i = 0; + foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { + $a[$i][0] = $row['d4_1']; + $a[$i][1] = $row['d4_2']; + $i++; + } + + for ($i = 0; $i < count($a); $i++) { + var_dump(stream_get_contents($a[$i][0])); + var_dump(stream_get_contents($a[$i][1])); + } + + //////////////////// + + echo "\nSixth Query\n"; + + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams + + $a = array(); + $i = 0; + foreach($db->query("select data1 as d4_1, data2 as d4_2 from bug57702 order by id") as $row) { + $a[$i][0] = $row['d4_1']; + $a[$i][1] = $row['d4_2']; + var_dump(stream_get_contents($a[$i][0])); + var_dump(stream_get_contents($a[$i][1])); + $i++; + } + + // Cleanup + $query = "drop table bug57702"; + $stmt = $db->prepare($query); + $stmt->execute(); + + print "done\n"; + +}); +?> +--EXPECTF-- +First Query +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) + +Second Query +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) + +Third Query +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) + +Fourth Query +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) +resource(%d) of type (stream) + +Fifth Query +string(11) "row 2 col 1" +string(11) "row 2 col 2" +string(11) "row 2 col 1" +string(11) "row 2 col 2" + +Sixth Query +string(11) "row 1 col 1" +string(11) "row 1 col 2" +string(11) "row 2 col 1" +string(11) "row 2 col 2" +done diff --git a/tests/swoole_pdo_oracle/bug60994.phpt b/tests/swoole_pdo_oracle/bug60994.phpt new file mode 100644 index 0000000000..eecdcd9fdb --- /dev/null +++ b/tests/swoole_pdo_oracle/bug60994.phpt @@ -0,0 +1,139 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI Bug #60994 (Reading a multibyte CLOB caps at 8192 characters) +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); + $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + $dbh->exec('CREATE TABLE pdo_oci_bug60994 (id NUMBER, data CLOB, data2 NCLOB)'); + + $id = null; + $insert = $dbh->prepare('INSERT INTO pdo_oci_bug60994 (id, data, data2) VALUES (:id, :data, :data2)'); + $insert->bindParam(':id', $id, \PDO::PARAM_STR); + $select = $dbh->prepare("SELECT data, data2 FROM pdo_oci_bug60994 WHERE id = :id"); + + + echo PHP_EOL, 'Test 1: j', PHP_EOL; + $string1 = 'abc' . str_repeat('j', 8187) . 'xyz'; // 8193 chars total works fine here (even 1 million works fine, subject to memory_limit) + $id = 1; + $insert->bindParam(':data', $string1, \PDO::PARAM_STR, strlen($string1)); // length in bytes + $insert->bindParam(':data2', $string1, \PDO::PARAM_STR, strlen($string1)); + $insert->execute(); + $select->bindParam(':id', $id, \PDO::PARAM_STR); + $select->execute(); + $row = $select->fetch(); + $stream1 = stream_get_contents($row['DATA']); + $start1 = mb_substr($stream1, 0, 10); + $ending1 = mb_substr($stream1, -10); + echo 'size of string1 is ', strlen($string1), ' bytes, ', mb_strlen($string1), ' chars.', PHP_EOL; + echo 'size of stream1 is ', strlen($stream1), ' bytes, ', mb_strlen($stream1), ' chars.', PHP_EOL; + echo 'beg of stream1 is ', $start1, PHP_EOL; + echo 'end of stream1 is ', $ending1, PHP_EOL; + if ($string1 != $stream1 || $stream1 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream1', PHP_EOL; + } + + echo PHP_EOL, 'Test 2: £', PHP_EOL; + $string2 = 'abc' . str_repeat('£', 8187) . 'xyz'; // 8193 chars total is when it breaks + $id = 2; + $insert->bindParam(':data', $string2, \PDO::PARAM_STR, strlen($string2)); // length in bytes + $insert->bindParam(':data2', $string2, \PDO::PARAM_STR, strlen($string2)); + $insert->execute(); + $select->bindParam(':id', $id, \PDO::PARAM_STR); + $select->execute(); + $row = $select->fetch(); + $stream2 = stream_get_contents($row['DATA']); + $start2 = mb_substr($stream2, 0, 10); + $ending2 = mb_substr($stream2, -10); + echo 'size of string2 is ', strlen($string2), ' bytes, ', mb_strlen($string2), ' chars.', PHP_EOL; + echo 'size of stream2 is ', strlen($stream2), ' bytes, ', mb_strlen($stream2), ' chars.', PHP_EOL; + echo 'beg of stream2 is ', $start2, PHP_EOL; + echo 'end of stream2 is ', $ending2, PHP_EOL; + if ($string2 != $stream2 || $stream2 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream2', PHP_EOL; + } + + echo PHP_EOL, 'Test 3: Җ', PHP_EOL; + $string3 = 'abc' . str_repeat('Җ', 8187) . 'xyz'; // 8193 chars total is when it breaks + $id = 3; + $insert->bindParam(':data', $string3, \PDO::PARAM_STR, strlen($string3)); // length in bytes + $insert->bindParam(':data2', $string3, \PDO::PARAM_STR, strlen($string3)); + $insert->execute(); + $select->bindParam(':id', $id, \PDO::PARAM_STR); + $select->execute(); + $row = $select->fetch(); + $stream3 = stream_get_contents($row['DATA']); + $start3 = mb_substr($stream3, 0, 10); + $ending3 = mb_substr($stream3, -10); + echo 'size of string3 is ', strlen($string3), ' bytes, ', mb_strlen($string3), ' chars.', PHP_EOL; + echo 'size of stream3 is ', strlen($stream3), ' bytes, ', mb_strlen($stream3), ' chars.', PHP_EOL; + echo 'beg of stream3 is ', $start3, PHP_EOL; + echo 'end of stream3 is ', $ending3, PHP_EOL; + if ($string3 != $stream3 || $stream3 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream3', PHP_EOL; + } + + echo PHP_EOL, 'Test 4: の', PHP_EOL; + $string4 = 'abc' . str_repeat('の', 8187) . 'xyz'; // 8193 chars total is when it breaks + $id = 4; + $insert->bindParam(':data', $string4, \PDO::PARAM_STR, strlen($string4)); // length in bytes + $insert->bindParam(':data2', $string4, \PDO::PARAM_STR, strlen($string4)); + $insert->execute(); + $select->bindParam(':id', $id, \PDO::PARAM_STR); + $select->execute(); + $row = $select->fetch(); + $stream4 = stream_get_contents($row['DATA']); + $start4 = mb_substr($stream4, 0, 10); + $ending4 = mb_substr($stream4, -10); + echo 'size of string4 is ', strlen($string4), ' bytes, ', mb_strlen($string4), ' chars.', PHP_EOL; + echo 'size of stream4 is ', strlen($stream4), ' bytes, ', mb_strlen($stream4), ' chars.', PHP_EOL; + echo 'beg of stream4 is ', $start4, PHP_EOL; + echo 'end of stream4 is ', $ending4, PHP_EOL; + if ($string4 != $stream4 || $stream4 != stream_get_contents($row['DATA2'])) { + echo 'Expected nclob value to match clob value for stream4', PHP_EOL; + } + $dbh->exec('DROP TABLE pdo_oci_bug60994'); +}); + +?> +--EXPECT-- +Test 1: j +size of string1 is 8193 bytes, 8193 chars. +size of stream1 is 8193 bytes, 8193 chars. +beg of stream1 is abcjjjjjjj +end of stream1 is jjjjjjjxyz + +Test 2: £ +size of string2 is 16380 bytes, 8193 chars. +size of stream2 is 16380 bytes, 8193 chars. +beg of stream2 is abc£££££££ +end of stream2 is £££££££xyz + +Test 3: Җ +size of string3 is 16380 bytes, 8193 chars. +size of stream3 is 16380 bytes, 8193 chars. +beg of stream3 is abcҖҖҖҖҖҖҖ +end of stream3 is ҖҖҖҖҖҖҖxyz + +Test 4: の +size of string4 is 24567 bytes, 8193 chars. +size of stream4 is 24567 bytes, 8193 chars. +beg of stream4 is abcののののののの +end of stream4 is のののののののxyz diff --git a/tests/swoole_pdo_oracle/bug_33707.phpt b/tests/swoole_pdo_oracle/bug_33707.phpt new file mode 100644 index 0000000000..2b9c5d1157 --- /dev/null +++ b/tests/swoole_pdo_oracle/bug_33707.phpt @@ -0,0 +1,35 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI Bug #33707 (Errors in select statements not reported) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $rs = $db->query('select blah from a_table_that_does_not_exist'); + var_dump($rs); + var_dump($db->errorInfo()); +}); +?> +--EXPECTF-- +bool(false) +array(3) { + [0]=> + string(5) "HY000" + [1]=> + int(942) + [2]=> + string(%d) "OCIStmtExecute: ORA-00942: table or view %Sdoes not exist + (%s:%d)" +} diff --git a/tests/swoole_pdo_oracle/checkliveness.phpt b/tests/swoole_pdo_oracle/checkliveness.phpt new file mode 100644 index 0000000000..da2930e3a7 --- /dev/null +++ b/tests/swoole_pdo_oracle/checkliveness.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI checkliveness (code coverage) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + try { + $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD, array(PDO::ATTR_PERSISTENT => true)); + } + catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit; + } + + // This triggers the call to check liveness + try { + $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD, array(PDO::ATTR_PERSISTENT => true)); + } + catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(); + exit; + } + + $db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); + + try { + $stmt = $db->prepare('SELECT * FROM dual'); + $stmt->execute(); + $row = $stmt->fetch(); + var_dump($row); + } catch (PDOException $e) { + print $e->getMessage(); + } + + $db = null; +}); +?> +--EXPECT-- +array(2) { + ["DUMMY"]=> + string(1) "X" + [0]=> + string(1) "X" +} diff --git a/tests/swoole_pdo_oracle/coroutint.phpt b/tests/swoole_pdo_oracle/coroutint.phpt new file mode 100644 index 0000000000..fc043e28cb --- /dev/null +++ b/tests/swoole_pdo_oracle/coroutint.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI coroutine +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_ALL]); +run(function() { + $db = PdoOracleTest::create(); + $db->exec("create table test (id int)"); + for($i = 0; $i < 10; $i++) { + go(function () use($db, $i){ + $stmt = $db->prepare("insert into test values (?)"); + $stmt->execute([$i]); + $stmt = $db->prepare("select id from test where id = ?"); + $stmt->execute([$i]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); + }); + } + sleep(1); + $db->exec("drop table test"); +}); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_pdo_oracle/oci_success_with_info.phpt b/tests/swoole_pdo_oracle/oci_success_with_info.phpt new file mode 100644 index 0000000000..435e3c258f --- /dev/null +++ b/tests/swoole_pdo_oracle/oci_success_with_info.phpt @@ -0,0 +1,126 @@ +--TEST-- +swoole_pdo_oracle: Handling OCI_SUCCESS_WITH_INFO +--SKIPIF-- + +--FILE-- +exec(<<<'SQL' +BEGIN + EXECUTE IMMEDIATE 'DROP PROFILE BUG77120_PROFILE CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -2380 THEN + RAISE; + END IF; +END; +SQL + ); +} + +function dropUser(PDO $conn): void { + $conn->exec(<<<'SQL' +BEGIN + EXECUTE IMMEDIATE 'DROP USER BUG77120_USER CASCADE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -1918 THEN + RAISE; + END IF; +END; +SQL + ); +} + +function triggerCompilationError(PDO $conn): void { + $conn->exec(<<<'SQL' +CREATE OR REPLACE FUNCTION BUG77120(INT A) RETURN INT +AS +BEGIN + RETURN 0; +END; +SQL + ); +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $conn = connectAsAdmin(); + + dropUser($conn); + dropProfile($conn); + + $password = bin2hex(random_bytes(8)); + + $conn->exec('CREATE PROFILE BUG77120_PROFILE LIMIT PASSWORD_LIFE_TIME 1/86400 PASSWORD_GRACE_TIME 1'); + $conn->exec('CREATE USER BUG77120_USER IDENTIFIED BY "' . $password . '" PROFILE BUG77120_PROFILE'); + $conn->exec('GRANT CREATE SESSION TO BUG77120_USER'); + + // let the password expire + sleep(3); // 2 seconds is causing random test failures + + $conn = connectAsUser('BUG77120_USER', $password); + var_dump($conn->errorInfo()); + + $conn = connectAsAdmin(); + dropUser($conn); + dropProfile($conn); + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + triggerCompilationError($conn); + var_dump($conn->errorInfo()); + + $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + triggerCompilationError($conn); + var_dump($conn->errorInfo()); +}); + +?> +--EXPECTF-- +array(3) { + [0]=> + string(5) "HY000" + [1]=> + int(28002) + [2]=> + string(%d) "OCISessionBegin: OCI_SUCCESS_WITH_INFO: ORA-28002: %s + (%s:%d)" +} +array(3) { + [0]=> + string(5) "HY000" + [1]=> + int(24344) + [2]=> + string(%d) "OCIStmtExecute: OCI_SUCCESS_WITH_INFO: ORA-24344: %s + (%s:%d)" +} +array(3) { + [0]=> + string(5) "HY000" + [1]=> + int(24344) + [2]=> + string(%d) "OCIStmtExecute: OCI_SUCCESS_WITH_INFO: ORA-24344: %s + (%s:%d)" +} diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_action.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_action.phpt new file mode 100644 index 0000000000..bd3e6d113e --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_action.phpt @@ -0,0 +1,57 @@ +--TEST-- +swoole_pdo_oracle: Setting session action +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $query = 'select action from v$session where sid = sys_context(\'USERENV\', \'SID\')'; + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'ACTION NOT SET: '; + var_dump($row['action']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, "some action")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'ACTION SET: '; + var_dump($row['action']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, "something else!")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'ACTION RESET: '; + var_dump($row['action']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_ACTION, null)); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'ACTION NULLED: '; + var_dump($row['action']); + + echo "Done\n"; +}); +?> +--EXPECT-- +ACTION NOT SET: NULL +bool(true) +ACTION SET: string(11) "some action" +bool(true) +ACTION RESET: string(15) "something else!" +bool(true) +ACTION NULLED: NULL +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_1.phpt new file mode 100644 index 0000000000..a403e53b77 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_1.phpt @@ -0,0 +1,67 @@ +--TEST-- +swoole_pdo_oracle: Basic autocommit functionality +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $dbh->exec("drop table pdo_ac_tab"); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + print "PDO::ATTR_AUTOCOMMIT: Default: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + + echo "Change setting to false - "; + + $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); + + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + + echo "Change setting back to true - "; + + $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, true); + + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + + // Use 2nd connection to check that autocommit does commit + + echo "Insert data\n"; + $dbh->exec("create table pdo_ac_tab (col1 varchar2(20))"); + $dbh->exec("insert into pdo_ac_tab (col1) values ('some data')"); + + $dbh2 = PdoOracleTest::create(); + + echo "Second connection should be able to see committed data\n"; + $s = $dbh2->prepare("select col1 from pdo_ac_tab"); + $s->execute(); + while ($r = $s->fetch()) { + echo "Data is: " . $r[0] . "\n"; + } + + $dbh->exec("drop table pdo_ac_tab"); + + echo "Done\n"; +}); +?> +--EXPECT-- +PDO::ATTR_AUTOCOMMIT: Default: bool(true) +Change setting to false - PDO::ATTR_AUTOCOMMIT: bool(false) +Change setting back to true - PDO::ATTR_AUTOCOMMIT: bool(true) +Insert data +Second connection should be able to see committed data +Data is: some data +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_2.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_2.phpt new file mode 100644 index 0000000000..142e051c2f --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_2.phpt @@ -0,0 +1,131 @@ +--TEST-- +swoole_pdo_oracle: beginTransaction and native transactions +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $dbh->exec("drop table pdo_ac_tab"); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $dbh->exec("create table pdo_ac_tab (col1 varchar2(25))"); + + echo "Test 1 Check beginTransaction insertion\n"; + + $dbh->beginTransaction(); + try { + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 1')"); + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 2')"); + $dbh->commit(); + } + catch (PDOException $e) { + echo "Caught unexpected exception at line " . __LINE__ . "\n"; + echo $e->getMessage() . "\n"; + $dbh->rollback(); + } + + echo "Test 2 Cause an exception and test beginTransaction rollback\n"; + + $dbh->beginTransaction(); + try { + $dbh->exec("insert into pdo_ac_tab (col1) values ('not committed #1')"); + $dbh->exec("insert into pdo_ac_tab (col1) values ('data that is too long to fit and will barf')"); + $dbh->commit(); + } + catch (PDOException $e) { + echo "Caught expected exception at line " . __LINE__ . "\n"; + echo $e->getMessage() . "\n"; + $dbh->rollback(); + } + + echo "Test 3 Setting ATTR_AUTOCOMMIT to true will commit and end the transaction\n"; + + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 3')"); + $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, true); + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + try { + $dbh->rollback(); + } + catch (PDOException $e) { + echo "Caught expected exception at line " . __LINE__ . "\n"; + echo $e->getMessage() . "\n"; + } + + echo "Test 4 Setting ATTR_AUTOCOMMIT to false will commit and end the transaction\n"; + + $dbh->beginTransaction(); + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 4')"); + $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + try { + $dbh->rollback(); + } + catch (PDOException $e) { + echo "Caught expected exception at line " . __LINE__ . "\n"; + echo $e->getMessage() . "\n"; + } + + echo "Test 5 Handle transactions ourselves\n"; + + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + + $dbh->exec("insert into pdo_ac_tab (col1) values ('not committed #2')"); + $dbh->exec("rollback"); + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 5')"); + $dbh->exec("insert into pdo_ac_tab (col1) values ('data 6')"); + + $dbh->exec("commit"); + + // Open new connection to really verify what was inserted + + $dbh2 = PdoOracleTest::create(); + + echo "Query Results are:\n"; + $s = $dbh2->prepare("select col1 from pdo_ac_tab"); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Done\n"; +}); +?> +--EXPECTF-- +Test 1 Check beginTransaction insertion +Test 2 Cause an exception and test beginTransaction rollback +Caught expected exception at line %d +SQLSTATE[HY000]: General error: 12899 OCIStmtExecute: ORA-12899: %s +%s +Test 3 Setting ATTR_AUTOCOMMIT to true will commit and end the transaction +PDO::ATTR_AUTOCOMMIT: bool(true) +Caught expected exception at line %d +There is no active transaction +Test 4 Setting ATTR_AUTOCOMMIT to false will commit and end the transaction +PDO::ATTR_AUTOCOMMIT: bool(false) +Caught expected exception at line %d +There is no active transaction +Test 5 Handle transactions ourselves +PDO::ATTR_AUTOCOMMIT: bool(false) +Query Results are: +data 1 +data 2 +data 3 +data 4 +data 5 +data 6 +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_3.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_3.phpt new file mode 100644 index 0000000000..a39c3a6d62 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_autocommit_3.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_pdo_oracle: closing a connection in non-autocommit mode commits data +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + + // Check connection can be created with AUTOCOMMIT off + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, false); + $dbh->exec("drop table pdo_ac_tab"); + + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + print "PDO::ATTR_AUTOCOMMIT: "; + var_dump($dbh->getAttribute(PDO::ATTR_AUTOCOMMIT)); + + echo "Insert data\n"; + + $dbh->exec("create table pdo_ac_tab (col1 varchar2(20))"); + + $dbh->exec("insert into pdo_ac_tab (col1) values ('some data')"); + + $dbh = null; // close first connection + + echo "Second connection should be able to see committed data\n"; + $dbh2 = PdoOracleTest::create(); + $s = $dbh2->prepare("select col1 from pdo_ac_tab"); + $s->execute(); + while ($r = $s->fetch()) { + echo "Data is: " . $r[0] . "\n"; + } + + $dbh2->exec("drop table pdo_ac_tab"); + + echo "Done\n"; +}); +?> +--EXPECT-- +PDO::ATTR_AUTOCOMMIT: bool(false) +Insert data +Second connection should be able to see committed data +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_call_timeout.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_call_timeout.phpt new file mode 100644 index 0000000000..94a337364e --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_call_timeout.phpt @@ -0,0 +1,69 @@ +--TEST-- +swoole_pdo_oracle: Setting and using call timeout +--SKIPIF-- + +getAttribute(PDO::ATTR_CLIENT_VERSION), $matches); +if (!(isset($matches[0]) && $matches[0] >= 18)) { + die("skip works only with Oracle 18c or greater version of Oracle client libraries"); +} + +?> +--FILE-- +prepare("begin dbms_lock.sleep(:t); end;"); + + if (!$stmt) { + $error = $dbh->errorInfo(); + echo "Prepare error was ", $error[2], "\n"; + return; + } + $stmt->bindParam(":t", $t, PDO::PARAM_INT); + + $r = $stmt->execute(); + if ($r) { + echo "Execute succeeded\n"; + } else { + $error = $dbh->errorInfo(); + echo "Execute error was ", $error[2], "\n"; + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + echo "Test 1\n"; + + $dbh->setAttribute(PDO::OCI_ATTR_CALL_TIMEOUT, 4000); // milliseconds + + echo "call timeout:\n"; + var_dump($dbh->getAttribute(PDO::OCI_ATTR_CALL_TIMEOUT)); + + $r = mysleep($dbh, 8); // seconds +}); +?> +===DONE=== + +--EXPECTF-- +Test 1 +call timeout: +int(4000) +Execute error was OCIStmtExecute: ORA-%r(03136|03156)%r: %s + (%s:%d) +===DONE=== diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_case.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_case.phpt new file mode 100644 index 0000000000..8947f97ea2 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_case.phpt @@ -0,0 +1,85 @@ +--TEST-- +swoole_pdo_oracle: Column Case +--SKIPIF-- + + +--FILE-- +getAttribute(PDO::ATTR_CASE)); + $s = $dbh->prepare("select dummy from dual"); + $s->execute(); + while ($r = $s->fetch(PDO::FETCH_ASSOC)) { + var_dump($r); + } +} + +function do_query2($dbh, $mode) +{ + echo "Mode desired is $mode\n"; + $s = $dbh->prepare("select dummy from dual", array(PDO::ATTR_CASE, $mode)); + $s->execute(); + while ($r = $s->fetch(PDO::FETCH_ASSOC)) { + var_dump($r); + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + echo "Test 1 - Force column names to lower case\n"; + $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + do_query1($dbh); + + echo "Test 2 - Leave column names as returned by the database driver\n"; + $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); + do_query1($dbh); + + echo "Test 3 - Force column names to upper case\n"; + $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_UPPER); + do_query1($dbh); + + echo "Test 4 - Setting on statement has no effect. Attempt lower case but get upper\n"; + $dbh->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); // reset + do_query2($dbh, PDO::CASE_LOWER); + + echo "Done\n"; +}); +?> +--EXPECT-- +Test 1 - Force column names to lower case +int(2) +array(1) { + ["dummy"]=> + string(1) "X" +} +Test 2 - Leave column names as returned by the database driver +int(0) +array(1) { + ["DUMMY"]=> + string(1) "X" +} +Test 3 - Force column names to upper case +int(1) +array(1) { + ["DUMMY"]=> + string(1) "X" +} +Test 4 - Setting on statement has no effect. Attempt lower case but get upper +Mode desired is 2 +array(1) { + ["DUMMY"]=> + string(1) "X" +} +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_client.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_client.phpt new file mode 100644 index 0000000000..4cc54a032b --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_client.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_pdo_oracle: Client version +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + echo "ATTR_CLIENT_VERSION: "; + $cv = $dbh->getAttribute(PDO::ATTR_CLIENT_VERSION); + var_dump($cv); + + $s = explode(".", $cv); + if (count($s) > 1 && (($s[0] == 10 && $s[1] >= 2) || $s[0] >= 11)) { + if (count($s) != 5) { + echo "Wrong number of values in array\nVersion was: "; + var_dump($cv); + } else { + echo "Version OK, so far as can be portably checked\n"; + } + } else { + if (count($s) != 2) { + echo "Wrong number of values in array\nVersion was: "; + var_dump($cv); + } else { + echo "Version OK, so far as can be portably checked\n"; + } + } + + echo "Done\n"; +}); +?> +--EXPECTF-- +ATTR_CLIENT_VERSION: string(%d) "%d.%s" +Version OK, so far as can be portably checked +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_client_identifier.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_client_identifier.phpt new file mode 100644 index 0000000000..a9004e4cc8 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_client_identifier.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_pdo_oracle: Setting session client identifier +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $query = 'select client_identifier from v$session where sid = sys_context(\'USERENV\', \'SID\')'; + + $dbh = PdoOracleTest::create(); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_IDENTIFIER NOT SET: '; + var_dump($row['client_identifier']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, "some client identifier")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_IDENTIFIER SET: '; + var_dump($row['client_identifier']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, "something else!")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_IDENTIFIER RESET: '; + var_dump($row['client_identifier']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_IDENTIFIER, null)); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_IDENTIFIER NULLED: '; + var_dump($row['client_identifier']); + + echo "Done\n"; +}); +?> +--EXPECT-- +CLIENT_IDENTIFIER NOT SET: NULL +bool(true) +CLIENT_IDENTIFIER SET: string(22) "some client identifier" +bool(true) +CLIENT_IDENTIFIER RESET: string(15) "something else!" +bool(true) +CLIENT_IDENTIFIER NULLED: NULL +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_client_info.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_client_info.phpt new file mode 100644 index 0000000000..4e2b953a7c --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_client_info.phpt @@ -0,0 +1,57 @@ +--TEST-- +swoole_pdo_oracle: Setting session client info +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $query = 'select client_info from v$session where sid = sys_context(\'USERENV\', \'SID\')'; + $dbh = PdoOracleTest::create(); + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_INFO NOT SET: '; + var_dump($row['client_info']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, "some client info")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_INFO SET: '; + var_dump($row['client_info']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, "something else!")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_INFO RESET: '; + var_dump($row['client_info']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_CLIENT_INFO, null)); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'CLIENT_INFO NULLED: '; + var_dump($row['client_info']); + + echo "Done\n"; +}); +?> +--EXPECT-- +CLIENT_INFO NOT SET: NULL +bool(true) +CLIENT_INFO SET: string(16) "some client info" +bool(true) +CLIENT_INFO RESET: string(15) "something else!" +bool(true) +CLIENT_INFO NULLED: NULL +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_drivername.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_drivername.phpt new file mode 100644 index 0000000000..2444222c4a --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_drivername.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_pdo_oracle: verify driver name +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + var_dump($dbh->getAttribute(PDO::ATTR_DRIVER_NAME)); + echo "Done\n"; +}); +?> +--EXPECT-- +string(3) "oci" +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_module.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_module.phpt new file mode 100644 index 0000000000..1d4a421869 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_module.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_pdo_oracle: Setting session module +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); + +run(function() { + $query = 'select module from v$session where sid = sys_context(\'USERENV\', \'SID\')'; + + $dbh = PdoOracleTest::create(); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, "some module")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'MODULE SET: '; + var_dump($row['module']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, "something else!")); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'MODULE RESET: '; + var_dump($row['module']); + + var_dump($dbh->setAttribute(PDO::OCI_ATTR_MODULE, null)); + + $stmt = $dbh->query($query); + $row = $stmt->fetch(); + echo 'MODULE NULLED: '; + var_dump($row['module']); + + echo "Done\n"; +}); +?> +--EXPECT-- +bool(true) +MODULE SET: string(11) "some module" +bool(true) +MODULE RESET: string(15) "something else!" +bool(true) +MODULE NULLED: NULL +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_nulls_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_nulls_1.phpt new file mode 100644 index 0000000000..37e40340ac --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_nulls_1.phpt @@ -0,0 +1,64 @@ +--TEST-- +swoole_pdo_oracle: Oracle Nulls +--SKIPIF-- + + +--FILE-- +getAttribute(PDO::ATTR_ORACLE_NULLS)); + $s = $dbh->prepare("select '' as myempty, null as mynull from dual"); + $s->execute(); + while ($r = $s->fetch()) { + var_dump($r[0]); + var_dump($r[1]); + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + print "PDO::ATTR_ORACLE_NULLS: Default: "; + do_query($dbh); + + print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_NATURAL: "; + $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); // No conversion. + + do_query($dbh); + + print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_EMPTY_STRING: "; + $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_EMPTY_STRING); // Empty string is converted to NULL. + + do_query($dbh); + + print "PDO::ATTR_ORACLE_NULLS: PDO::NULL_TO_STRING: "; + $dbh->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_TO_STRING); // NULL is converted to an empty string. + + do_query($dbh); + + echo "Done\n"; +}); +?> +--EXPECT-- +PDO::ATTR_ORACLE_NULLS: Default: int(0) +NULL +NULL +PDO::ATTR_ORACLE_NULLS: PDO::NULL_NATURAL: int(0) +NULL +NULL +PDO::ATTR_ORACLE_NULLS: PDO::NULL_EMPTY_STRING: int(1) +NULL +NULL +PDO::ATTR_ORACLE_NULLS: PDO::NULL_TO_STRING: int(2) +string(0) "" +string(0) "" +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_1.phpt new file mode 100644 index 0000000000..6d45fe3949 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_1.phpt @@ -0,0 +1,80 @@ +--TEST-- +swoole_pdo_oracle: Set prefetch on connection +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + echo "Test connect\n"; + $dbh->setAttribute(PDO::ATTR_PREFETCH, 101); + + echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; + + // Verify can fetch + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Test set 102\n"; + $dbh->setAttribute(PDO::ATTR_PREFETCH, 102); + echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; + + // Verify can fetch + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Test set -1: (Uses 0)\n"; + $dbh->setAttribute(PDO::ATTR_PREFETCH, -1); + echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; + + // Verify can fetch + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Test set PHP_INT_MAX: (Uses default)\n"; + $dbh->setAttribute(PDO::ATTR_PREFETCH, PHP_INT_MAX); + echo $dbh->getAttribute(PDO::ATTR_PREFETCH), "\n"; + + // Verify can fetch + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Done\n"; +}); +?> +--EXPECT-- +Test connect +101 +X +Test set 102 +102 +X +Test set -1: (Uses 0) +0 +X +Test set PHP_INT_MAX: (Uses default) +100 +X +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_2.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_2.phpt new file mode 100644 index 0000000000..2cf9eee1f6 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_prefetch_2.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_pdo_oracle: prefetch on statements +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $s = $dbh->prepare("select '' as myempty, null as mynull from dual", array(PDO::ATTR_PREFETCH => 101)); + + echo "Test 1: Can't set prefetch after prepare\n"; + var_dump($s->setAttribute(PDO::ATTR_PREFETCH, 102)); + + // Verify can fetch + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Test 2: Turn off prefetching\n"; + $s = $dbh->prepare("select '' as myempty, null as mynull from dual", array(PDO::ATTR_PREFETCH => 0)); + $s = $dbh->prepare("select dummy from dual" ); + $s->execute(); + while ($r = $s->fetch()) { + echo $r[0] . "\n"; + } + + echo "Done\n"; +}); +?> +--EXPECTF-- +Test 1: Can't set prefetch after prepare + +Fatal error: Uncaught PDOException: SQLSTATE[IM001]: Driver does not support this function: This driver doesn't support setting attributes in %s:%d +Stack trace: +#0 %s(%d): PDOStatement->setAttribute(1, 102) +%A + thrown in %s on line %d diff --git a/tests/swoole_pdo_oracle/pdo_oci_attr_server.phpt b/tests/swoole_pdo_oracle/pdo_oci_attr_server.phpt new file mode 100644 index 0000000000..9ad34f30fa --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_attr_server.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_pdo_oracle: Server version and info +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + echo "Test 1\n"; + echo "ATTR_SERVER_VERSION: "; + var_dump($dbh->getAttribute(PDO::ATTR_SERVER_VERSION)); + + echo "Test 2\n"; + echo "ATTR_SERVER_INFO\n"; + $si = $dbh->getAttribute(PDO::ATTR_SERVER_INFO); + $pos = strpos($si, "Oracle"); + if ($pos === 0) { + echo "Found 'Oracle' at position $pos as expected\n"; + } else { + echo "Unexpected result. Server info was:\n"; + var_dump($si); + } + + echo "Done\n"; +}); +?> +--EXPECTF-- +Test 1 +ATTR_SERVER_VERSION: string(%d) "%d.%d.%d.%d.%d" +Test 2 +ATTR_SERVER_INFO +Found 'Oracle' at position 0 as expected +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_class_constants.phpt b/tests/swoole_pdo_oracle/pdo_oci_class_constants.phpt new file mode 100644 index 0000000000..033586d594 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_class_constants.phpt @@ -0,0 +1,65 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI specific class constants +--SKIPIF-- + + +--FILE-- + true, + 'OCI_ATTR_ACTION' => true, + 'OCI_ATTR_CLIENT_IDENTIFIER' => true, + 'OCI_ATTR_MODULE' => true, + 'OCI_ATTR_CALL_TIMEOUT' => true, +]; + +$ref = new ReflectionClass('PDO'); +$constants = $ref->getConstants(); +$values = []; + +foreach ($constants as $name => $value) { + if (substr($name, 0, 8) == 'OCI_ATTR') { + if (!isset($values[$value])) { + $values[$value] = [$name]; + } else { + $values[$value][] = $name; + } + + if (isset($expected[$name])) { + unset($expected[$name]); + unset($constants[$name]); + } + + } else { + unset($constants[$name]); + } +} + +if (!empty($constants)) { + printf("[001] Dumping list of unexpected constants\n"); + var_dump($constants); +} + +if (!empty($expected)) { + printf("[002] Dumping list of missing constants\n"); + var_dump($expected); +} + +if (!empty($values)) { + foreach ($values as $value => $constants) { + if (count($constants) > 1) { + printf("[003] Several constants share the same value '%s'\n", $value); + var_dump($constants); + } + } +} + +print "done!"; +?> +--EXPECT-- +done! diff --git a/tests/swoole_pdo_oracle/pdo_oci_debugdumpparams.phpt b/tests/swoole_pdo_oracle/pdo_oci_debugdumpparams.phpt new file mode 100644 index 0000000000..64927463e6 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_debugdumpparams.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_pdo_oracle:debugDumpParams() truncates query) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); +$stmt = $db->query(" +SELECT ' + Dumps the information contained by a prepared statement directly on the output. It will provide the SQL query in use, the number of parameters used (Params), the list of parameters, with their name, type (paramtype) as an integer, their key name or position, and the position in the query (if this is supported by the PDO driver, otherwise, it will be -1). + This is a debug function, which dump directly the data on the normal output. + Tip: + As with anything that outputs its result directly to the browser, the output-control functions can be used to capture the output of this function, and save it in a string (for example). + This will only dumps the parameters in the statement at the moment of the dump. Extra parameters are not stored in the statement, and not displayed. +' FROM DUAL +"); +var_dump($stmt->debugDumpParams()); +}); +?> +--EXPECTF-- +SQL: [%d] +SELECT ' + Dumps the information contained by a prepared statement directly on the output. It will provide the SQL query in use, the number of parameters used (Params), the list of parameters, with their name, type (paramtype) as an integer, their key name or position, and the position in the query (if this is supported by the PDO driver, otherwise, it will be -1). + This is a debug function, which dump directly the data on the normal output. + Tip: + As with anything that outputs its result directly to the browser, the output-control functions can be used to capture the output of this function, and save it in a string (for example). + This will only dumps the parameters in the statement at the moment of the dump. Extra parameters are not stored in the statement, and not displayed. +' FROM DUAL + +Params: 0 +NULL diff --git a/tests/swoole_pdo_oracle/pdo_oci_fread_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_fread_1.phpt new file mode 100644 index 0000000000..2dc11a2fb1 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_fread_1.phpt @@ -0,0 +1,65 @@ +--TEST-- +swoole_pdo_oracle: check fread() EOF +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + // Initialization + $stmtarray = array( + "begin execute immediate 'drop table pdo_oci_fread_tab'; exception when others then null; end;", + "create table pdo_oci_fread_tab (id number, data clob)", + "declare + lob1 clob := 'abc' || lpad('j',4020,'j') || 'xyz'; + begin + insert into pdo_oci_fread_tab (id,data) values (1, lob1); + end;" + ); + + foreach ($stmtarray as $stmt) { + $dbh->exec($stmt); + } + + echo "Test 1\n"; + + $s = $dbh->query("select data from pdo_oci_fread_tab where id = 1"); + $r = $s->fetch(); + $sh = $r['data']; + + while (1) { + $buffer = fread($sh,1024); + if (!$buffer) { + break; + } + echo '*'.$buffer.'*'; + } + echo "\n"; + fclose($sh); + + // Clean up + + $stmtarray = array( + "drop table pdo_oci_fread_tab" + ); + + foreach ($stmtarray as $stmt) { + $dbh->exec($stmt); + } +}); +?> +--EXPECT-- +Test 1 +*abcjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj**jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjxyz* diff --git a/tests/swoole_pdo_oracle/pdo_oci_phpinfo.phpt b/tests/swoole_pdo_oracle/pdo_oci_phpinfo.phpt new file mode 100644 index 0000000000..0fa5df9794 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_phpinfo.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_pdo_oracle: phpinfo() output +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + ob_start(); + phpinfo(); + $tmp = ob_get_contents(); + ob_end_clean(); + + $reg = 'coroutine_oracle => enabled'; + if (!preg_match("/$reg/", $tmp)) { + printf("[001] Cannot find OCI PDO driver line in phpinfo() output\n"); + } + + print "done!"; +}); +?> +--EXPECT-- +done! diff --git a/tests/swoole_pdo_oracle/pdo_oci_quote1.phpt b/tests/swoole_pdo_oracle/pdo_oci_quote1.phpt new file mode 100644 index 0000000000..fe6b359a0f --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_quote1.phpt @@ -0,0 +1,156 @@ +--TEST-- +swoole_pdo_oracle: Test PDO->quote() for PDO_OCI +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $db->query("create table poq_tab (t varchar2(100))"); + $stmt = $db->prepare('select * from poq_tab'); + + // The intent is that the fetched data be identical to the unquoted string. + // Remember!: use bind variables instead of PDO->quote() + + $a = array("", "a", "ab", "abc", "ab'cd", "a\b\n", "'", "''", "a'", "'z", "a''b", '"'); + foreach ($a as $u) { + $q = $db->quote($u); + echo "Unquoted : "; + var_dump($u); + echo "Quoted : "; + var_dump($q); + + $db->exec("delete from poq_tab"); + + $db->query("insert into poq_tab (t) values($q)"); + $stmt->execute(); + var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); + } + + echo "Done\n"; + $db->exec("drop table poq_tab"); +}); +?> +--EXPECT-- +Unquoted : string(0) "" +Quoted : string(2) "''" +array(1) { + [0]=> + array(1) { + ["t"]=> + NULL + } +} +Unquoted : string(1) "a" +Quoted : string(3) "'a'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(1) "a" + } +} +Unquoted : string(2) "ab" +Quoted : string(4) "'ab'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(2) "ab" + } +} +Unquoted : string(3) "abc" +Quoted : string(5) "'abc'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(3) "abc" + } +} +Unquoted : string(5) "ab'cd" +Quoted : string(8) "'ab''cd'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(5) "ab'cd" + } +} +Unquoted : string(4) "a\b +" +Quoted : string(6) "'a\b +'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(4) "a\b +" + } +} +Unquoted : string(1) "'" +Quoted : string(4) "''''" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(1) "'" + } +} +Unquoted : string(2) "''" +Quoted : string(6) "''''''" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(2) "''" + } +} +Unquoted : string(2) "a'" +Quoted : string(5) "'a'''" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(2) "a'" + } +} +Unquoted : string(2) "'z" +Quoted : string(5) "'''z'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(2) "'z" + } +} +Unquoted : string(4) "a''b" +Quoted : string(8) "'a''''b'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(4) "a''b" + } +} +Unquoted : string(1) """ +Quoted : string(3) "'"'" +array(1) { + [0]=> + array(1) { + ["t"]=> + string(1) """ + } +} +Done diff --git a/tests/swoole_pdo_oracle/pdo_oci_stmt_getcolumnmeta.phpt b/tests/swoole_pdo_oracle/pdo_oci_stmt_getcolumnmeta.phpt new file mode 100644 index 0000000000..0f2563a9a1 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_stmt_getcolumnmeta.phpt @@ -0,0 +1,338 @@ +--TEST-- +swoole_pdo_oracle: PDOStatement->getColumnMeta +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + try { + $db = PdoOracleTest::create(); + $db->exec(<<exec("CREATE TABLE test(id INT)"); + + $db->beginTransaction(); + + $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC'); + + echo "Test 1. calling function with invalid parameters\n"; + + // execute() has not been called yet + // NOTE: no warning + $tmp = $stmt->getColumnMeta(0); + printf(" 1.1 Expecting false got %s\n", var_export($tmp, true)); + + echo(" 1.2 "); + $stmt->execute(); + // PDOStatement::getColumnMeta() expects exactly 1 argument, 0 given in + try { + $tmp = $stmt->getColumnMeta(); + } catch (ArgumentCountError $e) { + if (false !== $tmp) { + printf("[1.2] Expecting false got %s\n", var_export($tmp, true)); + } + echo $e->getMessage(), "\n"; + } + + // invalid offset + echo " 1.3 "; + try { + $tmp = $stmt->getColumnMeta(-1); + } catch (ValueError $e) { + if (false !== $tmp) { + printf("[1.3] Expecting false got %s\n", var_export($tmp, true)); + } + echo $e->getMessage(), "\n"; + } + + // PDOStatement::getColumnMeta(): Argument #1 must be of type int, array given in + echo " 1.4 "; + try { + $tmp = $stmt->getColumnMeta(array()); + } catch (TypeError $e) { + if (false !== $tmp) + printf("[1.4] Expecting false got %s\n", var_export($tmp, true)); + echo $e->getMessage(), "\n"; + } + + // PDOStatement::getColumnMeta() expects exactly 1 argument, 2 given in + echo " 1.5 "; + try { + $tmp = $stmt->getColumnMeta(1, 1); + } catch (ArgumentCountError $e) { + if (false !== $tmp) + printf("[1.5] Expecting false got %s\n", var_export($tmp, true)); + echo $e->getMessage(), "\n"; + } + + // invalid offset + $tmp = $stmt->getColumnMeta(1); + printf(" 1.6 Expecting false because of invalid offset got %s\n", var_export($tmp, true)); + + echo "Test 2. testing return values\n"; + echo "Test 2.1 testing array returned\n"; + + $stmt = $db->prepare('SELECT id FROM test ORDER BY id ASC'); + $stmt->execute(); + $native = $stmt->getColumnMeta(0); + if (count($native) == 0) { + printf("[008] Meta data seems wrong, %s / %s\n", + var_export($native, true), var_export($emulated, true)); + } + + + function test_return($meta, $offset, $native_type, $pdo_type){ + if (empty($meta)) { + printf("[%03d + 2] getColumnMeta() failed, %d - %s\n", $offset, + $stmt->errorCode(), var_export($stmt->errorInfo(), true)); + return false; + } + $elements = array('flags', 'scale', 'name', 'len', 'precision', 'pdo_type'); + foreach ($elements as $k => $element) + if (!isset($meta[$element])) { + printf("[%03d + 3] Element %s missing, %s\n", $offset, + $element, var_export($meta, true)); + return false; + } + + if (!is_null($native_type)) { + if (!isset($meta['native_type'])) { + printf("[%03d + 4] Element native_type missing, %s\n", $offset, + var_export($meta, true)); + return false; + } + + if (!is_array($native_type)) + $native_type = array($native_type); + + $found = false; + foreach ($native_type as $k => $type) { + if ($meta['native_type'] == $type) { + $found = true; + break; + } + } + + if (!$found) { + printf("[%03d + 5] Expecting native type %s, %s\n", $offset, + var_export($native_type, true), var_export($meta, true)); + return false; + } + } + + if (!is_null($pdo_type) && ($meta['pdo_type'] != $pdo_type)) { + printf("[%03d + 6] Expecting PDO type %s got %s (%s)\n", $offset, + $pdo_type, var_export($meta, true), var_export($meta['native_type'])); + return false; + } + + return true; + } + + + function test_meta(&$db, $offset, $sql_type, $value, $native_type, $pdo_type) { + + $db->exec(<<prepare($sql); + $stmt->execute(); + + if (!$db->exec(sprintf("INSERT INTO test(id, label) VALUES (1, '%s')", $value))) { + printf("[%03d] + 1] Insert failed, %d - %s\n", $offset, + $db->errorCode(), var_export($db->errorInfo(), true)); + return false; + } + + $stmt = $db->prepare('SELECT id, label FROM test'); + $stmt->execute(); + $meta = $stmt->getColumnMeta(1); + return test_return($meta, $offset, $native_type, $pdo_type); + } + + echo "Test 2.2 testing numeric columns\n"; + + test_meta($db, 20, 'NUMBER' , 0 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 30, 'NUMBER' , 256 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 40, 'INT' , 256 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 50, 'INTEGER' , 256 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 60, 'NUMBER' , 256.01 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 70, 'NUMBER' , -8388608 , 'NUMBER', PDO::PARAM_STR); + + test_meta($db, 80, 'NUMBER' , 2147483648 , 'NUMBER', PDO::PARAM_STR); + test_meta($db, 90, 'NUMBER' , 4294967295 , 'NUMBER', PDO::PARAM_STR); + + test_meta($db, 100, 'DEC' , 1.01 , 'NUMBER' , PDO::PARAM_STR); + test_meta($db, 110, 'DECIMAL' , 1.01 , 'NUMBER' , PDO::PARAM_STR); + test_meta($db, 120, 'FLOAT' , 1.01 , 'FLOAT' , PDO::PARAM_STR); + test_meta($db, 130, 'DOUBLE PRECISION', 1.01 , 'FLOAT' , PDO::PARAM_STR); + test_meta($db, 140, 'BINARY_FLOAT' , 1.01 , 'BINARY_FLOAT' , PDO::PARAM_STR); + test_meta($db, 150, 'BINARY_DOUBLE' , 1.01 , 'BINARY_DOUBLE', PDO::PARAM_STR); + + echo "Test 2.3 testing temporal columns\n"; + + $db->exec("alter session set nls_date_format='YYYY-MM-DD'"); + test_meta($db, 160, 'DATE' , '2008-04-23' , 'DATE', PDO::PARAM_STR); + + echo "Test 2.4 testing string columns\n"; + + test_meta($db, 170, 'CHAR(1)' , 'a' , 'CHAR' , PDO::PARAM_STR); + test_meta($db, 180, 'CHAR(10)' , '0123456789' , 'CHAR' , PDO::PARAM_STR); + test_meta($db, 190, 'CHAR(255)' , str_repeat('z', 255) , 'CHAR' , PDO::PARAM_STR); + test_meta($db, 200, 'VARCHAR(1)' , 'a' , 'VARCHAR2' , PDO::PARAM_STR); + test_meta($db, 210, 'VARCHAR(10)' , '0123456789' , 'VARCHAR2' , PDO::PARAM_STR); + test_meta($db, 220, 'VARCHAR(255)' , str_repeat('z', 255) , 'VARCHAR2' , PDO::PARAM_STR); + test_meta($db, 230, 'VARCHAR2(1)' , 'a' , 'VARCHAR2' , PDO::PARAM_STR); + test_meta($db, 240, 'VARCHAR2(10)' , '0123456789' , 'VARCHAR2' , PDO::PARAM_STR); + test_meta($db, 250, 'VARCHAR2(255)' , str_repeat('z', 255) , 'VARCHAR2' , PDO::PARAM_STR); + + test_meta($db, 260, 'NCHAR(1)' , 'a' , 'NCHAR' , PDO::PARAM_STR); + test_meta($db, 270, 'NCHAR(10)' , '0123456789' , 'NCHAR' , PDO::PARAM_STR); + test_meta($db, 280, 'NCHAR(255)' , str_repeat('z', 255) , 'NCHAR' , PDO::PARAM_STR); + test_meta($db, 290, 'NVARCHAR2(1)' , 'a' , 'NVARCHAR2', PDO::PARAM_STR); + test_meta($db, 300, 'NVARCHAR2(10)' , '0123456789' , 'NVARCHAR2', PDO::PARAM_STR); + test_meta($db, 310, 'NVARCHAR2(255)', str_repeat('z', 255) , 'NVARCHAR2', PDO::PARAM_STR); + + echo "Test 2.5 testing lobs columns\n"; + + test_meta($db, 320, 'CLOB' , str_repeat('b', 255) , 'CLOB' , PDO::PARAM_LOB); + test_meta($db, 330, 'BLOB' , str_repeat('b', 256) , 'BLOB' , PDO::PARAM_LOB); + test_meta($db, 340, 'NCLOB' , str_repeat('b', 255) , 'NCLOB' , PDO::PARAM_LOB); + + test_meta($db, 350, 'LONG' , str_repeat('b', 256) , 'LONG' , PDO::PARAM_STR); + test_meta($db, 360, 'LONG RAW' , str_repeat('b', 256) , 'LONG RAW', PDO::PARAM_STR); + test_meta($db, 370, 'RAW(256)' , str_repeat('b', 256) , 'RAW' , PDO::PARAM_STR); + + + $db->exec(<<query('SELECT count(*) FROM dual'); + $meta = $stmt->getColumnMeta(0); + test_return($meta, 380, 'NUMBER', PDO::PARAM_STR); + $stmt = $db->query("SELECT TO_DATE('2008-04-23') FROM dual"); + $meta = $stmt->getColumnMeta(0); + test_return($meta, 390, 'DATE', PDO::PARAM_STR); + $stmt = $db->query("SELECT TO_CHAR(542) FROM dual"); + $meta = $stmt->getColumnMeta(0); + test_return($meta, 400, 'VARCHAR2', PDO::PARAM_STR); + + + echo "Test 2.7 testing flags returned\n"; + + $sql = sprintf('CREATE TABLE test(id INT NOT NULL, label INT NULL)'); + $stmt = $db->prepare($sql); + $stmt->execute(); + $db->exec('INSERT INTO test(id, label) VALUES (1, 1)'); + $stmt = $db->query('SELECT id, label FROM test'); + $meta = $stmt->getColumnMeta(0); + // verify the flags array contains a not_null flag and not nullable flags + if (!isset($meta['flags'])) { + printf("[1001] No flags contained in metadata %s\n", var_export($meta, true)); + } else { + $flags = $meta['flags']; + $found = false; + foreach ($flags as $k => $flag) { + if ($flag == 'not_null') + $found = true; + if ($flag == 'nullable') + printf("[1003] Flags seem wrong %s\n", var_export($meta, true)); + } + if (!$found) + printf("[1002] Flags seem wrong %s\n", var_export($meta, true)); + } + $meta = $stmt->getColumnMeta(1); + // verify the flags array contains a nullable flag and not not_null flags + if (!isset($meta['flags'])) { + printf("[1003] No flags contained in metadata %s\n", var_export($meta, true)); + } else { + $flags = $meta['flags']; + $found = false; + foreach ($flags as $k => $flag) { + if ($flag == 'not_null') + printf("[1004] Flags seem wrong %s\n", var_export($meta, true)); + if ($flag == 'nullable') + $found = true; + } + if (!$found) + printf("[1005] Flags seem wrong %s\n", var_export($meta, true)); + } + + } catch (PDOException $e) { + // we should never get here, we use warnings, but never trust a system... + printf("[001] %s, [%s} %s\n", + $e->getMessage(), $db->errorInfo(), implode(' ', $db->errorInfo())); + } + + $db->exec(<< +--EXPECT-- +Preparations before the test +Test 1. calling function with invalid parameters + 1.1 Expecting false got false + 1.2 PDOStatement::getColumnMeta() expects exactly 1 argument, 0 given + 1.3 PDOStatement::getColumnMeta(): Argument #1 ($column) must be greater than or equal to 0 + 1.4 PDOStatement::getColumnMeta(): Argument #1 ($column) must be of type int, array given + 1.5 PDOStatement::getColumnMeta() expects exactly 1 argument, 2 given + 1.6 Expecting false because of invalid offset got false +Test 2. testing return values +Test 2.1 testing array returned +Test 2.2 testing numeric columns +Test 2.3 testing temporal columns +Test 2.4 testing string columns +Test 2.5 testing lobs columns +Test 2.6 testing function return +Test 2.7 testing flags returned +done! diff --git a/tests/swoole_pdo_oracle/pdo_oci_stream_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_stream_1.phpt new file mode 100644 index 0000000000..5ebb28e1e0 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_stream_1.phpt @@ -0,0 +1,118 @@ +--TEST-- +swoole_pdo_oracle: stream_get_contents length & offset test +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + + $dbh->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + + // Initialization + + $stmtarray = array( + "create table pdo_oci_stream_1_tab (id number, data clob)", + ); + + foreach ($stmtarray as $stmt) { + $dbh->exec($stmt); + } + + $dbh->exec(" + declare + lob1 clob := 'abc' || lpad('j',30000,'j') || 'xyz'; + begin + insert into pdo_oci_stream_1_tab (id,data) values (1, 'abcdefghijklmnopqrstuvwxyz'); + insert into pdo_oci_stream_1_tab (id,data) values (2, lob1); + end;"); + + echo "Test 1\n"; + + $s = $dbh->prepare("select data from pdo_oci_stream_1_tab where id = 1"); + $s->execute(); + $r = $s->fetch(); + + // stream_get_contents ( resource $handle [, int $maxlength = -1 [, int $offset = -1 ]] ) + echo 'Read '.stream_get_contents($r['data'], 1, 1)."$\n"; // b + echo 'Read '.stream_get_contents($r['data'], 2, 1)."$\n"; // cd + echo 'Read '.stream_get_contents($r['data'], 2, 0)."$\n"; // ab + echo 'Read '.stream_get_contents($r['data'], 26, 0)."$\n"; // abcdefghijklmnopqrstuvwxyz + echo 'Read '.stream_get_contents($r['data'], 27, 0)."$\n"; // abcdefghijklmnopqrstuvwxyz + echo 'Read '.stream_get_contents($r['data'], 27, 1)."$\n"; // bcdefghijklmnopqrstuvwxyz + echo 'Read '.stream_get_contents($r['data'], 1, 20)."$\n"; // u + echo 'Read '.stream_get_contents($r['data'], 1, 25)."$\n"; // z + echo 'Read '.stream_get_contents($r['data'], 1, 26)."$\n"; // + echo 'Read '.stream_get_contents($r['data'], 1, 0)."$\n"; // a + + echo "\nTest 2\n"; + + $s = $dbh->prepare("select data from pdo_oci_stream_1_tab where id = 2"); + $s->execute(); + $r = $s->fetch(); + + echo 'Read '.stream_get_contents($r['data'], 5, 0)."\n"; // abcjj + echo 'Read '.stream_get_contents($r['data'], 5, 2)."\n"; // cjjjj + echo 'Read '.stream_get_contents($r['data'], 6, 1)."\n"; // bcjjjj + echo 'Read '.strlen(stream_get_contents($r['data'], -1,0))."\n"; // 30006 + echo 'Read '.strlen(stream_get_contents($r['data'], 0,0))."\n"; // 0 + echo 'Read '.strlen(stream_get_contents($r['data'], 0,1))."\n"; // 0 + echo 'Read '.strlen(stream_get_contents($r['data'], 10,100))."\n"; // 10 + echo 'Read '.stream_get_contents($r['data'], 6, 30000)."\n"; // jjjxyz + echo 'Read '.stream_get_contents($r['data'], 7, 30000)."\n"; // jjjxyz + echo 'Read '.strlen(stream_get_contents($r['data']))."\n"; // 0 + echo 'Read '.strlen(stream_get_contents($r['data'], 0))."\n"; // 0 + echo 'Read '.strlen(stream_get_contents($r['data'], -1))."\n"; // 0 + echo 'Read '.stream_get_contents($r['data'], -1, 30000)."\n"; // jjjxyz + + // Clean up + + $stmtarray = array( + "drop table pdo_oci_stream_1_tab" + ); + + foreach ($stmtarray as $stmt) { + $dbh->exec($stmt); + } +}); +?> +--EXPECT-- +Test 1 +Read b$ +Read cd$ +Read ab$ +Read abcdefghijklmnopqrstuvwxyz$ +Read abcdefghijklmnopqrstuvwxyz$ +Read bcdefghijklmnopqrstuvwxyz$ +Read u$ +Read z$ +Read $ +Read a$ + +Test 2 +Read abcjj +Read cjjjj +Read bcjjjj +Read 30006 +Read 0 +Read 0 +Read 10 +Read jjjxyz +Read jjjxyz +Read 0 +Read 0 +Read 0 +Read jjjxyz diff --git a/tests/swoole_pdo_oracle/pdo_oci_stream_2.phpt b/tests/swoole_pdo_oracle/pdo_oci_stream_2.phpt new file mode 100644 index 0000000000..be3b350064 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_stream_2.phpt @@ -0,0 +1,128 @@ +--TEST-- +swoole_pdo_oracle: Insert and fetch 1K records from a table that contains 1 number and 2 LOB columns (stress test) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $query = "begin execute immediate 'drop table pdo_oci_stream_2'; exception when others then if sqlcode <> -942 then raise; end if; end;"; + $stmt = $db->prepare($query); + $stmt->execute(); + + $query = "create table pdo_oci_stream_2 (id number, data1 blob, data2 blob)"; + $stmt = $db->prepare($query); + $stmt->execute(); + + function do_insert($db, $id, $data1, $data2) + { + $db->beginTransaction(); + $stmt = $db->prepare("insert into pdo_oci_stream_2 (id, data1, data2) values (:id, empty_blob(), empty_blob()) returning data1, data2 into :blob1, :blob2"); + $stmt->bindParam(':id', $id); + $stmt->bindParam(':blob1', $blob1, PDO::PARAM_LOB); + $stmt->bindParam(':blob2', $blob2, PDO::PARAM_LOB); + $blob1 = null; + $blob2 = null; + $stmt->execute(); + + fwrite($blob1, $data1); + fclose($blob1); + fwrite($blob2, $data2); + fclose($blob2); + $db->commit(); + } + + $a1 = str_repeat('a', 4086); + $a2 = str_repeat('b', 4087); + $a3 = str_repeat('c', 4088); + $a4 = str_repeat('d', 4089); + $a5 = str_repeat('e', 4090); + $a6 = str_repeat('f', 4091); + $a7 = str_repeat('g', 4092); + $a8 = str_repeat('h', 4093); + $a9 = str_repeat('i', 4094); + $a10 = str_repeat('j', 4095); + + printf("Inserting 1000 Records ... "); + for($i=0; $i<100; $i++) { + do_insert($db, $i * 10 + 1, $a1, $a10); + do_insert($db, $i * 10 + 2, $a2, $a9); + do_insert($db, $i * 10 + 3, $a3, $a8); + do_insert($db, $i * 10 + 4, $a4, $a7); + do_insert($db, $i * 10 + 5, $a5, $a6); + do_insert($db, $i * 10 + 6, $a6, $a5); + do_insert($db, $i * 10 + 7, $a7, $a4); + do_insert($db, $i * 10 + 8, $a8, $a3); + do_insert($db, $i * 10 + 9, $a9, $a2); + do_insert($db, $i * 10 + 10, $a10, $a1); + } + printf("Done\n"); + + /* Cleanup is done in pdo_oci_stream_2b.phpt */ + //$db->exec("drop table pdo_oci_stream_2"); + + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); // Let's use streams + + // Since each column only has one lob descriptor, the last row is + // shown twice because the lob descriptor for each column is reused in + // the stream + + $i = 0; + $j = 9; + $a_val = ord('a'); + foreach($db->query("select data1 as d4_1, data2 as d4_2 from pdo_oci_stream_2 order by id") as $row) { + $a = $row['d4_1']; + $a1 = $row['d4_2']; + + $str1 = stream_get_contents($a); + $str2 = stream_get_contents($a1); + + $str1len = strlen($str1); + $str2len = strlen($str2); + + $b = ord($str1[0]); + $b1 = ord($str2[0]); + + if (($b != ($a_val + $i)) && ($str1len != (4086 + $i)) && + ($b1 != ($a_val + $j)) && ($str2len != (4086 + $j))) { + printf("There is a bug!\n"); + printf("Col1:\n"); + printf("a_val = %d\n", $a_val); + printf("b = %d\n", $b); + printf("i = %d\n", $i); + printf("str1len = %d\n", $str1len); + + printf("Col2:\n"); + printf("a_val = %d\n", $a_val); + printf("b1 = %d\n", $b1); + printf("j = %d\n", $j); + printf("str2len = %d\n", $str1len); + + } + $i++; + if ($i>9) + $i = 0; + $j--; + if ($j<0) + $j = 9; + } + echo "Fetch operation done!\n"; + + /* Cleanup */ + $db->exec("drop table pdo_oci_stream_2"); +}); +?> +--EXPECT-- +Inserting 1000 Records ... Done +Fetch operation done! diff --git a/tests/swoole_pdo_oracle/pdo_oci_templob_1.phpt b/tests/swoole_pdo_oracle/pdo_oci_templob_1.phpt new file mode 100644 index 0000000000..4828ac5e8c --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oci_templob_1.phpt @@ -0,0 +1,88 @@ +--TEST-- +swoole_pdo_oracle: Test to verify all implicitly created temporary LOB are cleaned up +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $db = PdoOracleTest::create(); + $clobquery1 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery2 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery3 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery4 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery5 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery6 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery7 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery8 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery9 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + $clobquery10 = "select TO_CLOB('Hello World') CLOB_DATA from dual"; + + $stmt= $db->prepare($clobquery1); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery2); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery3); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery4); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery5); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery6); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery7); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery8); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery9); + $stmt->execute(); + $row = $stmt->fetch(); + $stmt= $db->prepare($clobquery10); + $stmt->execute(); + $row = $stmt->fetch(); + + $query1 = "SELECT SYS_CONTEXT('USERENV', 'SID') SID FROM DUAL"; + + $stmt1 = $db->prepare($query1); + $stmt1->execute(); + + $row1 = $stmt1->fetch(); + $sid_value = $row1[0]; + + $query2 = "SELECT (CACHE_LOBS+NOCACHE_LOBS+ABSTRACT_LOBS) FROM V\$TEMPORARY_LOBS WHERE SID = :SID_VALUE"; + + $stmt2 = $db->prepare($query2); + $stmt2->bindParam(':SID_VALUE', $sid_value); + $stmt2->execute(); + + $row2 = $stmt2->fetch(); + /* 1 temporary LOB still exists in V$TEMPORARY_LOBS since the destructor of $stmt is not yet called by PHP */ + if ($row2[0] > 1) + { + echo "TEMP_LOB is not yet cleared!" . $row2[0] . "\n"; + } + else + { + echo "Success! All the temporary LOB in previously closed statements are properly cleaned.\n"; + } +}); +?> +--EXPECTF-- +TEMP_LOB is not yet cleared!10 diff --git a/tests/swoole_pdo_oracle/pdo_oracle.inc b/tests/swoole_pdo_oracle/pdo_oracle.inc new file mode 100644 index 0000000000..3ebe26d8e4 --- /dev/null +++ b/tests/swoole_pdo_oracle/pdo_oracle.inc @@ -0,0 +1,20 @@ +getMessage()); + } + } + + public static function create(): PDO + { + $db = new PDO(ORACLE_TNS, ORACLE_USER, ORACLE_PASSWORD); + $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + return $db; + } +} diff --git a/tests/swoole_pdo_oracle/pecl_bug_11345.phpt b/tests/swoole_pdo_oracle/pecl_bug_11345.phpt new file mode 100644 index 0000000000..4b4fe28eb8 --- /dev/null +++ b/tests/swoole_pdo_oracle/pecl_bug_11345.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_pdo_oracle: PECL PDO_OCI Bug #11345 (Test invalid character set name) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + try { + $dbh = new PDO('oci:dbname=xxx;charset=yyy', 'abc', 'def'); + } + catch (PDOException $e) { + echo 'Connection failed: ' . $e->getMessage(). "\n"; + } +}); +?> +--EXPECTF-- +Connection failed: SQLSTATE[HY000]: OCINlsCharSetNameToId: unknown character set name (%s) diff --git a/tests/swoole_pdo_oracle/pecl_bug_6364.phpt b/tests/swoole_pdo_oracle/pecl_bug_6364.phpt new file mode 100644 index 0000000000..1fb60a9d22 --- /dev/null +++ b/tests/swoole_pdo_oracle/pecl_bug_6364.phpt @@ -0,0 +1,73 @@ +--TEST-- +swoole_pdo_oracle: PECL PDO_OCI Bug #6364 (segmentation fault on stored procedure call with OUT binds) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_ORACLE]); +run(function() { + $dbh = PdoOracleTest::create(); + $dbh->exec ("create table bug_6364_t (c1 varchar2(10), c2 varchar2(10), c3 varchar2(10), c4 varchar2(10), c5 varchar2(10))"); + + $dbh->exec ("create or replace procedure bug_6364_sp(p1 IN varchar2, p2 IN varchar2, p3 IN varchar2, p4 OUT varchar2, p5 OUT varchar2) as begin insert into bug_6364_t (c1, c2, c3) values (p1, p2, p3); p4 := 'val4'; p5 := 'val5'; end;"); + + $stmt = $dbh->prepare("call bug_6364_sp('p1','p2','p3',?,?)"); + + $out_param1 = "a"; + $out_param2 = "a"; + + $stmt->bindParam(1, $out_param1,PDO::PARAM_STR, 1024); + $stmt->bindParam(2, $out_param2,PDO::PARAM_STR, 1024); + + $stmt->execute() or die ("Execution error: " . var_dump($dbh->errorInfo())); + + var_dump($out_param1); + var_dump($out_param2); + + foreach ($dbh->query("select * from bug_6364_t") as $row) { + var_dump($row); + } + + print "Done\n"; + + // Cleanup + $dbh->exec ("drop procedure bug_6364_sp"); + $dbh->exec ("drop table bug_6364_t"); +}); +?> +--EXPECT-- +string(4) "val4" +string(4) "val5" +array(10) { + ["c1"]=> + string(2) "p1" + [0]=> + string(2) "p1" + ["c2"]=> + string(2) "p2" + [1]=> + string(2) "p2" + ["c3"]=> + string(2) "p3" + [2]=> + string(2) "p3" + ["c4"]=> + NULL + [3]=> + NULL + ["c5"]=> + NULL + [4]=> + NULL +} +Done diff --git a/tests/swoole_pdo_oracle/transcation.phpt b/tests/swoole_pdo_oracle/transcation.phpt new file mode 100644 index 0000000000..39bf771ac4 --- /dev/null +++ b/tests/swoole_pdo_oracle/transcation.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI transcation1 +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_ALL]); +run(function() { + $db = PdoOracleTest::create(); + $db->exec('create table transcation1 (id int)'); + go(function () use($db){ + $db->beginTransaction(); + $stmt = $db->prepare("insert into transcation1 values (?)"); + $stmt->execute([1]); + go(function () use($db){ + $db->beginTransaction(); + $stmt = $db->prepare("insert into transcation1 values (?)"); + $stmt->execute([2]); + $db->rollback(); + }); + sleep(2); + $db->commit(); + $stmt = $db->prepare("select id from transcation1 where id = ?"); + $stmt->execute([1]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 1); + }); + sleep(4); +}); +?> +--EXPECTF-- +Fatal error: Uncaught PDOException: There is already an active transaction in %s:%d +Stack trace: +#0 %s(%d): PDO->beginTransaction() +%A + thrown in %s on line %d diff --git a/tests/swoole_pdo_oracle/transcation2.phpt b/tests/swoole_pdo_oracle/transcation2.phpt new file mode 100644 index 0000000000..6ce9749f37 --- /dev/null +++ b/tests/swoole_pdo_oracle/transcation2.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_pdo_oracle: PDO OCI transcation2 +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_ALL]); +run(function() { + $db = PdoOracleTest::create(); + $db->exec('create table transcation2 (id int)'); + + go(function() { + $db = PdoOracleTest::create(); + $db->beginTransaction(); + $stmt = $db->prepare("insert into transcation2 values (?)"); + $stmt->execute([1]); + $db->commit(); + $stmt = $db->prepare("select id from transcation2 where id = ?"); + $stmt->execute([1]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 1); + }); + + go(function(){ + $db = PdoOracleTest::create(); + $db->beginTransaction(); + $stmt = $db->prepare("insert into transcation2 values (?)"); + $stmt->execute([2]); + $db->commit(); + $stmt = $db->prepare("select id from transcation2 where id = ?"); + $stmt->execute([2]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == 2); + }); + sleep(1); + $db->exec('drop table transcation2'); +}); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/swoole_pdo_pgsql/base.phpt b/tests/swoole_pdo_pgsql/base.phpt new file mode 100644 index 0000000000..068ba9a855 --- /dev/null +++ b/tests/swoole_pdo_pgsql/base.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_pdo_pgsql: test hook pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT * FROM pg_catalog.pg_tables limit 1'); + $statement->execute(); + var_dump($statement->fetchAll(PDO::FETCH_COLUMN)); + }); + + Co\go(function () { + $pdo = pdo_pgsql_test_inc::create(); + $statement = $pdo->prepare('SELECT * FROM pg_catalog.pg_tables limit 1'); + $statement->execute(); + var_dump($statement->fetchAll(PDO::FETCH_COLUMN)); + }); +}); + +echo "Done\n"; +?> +--EXPECTF-- +array(1) { + [0]=> + string(%d) "%s" +} +array(1) { + [0]=> + string(%d) "%s" +} +Done diff --git a/tests/swoole_pdo_pgsql/blocking.phpt b/tests/swoole_pdo_pgsql/blocking.phpt new file mode 100644 index 0000000000..737f3d5976 --- /dev/null +++ b/tests/swoole_pdo_pgsql/blocking.phpt @@ -0,0 +1,34 @@ +--TEST-- +swoole_pdo_pgsql: test hook pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT pg_sleep(1)'); + $statement->execute(); + Assert::eq($sleep_count, 0); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_pgsql/bug_5635.phpt b/tests/swoole_pdo_pgsql/bug_5635.phpt new file mode 100644 index 0000000000..b9fc3e49f8 --- /dev/null +++ b/tests/swoole_pdo_pgsql/bug_5635.phpt @@ -0,0 +1,71 @@ +--TEST-- +swoole_pdo_pgsql: Github bug #5635 +--SKIPIF-- + +--FILE-- +exec('create table bug_5635 (id int, data varchar(1024));'); +$pdo->exec(<< SWOOLE_HOOK_PDO_PGSQL]); +run(function() { + $waitGroup = new WaitGroup(); + $channel = new Channel(1); + + Coroutine::create(function() use ($waitGroup, $channel) { + $start = time(); + $waitGroup->add(); + $pdo = pdo_pgsql_test_inc::create(); + $stmt = $pdo->query("select * from bug_5635;"); + $data = $stmt->fetchAll(); + Assert::true(count($data) == 5000000); + $channel->push($data ?? [], 10); + $waitGroup->done(); + echo 'DONE' . PHP_EOL; + }); + + Coroutine::create(function() use ($waitGroup, $channel) { + $waitGroup->add(); + $result = $channel->pop(0.5); + if (!$result) { + echo 'channel pop timeout' . PHP_EOL; + } + $waitGroup->done(); + }); + + var_dump(1); + Coroutine::sleep(0.2); + var_dump(2); + $waitGroup->wait(); +}); +?> +--CLEAN-- +exec('drop table bug_5635;'); +?> +--EXPECTF-- +int(1) +int(2) +channel pop timeout +DONE diff --git a/tests/swoole_pdo_pgsql/libpq_version.phpt b/tests/swoole_pdo_pgsql/libpq_version.phpt new file mode 100644 index 0000000000..b3f4715c54 --- /dev/null +++ b/tests/swoole_pdo_pgsql/libpq_version.phpt @@ -0,0 +1,11 @@ +--TEST-- +swoole_pdo_pgsql: libpq version +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/tests/swoole_pdo_pgsql/pdo_pgsql.inc b/tests/swoole_pdo_pgsql/pdo_pgsql.inc new file mode 100644 index 0000000000..2e2c914a73 --- /dev/null +++ b/tests/swoole_pdo_pgsql/pdo_pgsql.inc @@ -0,0 +1,46 @@ +exec('CREATE TABLE ' . self::TABLE . ' ( + id SERIAL primary key NOT NULL, + name character varying(32), + age integer)'); + } + + $pdo->exec('TRUNCATE ' . self::TABLE); + } + + public static function getTable() + { + return self::TABLE; + } + + public static function tableExists($pdo, $table) + { + try { + $result = $pdo->query("SELECT 1 FROM {$table} LIMIT 1"); + } catch (Exception $e) { + return false; + } + + return $result !== false; + } + + public static function create(): PDO + { + $host = PGSQL_HOST; + $port = PGSQL_PORT; + $user = PGSQL_USER; + $password = PGSQL_PASSWORD; + $dbname = PGSQL_DBNAME; + return new PDO("pgsql:host={$host};port={$port};dbname={$dbname}", $user, $password); + } +} diff --git a/tests/swoole_pdo_pgsql/query.phpt b/tests/swoole_pdo_pgsql/query.phpt new file mode 100644 index 0000000000..99f35ce1a8 --- /dev/null +++ b/tests/swoole_pdo_pgsql/query.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_pdo_pgsql: test query +--SKIPIF-- + +--FILE-- +prepare('INSERT INTO ' . pdo_pgsql_test_inc::getTable() . ' (name, age) values (?, ?)'); + + $list = []; + for ($i = 0; $i < N; $i++) { + $name = base64_encode(random_bytes(8)); + $age = random_int(18, 35); + $stmt->bindValue(1, $name); + $stmt->bindValue(2, $age); + $stmt->execute(); + + $list[] = [ + 'id' => $pdo->lastInsertId(), + 'name' => $name, + 'age' => $age, + ]; + } + + foreach ($list as $rs) { + Co\go(function () use ($rs) { + $pdo = pdo_pgsql_test_inc::create(); + $statement = $pdo->query('select * from ' . pdo_pgsql_test_inc::getTable() . ' where id = ' . $rs['id'] . ' limit 1'); + Assert::eq($statement->fetch(PDO::FETCH_ASSOC), $rs); + }); + } +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_pgsql/race.phpt b/tests/swoole_pdo_pgsql/race.phpt new file mode 100644 index 0000000000..172692a5ff --- /dev/null +++ b/tests/swoole_pdo_pgsql/race.phpt @@ -0,0 +1,34 @@ +--TEST-- +swoole_pdo_pgsql: race +--SKIPIF-- + +--FILE-- +prepare('SELECT pg_sleep(1)'); + try { + $statement->execute(); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); + } catch (\PDOException $e) { + $msg[] = $e->getMessage(); + } + }); + } + Assert::count($msg, 1); + Assert::contains($msg[0], 'SQLSTATE[HY000]: General error'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_pgsql/sleep.phpt b/tests/swoole_pdo_pgsql/sleep.phpt new file mode 100644 index 0000000000..ff931a7011 --- /dev/null +++ b/tests/swoole_pdo_pgsql/sleep.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_pdo_pgsql: test hook pgsql +--SKIPIF-- + +--FILE-- +prepare('SELECT pg_sleep(1)'); + $statement->execute(); + Assert::eq($sleep_count, N); + Assert::keyExists($statement->fetchAll(PDO::FETCH_ASSOC)[0], 'pg_sleep'); +}); + +echo "Done\n"; +?> +--EXPECTF-- +Done diff --git a/tests/swoole_pdo_pgsql/transaction.phpt b/tests/swoole_pdo_pgsql/transaction.phpt new file mode 100644 index 0000000000..e7a594e3df --- /dev/null +++ b/tests/swoole_pdo_pgsql/transaction.phpt @@ -0,0 +1,64 @@ +--TEST-- +swoole_pdo_pgsql: test query +--SKIPIF-- + +--FILE-- +prepare('INSERT INTO ' . pdo_pgsql_test_inc::getTable() . ' (name, age) values (?, ?)'); + $stmt->bindValue(1, base64_encode(random_bytes(8))); + $stmt->bindValue(2, random_int(18, 35)); + $stmt->execute(); + + var_dump('insert'); + + Co::join([Co\go(static function (): void { + $pdo = pdo_pgsql_test_inc::create(); + try { + $pdo->beginTransaction(); + + $pdo->exec('DROP TABLE IF EXISTS ' . pdo_pgsql_test_inc::getTable()); + throw new Exception('interrupt!!!'); + $pdo->commit(); + } catch (\Exception $e) { + $pdo->rollBack(); + var_dump('rollback'); + } + })]); + + var_dump('wait1'); + var_dump(pdo_pgsql_test_inc::tableExists($pdo, pdo_pgsql_test_inc::getTable())); + + Co::join([Co\go(static function (): void { + $pdo = pdo_pgsql_test_inc::create(); + try { + $pdo->beginTransaction(); + + $pdo->exec('DROP TABLE IF EXISTS ' . pdo_pgsql_test_inc::getTable()); + $pdo->commit(); + } catch (\Exception $e) { + $pdo->rollBack(); + var_dump($e->getMessage()); + } + })]); + + var_dump('wait2'); + var_dump(pdo_pgsql_test_inc::tableExists($pdo, pdo_pgsql_test_inc::getTable())); +}); + +echo "Done\n"; +?> +--EXPECTF-- +string(6) "insert" +string(8) "rollback" +string(5) "wait1" +bool(true) +string(5) "wait2" +bool(false) +Done diff --git a/tests/swoole_pdo_sqlite/bug33841.phpt b/tests/swoole_pdo_sqlite/bug33841.phpt new file mode 100644 index 0000000000..3c9cf6cbec --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug33841.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLite Bug #33841 (rowCount() does not work on prepared statements) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = PdoSqliteTest::create(); + + $db->exec('CREATE TABLE test (text)'); + + $stmt = $db->prepare("INSERT INTO test VALUES ( :text )"); + $stmt->bindParam(':text', $name); + $name = 'test1'; + var_dump($stmt->execute(), $stmt->rowCount()); + + $stmt = $db->prepare("UPDATE test SET text = :text "); + $stmt->bindParam(':text', $name); + $name = 'test2'; + var_dump($stmt->execute(), $stmt->rowCount()); +}); +?> +--EXPECT-- +bool(true) +int(1) +bool(true) +int(1) diff --git a/tests/swoole_pdo_sqlite/bug35336.phpt b/tests/swoole_pdo_sqlite/bug35336.phpt new file mode 100644 index 0000000000..dc379bbcb1 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug35336.phpt @@ -0,0 +1,34 @@ +--TEST-- +swoole_pdo_sqlite:FETCH_CLASS + __set()) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $a = new PDO("sqlite::memory:");// pool ("sqlite::memory:"); + $a->query ("CREATE TABLE test (a integer primary key, b text)"); + $b = $a->prepare("insert into test (b) values (?)"); + $b->execute(array (5)); + $rez = $a->query ("SELECT * FROM test")->fetchAll(PDO::FETCH_CLASS, 'EEE'); + + echo "Done\n"; +}); +?> +--EXPECT-- +hello world +hello world +Done diff --git a/tests/swoole_pdo_sqlite/bug38334.phpt b/tests/swoole_pdo_sqlite/bug38334.phpt new file mode 100644 index 0000000000..0678500209 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug38334.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_pdo_sqlite: Proper data-type support for PDO_SQLITE +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->exec('CREATE TABLE test (i INTEGER , f DOUBLE, s VARCHAR(255))'); + $db->exec('INSERT INTO test VALUES (42, 46.7, "test")'); + var_dump($db->query('SELECT * FROM test')->fetch(PDO::FETCH_ASSOC)); + + // Check handling of integers larger than 32-bit. + $db->exec('INSERT INTO test VALUES (10000000000, 0.0, "")'); + $i = $db->query('SELECT i FROM test WHERE f = 0.0')->fetchColumn(0); + if (PHP_INT_SIZE >= 8) { + var_dump($i === 10000000000); + } else { + var_dump($i === '10000000000'); + } + + // Check storing of strings into integer/float columns. + $db->exec('INSERT INTO test VALUES ("test", "test", "x")'); + var_dump($db->query('SELECT * FROM test WHERE s = "x"')->fetch(PDO::FETCH_ASSOC)); +}); +?> +--EXPECT-- +array(3) { + ["i"]=> + int(42) + ["f"]=> + float(46.7) + ["s"]=> + string(4) "test" +} +bool(true) +array(3) { + ["i"]=> + string(4) "test" + ["f"]=> + string(4) "test" + ["s"]=> + string(1) "x" +} diff --git a/tests/swoole_pdo_sqlite/bug43831.phpt b/tests/swoole_pdo_sqlite/bug43831.phpt new file mode 100644 index 0000000000..7157e332d7 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug43831.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_pdo_sqlite: Bug #43831 ($this gets mangled when extending PDO with persistent connection) +--SKIPIF-- + + +--FILE-- + true)); + } +} + +class Baz extends PDO { + function __construct($dsn) { + parent::__construct($dsn, null, null, array(PDO::ATTR_PERSISTENT => true)); + } +} + +class Bar extends Baz { + function quux() { + echo get_class($this), "\n"; + $foo = new Foo("sqlite::memory:"); + echo get_class($this), "\n"; + } +} + +class MyPDO extends PDO {} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $bar = new Bar("sqlite::memory:"); + $bar->quux(); + + $bar = new PDO("sqlite::memory:", null, null, array(PDO::ATTR_PERSISTENT => true)); + $baz = new MyPDO("sqlite::memory:", null, null, array(PDO::ATTR_PERSISTENT => true)); + + var_dump($bar); + unset($bar); + var_dump($baz); + var_dump($bar); +}); +?> +--EXPECTF-- +Bar +Bar +object(PDO)#%d (0) { +} +object(MyPDO)#%d (0) { +} + +Warning: Undefined variable $bar in %s on line %d +NULL diff --git a/tests/swoole_pdo_sqlite/bug44327_2_1.phpt b/tests/swoole_pdo_sqlite/bug44327_2_1.phpt new file mode 100644 index 0000000000..751296b080 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug44327_2_1.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_pdo_sqlite:queryString property & numeric offsets / Crash) +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $x = $db->query('select 1 as queryString'); + var_dump($x, $x->queryString); + + $y = $x->fetch(); + var_dump($y, @$y->queryString); + + print "--------------------------------------------\n"; + + $x = $db->query('select 1 as queryString'); + var_dump($x, $x->queryString); + + $y = $x->fetch(PDO::FETCH_LAZY); + var_dump($y, $y->queryString); +}); +?> +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(23) "select 1 as queryString" +} +string(23) "select 1 as queryString" +array(2) { + ["queryString"]=> + string(1) "1" + [0]=> + string(1) "1" +} +NULL +-------------------------------------------- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(23) "select 1 as queryString" +} +string(23) "select 1 as queryString" +object(PDORow)#%d (1) { + ["queryString"]=> + string(1) "1" +} +string(1) "1" diff --git a/tests/swoole_pdo_sqlite/bug44327_2_2.phpt b/tests/swoole_pdo_sqlite/bug44327_2_2.phpt new file mode 100644 index 0000000000..ae159df11c --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug44327_2_2.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_pdo_sqlite:queryString property & numeric offsets / Crash) +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $x = $db->query('select 1 as queryString'); + var_dump($x, $x->queryString); + + $y = $x->fetch(); + var_dump($y, @$y->queryString); + + print "--------------------------------------------\n"; + + $x = $db->query('select 1 as queryString'); + var_dump($x, $x->queryString); + + $y = $x->fetch(PDO::FETCH_LAZY); + var_dump($y, $y->queryString); +}); +?> +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(23) "select 1 as queryString" +} +string(23) "select 1 as queryString" +array(2) { + ["queryString"]=> + int(1) + [0]=> + int(1) +} +NULL +-------------------------------------------- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(23) "select 1 as queryString" +} +string(23) "select 1 as queryString" +object(PDORow)#%d (1) { + ["queryString"]=> + string(23) "select 1 as queryString" +} +string(23) "select 1 as queryString" diff --git a/tests/swoole_pdo_sqlite/bug44327_3_1.phpt b/tests/swoole_pdo_sqlite/bug44327_3_1.phpt new file mode 100644 index 0000000000..065cdab437 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug44327_3_1.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_pdo_sqlite:queryString property & numeric offsets / Crash) +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $x = $db->query('select 1 as queryStringxx'); + $y = $x->fetch(PDO::FETCH_LAZY); + var_dump($y, $y->queryString, $y->queryStringzz, $y->queryStringxx); + + print "---\n"; + + var_dump($y[5], $y->{3}); +}); +?> +--EXPECTF-- +object(PDORow)#%d (2) { + ["queryString"]=> + string(25) "select 1 as queryStringxx" + ["queryStringxx"]=> + string(1) "1" +} +string(25) "select 1 as queryStringxx" +NULL +string(1) "1" +--- +NULL +NULL diff --git a/tests/swoole_pdo_sqlite/bug44327_3_2.phpt b/tests/swoole_pdo_sqlite/bug44327_3_2.phpt new file mode 100644 index 0000000000..3f1a16ab46 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug44327_3_2.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_pdo_sqlite:queryString property & numeric offsets / Crash) +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $x = $db->query('select 1 as queryStringxx'); + $y = $x->fetch(PDO::FETCH_LAZY); + var_dump($y, $y->queryString, $y->queryStringzz, $y->queryStringxx); + + print "---\n"; + + var_dump($y[5], $y->{3}); +}); +?> +--EXPECTF-- +object(PDORow)#%d (2) { + ["queryString"]=> + string(25) "select 1 as queryStringxx" + ["queryStringxx"]=> + int(1) +} +string(25) "select 1 as queryStringxx" +NULL +int(1) +--- +NULL +NULL diff --git a/tests/swoole_pdo_sqlite/bug46139.phpt b/tests/swoole_pdo_sqlite/bug46139.phpt new file mode 100644 index 0000000000..2926e93a40 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug46139.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_pdo_sqlite: Bug #46139 (PDOStatement->setFetchMode() forgets FETCH_PROPS_LATE) +--SKIPIF-- + + +--FILE-- +test); + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = PdoSqliteTest::create(); + + $stmt = $db->query("SELECT 'foo' test, 1"); + $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); + $r1 = $stmt->fetch(); + printf("'%s'\n", $r1->test); + + $stmt = $db->query("SELECT 'foo' test, 1"); + $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); + $r1 = $stmt->fetchAll(); + printf("'%s'\n", $r1[0]->test); + + $stmt = $db->query("SELECT 'foo' test, 1"); + $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Person'); + $r1 = $stmt->fetch(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE); + printf("'%s'\n", $r1->test); +}); +?> +--EXPECT-- +NULL +'foo' +NULL +'foo' +NULL +'foo' diff --git a/tests/swoole_pdo_sqlite/bug46542.phpt b/tests/swoole_pdo_sqlite/bug46542.phpt new file mode 100644 index 0000000000..07eeec10d7 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug46542.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_pdo_sqlite: Bug #46542 Extending PDO class with a __call() function +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $a = new A('sqlite:' . __DIR__ . '/dummy.db'); + + $a->truc(); + $a->TRUC(); +}); +?> +--CLEAN-- + +--EXPECT-- +A::truc +A::TRUC diff --git a/tests/swoole_pdo_sqlite/bug48773.phpt b/tests/swoole_pdo_sqlite/bug48773.phpt new file mode 100644 index 0000000000..6d5b53a797 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug48773.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_pdo_sqlite:ATTR_STATEMENT_CLASS with ctor_args) +--SKIPIF-- + + +--FILE-- +setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new foo('sqlite::memory:', '', ''); + $stmt = $db->query('SELECT 1'); + var_dump($stmt); +}); +?> +--EXPECTF-- +object(bar)#%d (1) { + ["queryString"]=> + string(8) "SELECT 1" +} diff --git a/tests/swoole_pdo_sqlite/bug50728.phpt b/tests/swoole_pdo_sqlite/bug50728.phpt new file mode 100644 index 0000000000..d558000209 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug50728.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_pdo_sqlite: Bug #50728 (All PDOExceptions hardcode 'code' property to 0) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + try { + $a = new PDO("sqlite:/this/path/should/not/exist.db"); + } catch (PDOException $e) { + var_dump($e->getCode()); + } +}); +?> +--EXPECT-- +int(14) diff --git a/tests/swoole_pdo_sqlite/bug52487.phpt b/tests/swoole_pdo_sqlite/bug52487.phpt new file mode 100644 index 0000000000..b34b2bb935 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug52487.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_pdo_sqlite:FETCH_INTO leaks memory) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = PdoSqliteTest::create(); + $stmt = $db->prepare("select 1 as attr"); + for ($i = 0; $i < 10; $i++) { + $stmt->setFetchMode(PDO::FETCH_INTO, new stdClass); + } + + print "ok\n"; +}); +?> +--EXPECT-- +ok diff --git a/tests/swoole_pdo_sqlite/bug66033.phpt b/tests/swoole_pdo_sqlite/bug66033.phpt new file mode 100644 index 0000000000..496005a378 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug66033.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_pdo_sqlite: Bug #66033 (Segmentation Fault when constructor of PDO statement throws an exception) +--SKIPIF-- + + +--FILE-- +dbh = $dbh; + throw new Exception("Blah"); + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO('sqlite::memory:', null, null); + $pdo->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('DBStatement', + array($pdo))); + $pdo->exec("CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY, + title TEXT, + message TEXT, + time INTEGER)"); + + try { + $pdoStatement = $pdo->query("select * from messages"); + } catch (Exception $e) { + var_dump($e->getMessage()); + } +}); +?> +--EXPECT-- +string(4) "Blah" diff --git a/tests/swoole_pdo_sqlite/bug70862.phpt b/tests/swoole_pdo_sqlite/bug70862.phpt new file mode 100644 index 0000000000..a783456187 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug70862.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_pdo_sqlite: Testing sqliteCreateCollation() +--SKIPIF-- += 80200) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.1 or lower'); +} +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $db->exec('CREATE TABLE test(field BLOB)'); + + $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + + class HelloWrapper { + public function stream_open() { return true; } + public function stream_eof() { return true; } + public function stream_read() { return NULL; } + public function stream_stat() { return array(); } + } + stream_wrapper_register("hello", "HelloWrapper"); + + $f = fopen("hello://there", "r"); + + $stmt = $db->prepare('INSERT INTO test(field) VALUES (:para)'); + $stmt->bindParam(":para", $f, PDO::PARAM_LOB); + $stmt->execute(); + + var_dump($f); +}); +?> ++++DONE+++ +--EXPECT-- +string(0) "" ++++DONE+++ diff --git a/tests/swoole_pdo_sqlite/bug70862_1.phpt b/tests/swoole_pdo_sqlite/bug70862_1.phpt new file mode 100644 index 0000000000..bbf8565401 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug70862_1.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_pdo_sqlite: Testing sqliteCreateCollation() +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + $db->exec('CREATE TABLE test(field BLOB)'); + + $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + + class HelloWrapper { + public function stream_open() { return true; } + public function stream_eof() { return true; } + public function stream_read() { return NULL; } + public function stream_stat() { return array(); } + } + stream_wrapper_register("hello", "HelloWrapper"); + + $f = fopen("hello://there", "r"); + + $stmt = $db->prepare('INSERT INTO test(field) VALUES (:para)'); + $stmt->bindParam(":para", $f, PDO::PARAM_LOB); + $stmt->execute(); + + var_dump($f); +}); +?> ++++DONE+++ +--EXPECTF-- + +Deprecated: Creation of dynamic property HelloWrapper::$context is deprecated in %s on line %d +string(0) "" ++++DONE+++ diff --git a/tests/swoole_pdo_sqlite/bug78192_1.phpt b/tests/swoole_pdo_sqlite/bug78192_1.phpt new file mode 100644 index 0000000000..a415a55e67 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug78192_1.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLite Bug #78192 SegFault when reuse statement after schema change +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $connection = new \PDO('sqlite::memory:'); + $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $connection->query('CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)'); + + $stmt = $connection->prepare('INSERT INTO user (id, name) VALUES(:id, :name)'); + $stmt->execute([ + 'id' => 10, + 'name' => 'test', + ]); + + $stmt = $connection->prepare('SELECT * FROM user WHERE id = :id'); + $stmt->execute(['id' => 10]); + var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); + + $connection->query('ALTER TABLE user ADD new_col VARCHAR(255)'); + $stmt->execute(['id' => 10]); + var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); +}); +?> +--EXPECT-- +array(1) { + [0]=> + array(2) { + ["id"]=> + string(2) "10" + ["name"]=> + string(4) "test" + } +} +array(1) { + [0]=> + array(3) { + ["id"]=> + string(2) "10" + ["name"]=> + string(4) "test" + ["new_col"]=> + NULL + } +} diff --git a/tests/swoole_pdo_sqlite/bug78192_2.phpt b/tests/swoole_pdo_sqlite/bug78192_2.phpt new file mode 100644 index 0000000000..67c52bf836 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug78192_2.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLite Bug #78192 SegFault when reuse statement after schema change +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $connection = new \PDO('sqlite::memory:'); + $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $connection->query('CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)'); + + $stmt = $connection->prepare('INSERT INTO user (id, name) VALUES(:id, :name)'); + $stmt->execute([ + 'id' => 10, + 'name' => 'test', + ]); + + $stmt = $connection->prepare('SELECT * FROM user WHERE id = :id'); + $stmt->execute(['id' => 10]); + var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); + + $connection->query('ALTER TABLE user ADD new_col VARCHAR(255)'); + $stmt->execute(['id' => 10]); + var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC)); +}); +?> +--EXPECT-- +array(1) { + [0]=> + array(2) { + ["id"]=> + int(10) + ["name"]=> + string(4) "test" + } +} +array(1) { + [0]=> + array(3) { + ["id"]=> + int(10) + ["name"]=> + string(4) "test" + ["new_col"]=> + NULL + } +} diff --git a/tests/swoole_pdo_sqlite/bug79664_1.phpt b/tests/swoole_pdo_sqlite/bug79664_1.phpt new file mode 100644 index 0000000000..311deed108 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug79664_1.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_pdo_sqlite:getColumnMeta fails on empty result set) +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO('sqlite::memory:', null, null, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ]); + $stmt = $pdo->query('select 1 where 0'); + if ($stmt->columnCount()) { + var_dump($stmt->getColumnMeta(0)); + } +}); +?> +--EXPECT-- +array(6) { + ["native_type"]=> + string(4) "null" + ["flags"]=> + array(0) { + } + ["name"]=> + string(1) "1" + ["len"]=> + int(-1) + ["precision"]=> + int(0) + ["pdo_type"]=> + int(2) +} diff --git a/tests/swoole_pdo_sqlite/bug79664_2.phpt b/tests/swoole_pdo_sqlite/bug79664_2.phpt new file mode 100644 index 0000000000..daffb554ed --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug79664_2.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_pdo_sqlite:getColumnMeta fails on empty result set) +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO('sqlite::memory:', null, null, [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ]); + $stmt = $pdo->query('select 1 where 0'); + if ($stmt->columnCount()) { + var_dump($stmt->getColumnMeta(0)); + } +}); +?> +--EXPECT-- +array(6) { + ["native_type"]=> + string(4) "null" + ["pdo_type"]=> + int(0) + ["flags"]=> + array(0) { + } + ["name"]=> + string(1) "1" + ["len"]=> + int(-1) + ["precision"]=> + int(0) +} diff --git a/tests/swoole_pdo_sqlite/bug81740.phpt b/tests/swoole_pdo_sqlite/bug81740.phpt new file mode 100644 index 0000000000..14d3d4f15f --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug81740.phpt @@ -0,0 +1,26 @@ +--TEST-- +swoole_pdo_sqlite:quote() may return unquoted string) +--SKIPIF-- + + +--INI-- +memory_limit=-1 +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO("sqlite::memory:"); + $string = str_repeat("a", 0x80000000); + var_dump($pdo->quote($string)); +}); +?> +--EXPECT-- +bool(false) diff --git a/tests/swoole_pdo_sqlite/bug_42589.phpt b/tests/swoole_pdo_sqlite/bug_42589.phpt new file mode 100644 index 0000000000..a561a3cc4e --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_42589.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLite Feature Request #42589 (getColumnMeta() should also return table name) +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO("sqlite::memory:"); + + $db->exec('CREATE TABLE test (field1 VARCHAR(10))'); + $db->exec('INSERT INTO test VALUES("test")'); + + $result = $db->query('SELECT * FROM test t1 LEFT JOIN test t2 ON t1.field1 = t2.field1'); + $meta1 = $result->getColumnMeta(0); + $meta2 = $result->getColumnMeta(1); + + var_dump(!empty($meta1['table']) && $meta1['table'] == 'test'); + var_dump(!empty($meta2['table']) && $meta2['table'] == 'test'); +}); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_1.phpt b/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_1.phpt new file mode 100644 index 0000000000..8f5b5388f0 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_1.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_pdo_sqlite: SQLite variant +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO("sqlite:".__DIR__."/foo.db"); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, NULL)); + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 1)); + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 'nonsense')); + + @unlink(__DIR__."/foo.db"); +}); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_2.phpt b/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_2.phpt new file mode 100644 index 0000000000..e474199e56 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_44159_sqlite_version_2.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_pdo_sqlite: SQLite variant +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO("sqlite:".__DIR__."/foo.db"); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + try { + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, NULL)); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 1)); + try { + var_dump($pdo->setAttribute(PDO::NULL_TO_STRING, 'nonsense')); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + @unlink(__DIR__."/foo.db"); +}); +?> +--EXPECT-- +Attribute value must be of type int for selected attribute, null given +bool(true) +Attribute value must be of type int for selected attribute, string given diff --git a/tests/swoole_pdo_sqlite/bug_47769.phpt b/tests/swoole_pdo_sqlite/bug_47769.phpt new file mode 100644 index 0000000000..7fe5a5a9d0 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_47769.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_pdo_sqlite: Bug #47769 (Strange extends PDO) +--SKIPIF-- + + +--FILE-- +isProtected(); + $this->isPrivate(); + print $str ."\n"; + + return $str; + } +} + +Co::set(['hook_flags'=> SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $test = new test('sqlite::memory:'); + $test->quote('foo'); + $test->isProtected(); +}); +?> +--EXPECTF-- +this is a protected method. +this is a private method. +foo + +Fatal error: Uncaught Error: Call to protected method test::isProtected() from global scope in %s:%d +Stack trace: +%A + thrown in %s on line %d diff --git a/tests/swoole_pdo_sqlite/bug_63916-2.phpt b/tests/swoole_pdo_sqlite/bug_63916-2.phpt new file mode 100644 index 0000000000..365c0a3c19 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_63916-2.phpt @@ -0,0 +1,34 @@ +--TEST-- +swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite +--SKIPIF-- + + 4) die('skip 32-bit only'); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $num = PHP_INT_MAX; // 32 bits + $conn = new PDO('sqlite::memory:'); + $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); + + $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); + $stmt->bindValue(':id', 1, PDO::PARAM_INT); + $stmt->bindValue(':num', $num, PDO::PARAM_INT); + $stmt->execute(); + + $stmt = $conn->query('SELECT num FROM users'); + $result = $stmt->fetchAll(PDO::FETCH_COLUMN); + + var_dump($num,$result[0]); +}); +?> +--EXPECT-- +int(2147483647) +int(2147483647) diff --git a/tests/swoole_pdo_sqlite/bug_63916_1.phpt b/tests/swoole_pdo_sqlite/bug_63916_1.phpt new file mode 100644 index 0000000000..0a917fdfaf --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_63916_1.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite +--SKIPIF-- += 80100) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.0 or lower'); +} + +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +if (PHP_INT_SIZE < 8) die('skip'); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $num = 100004313234244; // exceeds 32 bits + $conn = new PDO('sqlite::memory:'); + $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); + + $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); + $stmt->bindValue(':id', 1, PDO::PARAM_INT); + $stmt->bindValue(':num', $num, PDO::PARAM_INT); + $stmt->execute(); + + $stmt = $conn->query('SELECT num FROM users'); + $result = $stmt->fetchAll(PDO::FETCH_COLUMN); + + var_dump($num,$result[0]); +}); +?> +--EXPECT-- +int(100004313234244) +string(15) "100004313234244" diff --git a/tests/swoole_pdo_sqlite/bug_63916_2.phpt b/tests/swoole_pdo_sqlite/bug_63916_2.phpt new file mode 100644 index 0000000000..4cd75f352b --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_63916_2.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_pdo_sqlite:PARAM_INT casts to 32bit int internally even on 64bit builds in pdo_sqlite +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $num = 100004313234244; // exceeds 32 bits + $conn = new PDO('sqlite::memory:'); + $conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))'); + + $stmt = $conn->prepare('insert into users (id, num) values (:id, :num)'); + $stmt->bindValue(':id', 1, PDO::PARAM_INT); + $stmt->bindValue(':num', $num, PDO::PARAM_INT); + $stmt->execute(); + + $stmt = $conn->query('SELECT num FROM users'); + $result = $stmt->fetchAll(PDO::FETCH_COLUMN); + + var_dump($num,$result[0]); +}); +?> +--EXPECT-- +int(100004313234244) +int(100004313234244) diff --git a/tests/swoole_pdo_sqlite/bug_64705.phpt b/tests/swoole_pdo_sqlite/bug_64705.phpt new file mode 100644 index 0000000000..4685087c94 --- /dev/null +++ b/tests/swoole_pdo_sqlite/bug_64705.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_pdo_sqlite:__construct() fails +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $dsn = 'sqlite:./bug64705NonExistingDir/bug64705NonExistingDb'; + try { + $pdo = new \PDO($dsn, null, null); + } catch (\PDOException $e) { + var_dump(!empty($e->errorInfo) && is_array($e->errorInfo)); + } +}); +?> +--EXPECT-- +bool(true) diff --git a/tests/swoole_pdo_sqlite/coroutine.phpt b/tests/swoole_pdo_sqlite/coroutine.phpt new file mode 100644 index 0000000000..0912bb55e9 --- /dev/null +++ b/tests/swoole_pdo_sqlite/coroutine.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLITE coroutine +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->exec('create table test (id int)'); + for($i = 0; $i <= 20; $i++) { + go(function() use ($i, $db) { + $stmt = $db->prepare('insert into test values(?)'); + $stmt->execute([$i]); + $stmt = $db->prepare('select id from test where id = ?'); + $stmt->execute([$i]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); + }); + } +}); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_pdo_sqlite/coroutine2.phpt b/tests/swoole_pdo_sqlite/coroutine2.phpt new file mode 100644 index 0000000000..1b884d05c2 --- /dev/null +++ b/tests/swoole_pdo_sqlite/coroutine2.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLITE coroutine +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +$db = new PDO('sqlite:test.db'); +$db->exec('create table if not exists test (id int)'); +$db->exec('delete from test'); + +run(function() { + for($i = 0; $i <= 20; $i++) { + go(function() use ($i) { + $db = new PDO('sqlite:test.db'); + $stmt = $db->prepare('insert into test values(?)'); + $stmt->execute([$i]); + $stmt = $db->prepare('select id from test where id = ?'); + $stmt->execute([$i]); + var_dump($stmt->fetch(PDO::FETCH_ASSOC)['id'] == $i); + }); + } +}); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_pdo_sqlite/debugdumpparams_001.phpt b/tests/swoole_pdo_sqlite/debugdumpparams_001.phpt new file mode 100644 index 0000000000..fef462db10 --- /dev/null +++ b/tests/swoole_pdo_sqlite/debugdumpparams_001.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_pdo_sqlite:debugDumpParams() with bound params +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $x= $db->prepare('select :a, :b, ?'); + $x->bindValue(':a', 1, PDO::PARAM_INT); + $x->bindValue(':b', 'foo'); + $x->bindValue(3, 1313); + var_dump($x->debugDumpParams()); +}); +?> +--EXPECT-- +SQL: [16] select :a, :b, ? +Params: 3 +Key: Name: [2] :a +paramno=-1 +name=[2] ":a" +is_param=1 +param_type=1 +Key: Name: [2] :b +paramno=-1 +name=[2] ":b" +is_param=1 +param_type=2 +Key: Position #2: +paramno=2 +name=[0] "" +is_param=1 +param_type=2 +NULL diff --git a/tests/swoole_pdo_sqlite/gh9032.phpt b/tests/swoole_pdo_sqlite/gh9032.phpt new file mode 100644 index 0000000000..2186c40028 --- /dev/null +++ b/tests/swoole_pdo_sqlite/gh9032.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_pdo_sqlite: SQLite3 authorizer crashes on NULL values +--SKIPIF-- + + +--INI-- +open_basedir=. +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); + + $db->exec('attach database \':memory:\' AS "db1"'); + var_dump($db->exec('create table db1.r (id int)')); + + try { + $st = $db->prepare('attach database :a AS "db2"'); + $st->execute([':a' => ':memory:']); + var_dump($db->exec('create table db2.r (id int)')); + } catch (PDOException $ex) { + echo $ex->getMessage(), PHP_EOL; + } +}); +?> +--EXPECT-- +int(0) +int(0) diff --git a/tests/swoole_pdo_sqlite/open_basedir.phpt b/tests/swoole_pdo_sqlite/open_basedir.phpt new file mode 100644 index 0000000000..225256c5ef --- /dev/null +++ b/tests/swoole_pdo_sqlite/open_basedir.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_pdo_sqlite: PDO SQLite open_basedir check +--SKIPIF-- + +--INI-- +open_basedir=. +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function () { + chdir(__DIR__); + + try { + $db = new PDO('sqlite:../not_in_open_basedir.sqlite'); + } catch (Exception $e) { + echo $e->getMessage() . "\n"; + } + try { + $db = new PDO('sqlite:file:../not_in_open_basedir.sqlite'); + } catch (Exception $e) { + echo $e->getMessage() . "\n"; + } + try { + $db = new PDO('sqlite:file:../not_in_open_basedir.sqlite?mode=ro'); + } catch (Exception $e) { + echo $e->getMessage() . "\n"; + } +}); +?> +--EXPECT-- +open_basedir prohibits opening ../not_in_open_basedir.sqlite +open_basedir prohibits opening file:../not_in_open_basedir.sqlite +open_basedir prohibits opening file:../not_in_open_basedir.sqlite?mode=ro diff --git a/tests/swoole_pdo_sqlite/pdo_035.phpt b/tests/swoole_pdo_sqlite/pdo_035.phpt new file mode 100644 index 0000000000..c91f6fbec9 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_035.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_pdo_sqlite: PDORow + get_parent_class() +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->exec('CREATE TABLE test (id int)'); + $db->exec('INSERT INTO test VALUES (23)'); + + $stmt = $db->prepare('SELECT id FROM test'); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_LAZY); + + echo get_class($result), "\n"; + var_dump(get_parent_class($result)); + + try { + $result->foo = 1; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + $result[0] = 1; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + unset($result->foo); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + try { + unset($result[0]); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +}); +?> +--EXPECT-- +PDORow +bool(false) +Cannot write to PDORow property +Cannot write to PDORow offset +Cannot unset PDORow property +Cannot unset PDORow offset diff --git a/tests/swoole_pdo_sqlite/pdo_fetch_func_001.phpt b/tests/swoole_pdo_sqlite/pdo_fetch_func_001.phpt new file mode 100644 index 0000000000..6b8fbbb532 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_fetch_func_001.phpt @@ -0,0 +1,154 @@ +--TEST-- +swoole_pdo_sqlite:FETCH_FUNC +--SKIPIF-- += 80200) { + require __DIR__ . '/../include/skipif.inc'; + skip('php version 8.1 or lower'); +} +require __DIR__ . '/../include/bootstrap.php'; +require __DIR__ . '/pdo_sqlite.inc'; +PdoSqliteTest::skip(); +?> +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $db->exec('CREATE TABLE testing (id INTEGER , name VARCHAR)'); + $db->exec('INSERT INTO testing VALUES(1, "php")'); + $db->exec('INSERT INTO testing VALUES(2, "")'); + + $st = $db->query('SELECT * FROM testing'); + $st->fetchAll(PDO::FETCH_FUNC, function($x, $y) use ($st) { var_dump($st); print "data: $x, $y\n"; }); + + $st = $db->query('SELECT name FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 'strtoupper')); + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + class foo { + public function method($x) { + return "--- $x ---"; + } + } + class bar extends foo { + public function __construct($db) { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array($this, 'parent::method'))); + } + + static public function test($x, $y) { + return $x .'---'. $y; + } + + private function test2($x, $y) { + return $x; + } + + public function test3($x, $y) { + return $x .'==='. $y; + } + } + + new bar($db); + + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test'))); + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } +}); +?> +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(21) "SELECT * FROM testing" +} +data: 1, php +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(21) "SELECT * FROM testing" +} +data: 2, +array(2) { + [0]=> + string(3) "PHP" + [1]=> + string(0) "" +} +function "nothing" not found or invalid function name +function "" not found or invalid function name +PDOStatement::fetchAll(): Argument #2 must be a callable, null given +no array or string given +cannot access "self" when no class scope is active +array(2) { + [0]=> + string(9) "--- 1 ---" + [1]=> + string(9) "--- 2 ---" +} +array(2) { + [0]=> + string(7) "1---php" + [1]=> + string(4) "2---" +} +non-static method bar::test2() cannot be called statically +non-static method bar::test3() cannot be called statically +class bar does not have a method "inexistent" diff --git a/tests/swoole_pdo_sqlite/pdo_fetch_func_001_1.phpt b/tests/swoole_pdo_sqlite/pdo_fetch_func_001_1.phpt new file mode 100644 index 0000000000..a01364b6e9 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_fetch_func_001_1.phpt @@ -0,0 +1,156 @@ +--TEST-- +swoole_pdo_sqlite:FETCH_FUNC +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $db->exec('CREATE TABLE testing (id INTEGER , name VARCHAR)'); + $db->exec('INSERT INTO testing VALUES(1, "php")'); + $db->exec('INSERT INTO testing VALUES(2, "")'); + + $st = $db->query('SELECT * FROM testing'); + $st->fetchAll(PDO::FETCH_FUNC, function($x, $y) use ($st) { var_dump($st); print "data: $x, $y\n"; }); + + $st = $db->query('SELECT name FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 'strtoupper')); + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 'nothing')); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, '')); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, NULL)); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, 1)); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('self', 'foo'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + class foo { + public function method($x) { + return "--- $x ---"; + } + } + class bar extends foo { + public function __construct($db) { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array($this, 'parent::method'))); + } + + static public function test($x, $y) { + return $x .'---'. $y; + } + + private function test2($x, $y) { + return $x; + } + + public function test3($x, $y) { + return $x .'==='. $y; + } + } + + new bar($db); + + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test'))); + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test2'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'test3'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } + + try { + $st = $db->query('SELECT * FROM testing'); + var_dump($st->fetchAll(PDO::FETCH_FUNC, array('bar', 'inexistent'))); + } catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; + } +}); +?> +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(21) "SELECT * FROM testing" +} +data: 1, php +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(21) "SELECT * FROM testing" +} +data: 2, +array(2) { + [0]=> + string(3) "PHP" + [1]=> + string(0) "" +} +function "nothing" not found or invalid function name +function "" not found or invalid function name +PDOStatement::fetchAll(): Argument #2 must be a callable, null given +no array or string given +cannot access "self" when no class scope is active + +Deprecated: Callables of the form ["bar", "parent::method"] are deprecated in %s on line %d +array(2) { + [0]=> + string(9) "--- 1 ---" + [1]=> + string(9) "--- 2 ---" +} +array(2) { + [0]=> + string(7) "1---php" + [1]=> + string(4) "2---" +} +non-static method bar::test2() cannot be called statically +non-static method bar::test3() cannot be called statically +class bar does not have a method "inexistent" diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite.inc b/tests/swoole_pdo_sqlite/pdo_sqlite.inc new file mode 100644 index 0000000000..84c983dd69 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite.inc @@ -0,0 +1,18 @@ +getMessage()); + } + } + + public static function create(): PDO + { + return new PDO(SQLITE_DSN); + } +} diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_extendederror_attr.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_extendederror_attr.phpt new file mode 100644 index 0000000000..4931463243 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_extendederror_attr.phpt @@ -0,0 +1,61 @@ +--TEST-- +swoole_pdo_sqlite: Testing PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + echo "Creating new PDO" . PHP_EOL; + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )"); + + echo "Inserting first time which should succeed" . PHP_EOL; + $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); + $errorInfo = $db->errorInfo(); + echo sprintf("First Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; + + echo "Inserting second time which should fail" . PHP_EOL; + $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); + $errorInfo = $db->errorInfo(); + echo sprintf("Second Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; + + + echo "Creating new PDO with Extended Result Codes turned on" . PHP_EOL; + $const = PHP_VERSION_ID >= 80500 ? PDO\SQLITE::ATTR_EXTENDED_RESULT_CODES : PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES; + $db = new PDO('sqlite::memory:', '', '', [$const => TRUE]); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $db->exec("CREATE TABLE dog ( id INTEGER PRIMARY KEY, name TEXT, annoying INTEGER )"); + + echo "Inserting first time which should succeed" . PHP_EOL; + $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); + $errorInfo = $db->errorInfo(); + echo sprintf("First (Extended) Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; + + echo "Inserting second time which should fail" . PHP_EOL; + $result = $db->exec("INSERT INTO dog VALUES (1, 'Annoying Dog', 1)"); + $errorInfo = $db->errorInfo(); + echo sprintf("Second (Extended) Error Info: SQLSTATE Error Code: (%s), Driver Specific Error Code: (%s)", $errorInfo[0], $errorInfo[1]) . PHP_EOL; +}); +?> +--EXPECT-- +Creating new PDO +Inserting first time which should succeed +First Error Info: SQLSTATE Error Code: (00000), Driver Specific Error Code: () +Inserting second time which should fail +Second Error Info: SQLSTATE Error Code: (23000), Driver Specific Error Code: (19) +Creating new PDO with Extended Result Codes turned on +Inserting first time which should succeed +First (Extended) Error Info: SQLSTATE Error Code: (00000), Driver Specific Error Code: () +Inserting second time which should fail +Second (Extended) Error Info: SQLSTATE Error Code: (HY000), Driver Specific Error Code: (1555) diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_filename_uri.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_filename_uri.phpt new file mode 100644 index 0000000000..6f44f1e5b8 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_filename_uri.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_pdo_sqlite: Testing filename uri +--SKIPIF-- + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + // create with default read-write|create mode + $filename = "file:" . __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_filename_uri.db"; + + $db = new PDO('sqlite:' . $filename); + + var_dump($db->exec('CREATE TABLE test1 (id INT);')); + + // create with readonly mode + $filename = "file:" . __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_filename_uri.db?mode=ro"; + + $db = new PDO('sqlite:' . $filename); + + var_dump($db->exec('CREATE TABLE test2 (id INT);')); +}); +?> +--CLEAN-- + +--EXPECTF-- +int(0) + +Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in %s +Stack trace: +%s +%A + thrown in %s diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_get_attribute.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_get_attribute.phpt new file mode 100644 index 0000000000..27355a325f --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_get_attribute.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_pdo_sqlite: Testing getAttribute() +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $pdo = new PDO('sqlite::memory:'); + var_dump($pdo->getAttribute(PDO::ATTR_SERVER_VERSION)); + var_dump($pdo->getAttribute(PDO::ATTR_CLIENT_VERSION)); +}); +?> +--EXPECTF-- +string(%d) "%s" +string(%d) "%s" diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_lastinsertid.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_lastinsertid.phpt new file mode 100644 index 0000000000..bb996b3f5e --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_lastinsertid.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_pdo_sqlite: Testing lastInsertId() +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->query('CREATE TABLE IF NOT EXISTS foo (id INT AUTO INCREMENT, name TEXT)'); + $db->query('INSERT INTO foo VALUES (NULL, "PHP")'); + $db->query('INSERT INTO foo VALUES (NULL, "PHP6")'); + var_dump($db->query('SELECT * FROM foo')); + var_dump($db->errorInfo()); + var_dump($db->lastInsertId()); + + $db->query('DROP TABLE foo'); +}); +?> +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(17) "SELECT * FROM foo" +} +array(3) { + [0]=> + string(5) "00000" + [1]=> + NULL + [2]=> + NULL +} +string(1) "2" diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_open_flags.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_open_flags.phpt new file mode 100644 index 0000000000..15b7a9d8dd --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_open_flags.phpt @@ -0,0 +1,51 @@ +--TEST-- +swoole_pdo_sqlite: Testing open flags +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function () { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'pdo_sqlite_open_flags.db'; + + // Default open flag is read-write|create + $db = new PDO('sqlite:' . $filename, null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); + + var_dump($db->exec('CREATE TABLE test1 (id INT);')); + if (PHP_VERSION_ID >= 80500) { + $key = Pdo\Sqlite::ATTR_OPEN_FLAGS; + $value = Pdo\SQLITE::OPEN_READONLY; + } else { + $key = PDO::SQLITE_ATTR_OPEN_FLAGS; + $value = PDO::SQLITE_OPEN_READONLY; + } + $db = new PDO('sqlite:' . $filename, null, null, [$key => $value, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); + + var_dump($db->exec('CREATE TABLE test2 (id INT);')); + + $db->exec('drop table test1'); + $db->exec('drop table test2'); +}); +?> +--CLEAN-- + +--EXPECTF-- +int(0) + +Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database in %s +Stack trace: +%s +%A + thrown in %s diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_statement_getattribute.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_statement_getattribute.phpt new file mode 100644 index 0000000000..5821edd6a8 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_statement_getattribute.phpt @@ -0,0 +1,31 @@ +--TEST-- +swoole_pdo_sqlite:getAttribute() +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + + $st = $db->prepare('SELECT 1;'); + + $const = PHP_VERSION_ID >= 80500 ? PDO\SQLITE::ATTR_READONLY_STATEMENT: PDO::SQLITE_ATTR_READONLY_STATEMENT; + + var_dump($st->getAttribute($const)); + + $st = $db->prepare('CREATE TABLE test (a TEXT);'); + + var_dump($st->getAttribute($const)); +}); +?> +--EXPECT-- +bool(true) +bool(false) diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_tostring_exception.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_tostring_exception.phpt new file mode 100644 index 0000000000..a103b2e0e1 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_tostring_exception.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_pdo_sqlite: __toString() exception during PDO Sqlite parameter binding +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->exec('CREATE TABLE t(id int, v varchar(255))'); + + $stmt = $db->prepare('INSERT INTO t VALUES(:i, :v)'); + $param1 = 1234; + $stmt->bindValue('i', $param1); + $param2 = "foo"; + $stmt->bindParam('v', $param2); + + $param2 = new throws; + + try { + $stmt->execute(); + } catch (Exception $e) { + echo "Exception thrown ...\n"; + } + + try { + $stmt->execute(); + } catch (Exception $e) { + echo "Exception thrown ...\n"; + } + + $query = $db->query("SELECT * FROM t"); + while ($row = $query->fetch(PDO::FETCH_ASSOC)) { + print_r($row); + } +}); +?> +--EXPECT-- +Exception thrown ... +Exception thrown ... diff --git a/tests/swoole_pdo_sqlite/pdo_sqlite_transaction.phpt b/tests/swoole_pdo_sqlite/pdo_sqlite_transaction.phpt new file mode 100644 index 0000000000..7866dbfb47 --- /dev/null +++ b/tests/swoole_pdo_sqlite/pdo_sqlite_transaction.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_pdo_sqlite: Testing transaction +--SKIPIF-- + + +--FILE-- + SWOOLE_HOOK_PDO_SQLITE]); +run(function() { + $db = new PDO('sqlite::memory:'); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + + $db->beginTransaction(); + + $db->query('CREATE TABLE IF NOT EXISTS foobar (id INT AUTO INCREMENT, name TEXT)'); + $db->commit(); + + $db->beginTransaction(); + $db->query('INSERT INTO foobar VALUES (NULL, "PHP")'); + $db->query('INSERT INTO foobar VALUES (NULL, "PHP6")'); + $db->rollback(); + + $r = $db->query('SELECT COUNT(*) FROM foobar'); + var_dump($r->rowCount()); + + + $db->query('DROP TABLE foobar'); +}); +?> +--EXPECTF-- +int(0) + +Warning: PDO::query(): SQLSTATE[HY000]: General error: 6 database table is locked in %s on line %d diff --git a/tests/swoole_pgsql_coro/connect.phpt b/tests/swoole_pgsql_coro/connect.phpt deleted file mode 100644 index cb56c5d72a..0000000000 --- a/tests/swoole_pgsql_coro/connect.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -swoole_pgsql_coro: connect ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/connect_failed.phpt b/tests/swoole_pgsql_coro/connect_failed.phpt deleted file mode 100644 index 85944d81a4..0000000000 --- a/tests/swoole_pgsql_coro/connect_failed.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -swoole_pgsql_coro: connect failed ---SKIPIF-- - ---FILE-- -connect('')); - - $connected = $pgsql->connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/escape.phpt b/tests/swoole_pgsql_coro/escape.phpt deleted file mode 100644 index 59dea5223f..0000000000 --- a/tests/swoole_pgsql_coro/escape.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -swoole_pgsql_coro: escape ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); - - $result = $pgsql->escape("' or 1=1 & 2"); - Assert::true(false !== $result, (string) $pgsql->error); - Assert::eq($result, "'' or 1=1 & 2"); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/insert.phpt b/tests/swoole_pgsql_coro/insert.phpt deleted file mode 100644 index d82e898d4f..0000000000 --- a/tests/swoole_pgsql_coro/insert.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -swoole_pgsql_coro: insert ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); - - $stmt = $pgsql->query("INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('Shanghai', 88, 10, 0.75,'1993-11-27') RETURNING id"); - Assert::true(false !== $stmt, (string) $pgsql->error); - Assert::eq($stmt->numRows(), 1); - Assert::greaterThan($stmt->fetchAssoc()['id'], 1); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/no_field_name.phpt b/tests/swoole_pgsql_coro/no_field_name.phpt deleted file mode 100644 index 35c114c510..0000000000 --- a/tests/swoole_pgsql_coro/no_field_name.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -swoole_pgsql_coro: no field name ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); - - $stmt = $pgsql->query('SELECT 11, 22'); - Assert::true(false !== $stmt, (string) $pgsql->error); - - $arr = $stmt->fetchAll(); - Assert::isArray($arr); - Assert::eq($arr[0]['?column?'], 11); - Assert::eq($arr[0]['?column?1'], 22); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/not_connected.phpt b/tests/swoole_pgsql_coro/not_connected.phpt deleted file mode 100644 index f508c5c056..0000000000 --- a/tests/swoole_pgsql_coro/not_connected.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -swoole_pgsql_coro: not connected ---SKIPIF-- - ---FILE-- -escape('')); - Assert::false($pgsql->escapeLiteral('')); - Assert::false($pgsql->escapeIdentifier('')); - Assert::false($pgsql->query('')); - Assert::false($pgsql->prepare('')); - Assert::false($pgsql->metaData('')); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/prepare.phpt b/tests/swoole_pgsql_coro/prepare.phpt deleted file mode 100644 index 8968244cc7..0000000000 --- a/tests/swoole_pgsql_coro/prepare.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -swoole_pgsql_coro: prepare ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); - - $stmt = $pgsql->prepare("INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ($1, $2, $3, $4, $5) RETURNING id"); - Assert::true(false !== $stmt, (string) $pgsql->error); - $result = $stmt->execute(['Beijing', rand(1000, 99999), 10, 0.75, '1993-11-23']); - Assert::true(false !== $result, (string) $pgsql->error); -}); -?> ---EXPECT-- diff --git a/tests/swoole_pgsql_coro/query.phpt b/tests/swoole_pgsql_coro/query.phpt deleted file mode 100644 index 499f27bc6c..0000000000 --- a/tests/swoole_pgsql_coro/query.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -swoole_pgsql_coro: query ---SKIPIF-- - ---FILE-- -connect(PGSQL_CONNECTION_STRING); - Assert::true($connected, (string) $pgsql->error); - - $stmt = $pgsql->query('SELECT * FROM weather;'); - Assert::true(false !== $stmt, (string) $pgsql->error); - - $arr = $stmt->fetchAll(); - Assert::isArray($arr); - Assert::eq($arr[0]['city'], 'San Francisco'); -}); -?> ---EXPECT-- diff --git a/tests/swoole_process/swoole_process_close.phpt b/tests/swoole_process/close.phpt similarity index 100% rename from tests/swoole_process/swoole_process_close.phpt rename to tests/swoole_process/close.phpt diff --git a/tests/swoole_process/swoole_process_ctor.phpt b/tests/swoole_process/ctor.phpt similarity index 100% rename from tests/swoole_process/swoole_process_ctor.phpt rename to tests/swoole_process/ctor.phpt diff --git a/tests/swoole_process/swoole_process_deamon.phpt b/tests/swoole_process/deamon.phpt similarity index 100% rename from tests/swoole_process/swoole_process_deamon.phpt rename to tests/swoole_process/deamon.phpt diff --git a/tests/swoole_process/exception.phpt b/tests/swoole_process/exception.phpt index 90a5865f7f..dc483fc7dd 100644 --- a/tests/swoole_process/exception.phpt +++ b/tests/swoole_process/exception.phpt @@ -46,7 +46,7 @@ class Process6 extends AbstractProcess Fatal error: Uncaught Error: Call to undefined function AAAA() in %s:%d Stack trace: #0 %s(%d): Process6->run() -#1 [internal function]: AbstractProcess->{closure}(Object(Swoole\Process)) +#1 [internal function]: AbstractProcess->{closure%S}(Object(Swoole\Process)) #2 %s(%d): Swoole\Process->start() #3 %s(%d): AbstractProcess->start() #4 {main} diff --git a/tests/swoole_process/swoole_process_exec.phpt b/tests/swoole_process/exec.phpt similarity index 100% rename from tests/swoole_process/swoole_process_exec.phpt rename to tests/swoole_process/exec.phpt diff --git a/tests/swoole_process/swoole_process_exit.phpt b/tests/swoole_process/exit.phpt similarity index 100% rename from tests/swoole_process/swoole_process_exit.phpt rename to tests/swoole_process/exit.phpt diff --git a/tests/swoole_process/swoole_process_freeQueue.phpt b/tests/swoole_process/freeQueue.phpt similarity index 100% rename from tests/swoole_process/swoole_process_freeQueue.phpt rename to tests/swoole_process/freeQueue.phpt diff --git a/tests/swoole_process/getaffinity.phpt b/tests/swoole_process/getaffinity.phpt new file mode 100644 index 0000000000..e467289658 --- /dev/null +++ b/tests/swoole_process/getaffinity.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_process: getAffinity +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_process/ignore_sigpipe.phpt b/tests/swoole_process/ignore_sigpipe.phpt index d85112923f..493303f1dc 100644 --- a/tests/swoole_process/ignore_sigpipe.phpt +++ b/tests/swoole_process/ignore_sigpipe.phpt @@ -6,12 +6,14 @@ swoole_process: ignore SIGPIPE setWaitTimeout(5); $pm->parentFunc = function ($pid) use ($pm) { @@ -20,14 +22,14 @@ $pm->parentFunc = function ($pid) use ($pm) { }; $pm->childFunc = function () use ($pm) { - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $serv->set(array( - "worker_num" => 1, + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, 'hook_flags' => SWOOLE_HOOK_ALL, - 'log_level' => SWOOLE_LOG_WARNING, - )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) { - $cli = new Co\Client(SWOOLE_SOCK_TCP); + 'log_level' => SWOOLE_LOG_ERROR, + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { + $cli = new Client(SWOOLE_SOCK_TCP); if ($cli->connect('127.0.0.1', $pm->getFreePort(), 1) == false) { echo "ERROR\n"; return; @@ -45,9 +47,7 @@ $pm->childFunc = function () use ($pm) { $serv->on(Constant::EVENT_CONNECT, function (Server $serv, $fd, $rid) { $serv->close($fd); }); - $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) { - - }); + $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; diff --git a/tests/swoole_process/swoole_process_kill.phpt b/tests/swoole_process/kill.phpt similarity index 100% rename from tests/swoole_process/swoole_process_kill.phpt rename to tests/swoole_process/kill.phpt diff --git a/tests/swoole_process/name.phpt b/tests/swoole_process/name.phpt new file mode 100644 index 0000000000..10adf5834f --- /dev/null +++ b/tests/swoole_process/name.phpt @@ -0,0 +1,30 @@ +--TEST-- +swoole_process: name +--SKIPIF-- + +--FILE-- +name($name); + sleep(PHP_INT_MAX); +}); + +$pid = $proc->start(); +$count = (int)trim(shell_exec("ps aux|grep $name|grep -v grep|wc -l")); +Assert::same($count, 1); +\Swoole\Process::kill($pid, SIGKILL); + +\Swoole\Process::wait(true); +echo "SUCCESS"; +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_process/null_callback.phpt b/tests/swoole_process/null_callback.phpt index dfdc67f8ac..faa89a89ff 100644 --- a/tests/swoole_process/null_callback.phpt +++ b/tests/swoole_process/null_callback.phpt @@ -14,4 +14,4 @@ $process->start(); ?> --EXPECTF-- -Fatal error: Swoole\Process::start(): Illegal callback function of Swoole\Process in %s +Warning: Swoole\Process::start(): illegal callback function in %s diff --git a/tests/swoole_process/swoole_process_pop.phpt b/tests/swoole_process/pop.phpt similarity index 100% rename from tests/swoole_process/swoole_process_pop.phpt rename to tests/swoole_process/pop.phpt diff --git a/tests/swoole_process/priority_error.phpt b/tests/swoole_process/priority_error.phpt new file mode 100644 index 0000000000..a96ed68df6 --- /dev/null +++ b/tests/swoole_process/priority_error.phpt @@ -0,0 +1,30 @@ +--TEST-- +swoole_process: priority [2] +--SKIPIF-- + +--FILE-- +getPriority(-1000, posix_getpid()), false); +Assert::eq(swoole_last_error(), SOCKET_EINVAL); + +Assert::eq($process->setPriority(-1000, posix_getpid(), PRIORITY), false); +Assert::eq(swoole_last_error(), SOCKET_EINVAL); + +Assert::eq(@$process->getPriority(PRIO_USER, null), false); +Assert::eq(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); + +Assert::eq(@$process->setPriority(PRIO_USER, PRIORITY, null), false); +Assert::eq(swoole_last_error(), SWOOLE_ERROR_INVALID_PARAMS); + +?> +--EXPECT-- diff --git a/tests/swoole_process/process_id.phpt b/tests/swoole_process/process_id.phpt new file mode 100644 index 0000000000..bdff3e0871 --- /dev/null +++ b/tests/swoole_process/process_id.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_process: Github bug #5825 +--SKIPIF-- + +--FILE-- +id, $n); + }); + Assert::same($process->id, $n); + $process->start(); +} +?> +--EXPECT-- diff --git a/tests/swoole_process/swoole_process_push.phpt b/tests/swoole_process/push.phpt similarity index 100% rename from tests/swoole_process/swoole_process_push.phpt rename to tests/swoole_process/push.phpt diff --git a/tests/swoole_process/swoole_process_read.phpt b/tests/swoole_process/read.phpt similarity index 100% rename from tests/swoole_process/swoole_process_read.phpt rename to tests/swoole_process/read.phpt diff --git a/tests/swoole_process/swoole_process_redirect.phpt b/tests/swoole_process/redirect.phpt similarity index 100% rename from tests/swoole_process/swoole_process_redirect.phpt rename to tests/swoole_process/redirect.phpt diff --git a/tests/swoole_process/setaffinity.phpt b/tests/swoole_process/setaffinity.phpt new file mode 100644 index 0000000000..7493e1f315 --- /dev/null +++ b/tests/swoole_process/setaffinity.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_process: setAffinity +--SKIPIF-- + +--FILE-- + 1) { + $r = Swoole\Process::setaffinity([0, 1]); + Assert::assert($r); +} +echo "SUCCESS"; +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_process/signal_in_manager.phpt b/tests/swoole_process/signal_in_manager.phpt index be1182b470..74ffbaaffe 100644 --- a/tests/swoole_process/signal_in_manager.phpt +++ b/tests/swoole_process/signal_in_manager.phpt @@ -13,7 +13,7 @@ const PID_FILE = __DIR__ . '/manager.pid'; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { - usleep(1000); + usleep(100000); $manager_pid = file_get_contents(PID_FILE); Process::kill($manager_pid, SIGINT); $pm->wait(); @@ -22,10 +22,13 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $serv->set(['worker_num' => 1, 'log_file' => '/dev/null']); + $serv->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + ]); $serv->on('ManagerStart', function (Server $serv) use ($pm) { file_put_contents(PID_FILE, $serv->getManagerPid()); - Process::signal(SIGINT, function () use($pm) { + Process::signal(SIGINT, function () use ($pm) { echo "SIGINT triggered\n"; $pm->wakeup(); }); diff --git a/tests/swoole_process/signal_in_manager_2.phpt b/tests/swoole_process/signal_in_manager_2.phpt new file mode 100644 index 0000000000..a66a3c0769 --- /dev/null +++ b/tests/swoole_process/signal_in_manager_2.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_process: signal in manager with task worker +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + usleep(100000); + $manager_pid = file_get_contents(PID_FILE); + Process::kill($manager_pid, SIGINT); + $pm->wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, + 'task_worker_num' => 1, + 'log_file' => '/dev/null', + ]); + $serv->on('ManagerStart', function (Server $serv) use ($pm) { + file_put_contents(PID_FILE, $serv->getManagerPid()); + Process::signal(SIGINT, function () use ($pm) { + echo "SIGINT triggered\n"; + $pm->wakeup(); + }); + $pm->wakeup(); + }); + $serv->on('Task', function ($server, $taskId, $workerId, $data) { + }); + $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +unlink(PID_FILE); +?> +--EXPECT-- +SIGINT triggered diff --git a/tests/swoole_process/signal_in_manager_3.phpt b/tests/swoole_process/signal_in_manager_3.phpt new file mode 100644 index 0000000000..9b6d4d79f2 --- /dev/null +++ b/tests/swoole_process/signal_in_manager_3.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_process: signal in manager with task worker - 2 +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + usleep(100000); + $manager_pid = file_get_contents(PID_FILE); + Process::kill($manager_pid, SIGINT); + $pm->wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set([ + 'worker_num' => 2, + 'task_worker_num' => 1, + 'log_file' => '/dev/null', + ]); + $serv->on('ManagerStart', function (Server $serv) use ($pm) { + file_put_contents(PID_FILE, $serv->getManagerPid()); + Process::signal(SIGINT, function () use ($pm) { + echo "SIGINT triggered\n"; + $pm->wakeup(); + }); + $pm->wakeup(); + }); + $serv->on('workerStop', function ($server) { + echo "worker exit\n"; + }); + $serv->on('Task', function ($server, $taskId, $workerId, $data) { + }); + $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +unlink(PID_FILE); +?> +--EXPECT-- +SIGINT triggered +worker exit +worker exit +worker exit diff --git a/tests/swoole_process/swoole_process_start.phpt b/tests/swoole_process/start.phpt similarity index 100% rename from tests/swoole_process/swoole_process_start.phpt rename to tests/swoole_process/start.phpt diff --git a/tests/swoole_process/swoole_process_name.phpt b/tests/swoole_process/swoole_process_name.phpt deleted file mode 100644 index 1888ddd274..0000000000 --- a/tests/swoole_process/swoole_process_name.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -swoole_process: name ---SKIPIF-- - ---FILE-- -name($name); - sleep(PHP_INT_MAX); -}); - -$pid = $proc->start(); -$count = (int)trim(`ps aux|grep $name|grep -v grep|wc -l`); -Assert::same($count, 1); -\Swoole\Process::kill($pid, SIGKILL); - -\Swoole\Process::wait(true); -echo "SUCCESS"; -?> ---EXPECT-- -SUCCESS diff --git a/tests/swoole_process/swoole_process_setaffinity.phpt b/tests/swoole_process/swoole_process_setaffinity.phpt deleted file mode 100644 index f69f30486e..0000000000 --- a/tests/swoole_process/swoole_process_setaffinity.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -swoole_process: setaffinity ---SKIPIF-- - ---FILE-- - 1) { - $r = Swoole\Process::setaffinity([0, 1]); - Assert::assert($r); -} -echo "SUCCESS"; -?> ---EXPECT-- -SUCCESS diff --git a/tests/swoole_process/swoole_process_useQueue.phpt b/tests/swoole_process/useQueue.phpt similarity index 100% rename from tests/swoole_process/swoole_process_useQueue.phpt rename to tests/swoole_process/useQueue.phpt diff --git a/tests/swoole_process/swoole_process_wait.phpt b/tests/swoole_process/wait.phpt similarity index 100% rename from tests/swoole_process/swoole_process_wait.phpt rename to tests/swoole_process/wait.phpt diff --git a/tests/swoole_process/swoole_process_write.phpt b/tests/swoole_process/write.phpt similarity index 100% rename from tests/swoole_process/swoole_process_write.phpt rename to tests/swoole_process/write.phpt diff --git a/tests/swoole_process/write_in_worker.phpt b/tests/swoole_process/write_in_worker.phpt index b6bc226477..18ca6726fa 100644 --- a/tests/swoole_process/write_in_worker.phpt +++ b/tests/swoole_process/write_in_worker.phpt @@ -1,18 +1,24 @@ --TEST-- swoole_process: write in worker --SKIPIF-- - + --FILE-- parentFunc = function () use ($pm) { $pm->kill(); }; $pm->childFunc = function () use ($pm, $counter) { - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $process = new Swoole\Process(function (Swoole\Process $process) use ($serv, $counter) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $process = new Process(function (Process $process) use ($pm, $serv, $counter) { if ($counter->get() != 1) { $counter->set(1); echo "process start\n"; @@ -21,22 +27,23 @@ $pm->childFunc = function () use ($pm, $counter) { Assert::same(strlen($data), 8192); } echo "process end\n"; + $pm->wakeup(); } }); $serv->set([ - "worker_num" => 1, + 'worker_num' => 1, 'log_file' => '/dev/null', ]); - $serv->on("WorkerStart", function (Swoole\Server $serv) use ($process, $pm) { + $serv->on('WorkerStart', function (Server $serv) use ($process, $pm) { usleep(1); for ($i = 0; $i < 1024; $i++) { Assert::same($process->write(str_repeat('A', 8192)), 8192); } - switch_process(); + }); + $serv->on('WorkerStop', function (Server $serv) use ($process) { echo "worker end\n"; - $pm->wakeup(); }); - $serv->on("Receive", function () { }); + $serv->on('Receive', function () {}); $serv->addProcess($process); $serv->start(); }; diff --git a/tests/swoole_process_pool/create_websocket_server.phpt b/tests/swoole_process_pool/create_websocket_server.phpt index c41bef0e0d..140aefda44 100644 --- a/tests/swoole_process_pool/create_websocket_server.phpt +++ b/tests/swoole_process_pool/create_websocket_server.phpt @@ -40,5 +40,4 @@ $pool->start(); echo "DONE\n"; ?> --EXPECTF-- -[%s] INFO Server is shutdown now DONE diff --git a/tests/swoole_process_pool/detach.phpt b/tests/swoole_process_pool/detach.phpt index a7b92ef52b..62a13a4495 100644 --- a/tests/swoole_process_pool/detach.phpt +++ b/tests/swoole_process_pool/detach.phpt @@ -19,8 +19,8 @@ $pm->initFreePorts(); $pm->parentFunc = function ($pid) use ($pm, $atomic) { foreach (range(1, 2) as $i) { - $fp = stream_socket_client("tcp://127.0.0.1:".$pm->getFreePort(), $errno, $errstr) or die("error: $errstr\n"); - $msg = "HELLO-{$i}"; + $fp = stream_socket_client("tcp://127.0.0.1:" . $pm->getFreePort(), $errno, $errstr) or die("error: $errstr\n"); + $msg = "HELLO-{$i}"; fwrite($fp, pack('N', strlen($msg)) . $msg); } $pm->wait(); @@ -32,25 +32,25 @@ $pm->parentFunc = function ($pid) use ($pm, $atomic) { $pm->childFunc = function () use ($pm, $atomic) { $pool = new Pool(1, SWOOLE_IPC_SOCKET); - $pool->on('WorkerStart', function (Pool $pool, $workerId) use($pm, $atomic) { + $pool->on('WorkerStart', function (Pool $pool, $workerId) use ($pm, $atomic) { echo("[Worker #{$workerId}] WorkerStart\n"); - if ($atomic->get() == 0) { - $pm->wakeup(); - } + if ($atomic->get() == 0) { + $pm->wakeup(); + } }); - $pool->on('Message', function (Pool $pool, $msg) use($pm, $atomic) { + $pool->on('Message', function (Pool $pool, $msg) use ($pm, $atomic) { if ($atomic->get() == 0) { $atomic->add(); $pool->detach(); $n = N; - while($n--) { + while ($n--) { usleep(1000); $atomic->add(); } $pm->wakeup(); } else { - echo $msg.PHP_EOL; + echo $msg . PHP_EOL; } }); diff --git a/tests/swoole_process_pool/getprocess_3.phpt b/tests/swoole_process_pool/getprocess_3.phpt index daed3045e3..df32c1dac1 100644 --- a/tests/swoole_process_pool/getprocess_3.phpt +++ b/tests/swoole_process_pool/getprocess_3.phpt @@ -13,15 +13,16 @@ use Swoole\Process; const N = 70000; $pool = new Pool(2, SWOOLE_IPC_UNIXSOCK); +$pool->set(['max_wait_time' => 2]); -$pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { +$pool->on('workerStart', function (Pool $pool, int $workerId) { if ($workerId == 0) { - usleep(1000); + usleep(100_000); $process1 = $pool->getProcess(1); phpt_var_dump($process1); $pid1 = $process1->pid; Process::kill($process1->pid, SIGTERM); - usleep(10000); + usleep(100_000); $process2 = $pool->getProcess(1); phpt_var_dump($process2); $pid2 = $process2->pid; diff --git a/tests/swoole_process_pool/getprocess_4.phpt b/tests/swoole_process_pool/getprocess_4.phpt new file mode 100644 index 0000000000..920c913957 --- /dev/null +++ b/tests/swoole_process_pool/getprocess_4.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_process_pool: get process 4 [async] +--SKIPIF-- + +--FILE-- +set(['enable_coroutine' => true]); + +$pool->on('workerStart', function (Pool $pool, int $workerId) { + if ($workerId == 0) { + usleep(1000); + $process1 = $pool->getProcess(1); + phpt_var_dump($process1); + $pid1 = $process1->pid; + Process::kill($process1->pid, SIGTERM); + usleep(100000); + $process2 = $pool->getProcess(1); + phpt_var_dump($process2); + $pid2 = $process2->pid; + Assert::notEq($pid1, $pid2); + $pool->shutdown(); + } +}); + +$pool->on("message", function ($pool, $data) { + +}); + +$pool->start(); + +?> +--EXPECT-- diff --git a/tests/swoole_process_pool/getprocess_5.phpt b/tests/swoole_process_pool/getprocess_5.phpt new file mode 100644 index 0000000000..69bbb9fe8f --- /dev/null +++ b/tests/swoole_process_pool/getprocess_5.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_process_pool: getProcess [5] +--SKIPIF-- + +--FILE-- +on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) { + if ($workerId == 0) { + $process1 = $pool->getProcess(); + $process2 = $pool->getProcess(1); + $process2->write(str_repeat('A', N)); + Assert::same(@$pool->getProcess(2), false); + + if ($process1->read() == 'shutdown') { + $pool->shutdown(); + } + } +}); + +$pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) { + Assert::length($data, N); + $process1 = $pool->getProcess(0); + $process1->write("shutdown"); +}); + +$pool->start(); +?> +--EXPECT-- diff --git a/tests/swoole_process_pool/master_callback.phpt b/tests/swoole_process_pool/master_callback.phpt new file mode 100644 index 0000000000..0bfb29f95c --- /dev/null +++ b/tests/swoole_process_pool/master_callback.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_process_pool: master callback +--SKIPIF-- + +--FILE-- +on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { + echo "worker start\n"; + Assert::true($pool->workerRunning); + Assert::eq($pool->workerId, 0); + Assert::eq($pool->workerPid, posix_getpid()); + pcntl_signal(SIGTERM, function (){ + + }); + $pool->shutdown(); + sleep(20); + echo "worker exit\n"; +}); + +$pool->on('workerStop', function (Swoole\Process\Pool $pool, int $workerId) { + Assert::false($pool->workerRunning); + echo "worker stop\n"; +}); + +$pool->on('start', function (Swoole\Process\Pool $pool) { + Assert::true($pool->running); + echo "start\n"; +}); + +$pool->on('shutdown', function (Swoole\Process\Pool $pool) { + Assert::false($pool->running); + echo "shutdown\n"; +}); + +$pool->start(); +?> +--EXPECT-- +start +worker start +shutdown +worker exit +worker stop diff --git a/tests/swoole_process_pool/max_wait_time.phpt b/tests/swoole_process_pool/max_wait_time.phpt new file mode 100644 index 0000000000..b437dddc55 --- /dev/null +++ b/tests/swoole_process_pool/max_wait_time.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_process_pool: max wait time +--SKIPIF-- + +--FILE-- +set([ + Constant::OPTION_ENABLE_COROUTINE => true, + Constant::OPTION_MAX_WAIT_TIME => 1, + ]); + + $pool->on('workerStart', function (Pool $pool, int $workerId) use ($atomic): void { + echo "workerStart: $workerId" . PHP_EOL; + $atomic->wait(-1); + }); + + $pool->on('start', function () use ($pool): void { + Timer::after(500, function () use ($pool): void { + $pool->shutdown(); + }); + echo 'start' . PHP_EOL; + }); + + $pool->on('shutdown', function () use ($atomic): void { + echo 'shutdown' . PHP_EOL; + }); + + $pool->start(); +})(); +?> +--EXPECTF-- +start +workerStart: %d +workerStart: %d +workerStart: %d +workerStart: %d +shutdown diff --git a/tests/swoole_process_pool/message_async.phpt b/tests/swoole_process_pool/message_async.phpt new file mode 100644 index 0000000000..2b3caecacc --- /dev/null +++ b/tests/swoole_process_pool/message_async.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_process_pool: message async [disable message bus] +--SKIPIF-- + +--FILE-- +set([ + 'enable_coroutine' => true, +]); + +$pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { + if ($workerId == 0) { + foreach ($in as $item) { + Assert::true($pool->sendMessage($item, 1)); + System::sleep(0.002); + } + } +}); + +$pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { + $out[] = $data; + if (count($out) == N) { + Assert::eq($in, $out); + echo "DONE\n"; + $pool->shutdown(); + } +}); + +$pool->start(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_process_pool/message_bus.phpt b/tests/swoole_process_pool/message_bus.phpt new file mode 100644 index 0000000000..1adbccd717 --- /dev/null +++ b/tests/swoole_process_pool/message_bus.phpt @@ -0,0 +1,47 @@ +--TEST-- +swoole_process_pool: message bus +--SKIPIF-- + +--FILE-- +set([ + 'enable_coroutine' => true, + 'enable_message_bus' => true, +]); + +$pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { + if ($workerId == 0) { + foreach ($in as $item) { + Assert::true($pool->sendMessage($item, 1)); + Co::sleep(0.002); + } + } +}); + +$pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { + $out[] = $data; + if (count($out) == N) { + Assert::eq($in, $out); + echo "DONE\n"; + $pool->shutdown(); + } +}); + +$pool->start(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_process_pool/message_bus_sync.phpt b/tests/swoole_process_pool/message_bus_sync.phpt new file mode 100644 index 0000000000..2d1369125c --- /dev/null +++ b/tests/swoole_process_pool/message_bus_sync.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_process_pool: message bus [sync] +--SKIPIF-- + +--FILE-- +set([ + 'enable_message_bus' => true, +]); + +$pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) use ($in) { + if ($workerId == 0) { + foreach ($in as $item) { + Assert::true($pool->sendMessage($item, 1)); + usleep(2000); + } + } +}); + +$pool->on(Constant::EVENT_MESSAGE, function ($pool, $data) use (&$out, $in) { + $out[] = $data; + if (count($out) == N) { + Assert::eq($in, $out); + echo "DONE\n"; + $pool->shutdown(); + } +}); + +$pool->start(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_process_pool/msgqueue.phpt b/tests/swoole_process_pool/msgqueue.phpt index 2f58e9416f..fe1dd64fba 100644 --- a/tests/swoole_process_pool/msgqueue.phpt +++ b/tests/swoole_process_pool/msgqueue.phpt @@ -10,20 +10,23 @@ if (function_exists('msg_get_queue') == false) { parentFunc = function ($pid) use ($pm) { +$pm->parentFunc = function ($pid) use ($pm, $atomic) { $seg = msg_get_queue(MSGQ_KEY); - foreach (range(1, 100) as $i) { + foreach (range(1, N) as $i) { $data = json_encode(['data' => base64_encode(random_bytes(1024)), 'id' => uniqid(), 'index' => $i,]); msg_send($seg, $i, $data, false); } - $pm->kill(); }; -$pm->childFunc = function () use ($pm) { +$pm->childFunc = function () use ($pm, $atomic) { $pool = new Swoole\Process\Pool(1, SWOOLE_IPC_MSGQUEUE, MSGQ_KEY); $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pm) { @@ -31,11 +34,16 @@ $pm->childFunc = function () use ($pm) { $pm->wakeup(); }); - $pool->on("message", function (Swoole\Process\Pool $pool, string $message) { + $pool->on("message", function (Swoole\Process\Pool $pool, string $message) use ($atomic) { $data = json_decode($message, true); Assert::assert($data); Assert::assert(is_array($data)); Assert::same(strlen(base64_decode($data['data'])), 1024); + $atomic->add(1); + if ($atomic->get() == 100) { + $pool->shutdown(); + echo "DONE\n"; + } }); $pool->on('workerStop', function (Swoole\Process\Pool $pool, int $workerId) { @@ -51,4 +59,5 @@ $pm->run(); ?> --EXPECT-- worker start +DONE worker stop diff --git a/tests/swoole_process_pool/msgqueue_2.phpt b/tests/swoole_process_pool/msgqueue_2.phpt new file mode 100644 index 0000000000..7df902db48 --- /dev/null +++ b/tests/swoole_process_pool/msgqueue_2.phpt @@ -0,0 +1,40 @@ +--TEST-- +swoole_process_pool: sysv msgqueue [2] +--SKIPIF-- + +--FILE-- +on('workerStart', function (Pool $pool, int $workerId) { + if ($workerId == 0) { + echo "worker start\n"; + Assert::true($pool->getProcess()->push('hello world' . PHP_EOL)); + } else { + echo $pool->getProcess()->pop(); + $pool->shutdown(); + } +}); + +$pool->on('workerStop', function (Pool $pool, int $workerId) { + if ($workerId == 1) { + echo "worker stop\n"; + } +}); + +$pool->start(); +?> +--EXPECT-- +worker start +hello world +worker stop diff --git a/tests/swoole_process_pool/reload.phpt b/tests/swoole_process_pool/reload.phpt index c297ef49c5..2a68f28e05 100644 --- a/tests/swoole_process_pool/reload.phpt +++ b/tests/swoole_process_pool/reload.phpt @@ -1,5 +1,5 @@ --TEST-- -swoole_process_pool: sysv msgqueue +swoole_process_pool: reload --SKIPIF-- parentFunc = function ($pid) use ($pm) { - for ($i = 0; $i < 5; $i++) - { + for ($i = 0; $i < 5; $i++) { Swoole\Process::kill($pid, SIGUSR1); usleep(10000); //判断进程是否存在 @@ -25,17 +24,15 @@ $pm->parentFunc = function ($pid) use ($pm) { }; $pm->childFunc = function () use ($pm) { - swoole_set_process_name(PROC_NAME); + cli_set_process_title(PROC_NAME); Co::set(['log_level' => SWOOLE_LOG_ERROR]); $pool = new Swoole\Process\Pool(2); - $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pm) - { + $pool->on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) use ($pm) { $pm->wakeup(); - Swoole\Timer::tick(1000, function () use ($workerId) - { + Swoole\Timer::tick(1000, function () use ($workerId) { echo "sleep [$workerId] \n"; }); Swoole\Process::signal(SIGTERM, function () { diff --git a/tests/swoole_process_pool/reuse_port.phpt b/tests/swoole_process_pool/reuse_port.phpt index 07648c9a54..61726f1f6a 100644 --- a/tests/swoole_process_pool/reuse_port.phpt +++ b/tests/swoole_process_pool/reuse_port.phpt @@ -7,18 +7,22 @@ swoole_process_pool: co\socket reuse port parentFunc = function ($pid) use ($pm) { - $sch = new Swoole\Coroutine\Scheduler(); + $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { - $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR [1]\n"; return; @@ -40,18 +44,16 @@ $pm->parentFunc = function ($pid) use ($pm) { $pids[$result['wid']] = 1; }); $sch->start(); - Assert::eq(count($pids), 2); + Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { - - $atomic = new \Swoole\Atomic(); - $pool = new Swoole\Process\Pool(2); + $atomic = new Atomic(); + $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { - $socket = new Socket(AF_INET, SOCK_STREAM, 0); $socket->setOption(SOL_SOCKET, SO_REUSEPORT, true); $socket->bind('127.0.0.1', $pm->getFreePort()); @@ -71,7 +73,7 @@ $pm->childFunc = function () use ($pm) { } continue; } - Co::sleep(0.005); + co::sleep(0.005); $data = $client->recv(); if (empty($data)) { $client->close(); diff --git a/tests/swoole_process_pool/socket_coro.phpt b/tests/swoole_process_pool/socket_coro.phpt index 19bb969b37..14b8620771 100644 --- a/tests/swoole_process_pool/socket_coro.phpt +++ b/tests/swoole_process_pool/socket_coro.phpt @@ -7,19 +7,22 @@ swoole_process_pool: co\socket parentFunc = function ($pid) use ($pm) { - $s = microtime(true); - $sch = new Swoole\Coroutine\Scheduler(); + $sch = new Scheduler(); $sch->parallel(2, function () use ($pm) { - $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR\n"; return; @@ -35,7 +38,7 @@ $pm->parentFunc = function ($pid) use ($pm) { }); $sch->start(); echo "DONE\n"; - Assert::lessThan(microtime(true) - $s, 0.15); + Assert::lessThan(microtime(true) - $s, 0.25); $pm->kill(); }; @@ -43,9 +46,9 @@ $pm->childFunc = function () use ($pm) { $socket = new Socket(AF_INET, SOCK_STREAM, 0); $socket->bind('127.0.0.1', $pm->getFreePort()); - $atomic = new \Swoole\Atomic(); + $atomic = new Atomic(); - $pool = new Swoole\Process\Pool(2); + $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($socket, $pm, $atomic) { $socket->listen(128); @@ -69,7 +72,7 @@ $pm->childFunc = function () use ($pm) { $client->close(); break; } - $client->send("Server[$id]: $data"); + $client->send("Server[{$id}]: {$data}"); } echo "worker stop\n"; }); diff --git a/tests/swoole_process_pool/worker_exit_1.phpt b/tests/swoole_process_pool/worker_exit_1.phpt new file mode 100644 index 0000000000..2b9462eee2 --- /dev/null +++ b/tests/swoole_process_pool/worker_exit_1.phpt @@ -0,0 +1,56 @@ +--TEST-- +swoole_process_pool: worker exit +--SKIPIF-- + +--FILE-- +on('workerStart', function (Swoole\Process\Pool $pool, int $workerId) { + echo "worker start\n"; + Assert::eq($pool->workerId, $workerId); + + $count = 0; + while ($GLOBALS['running']) { + Co::sleep(0.03); + echo "sleep\n"; + if (++$count === 3) { + $pool->shutdown(); + } + } +}); + +$pool->on('workerStop', function ($pool, $data) { + echo "worker stop\n"; +}); + +$pool->on('workerExit', function ($pool, $data) { + $GLOBALS['count']++; + if ($GLOBALS['count'] == 3) { + $GLOBALS['running'] = false; + } + echo ('worker exit') . PHP_EOL; +}); + +$pool->start(); +?> +--EXPECT-- +worker start +sleep +sleep +sleep +worker exit +sleep +worker exit +sleep +worker exit +worker stop diff --git a/tests/swoole_redis_coro/auth.phpt b/tests/swoole_redis_coro/auth.phpt deleted file mode 100644 index c872c10002..0000000000 --- a/tests/swoole_redis_coro/auth.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -swoole_redis_coro: redis auth ---SKIPIF-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); -if (!$redis->auth(REDIS_SERVER_PWD)) { - skip('no auth'); -} -?> ---FILE-- -getAuth()); - Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - Assert::false($redis->getAuth()); - Assert::assert(!$redis->auth(get_safe_random())); - Assert::same($redis->errCode, SOCKET_EINVAL); - Assert::false($redis->getAuth()); - Assert::assert($redis->auth(REDIS_SERVER_PWD)); - Assert::same($redis->getAuth(), REDIS_SERVER_PWD); - // auth by connect - $redis = new Swoole\Coroutine\Redis(['password' => REDIS_SERVER_PWD]); - Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - Assert::assert($redis->set('foo', $random = get_safe_random())); - Assert::same($redis->get('foo'), $random); - // auth failed when connect - $redis = new Swoole\Coroutine\Redis(['password' => get_safe_random()]); - Assert::assert(!$redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/auto_reconnect.phpt b/tests/swoole_redis_coro/auto_reconnect.phpt deleted file mode 100644 index 7806f315da..0000000000 --- a/tests/swoole_redis_coro/auto_reconnect.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_redis_coro: redis reconnect ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - $ret = $redis->close(); - Assert::assert($ret); - $ret = $redis->set('foo', 'bar'); - Assert::assert($ret); - $ret = $redis->get('foo'); - Assert::same($ret, 'bar'); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/auto_reconnect_ex.phpt b/tests/swoole_redis_coro/auto_reconnect_ex.phpt deleted file mode 100644 index fa4dabb587..0000000000 --- a/tests/swoole_redis_coro/auto_reconnect_ex.phpt +++ /dev/null @@ -1,68 +0,0 @@ ---TEST-- -swoole_redis_coro: auto reconnect after server side close the connection ---SKIPIF-- - ---FILE-- -parentFunc = function () use ($pm) { - Co\run(function () use ($pm) { - $redis = new Swoole\Coroutine\Redis; - $ret = $redis->connect('127.0.0.1', $pm->getFreePort()); - Assert::true($ret); - for ($n = MAX_REQUESTS; $n--;) { - $ret = $redis->set('random_val', $random = get_safe_random(128)); - Assert::true($ret, "code: {$redis->errCode}, msg={$redis->errMsg}"); - $ret = $redis->get('random_val'); - Assert::true($ret && $ret === $random, "code: {$redis->errCode}, msg={$redis->errMsg}"); - Co::sleep(0.001); - } - $redis->setOptions(['reconnect' => false]); - for ($n = MAX_REQUESTS; $n--;) { - $ret = $redis->set('random_val', $random = get_safe_random(128)); - Assert::true($n === MAX_REQUESTS ? $ret : !$ret); - $ret = $redis->get('random_val'); - Assert::true($n === MAX_REQUESTS ? ($ret && $ret === $random) : !$ret); - Co::sleep(0.001); - } - }); - $pm->kill(); - echo "DONE\n"; -}; -$pm->childFunc = function () use ($pm) { - $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->data = []; - $server->on('WorkerStart', function ($server) use ($pm) { - $pm->wakeup(); - }); - $server->setHandler('GET', function ($fd, $data) use ($server) { - if (count($data) == 0) { - return Server::format(Server::ERROR, "ERR wrong number of arguments for 'GET' command"); - } - $key = $data[0]; - if (empty($server->data[$key])) { - $server->send($fd, Server::format(Server::NIL)); - } else { - $server->send($fd, Server::format(Server::STRING, $server->data[$key])); - } - $server->close($fd); - }); - $server->setHandler('SET', function ($fd, $data) use ($server) { - if (count($data) < 2) { - $server->send($fd, Server::format(Server::ERROR, "ERR wrong number of arguments for 'SET' command")); - } - $key = $data[0]; - $server->data[$key] = $data[1]; - $server->send($fd, Server::format(Server::STATUS, 'OK')); - }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/basic.phpt b/tests/swoole_redis_coro/basic.phpt deleted file mode 100644 index 8afcfcaaab..0000000000 --- a/tests/swoole_redis_coro/basic.phpt +++ /dev/null @@ -1,63 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) -{ - go(function () use ($pm) { - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - $pm->kill(); - }); -}; - -$pm->childFunc = function () use ($pm) -{ - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $http->set(array( - 'log_file' => '/dev/null' - )); - $http->on("WorkerStart", function (Swoole\Server $serv) - { - /** - * @var $pm ProcessManager - */ - global $pm; - $pm->wakeup(); - }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) - { - $redis = new Swoole\Coroutine\Redis(); - $res = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - if (!$res) - { - fail: - $response->end("ERROR\n"); - return; - } - - $ret = $redis->set('key', 'value'); - if (!$ret) { - goto fail; - } - $ret = $redis->get('key'); - if (!$ret) { - goto fail; - } - Assert::same($ret, "value"); - if (strlen($ret) > 0) { - $response->end("OK\n"); - } - }); - $http->start(); -}; - -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -OK diff --git a/tests/swoole_redis_coro/bug_lock.phpt b/tests/swoole_redis_coro/bug_lock.phpt deleted file mode 100644 index 1eb2c6e5c3..0000000000 --- a/tests/swoole_redis_coro/bug_lock.phpt +++ /dev/null @@ -1,35 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client ---SKIPIF-- - ---FILE-- -lock('SWOOLE_TEST_LOCK')) { - echo "ERROR\n"; - $redis_lock->unlock('SWOOLE_TEST_LOCK'); - } else { - echo "FREE\n"; - } - } - SQLPool::release(); -}); - -Swoole\Event::wait(); -?> ---EXPECT-- -LOCK -FREE -LOCK -ERROR -LOCK -FREE diff --git a/tests/swoole_redis_coro/compatibility_mode/hExists.phpt b/tests/swoole_redis_coro/compatibility_mode/hExists.phpt deleted file mode 100644 index d93df48544..0000000000 --- a/tests/swoole_redis_coro/compatibility_mode/hExists.phpt +++ /dev/null @@ -1,23 +0,0 @@ ---TEST-- -swoole_redis_coro/compatibility_mode: hExists ---SKIPIF-- - ---FILE-- -setOptions(['compatibility_mode' => true]); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $redis->delete(KEY); - $redis->hSet(KEY, 'field', 'val1'); - - Assert::true($redis->hExists(KEY, 'field') === true); - Assert::true($redis->hExists(KEY, 'field_not_found') === false); -}); -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/connect_timeout.phpt b/tests/swoole_redis_coro/connect_timeout.phpt deleted file mode 100644 index cf49ac852c..0000000000 --- a/tests/swoole_redis_coro/connect_timeout.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client connect timeout ---SKIPIF-- - ---FILE-- - $timeout]); - $s = microtime(true); - $ret = $redis->connect('192.0.0.1', 9000); - Assert::assert(!$ret); - Assert::assert($redis->errCode === SOCKET_ETIMEDOUT); - time_approximate($timeout, microtime(true) - $s); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/connect_to_wrong.phpt b/tests/swoole_redis_coro/connect_to_wrong.phpt deleted file mode 100644 index c58384b409..0000000000 --- a/tests/swoole_redis_coro/connect_to_wrong.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client set options ---SKIPIF-- - ---FILE-- - -1]); -go(function () { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect(MYSQL_SERVER_HOST, MYSQL_SERVER_PORT); - Assert::assert(!$redis->set('foo', 'bar')); - Assert::same($redis->errType, SWOOLE_REDIS_ERR_PROTOCOL); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/connect_twice-2.phpt b/tests/swoole_redis_coro/connect_twice-2.phpt deleted file mode 100644 index 126862278f..0000000000 --- a/tests/swoole_redis_coro/connect_twice-2.phpt +++ /dev/null @@ -1,64 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) -{ - go(function () use ($pm) { - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - $pm->kill(); - }); -}; - -$pm->childFunc = function () use ($pm) -{ - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $http->set(array( - 'log_file' => '/dev/null' - )); - $http->on("WorkerStart", function (Swoole\Server $serv) - { - /** - * @var $pm ProcessManager - */ - global $pm; - $pm->wakeup(); - }); - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) - { - $redis = new Swoole\Coroutine\Redis(); - $res = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $res2 = @$redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::true($res2); - if (!$res) - { - fail: - $response->end("ERROR\n"); - return; - } - $ret = $redis->set('key', 'value'); - if (!$ret) { - goto fail; - } - $ret = $redis->get('key'); - if (!$ret) { - goto fail; - } - Assert::same($ret, "value"); - if (strlen($ret) > 0) { - $response->end("OK\n"); - } - }); - $http->start(); -}; - -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -OK diff --git a/tests/swoole_redis_coro/connect_twice.phpt b/tests/swoole_redis_coro/connect_twice.phpt deleted file mode 100644 index 6bc35009d8..0000000000 --- a/tests/swoole_redis_coro/connect_twice.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -swoole_redis_coro: connect twice ---SKIPIF-- - ---FILE-- - SWOOLE_LOG_TRACE, 'trace_flags' => SWOOLE_TRACE_ALL]); - -go(function () { - $redis = new Swoole\Coroutine\Redis(); - echo "connect [1]\n"; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::true($redis->connected); - echo "close [1]\n"; - $redis->close(); - Assert::false($redis->connected); - echo "connect [2]\n"; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::true($redis->connected); - echo "close [2]\n"; - $redis->close(); - Assert::false($redis->connected); -}); - -Swoole\Event::wait(); -?> ---EXPECT-- -connect [1] -close [1] -connect [2] -close [2] diff --git a/tests/swoole_redis_coro/curd.phpt b/tests/swoole_redis_coro/curd.phpt deleted file mode 100644 index fb3d0e6208..0000000000 --- a/tests/swoole_redis_coro/curd.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -swoole_redis_coro: use unixsocket ---SKIPIF-- - ---FILE-- - 0.5]); - Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - for ($c = MAX_CONCURRENCY_MID; $c--;) { - for ($n = MAX_REQUESTS; $n--;) { - $key = md5(get_safe_random(mt_rand(1, 128))); - $value = md5(get_safe_random(mt_rand(1, 128))); - Assert::assert($redis->set($key, $value)); - Assert::same($redis->get($key), $value); - Assert::assert($redis->delete($key)); - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/defer.phpt b/tests/swoole_redis_coro/defer.phpt deleted file mode 100644 index db4d238b1e..0000000000 --- a/tests/swoole_redis_coro/defer.phpt +++ /dev/null @@ -1,45 +0,0 @@ ---TEST-- -swoole_redis_coro: defer ---SKIPIF-- - ---FILE-- - SWOOLE_LOG_TRACE, 'trace_flags' => SWOOLE_TRACE_ALL]); - -go(function () { - $redis = new Swoole\Coroutine\Redis(); - echo "CONNECT [1]\n"; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $redis->setDefer(); - echo "SET [1]\n"; - $redis->set('key1', 'value'); - - $redis2 = new Swoole\Coroutine\Redis(); - echo "CONNECT [2]\n"; - $redis2->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $redis2->setDefer(); - echo "GET [2]\n"; - $redis2->get('key1'); - - echo "RECV [1]\n"; - $result1 = $redis->recv(); - var_dump($result1); - - echo "RECV [2]\n"; - $result2 = $redis2->recv(); - var_dump($result2); -}); - -Swoole\Event::wait(); -?> ---EXPECT-- -CONNECT [1] -SET [1] -CONNECT [2] -GET [2] -RECV [1] -bool(true) -RECV [2] -string(5) "value" diff --git a/tests/swoole_redis_coro/different_connect.phpt b/tests/swoole_redis_coro/different_connect.phpt deleted file mode 100644 index 51bcb46567..0000000000 --- a/tests/swoole_redis_coro/different_connect.phpt +++ /dev/null @@ -1,47 +0,0 @@ ---TEST-- -swoole_redis_coro: connect the same target and different ---SKIPIF-- - ---FILE-- - -1]); -function test(string $host, int $port = 0) -{ - $redis = new Swoole\Coroutine\Redis(); - Assert::same($redis->sock, -1); - - $real_connect_time = microtime(true); - $ret = $redis->connect($host, $port); - $real_connect_time = microtime(true) - $real_connect_time; - - Assert::assert($ret); - Assert::assert(($fd = $redis->sock) > 0); - - $fake_connect_time = 0; - for ($n = MAX_REQUESTS; $n--;) { - $fake_connect_time = microtime(true); - $ret = $redis->connect($host, $port); - $fake_connect_time = microtime(true) - $fake_connect_time; - Assert::assert($ret); - Assert::assert($fake_connect_time < $real_connect_time); - } - - $real_connect_time = microtime(true); - $redis->connect(MYSQL_SERVER_HOST, MYSQL_SERVER_PORT); - $real_connect_time = microtime(true) - $real_connect_time; - Assert::assert($fake_connect_time < $real_connect_time); - Assert::assert(!$redis->get('foo')); - Assert::same($redis->errType, SWOOLE_REDIS_ERR_PROTOCOL); -} - -go('test', REDIS_SERVER_HOST, REDIS_SERVER_PORT); -if (file_exists(REDIS_SERVER_PATH)) { - go('test', 'unix:' . str_repeat('/', mt_rand(1, 3)) . REDIS_SERVER_PATH); -} - -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/disable_retry.phpt b/tests/swoole_redis_coro/disable_retry.phpt deleted file mode 100644 index 6e5149a1cf..0000000000 --- a/tests/swoole_redis_coro/disable_retry.phpt +++ /dev/null @@ -1,68 +0,0 @@ ---TEST-- -swoole_redis_coro: disable retry ---SKIPIF-- - ---FILE-- -parentFunc = function () use ($pm) { - go(function () use ($pm) { - $redis = new Swoole\Coroutine\Redis; - $ret = $redis->connect('127.0.0.1', $pm->getFreePort()); - Assert::assert($ret); - $redis->setOptions(['retry' => false]); - for ($n = MAX_REQUESTS; $n--;) { - $ret = $redis->set('random_val', $random = get_safe_random(128)); - Assert::assert($ret); - $ret = $redis->get('random_val'); - if ($n % 2) { - Assert::same($ret, $random); - } else { - Assert::assert(!$ret); - } - } - $pm->kill(); - echo "DONE\n"; - }); -}; -$pm->childFunc = function () use ($pm) { - $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->data = []; - $server->on('WorkerStart', function ($server) use ($pm) { - $pm->wakeup(); - }); - $server->setHandler('GET', function ($fd, $data) use ($server) { - static $rid = 0; - if (count($data) == 0) { - $server->send($fd, Server::format(Server::ERROR, "ERR wrong number of arguments for 'GET' command")); - } - $key = $data[0]; - if ($rid++ % 2) { - $server->close($fd); - } else { - if (empty($server->data[$key])) { - $server->send($fd, Server::format(Server::NIL)); - } else { - $server->send($fd, Server::format(Server::STRING, $server->data[$key])); - } - } - }); - $server->setHandler('SET', function ($fd, $data) use ($server) { - if (count($data) < 2) { - $server->send($fd, Server::format(Server::ERROR, "ERR wrong number of arguments for 'SET' command")); - } - $key = $data[0]; - $server->data[$key] = $data[1]; - $server->send($fd, Server::format(Server::STATUS, 'OK')); - }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/donot_retry_after_failed.phpt b/tests/swoole_redis_coro/donot_retry_after_failed.phpt deleted file mode 100644 index 3528b9e401..0000000000 --- a/tests/swoole_redis_coro/donot_retry_after_failed.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -swoole_redis_coro: don not retry again after connect failed ---SKIPIF-- - ---FILE-- -bind('127.0.0.1'); -$info = $sock->getsockname(); -$port = $info['port']; - -$cid = go(function () use ($sock) { - $sock->listen(); - $sock->accept(); - co::yield(); - $sock->close(); -}); - -go(function () use ($cid, $port) { - $redis = new Swoole\Coroutine\Redis(); - $ret = $redis->connect('127.0.0.1', 65535); - Assert::assert(!$ret); - Assert::same($redis->errCode, SOCKET_ECONNREFUSED); - for ($n = MAX_REQUESTS; $n--;) { - $ret = $redis->get('foo'); - Assert::assert(!$ret); - Assert::same($redis->errType, SWOOLE_REDIS_ERR_CLOSED); - } - $ret = $redis->connect('127.0.0.1', $port); - Assert::assert($ret); - Assert::assert($redis->connected); - Assert::same($redis->errCode, 0, $redis->errCode); - Assert::same($redis->errMsg, '', $redis->errMsg); - co::sleep(0.001); - co::resume($cid); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/donot_retry_after_server_down.phpt b/tests/swoole_redis_coro/donot_retry_after_server_down.phpt deleted file mode 100644 index c16cf432f6..0000000000 --- a/tests/swoole_redis_coro/donot_retry_after_server_down.phpt +++ /dev/null @@ -1,62 +0,0 @@ ---TEST-- -swoole_redis_coro: do not retry after server down ---SKIPIF-- - ---FILE-- -parentFunc = function () use ($pm) { - go(function () use ($pm) { - $redis = new Swoole\Coroutine\Redis; - $ret = $redis->connect('127.0.0.1', $pm->getFreePort()); - Assert::assert($ret); - $ret = $redis->set('random_val', $random = get_safe_random(128)); - Assert::assert($ret); - $ret = $redis->get('random_val'); - Assert::same($ret, $random); - $pm->kill(); - Assert::assert(!$redis->get('random_val')); - Assert::same($redis->errCode, SOCKET_ECONNRESET); - for ($n = MAX_REQUESTS; $n--;) { - Assert::assert(!$redis->set('random_val', get_safe_random(128))); - Assert::same($redis->errCode, SOCKET_ECONNREFUSED); - Assert::assert(!$redis->get('random_val')); - Assert::same($redis->errCode, SOCKET_ECONNREFUSED); - } - }); -}; -$pm->childFunc = function () use ($pm) { - $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->data = []; - $server->on('workerStart', function ($server) use ($pm) { - $pm->wakeup(); - }); - $server->setHandler('GET', function ($fd, $data) use ($server) { - if (count($data) == 0) { - return Server::format(Server::ERROR, "ERR wrong number of arguments for 'GET' command"); - } - $key = $data[0]; - if (empty($server->data[$key])) { - $server->send($fd, Server::format(Server::NIL)); - } else { - $server->send($fd, Server::format(Server::STRING, $server->data[$key])); - } - }); - $server->setHandler('SET', function ($fd, $data) use ($server) { - if (count($data) < 2) { - $server->send($fd, Server::format(Server::ERROR, "ERR wrong number of arguments for 'SET' command")); - } - $key = $data[0]; - $server->data[$key] = $data[1]; - $server->send($fd, Server::format(Server::STATUS, 'OK')); - }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/err.phpt b/tests/swoole_redis_coro/err.phpt deleted file mode 100644 index 6b2ad5c466..0000000000 --- a/tests/swoole_redis_coro/err.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -swoole_redis_coro: redis error return ---SKIPIF-- - ---FILE-- - 3]); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $res = $redis->set('foo', 'bar'); - Assert::assert($res && $redis->errCode === 0 && $redis->errMsg === ''); - $res = $redis->hIncrBy('foo', 'bar', 123); - Assert::assert(!$res); - Assert::same($redis->errType, SWOOLE_REDIS_ERR_OTHER); - var_dump($redis->errMsg); - $res = $redis->set('foo', 'baz'); - Assert::assert($res && $redis->errCode === 0 && $redis->errMsg === ''); -}); -?> ---EXPECT-- -string(65) "WRONGTYPE Operation against a key holding the wrong kind of value" diff --git a/tests/swoole_redis_coro/getDbNum.phpt b/tests/swoole_redis_coro/getDbNum.phpt deleted file mode 100644 index 581ba0c552..0000000000 --- a/tests/swoole_redis_coro/getDbNum.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -swoole_redis_coro: redis select db ---SKIPIF-- - ---FILE-- -getDBNum()); - Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - // connected but not selected - Assert::same($redis->getDBNum(), 0); - // select and success - Assert::true($redis->select(1)); - Assert::same($redis->getDBNum(), 1); - // select but failed - Assert::false($redis->select(-1)); - Assert::same($redis->errCode, SOCKET_EINVAL); - Assert::false($redis->select(1001)); - Assert::same($redis->errCode, SOCKET_EINVAL); - Assert::same($redis->getDBNum(), 1); - - $redis = new Swoole\Coroutine\Redis(['database' => 1]); - // connected but not selected - Assert::false($redis->getDBNum()); - Assert::assert($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - // connected but not selected - Assert::same($redis->getDBNum(), 1); - // set database but failed - $redis = new Swoole\Coroutine\Redis(['database' => 1001]); - Assert::false($redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - Assert::false($redis->getDBNum()); - Assert::same($redis->errCode, SOCKET_EINVAL); - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/getOptions.phpt b/tests/swoole_redis_coro/getOptions.phpt deleted file mode 100644 index 7626bd2bc4..0000000000 --- a/tests/swoole_redis_coro/getOptions.phpt +++ /dev/null @@ -1,50 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client get options ---SKIPIF-- - ---FILE-- - 100, - 'socket_timeout' => 100, -]); -$redis = new Swoole\Coroutine\Redis(); -var_dump($redis->getOptions()); -$redis->setOptions([ - 'connect_timeout' => 0.001, - 'timeout' => 0.001, - 'serialize' => true, - 'reconnect' => 3 -]); -var_dump($redis->getOptions()); -?> ---EXPECT-- -array(6) { - ["connect_timeout"]=> - float(2) - ["timeout"]=> - float(100) - ["serialize"]=> - bool(false) - ["reconnect"]=> - int(1) - ["password"]=> - string(0) "" - ["database"]=> - int(0) -} -array(6) { - ["connect_timeout"]=> - float(0.001) - ["timeout"]=> - float(0.001) - ["serialize"]=> - bool(true) - ["reconnect"]=> - int(3) - ["password"]=> - string(0) "" - ["database"]=> - int(0) -} diff --git a/tests/swoole_redis_coro/hgetall.phpt b/tests/swoole_redis_coro/hgetall.phpt deleted file mode 100644 index 7990aa642d..0000000000 --- a/tests/swoole_redis_coro/hgetall.phpt +++ /dev/null @@ -1,97 +0,0 @@ ---TEST-- -swoole_redis_coro: hGetAll hmGet zRange zRevRange zRangeByScore zRevRangeByScore ---SKIPIF-- - ---FILE-- -setOptions(['compatibility_mode' => true]); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $redis->delete('hkey'); - $redis->hSet('hkey', false, 'val0'); - $redis->hSet('hkey', "field", 'val1'); - $redis->hSet('hkey', 5, 'val5'); - - $redis->delete('zkey'); - $redis->zAdd('zkey', "field", 'val0'); - $redis->zAdd('zkey', true, 'val1'); - $redis->zAdd('zkey', 5, 'val5'); - - echo "-----get---\n"; - var_dump($redis->get('novalue')); - echo "-----zRank---\n"; - var_dump($redis->zRank('novalue', 1)); - echo "-----hGetAll---\n"; - var_dump($redis->hGetAll('hkey')); - echo "-----hmGet---\n"; - var_dump($redis->hmGet('hkey', [3, 5])); - echo "-----zRange---\n"; - var_dump($redis->zRange('zkey', 0, 99, true)); - echo "-----zRevRange---\n"; - var_dump($redis->zRevRange('zkey', 0, 99, true)); - echo "-----zRangeByScore---\n"; - var_dump($redis->zRangeByScore('zkey', 0, 99, ['withscores' => true])); - echo "-----zRevRangeByScore---\n"; - var_dump($redis->zRevRangeByScore('zkey', 99, 0, ['withscores' => true])); -}); -?> ---EXPECT-- ------get--- -bool(false) ------zRank--- -bool(false) ------hGetAll--- -array(3) { - [""]=> - string(4) "val0" - ["field"]=> - string(4) "val1" - [5]=> - string(4) "val5" -} ------hmGet--- -array(2) { - [3]=> - bool(false) - [5]=> - string(4) "val5" -} ------zRange--- -array(3) { - ["val0"]=> - float(0) - ["val1"]=> - float(1) - ["val5"]=> - float(5) -} ------zRevRange--- -array(3) { - ["val5"]=> - float(5) - ["val1"]=> - float(1) - ["val0"]=> - float(0) -} ------zRangeByScore--- -array(3) { - ["val0"]=> - float(0) - ["val1"]=> - float(1) - ["val5"]=> - float(5) -} ------zRevRangeByScore--- -array(3) { - ["val5"]=> - float(5) - ["val1"]=> - float(1) - ["val0"]=> - float(0) -} diff --git a/tests/swoole_redis_coro/lock.phpt b/tests/swoole_redis_coro/lock.phpt deleted file mode 100644 index 6614474f4f..0000000000 --- a/tests/swoole_redis_coro/lock.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -swoole_redis_coro: redis lock ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $redis->delete('lock'); - $ret = $redis->set('lock', 1, ['nx', 'ex' => 1, 'px' => 1000]); // px will be ignored - Assert::assert($ret); - $ret = $redis->set('lock', 1, ['nx', 'ex' => 1, 'px' => 1000]); // px will be ignored - Assert::assert(!$ret); - $redis->delete('lock'); - $ret = $redis->set('lock', 1, ['nx', 'px' => 100]); - Assert::assert($ret); - usleep(50 * 1000); - $ret = $redis->set('lock', 1, ['nx', 'px' => 100]); - Assert::assert(!$ret); - usleep(100 * 1000); - $ret = $redis->set('lock', 1, ['nx', 'px' => 100]); - Assert::assert($ret); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/multi_exec.phpt b/tests/swoole_redis_coro/multi_exec.phpt deleted file mode 100644 index 0a3ed2f800..0000000000 --- a/tests/swoole_redis_coro/multi_exec.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -swoole_redis_coro: redis multi and exec ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT, false); - Assert::assert($result); - - Assert::assert($redis->hmset('u:i:1', ['a' => 'hello', 'b' => 'world'])); - Assert::assert($redis->hmset('u:i:2', ['a' => 'rango', 'b' => 'swoole'])); - Assert::assert($redis->multi()); - $redis->hmget('u:i:1', array('a', 'b')); - $redis->hmget('u:i:2', array('a', 'b')); - - $rs = $redis->exec(); - Assert::assert($rs and is_array($rs)); - Assert::same($rs[0][0], 'hello'); - Assert::same($rs[0][1], 'world'); - Assert::same($rs[1][0], 'rango'); - Assert::same($rs[1][1], 'swoole'); - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/pool.phpt b/tests/swoole_redis_coro/pool.phpt deleted file mode 100644 index 98294be259..0000000000 --- a/tests/swoole_redis_coro/pool.phpt +++ /dev/null @@ -1,79 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) -{ - go(function () use ($pm) { - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); - $pm->kill(); - }); -}; - -$count = 0; -$pool = new SplQueue(); - -$pm->childFunc = function () use ($pm) -{ - $http = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $http->set(array( - 'log_file' => '/dev/null' - )); - $http->on("WorkerStart", function (Swoole\Server $serv) - { - /** - * @var $pm ProcessManager - */ - global $pm; - $pm->wakeup(); - }); - - $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) - { - global $count, $pool; - if (count($pool) == 0) - { - $redis = new Swoole\Coroutine\Redis(); - $redis->id = $count; - $res = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - if ($res == false) - { - fail: - $response->end("ERROR\n"); - return; - } - $count++; - $pool->enqueue($redis); - } - - $redis = $pool->dequeue(); - $ret = $redis->set('key', 'value'); - if ($ret) - { - $response->end("OK[$count]\n"); - } - else - { - goto fail; - } - $pool->enqueue($redis); - - }); - - $http->start(); -}; - -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- -OK[1] -OK[1] -OK[1] diff --git a/tests/swoole_redis_coro/psubscribe_1.phpt b/tests/swoole_redis_coro/psubscribe_1.phpt deleted file mode 100644 index 1945ac083a..0000000000 --- a/tests/swoole_redis_coro/psubscribe_1.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -swoole_redis_coro: redis psubscribe ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $val = $redis->psubscribe(['test.*']); - Assert::assert($val); - $val = $redis->recv(); - Assert::assert($val[0] == 'psubscribe' && $val[1] == 'test.*'); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $val = $redis->recv(); - Assert::same($val[0] ?? '', 'pmessage'); - } - - $redis->close(); -}); - -go(function () { - $redis = new Co\redis; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - co::sleep(0.1); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $ret = $redis->publish('test.a', 'hello-' . $i); - Assert::assert($ret); - } -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/psubscribe_2.phpt b/tests/swoole_redis_coro/psubscribe_2.phpt deleted file mode 100644 index 255a5eef5d..0000000000 --- a/tests/swoole_redis_coro/psubscribe_2.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -swoole_redis_coro: redis psubscribe 2 ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $redis2 = new Co\Redis; - $redis2->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $channel = 'channel' . floor($i / 10) . $i; - $val = $redis->psubscribe([$channel . '*']); - Assert::assert($val); - - $val = $redis->recv(); - Assert::same($val[0], 'psubscribe'); - Assert::same($val[1], $channel . '*'); - - $channel .= 'test'; - - go(function () use ($channel, $redis2) { - $ret = $redis2->publish($channel, 'test' . $channel); - Assert::assert($ret); - }); - - $val = $redis->recv(); - Assert::same($val[0] ?? '', 'pmessage'); - } - - $redis->close(); - $redis2->close(); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/psubscribe_eof_1.phpt b/tests/swoole_redis_coro/psubscribe_eof_1.phpt deleted file mode 100644 index 2f3153fa2d..0000000000 --- a/tests/swoole_redis_coro/psubscribe_eof_1.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -swoole_redis_coro: redis psubscribe eof 1 ---SKIPIF-- - ---FILE-- -bind('127.0.0.1'); -$info = $sock->getsockname(); -$port = $info['port']; -go(function () use ($sock) { - $sock->listen(); - while ($client = $sock->accept(-1)) { - $client->close(); - } - echo "DONE\n"; -}); - -go(function () use ($sock, $port) { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect('127.0.0.1', $port); - for ($n = 0; $n < MAX_REQUESTS; $n++) { - $val = $redis->psubscribe(['test.*']); - Assert::assert($val); - $val = $redis->recv(); - Assert::false($val); - Assert::false($redis->connected); - Assert::assert(in_array($redis->errType, [SWOOLE_REDIS_ERR_IO, SWOOLE_REDIS_ERR_EOF], true)); - if ($redis->errType === SWOOLE_REDIS_ERR_IO) { - Assert::same($redis->errCode, SOCKET_ECONNRESET); - } - } - $redis->close(); - $sock->close(); -}); -Swoole\Event::wait(); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/psubscribe_eof_2.phpt b/tests/swoole_redis_coro/psubscribe_eof_2.phpt deleted file mode 100644 index d6aa4a1b33..0000000000 --- a/tests/swoole_redis_coro/psubscribe_eof_2.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -swoole_redis_coro: redis psubscribe eof 2 ---SKIPIF-- - ---FILE-- -bind('127.0.0.1'); -$info = $sock->getsockname(); -$port = $info['port']; -go(function () use ($sock) { - $sock->listen(); - - while ($client = $sock->accept(-1)) { - $client->recv(); - $client->send("*3\r\n\$10\r\npsubscribe\r\n\$8\r\nchannel1\r\n:1\r\n"); - co::sleep(0.1); - $client->close(); - } - - echo "DONE\n"; -}); -go(function () use ($sock, $port) { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect('127.0.0.1', $port); - - $val = $redis->psubscribe(['channel1']); - Assert::assert($val); - - $val = $redis->recv(); - Assert::assert($val[0] == 'psubscribe' && $val[1] == 'channel1'); - - $val = $redis->recv(); - Assert::false($val); - - Assert::false($redis->connected); - Assert::same($redis->errType, SWOOLE_REDIS_ERR_EOF); - - $redis->close(); - $sock->close(); -}); -Swoole\Event::wait(); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/punsubscribe.phpt b/tests/swoole_redis_coro/punsubscribe.phpt deleted file mode 100644 index 6326828cad..0000000000 --- a/tests/swoole_redis_coro/punsubscribe.phpt +++ /dev/null @@ -1,51 +0,0 @@ ---TEST-- -swoole_redis_coro: redis punsubscribe ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->psubscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'psubscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $ret = $redis->punsubscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'punsubscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert($ret); - - $ret = $redis->setDefer(false); - Assert::assert($ret); - - $redis->close(); -}); - -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/punsubscribe.php on line 22 diff --git a/tests/swoole_redis_coro/reconnect.phpt b/tests/swoole_redis_coro/reconnect.phpt deleted file mode 100644 index 613dd17990..0000000000 --- a/tests/swoole_redis_coro/reconnect.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -swoole_redis_coro: redis reconnect ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($res); - $redis->close(); - $res2 = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($res2); -}); -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/request_without_connected.phpt b/tests/swoole_redis_coro/request_without_connected.phpt deleted file mode 100644 index f323ff7db2..0000000000 --- a/tests/swoole_redis_coro/request_without_connected.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -swoole_redis_coro: redis request without connected ---SKIPIF-- - ---FILE-- -get('foo')); - echo "DONE\n"; -}); -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::get(): The host is empty in %s/tests/swoole_redis_coro/request_without_connected.php on line 5 -DONE diff --git a/tests/swoole_redis_coro/select.phpt b/tests/swoole_redis_coro/select.phpt deleted file mode 100644 index 770c4f28a2..0000000000 --- a/tests/swoole_redis_coro/select.phpt +++ /dev/null @@ -1,37 +0,0 @@ ---TEST-- -swoole_redis_coro: redis select ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT)); - Assert::assert($redis->select(0)); - Assert::assert($redis->set('foo', $random0 = get_safe_random())); - Assert::assert($redis->select(1)); - Assert::assert($redis->set('foo', $random1 = get_safe_random())); - $foo = $redis->get('foo'); - Assert::assert($foo !== $random0); - Assert::same($foo, $random1); - Assert::assert($redis->select(0)); - $foo = $redis->get('foo'); - Assert::same($foo, $random0); - Assert::assert($foo !== $random1); - Assert::assert($redis->select(1)); - - // test whether it's OK after automatic reconnected - $redis_killer = new Swoole\Coroutine\Redis; - $redis_killer->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $redis_killer->request(['CLIENT', 'KILL', 'TYPE', 'normal']); - - $foo = $redis->get('foo'); - Assert::assert($foo !== $random0); - Assert::same($foo, $random1); - - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/set.phpt b/tests/swoole_redis_coro/set.phpt deleted file mode 100644 index 20a8f46ad1..0000000000 --- a/tests/swoole_redis_coro/set.phpt +++ /dev/null @@ -1,43 +0,0 @@ ---TEST-- -swoole_redis_coro: set ---SKIPIF-- - ---FILE-- - SWOOLE_LOG_TRACE, 'trace_flags' => SWOOLE_TRACE_ALL]); - -go(function () { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($redis->set('key1', 'value')); - Assert::assert($redis->set('key1', 'value', 10)); - Assert::assert($redis->ttl('key1') == 10); - /** - * xx+ex - */ - Assert::assert($redis->set('key1', 'value', ['xx', 'ex' => 30])); - Assert::assert($redis->ttl('key1') == 30); - /** - * delete - */ - Assert::assert($redis->delete('key1')); - /** - * nx+ex - */ - Assert::assert($redis->set('key1', 'value', ['nx', 'ex' => 20])); - Assert::assert($redis->ttl('key1') == 20); - - /** - * px - */ - Assert::assert($redis->set('key1', 'value', ['xx', 'px' => 10000])); - Assert::assert($redis->ttl('key1') == 10); - echo "OK\n"; -}); - -Swoole\Event::wait(); -?> ---EXPECT-- -OK diff --git a/tests/swoole_redis_coro/setOptions.phpt b/tests/swoole_redis_coro/setOptions.phpt deleted file mode 100644 index eb5819fc6f..0000000000 --- a/tests/swoole_redis_coro/setOptions.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client set options ---SKIPIF-- - ---FILE-- - -1]); -go(function () { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - // read time out - $redis->setOptions(['timeout' => 0.001]); - $s = microtime(true); - $ret = $redis->brpoplpush('test', 'test2', 1); - $s = microtime(true) - $s; - time_approximate(0.001, $s, 1); - Assert::assert(!$ret); - - // read ok (after internal auto connect) - $redis->setOptions(['timeout' => 1]); - $ret = $redis->set('foo', 'bar'); - Assert::assert($ret); - Assert::same($redis->errCode, 0); - Assert::same($redis->errMsg, ''); - $redis->close(); - Assert::assert(!$redis->connected); - - // connect timeout - $redis->setOptions(['connect_timeout' => 0.001]); - $redis->connect('www.google.com', 80); - Assert::same($redis->errCode, SOCKET_ETIMEDOUT); -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/stream.phpt b/tests/swoole_redis_coro/stream.phpt deleted file mode 100644 index 00f7d03520..0000000000 --- a/tests/swoole_redis_coro/stream.phpt +++ /dev/null @@ -1,124 +0,0 @@ ---TEST-- -swoole_redis_coro: stream ---SKIPIF-- - ---FILE-- - SWOOLE_LOG_TRACE, 'trace_flags' => SWOOLE_TRACE_ALL]); - -Co\run(function() { - $redis = new Swoole\Coroutine\Redis(); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $ret = $redis->del('mystream'); - - // xGroupCreate - $ret = $redis->xGroupCreate('mystream', 'group1', '0-0', true); - Assert::assert($ret == '1'); - - // xGroupCreateConsumer - $ret = $redis->xGroupCreateConsumer('mystream', 'group1', 'consumer1'); - Assert::assert($ret == '1'); - $ret = $redis->xGroupCreateConsumer('mystream', 'group1', 'consumer2'); - Assert::assert($ret == '1'); - - // xAdd - $ret = $redis->xAdd('mystream', '0-1', ['field'=>'111'], ['nomkstream'=>true, 'maxlen'=>['~', 5], 'limit'=>5]); - Assert::assert($ret == '0-1'); - $ret = $redis->xAdd('mystream', '0-2', ['field'=>'222'], ['nomkstream'=>false, 'minid'=>['~', '0-0'], 'limit'=>5]); - Assert::assert($ret == '0-2'); - $ret = $redis->xAdd('mystream', '0-3', ['field'=>'333'], ['maxlen'=>['=', 5]]); - Assert::assert($ret == '0-3'); - $ret = $redis->xAdd('mystream', '0-4', ['field'=>'444'], ['maxlen'=>5]); - Assert::assert($ret, '0-4'); - $ret = $redis->xAdd('mystream', '0-5', ['field'=>'555']); - Assert::assert($ret, '0-5'); - - // xLen - $ret = $redis->xLen('mystream'); - Assert::assert($ret == '5'); - - // xRead - $ret = $redis->xRead(['mystream'=>'0-3'], ['count'=>1, 'block'=>100]); - Assert::assert($ret[0][1][0][0] == '0-4'); - - // xRange - $ret = $redis->xRange('mystream', '0-2', '0-3', 1); - Assert::assert($ret[0][0] == '0-2'); - - // xRevRange - $ret = $redis->xRevRange('mystream', '+', '-', 1); - Assert::assert($ret[0][0] == '0-5'); - - // xReadGroup - $ret = $redis->xReadGroup('group1', 'consumer1', ['mystream' => '>'], ['count'=>1, 'block'=>100, 'noack'=>true]); - Assert::assert($ret[0][1][0][0] == '0-1'); - $ret = $redis->xReadGroup('group1', 'consumer1', ['mystream' => '>'], ['count'=>1, 'block'=>100, 'noack'=>false]); - Assert::assert($ret[0][1][0][0] == '0-2'); - $ret = $redis->xReadGroup('group1', 'consumer1', ['mystream' => '>'], ['count'=>1]); - Assert::assert($ret[0][1][0][0] == '0-3'); - - // xPending - $ret = $redis->xPending('mystream', 'group1', ['start'=>'-', 'end'=>'+', 'count'=>5]); - Assert::assert(count($ret) == 2); - Assert::assert($ret[0][0] == '0-2'); - Assert::assert($ret[1][0] == '0-3'); - - // xAck - $ret = $redis->xAck('mystream', 'group1', ['0-2']); - Assert::assert($ret == '1'); - - // xClaim - $ret = $redis->xClaim('mystream', 'group1', 'consumer2', 0, ['0-3']); - Assert::assert($ret[0][0] == '0-3'); - - // xInfoConsumers - $ret = $redis->xInfoConsumers('mystream', 'group1'); - Assert::assert($ret[1][3] == '1'); - - // xAutoClaim - $ret = $redis->xAutoClaim('mystream', 'group1', 'consumer1', 0, '0-3'); - Assert::assert($ret[1][0][0] == '0-3'); - - // xInfoGroups - $ret = $redis->xInfoGroups('mystream'); - Assert::assert($ret[0][1] == 'group1'); - Assert::assert($ret[0][5] == '1'); - - // xInfoStream - $ret = $redis->xInfoStream('mystream'); - Assert::assert($ret[1] == '5'); - - // xDel - $ret = $redis->xDel('mystream', '0-1', '0-2'); - Assert::assert($ret == '2'); - - // xTrim - $ret = $redis->xTrim('mystream', ['maxlen'=>1]); - Assert::assert($ret == '2'); - $ret = $redis->xTrim('mystream', ['minid'=>['~', '0'], 'limit'=>1]); - Assert::assert($ret == '0'); - - // xGroupSetId - $ret = $redis->xGroupSetId('mystream', 'group1', '0-1'); - Assert::assert($ret == '1'); - - // xGroupDelConsumer - $ret = $redis->xGroupDelConsumer('mystream', 'group1', 'consumer1'); - Assert::assert($ret == '1'); - - // xGroupDestroy - $ret = $redis->xGroupDestroy('mystream', 'group1'); - Assert::assert($ret == '1'); - - $ret = $redis->del('mystream'); - - echo "OK\n"; -}); -?> ---EXPECT-- -OK diff --git a/tests/swoole_redis_coro/subscribe_1.phpt b/tests/swoole_redis_coro/subscribe_1.phpt deleted file mode 100644 index e9b4db226c..0000000000 --- a/tests/swoole_redis_coro/subscribe_1.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -swoole_redis_coro: redis subscribe 1 ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - $val = $redis->subscribe(['test']); - Assert::assert($val); - - $val = $redis->recv(); - Assert::assert($val[0] == 'subscribe' && $val[1] == 'test'); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $val = $redis->recv(); - Assert::same($val[0] ?? '', 'message'); - } - - $redis->close(); -}); - -go(function () { - $redis = new Co\redis; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - co::sleep(0.1); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $ret = $redis->publish('test', 'hello-' . $i); - Assert::assert($ret); - } -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/subscribe_2.phpt b/tests/swoole_redis_coro/subscribe_2.phpt deleted file mode 100644 index e60c810baa..0000000000 --- a/tests/swoole_redis_coro/subscribe_2.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -swoole_redis_coro: redis subscribe 2 ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $redis2 = new Co\Redis; - $redis2->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - for ($i = 0; $i < MAX_REQUESTS; $i++) { - $channel = 'channel' . $i; - $val = $redis->subscribe([$channel]); - Assert::assert($val); - - $val = $redis->recv(); - Assert::assert($val[0] == 'subscribe' && $val[1] == $channel); - - go(function () use ($channel, $redis2) { - $ret = $redis2->publish($channel, 'test' . $channel); - Assert::assert($ret); - }); - - $val = $redis->recv(); - Assert::same($val[0] ?? '', 'message'); - Assert::same($val[1] ?? '', $channel); - Assert::same($val[2] ?? '', 'test' . $channel); - } - - $redis->close(); - $redis2->close(); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/subscribe_multi.phpt b/tests/swoole_redis_coro/subscribe_multi.phpt deleted file mode 100644 index ce26999540..0000000000 --- a/tests/swoole_redis_coro/subscribe_multi.phpt +++ /dev/null @@ -1,46 +0,0 @@ ---TEST-- -swoole_redis_coro: redis subscribe multi ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $val = $redis->subscribe(['test1', 'test2', 'test3']); - Assert::assert($val); - - for ($i = 0; $i < 3; ++$i) - { - $val = $redis->recv(); - Assert::same($val[0], 'subscribe'); - } - - for ($i = 0; $i < 3; $i++) { - $val = $redis->recv(); - Assert::same($val[0] ?? '', 'message'); - } - - $redis->close(); -}); - -go(function () { - $redis = new Co\redis; - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - co::sleep(0.1); - - $ret = $redis->publish('test1', 'hello'); - Assert::assert($ret); - - $ret = $redis->publish('test2', 'hello'); - Assert::assert($ret); - - $ret = $redis->publish('test3', 'hello'); - Assert::assert($ret); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/subscribe_punsubscribe.phpt b/tests/swoole_redis_coro/subscribe_punsubscribe.phpt deleted file mode 100644 index daaba685cb..0000000000 --- a/tests/swoole_redis_coro/subscribe_punsubscribe.phpt +++ /dev/null @@ -1,54 +0,0 @@ ---TEST-- -swoole_redis_coro: redis subscribe and use punsubscribe ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->subscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'subscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $ret = $redis->punsubscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'punsubscribe'); - Assert::same($ret[1], 'channel1'); - Assert::same($ret[2], 1); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $redis->close(); -}); - -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/subscribe_punsubscribe.php on line 22 - -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/subscribe_punsubscribe.php on line 39 diff --git a/tests/swoole_redis_coro/subscribe_reconnect.phpt b/tests/swoole_redis_coro/subscribe_reconnect.phpt deleted file mode 100644 index da9026bbda..0000000000 --- a/tests/swoole_redis_coro/subscribe_reconnect.phpt +++ /dev/null @@ -1,35 +0,0 @@ ---TEST-- -swoole_redis_coro: redis subscribe reconnect ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->subscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'subscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - $redis->close(); - - $ret = $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->set('a', '1'); - Assert::assert($ret); - - $redis->close(); -}); - -?> ---EXPECT-- diff --git a/tests/swoole_redis_coro/timeout.phpt b/tests/swoole_redis_coro/timeout.phpt deleted file mode 100644 index d22fd33e1c..0000000000 --- a/tests/swoole_redis_coro/timeout.phpt +++ /dev/null @@ -1,52 +0,0 @@ ---TEST-- -swoole_redis_coro: redis client timeout ---SKIPIF-- - ---FILE-- - 0.5]); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $keyArray = [QUEUE_KEY_1, QUEUE_KEY_2]; - - $s = microtime(true); - $res = $redis->blpop($keyArray, 3); - Assert::assert(!$res); - Assert::same($redis->errCode, SOCKET_ETIMEDOUT); - $s = microtime(true) - $s; - time_approximate(0.5, $s); // would not retry after timeout - - $s = microtime(true); - $res = $redis->brpoplpush(QUEUE_KEY_1, QUEUE_KEY_2, 3); - Assert::assert(!$res); - Assert::same($redis->errCode, SOCKET_ETIMEDOUT); - $s = microtime(true) - $s; - time_approximate(0.5, $s); // would not retry after timeout - - // right way: no timeout - $redis->setOptions(['timeout' => -1]); - - $s = microtime(true); - $res = $redis->blpop($keyArray, 1); - Assert::same($res, null); - Assert::same($redis->errCode, 0); - $s = microtime(true) - $s; - time_approximate(1, $s); - - $s = microtime(true); - $res = $redis->brpoplpush(QUEUE_KEY_1, QUEUE_KEY_2, 1); - Assert::same($res, null); - Assert::same($redis->errCode, 0); - $s = microtime(true) - $s; - time_approximate(1, $s); -}); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/unconnected.phpt b/tests/swoole_redis_coro/unconnected.phpt deleted file mode 100644 index 83f3d4579f..0000000000 --- a/tests/swoole_redis_coro/unconnected.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -swoole_redis_coro: redis unconnected recv ---SKIPIF-- - ---FILE-- -setDefer(true); - Assert::false($redis->recv()); - echo "DONE\n"; -}); -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/unixsocket.phpt b/tests/swoole_redis_coro/unixsocket.phpt deleted file mode 100644 index bbc9aa8302..0000000000 --- a/tests/swoole_redis_coro/unixsocket.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -swoole_redis_coro: use unixsocket ---SKIPIF-- - ---FILE-- - 100]); - Assert::assert($redis->connect('unix:/' . REDIS_SERVER_PATH, 0)); - for ($c = MAX_CONCURRENCY_MID; $c--;) { - for ($n = MAX_REQUESTS; $n--;) { - $key = md5(get_safe_random(mt_rand(1, 128))); - $value = md5(get_safe_random(mt_rand(1, 128))); - Assert::assert($redis->set($key, $value)); - Assert::same($redis->get($key), $value); - Assert::assert($redis->delete($key)); - } - } -}); -Swoole\Event::wait(); -echo "DONE\n"; -?> ---EXPECT-- -DONE diff --git a/tests/swoole_redis_coro/unsubscribe.phpt b/tests/swoole_redis_coro/unsubscribe.phpt deleted file mode 100644 index 562351cf30..0000000000 --- a/tests/swoole_redis_coro/unsubscribe.phpt +++ /dev/null @@ -1,51 +0,0 @@ ---TEST-- -swoole_redis_coro: redis unsubscribe ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->subscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'subscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $ret = $redis->unsubscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'unsubscribe'); - Assert::same($ret[1], 'channel1'); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert($ret); - - $ret = $redis->setDefer(false); - Assert::assert($ret); - - $redis->close(); -}); - -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/unsubscribe.php on line 22 diff --git a/tests/swoole_redis_coro/unsubscribe_all.phpt b/tests/swoole_redis_coro/unsubscribe_all.phpt deleted file mode 100644 index 3ffbffeb04..0000000000 --- a/tests/swoole_redis_coro/unsubscribe_all.phpt +++ /dev/null @@ -1,56 +0,0 @@ ---TEST-- -swoole_redis_coro: redis unsubscribe all ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->subscribe(['channel1', 'channel2']); - Assert::assert($ret); - - for ($i = 0; $i < 2; ++$i) - { - $ret = $redis->recv(); - Assert::same($ret[0], 'subscribe'); - } - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $ret = $redis->unsubscribe(['channel1', 'channel2']); - Assert::assert($ret); - - for ($i = 0; $i < 2; ++$i) - { - $ret = $redis->recv(); - Assert::same($ret[0], 'unsubscribe'); - Assert::same($ret[2], 1 - $i); - } - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert($ret); - - $ret = $redis->setDefer(false); - Assert::assert($ret); - - $redis->close(); -}); - -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/unsubscribe_all.php on line 24 diff --git a/tests/swoole_redis_coro/unsubscribe_not_all.phpt b/tests/swoole_redis_coro/unsubscribe_not_all.phpt deleted file mode 100644 index 69776291c4..0000000000 --- a/tests/swoole_redis_coro/unsubscribe_not_all.phpt +++ /dev/null @@ -1,56 +0,0 @@ ---TEST-- -swoole_redis_coro: redis unsubscribe not all ---SKIPIF-- - ---FILE-- -connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - Assert::assert($ret); - - $ret = $redis->subscribe(['channel1', 'channel2']); - Assert::assert($ret); - - for ($i = 0; $i < 2; ++$i) - { - $ret = $redis->recv(); - Assert::same($ret[0], 'subscribe'); - } - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $ret = $redis->unsubscribe(['channel1']); - Assert::assert($ret); - - $ret = $redis->recv(); - Assert::same($ret[0], 'unsubscribe'); - Assert::same($ret[1], 'channel1'); - Assert::same($ret[2], 1); - - $ret = $redis->getDefer(); - Assert::assert(!$ret); - - $ret = $redis->set('a', '1'); - Assert::assert(!$ret); - - $ret = $redis->setDefer(false); - Assert::assert(!$ret); - - $redis->close(); -}); - -?> ---EXPECTF-- -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/unsubscribe_not_all.php on line 24 - -Warning: Swoole\Coroutine\Redis::setDefer(): you should not use setDefer after subscribe in %s/tests/swoole_redis_coro/unsubscribe_not_all.php on line 41 diff --git a/tests/swoole_redis_coro/zpop.phpt b/tests/swoole_redis_coro/zpop.phpt deleted file mode 100644 index 0902599246..0000000000 --- a/tests/swoole_redis_coro/zpop.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -swoole_redis_coro: zPopMin zPopMax bzPopMin bzPopMax ---SKIPIF-- - ---FILE-- -setOptions(['compatibility_mode' => true]); - $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); - - $redis->delete('zkeyA'); - $redis->zAdd('zkeyA', 1, 'val1'); - $redis->zAdd('zkeyA', 2, 'val2'); - $redis->zAdd('zkeyA', 3, 'val3'); - $redis->zAdd('zkeyA', 4, 'val4'); - $redis->zAdd('zkeyA', 5, 'val5'); - - $redis->delete('zkeyB'); - $redis->zAdd('zkeyB', 1, 'val1'); - $redis->zAdd('zkeyB', 2, 'val2'); - $redis->zAdd('zkeyB', 3, 'val3'); - $redis->zAdd('zkeyB', 4, 'val4'); - $redis->zAdd('zkeyB', 5, 'val5'); - - echo "-----zPopMin---\n"; - var_dump($redis->zPopMin('zkeyA')); - echo "-----zPopMax---\n"; - var_dump($redis->zPopMax('zkeyB')); - echo "-----bzPopMin---\n"; - var_dump($redis->bzPopMin(['zkeyB','zkeyA'], 2)); - echo "-----bzPopMax---\n"; - var_dump($redis->bzPopMax('zkeyB','zkeyA', 2)); - echo "-----bzPopMin no data---\n"; - var_dump($redis->bzPopMin('zkeyC','zkeyD', 2)); -}); -?> ---EXPECT-- ------zPopMin--- -array(2) { - [0]=> - string(4) "val1" - [1]=> - string(1) "1" -} ------zPopMax--- -array(2) { - [0]=> - string(4) "val5" - [1]=> - string(1) "5" -} ------bzPopMin--- -array(3) { - [0]=> - string(5) "zkeyB" - [1]=> - string(4) "val1" - [2]=> - string(1) "1" -} ------bzPopMax--- -array(3) { - [0]=> - string(5) "zkeyB" - [1]=> - string(4) "val4" - [2]=> - string(1) "4" -} ------bzPopMin no data--- -NULL diff --git a/tests/swoole_redis_server/big_packet.phpt b/tests/swoole_redis_server/big_packet.phpt index 390b0d2ace..a0053e111c 100644 --- a/tests/swoole_redis_server/big_packet.phpt +++ b/tests/swoole_redis_server/big_packet.phpt @@ -5,13 +5,13 @@ swoole_redis_server: test big packet --FILE-- parentFunc = function ($pid) use ($pm) -{ +$pm->parentFunc = function ($pid) use ($pm) { $redis = new redis; $redis->connect('127.0.0.1', $pm->getFreePort()); $redis->set('big_value', str_repeat('A', VALUE_LEN)); @@ -20,30 +20,24 @@ $pm->parentFunc = function ($pid) use ($pm) Swoole\Process::kill($pid); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $server->data = array(); $server->setHandler('GET', function ($fd, $data) use ($server) { - if (count($data) == 0) - { + if (count($data) == 0) { return Server::format(Server::ERROR, "ERR wrong number of arguments for 'GET' command"); } $key = $data[0]; - if (empty($server->data[$key])) - { + if (empty($server->data[$key])) { $server->send($fd, Server::format(Server::NIL)); - } - else - { + } else { $server->send($fd, Server::format(Server::STRING, $server->data[$key])); } }); $server->setHandler('SET', function ($fd, $data) use ($server) { - if (count($data) < 2) - { + if (count($data) < 2) { return Server::format(Server::ERROR, "ERR wrong number of arguments for 'SET' command"); } $key = $data[0]; diff --git a/tests/swoole_redis_server/empty.phpt b/tests/swoole_redis_server/empty.phpt new file mode 100644 index 0000000000..1e50c40af9 --- /dev/null +++ b/tests/swoole_redis_server/empty.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_redis_server: empty +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); + $map = $redis->get('map'); + Assert::eq($map, [0, '', false, null]); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->setHandler('GET', function ($fd, $data) use ($server) { + $server->send($fd, Server::format(Server::SET, [0, '', false, null])); + }); + $server->on('WorkerStart', function ($server) use ($pm) { + $pm->wakeup(); + }); + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_redis_server/format.phpt b/tests/swoole_redis_server/format.phpt index c68e37f4ba..cc1858815b 100644 --- a/tests/swoole_redis_server/format.phpt +++ b/tests/swoole_redis_server/format.phpt @@ -14,7 +14,7 @@ echo Server::format(Server::STATUS, "SUCCESS"); echo Server::format(Server::INT, 1000); echo Server::format(Server::STRING, "hello swoole"); echo Server::format(Server::SET, ["php", "is", "best"]); -echo Server::format(Server::MAP, ["php" => 99, "java" => 88, "c++" => '666']); +echo Server::format(Server::MAP, ["php" => '99', "java" => '88', "c++" => '666', 9999 => 'hello']); ?> --EXPECT-- -ERR @@ -32,7 +32,7 @@ $2 is $4 best -*6 +*8 $3 php $2 @@ -45,3 +45,7 @@ $3 c++ $3 666 +$4 +9999 +$5 +hello diff --git a/tests/swoole_redis_server/large_data_map.phpt b/tests/swoole_redis_server/large_data_map.phpt new file mode 100644 index 0000000000..ce9136e53e --- /dev/null +++ b/tests/swoole_redis_server/large_data_map.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_redis_server: large data map +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); + $map = $redis->get('map'); + Assert::notEmpty($map); + + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->setHandler('GET', function ($fd, $data) use ($server) { + $key = $data[0]; + if ($key == 'map') { + $out = Server::format(Server::MAP, ['a' => str_repeat('a', N), 'b' => str_repeat('b', N * 3)]); + } + $server->send($fd, $out); + }); + + $server->on('WorkerStart', function ($server) use ($pm) { + $pm->wakeup(); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_redis_server/nested_map.phpt b/tests/swoole_redis_server/nested_map.phpt new file mode 100644 index 0000000000..ffa27ae687 --- /dev/null +++ b/tests/swoole_redis_server/nested_map.phpt @@ -0,0 +1,69 @@ +--TEST-- +swoole_redis_server: nested map +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $redis = new Predis\Client('tcp://127.0.0.1:' . $pm->getFreePort()); + $map = $redis->get('map'); + Assert::notEmpty($map); + Assert::eq($map[0], 'uuid'); + Assert::eq($map[1], UUID); + Assert::isArray($map[3]); + Assert::eq($map[5], NUMBER); + Assert::eq($map[3][1], NUMBER); + Assert::eq($map[3][2], UUID); + + $set = $redis->get('set'); + Assert::notEmpty($set); + Assert::eq($set[0], UUID); + Assert::isArray($set[1]); + Assert::eq($set[2], NUMBER); + Assert::eq($set[1][1], NUMBER); + Assert::eq($set[1][3], UUID); + + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->setHandler('GET', function ($fd, $data) use ($server) { + $key = $data[0]; + if ($key == 'map') { + $out = Server::format(Server::MAP, [ + 'uuid' => UUID, + 'list' => [1, NUMBER, UUID], + 'number' => NUMBER, + ]); + } elseif ($key == 'set') { + $out = Server::format(Server::SET, [ + UUID, + ['number' => NUMBER, 'uuid' => UUID], + NUMBER, + ]); + } else { + $out = Server::format(Server::ERROR, 'bad key'); + } + $server->send($fd, $out); + }); + + $server->on('WorkerStart', function ($server) use ($pm) { + $pm->wakeup(); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_runtime/async_protocol.phpt b/tests/swoole_runtime/async_protocol.phpt new file mode 100644 index 0000000000..64ce27bd68 --- /dev/null +++ b/tests/swoole_runtime/async_protocol.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_runtime: async protocol +--SKIPIF-- + +--FILE-- +bind(__DIR__ . '/test.sock'); + + for ($i = 0; $i < N; $i++) { + $peer = null; + $data = $socket->recvfrom($peer); + echo "[Server] recv : $data\n"; + } +}); + +go(function () { + $fp = stream_socket_client("async.udg://".__DIR__."/test.sock", $errno, $errstr, 30); + if (!$fp) { + echo "$errstr ($errno)
    \n"; + } else { + for ($i = 0; $i < N; $i++) { + fwrite($fp, "hello-{$i}"); + } + fclose($fp); + } +}); +Swoole\Event::wait(); +?> +--EXPECT-- +[Server] recv : hello-0 +[Server] recv : hello-1 +[Server] recv : hello-2 +[Server] recv : hello-3 +[Server] recv : hello-4 diff --git a/tests/swoole_runtime/base.phpt b/tests/swoole_runtime/base.phpt index ef2e31fef0..9f907e3edd 100644 --- a/tests/swoole_runtime/base.phpt +++ b/tests/swoole_runtime/base.phpt @@ -7,7 +7,7 @@ swoole_runtime: base require __DIR__ . '/../include/bootstrap.php'; $server = SwooleTest\CoServer::createTcpGreeting(); $server->run(); -Swoole\Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_SLEEP); +Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL ^ SWOOLE_HOOK_SLEEP); go(function () { usleep(1000); echo '1' . PHP_EOL; @@ -42,7 +42,7 @@ go(function () use ($server) { $server->shutdown(); }); echo '5' . PHP_EOL; -Swoole\Runtime::enableCoroutine(true); // all +Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL); // all go(function () { usleep(5 * 1000); echo 'sleep1' . PHP_EOL; @@ -57,7 +57,7 @@ go(function () use ($server) { }); echo '7' . PHP_EOL; Swoole\Event::wait(); -Swoole\Runtime::enableCoroutine(false); // disable all +Swoole\Runtime::enableCoroutine(0); // disable all ?> --EXPECT-- 1 diff --git a/tests/swoole_runtime/bindto2.phpt b/tests/swoole_runtime/bindto2.phpt new file mode 100644 index 0000000000..2b0fb6b8cb --- /dev/null +++ b/tests/swoole_runtime/bindto2.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_runtime: socket context bindto +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Runtime::enableCoroutine(); + + run(function () use($pm) { + $context = stream_context_create([ + 'socket' => [ + 'bindto' => ':9100', + ], + ]); + + file_get_contents("http://127.0.0.1:{$pm->getFreePort()}", false, $context); + $pm->kill(); + echo "Done\n"; + }); +}; +$pm->childFunc = function () use ($pm) { + run(function () use ($pm) { + $server = new Server("127.0.0.1", $pm->getFreePort(), false); + $server->handle('/', function (Request $request, Response $response) { + Assert::eq($request->server['remote_addr'], '127.0.0.1'); + Assert::eq($request->server['remote_port'], 9100); + $response->end('success'); + }); + $pm->wakeup(); + $server->start(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +Done diff --git a/tests/swoole_runtime/bug_4657.phpt b/tests/swoole_runtime/bug_4657.phpt index ee44c82ae9..9989bb6a9d 100644 --- a/tests/swoole_runtime/bug_4657.phpt +++ b/tests/swoole_runtime/bug_4657.phpt @@ -6,39 +6,20 @@ require __DIR__ . '/../include/skipif.inc'; ?> --FILE-- --EXPECTF-- -object(Socket)#%d (%d) { -} -object(Swoole\Coroutine\Socket)#%d (%d) { - ["fd"]=> - int(%d) - ["domain"]=> - int(%d) - ["type"]=> - int(%d) - ["protocol"]=> - int(%d) - ["errCode"]=> - int(0) - ["errMsg"]=> - string(0) "" -} -bool(true) -object(Socket)#%d (%d) { -} diff --git a/tests/swoole_runtime/bug_5104.phpt b/tests/swoole_runtime/bug_5104.phpt new file mode 100644 index 0000000000..191676a14a --- /dev/null +++ b/tests/swoole_runtime/bug_5104.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_runtime: Github#5104 https://github.com/swoole/swoole-src/issues/5104 +--SKIPIF-- + +--FILE-- + +--EXPECT-- +DONE diff --git a/tests/swoole_runtime/cancel_sleep.phpt b/tests/swoole_runtime/cancel_sleep.phpt new file mode 100644 index 0000000000..e091e43e24 --- /dev/null +++ b/tests/swoole_runtime/cancel_sleep.phpt @@ -0,0 +1,50 @@ +--TEST-- +swoole_runtime: cancel sleep +--SKIPIF-- + +--FILE-- + +--EXPECT-- +success1 +success2 +success3 diff --git a/tests/swoole_runtime/exec/exec.phpt b/tests/swoole_runtime/exec/exec.phpt new file mode 100644 index 0000000000..865c8e77ef --- /dev/null +++ b/tests/swoole_runtime/exec/exec.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_runtime/unsafe: dns +--SKIPIF-- + +--FILE-- +getChildOutput(), 'ping statistics'), 3); +Assert::eq($pm->getChildExitStatus(), 0); +?> +--EXPECTF-- diff --git a/tests/swoole_runtime/file_hook/async_file.phpt b/tests/swoole_runtime/file_hook/async_file.phpt new file mode 100644 index 0000000000..e3ca321692 --- /dev/null +++ b/tests/swoole_runtime/file_hook/async_file.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_runtime/file_hook: async file +--SKIPIF-- + +--FILE-- += 1); + } + Swoole\Runtime::enableCoroutine(false); + Assert::same(md5($content), md5_file(TEST_IMAGE)); +}); +?> +--EXPECT-- diff --git a/tests/swoole_runtime/file_hook/bug_4327.phpt b/tests/swoole_runtime/file_hook/bug_4327.phpt index d776fad870..1eaa823f2c 100644 --- a/tests/swoole_runtime/file_hook/bug_4327.phpt +++ b/tests/swoole_runtime/file_hook/bug_4327.phpt @@ -15,38 +15,46 @@ require __DIR__.'/../../include/bootstrap.php'; Swoole\Runtime::enableCoroutine($flags = SWOOLE_HOOK_ALL); +const __ROOT_DIR = 'tmp/'; + function createDirectories($protocol = "") { $barrier = Barrier::make(); - $first = "$protocol/".rand(0, 1000); - $second = "/".rand(0, 1000); - $third = "/".rand(0, 1000)."/"; + $first = "$protocol/" . __ROOT_DIR . rand(0, 1000); + $second = "/" . rand(0, 1000); + $third = "/" . rand(0, 1000) . "/"; for ($i = 0; $i < 5; $i++) { Coroutine::create(static function () use ($i, $first, $second, $third, $barrier) { - if (!mkdir($directory = $first.$second.$third.$i, 0755, true) && !is_dir($directory)) { + if (!mkdir($directory = $first . $second . $third . $i, 0755, true) && !is_dir($directory)) { throw new Exception("create directory failed"); } rmdir($directory); }); } - echo "SUCCESS".PHP_EOL; + echo "SUCCESS" . PHP_EOL; Barrier::wait($barrier); - rmdir($first.$second.$third); - rmdir($first.$second); + rmdir($first . $second . $third); + rmdir($first . $second); rmdir($first); } - run(function () { createDirectories(); createDirectories("file://"); }); -Swoole\Runtime::enableCoroutine(false); -createDirectories(); -createDirectories("file://"); +if (defined('SWOOLE_THREAD')) { + echo "SUCCESS" . PHP_EOL; + echo "SUCCESS" . PHP_EOL; +} else { + run(function () { + Swoole\Runtime::enableCoroutine(false); + createDirectories(); + createDirectories("file://"); + }); +} ?> --EXPECT-- SUCCESS diff --git a/tests/swoole_runtime/file_hook/co_fread.phpt b/tests/swoole_runtime/file_hook/co_fread.phpt deleted file mode 100644 index e15062caee..0000000000 --- a/tests/swoole_runtime/file_hook/co_fread.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -swoole_runtime/file_hook: fread ---SKIPIF-- - ---FILE-- - ---EXPECT-- -open -read diff --git a/tests/swoole_runtime/file_hook/fgets.phpt b/tests/swoole_runtime/file_hook/fgets.phpt new file mode 100644 index 0000000000..9631b564c1 --- /dev/null +++ b/tests/swoole_runtime/file_hook/fgets.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_runtime/file_hook: fgets +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_runtime/file_hook/fread.phpt b/tests/swoole_runtime/file_hook/fread.phpt new file mode 100644 index 0000000000..a8d45b341c --- /dev/null +++ b/tests/swoole_runtime/file_hook/fread.phpt @@ -0,0 +1,27 @@ +--TEST-- +swoole_runtime/file_hook: fread +--SKIPIF-- + +--FILE-- + +--EXPECT-- +open +read diff --git a/tests/swoole_runtime/file_hook/readdir.phpt b/tests/swoole_runtime/file_hook/readdir.phpt new file mode 100644 index 0000000000..e9f5f75c79 --- /dev/null +++ b/tests/swoole_runtime/file_hook/readdir.phpt @@ -0,0 +1,27 @@ +--TEST-- +swoole_runtime/file_hook: readdir +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_runtime/file_lock/async_file.phpt b/tests/swoole_runtime/file_lock/async_file.phpt new file mode 100644 index 0000000000..1b5056dd08 --- /dev/null +++ b/tests/swoole_runtime/file_lock/async_file.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_runtime/file_lock: async file +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_runtime/file_lock/file_lock_1.phpt b/tests/swoole_runtime/file_lock/file_lock_1.phpt deleted file mode 100644 index dbbf982671..0000000000 --- a/tests/swoole_runtime/file_lock/file_lock_1.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -swoole_runtime/file_lock: file_lock_1 ---SKIPIF-- - ---FILE-- - ---EXPECTF-- diff --git a/tests/swoole_runtime/file_lock/lock_ex.phpt b/tests/swoole_runtime/file_lock/lock_ex.phpt new file mode 100644 index 0000000000..4b3a521956 --- /dev/null +++ b/tests/swoole_runtime/file_lock/lock_ex.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_runtime/file_lock: file_lock_1 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/tests/swoole_runtime/file_lock/lock_sh_1.phpt b/tests/swoole_runtime/file_lock/lock_sh_1.phpt index 6cfa595b50..b9b6e88170 100644 --- a/tests/swoole_runtime/file_lock/lock_sh_1.phpt +++ b/tests/swoole_runtime/file_lock/lock_sh_1.phpt @@ -2,7 +2,6 @@ swoole_runtime/file_lock: lock_sh_1 --SKIPIF-- --FILE-- +--FILE-- + +--EXPECTF-- +DONE diff --git a/tests/swoole_runtime/gethostbyname.phpt b/tests/swoole_runtime/gethostbyname.phpt index d9c7a406a6..1cabaf1d1c 100644 --- a/tests/swoole_runtime/gethostbyname.phpt +++ b/tests/swoole_runtime/gethostbyname.phpt @@ -10,9 +10,12 @@ require __DIR__ . '/../include/bootstrap.php'; $host = 'www.tsinghua.edu.cn'; $ip1 = gethostbyname($host); Swoole\Runtime::enableCoroutine(); -go(function () use($ip1, $host) { +Swoole\Coroutine\run(function () use($ip1, $host) { $ip2 = gethostbyname($host); Assert::same($ip1, $ip2); + + $iplist = gethostbynamel('www.taobao.com'); + Assert::greaterThanEq(count($iplist), 1); }); ?> --EXPECTF-- diff --git a/tests/swoole_runtime/out_of_coroutine.phpt b/tests/swoole_runtime/out_of_coroutine.phpt new file mode 100644 index 0000000000..0895040b0d --- /dev/null +++ b/tests/swoole_runtime/out_of_coroutine.phpt @@ -0,0 +1,16 @@ +--TEST-- +swoole_runtime: out of coroutine +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_runtime/remote_object/dns.phpt b/tests/swoole_runtime/remote_object/dns.phpt new file mode 100644 index 0000000000..be5d388ef1 --- /dev/null +++ b/tests/swoole_runtime/remote_object/dns.phpt @@ -0,0 +1,30 @@ +--TEST-- +swoole_runtime/remote_object: dns +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/tests/swoole_runtime/sleep.phpt b/tests/swoole_runtime/sleep.phpt index 381250ea0b..f60b04e99c 100644 --- a/tests/swoole_runtime/sleep.phpt +++ b/tests/swoole_runtime/sleep.phpt @@ -12,7 +12,11 @@ go(function () { Assert::eq(sleep(1), 0); time_approximate(1, microtime(true) - $s); Assert::eq(sleep(0), 0); - Assert::false(sleep(-1), -1); + try { + sleep(-1); + } catch (Throwable $e) { + Assert::contains($e->getMessage(), 'must be greater than or equal to 0'); + } // usleep $s = microtime(true); @@ -20,10 +24,19 @@ go(function () { usleep($t * 1000 * 1000); time_approximate($t, microtime(true) - $s); usleep(0); - usleep(-1); + try { + usleep(-1); + } catch (Throwable $e) { + Assert::contains($e->getMessage(), 'must be greater than or equal to 0'); + } // time_nanosleep - Assert::false(time_nanosleep(-1, 1)); + try { + time_nanosleep(-1, 1); + } catch (Throwable $e) { + Assert::contains($e->getMessage(), 'must be greater than or equal to 0'); + } + Assert::true(time_nanosleep(0, 1)); Assert::true(time_nanosleep(0, 1000 * 1000)); @@ -40,12 +53,6 @@ echo "\nDONE\n"; --EXPECTF-- NON-BLOCKED -Warning: sleep(): Number of seconds must be greater than or equal to 0 in %s on line %d - -Warning: usleep(): Number of seconds must be greater than or equal to 0 in %s on line %d - -Warning: time_nanosleep(): The seconds value must be greater than 0 in %s on line %d - -Warning: time_sleep_until(): Sleep until to time is less than current time in %s on line %d +Warning: time_sleep_until(): Argument #1 ($timestamp) must be greater than or equal to the current time in %s on line %d DONE diff --git a/tests/swoole_runtime/sockets/basic/mcast_ipv6_send.phpt b/tests/swoole_runtime/sockets/basic/mcast_ipv6_send.phpt index 2582076d72..315ceaa50e 100644 --- a/tests/swoole_runtime/sockets/basic/mcast_ipv6_send.phpt +++ b/tests/swoole_runtime/sockets/basic/mcast_ipv6_send.phpt @@ -13,7 +13,9 @@ $level = IPPROTO_IPV6; $s = socket_create(AF_INET6, SOCK_DGRAM, SOL_UDP) or die("skip Can not create socket"); if (socket_set_option($s, $level, IPV6_MULTICAST_IF, 1) === false) { die("skip interface 1 either doesn't exist or has no ipv6 address"); -}?> +} +skip_if_not_linux(); +?> --FILE-- --EXPECTF-- -Warning: Swoole\Coroutine\Socket::setOption(): setsockopt(4) failed, Error: Permission denied[13] in %s on line %d +Warning: Swoole\Coroutine\Socket::setOption(): setsockopt(%d) failed, Error: Permission denied[13] in %s on line %d --CREDITS-- Moritz Neuhaeuser, info@xcompile.net PHP Testfest Berlin 2009-05-10 diff --git a/tests/swoole_runtime/sockets/basic/socket_set_option_in6_pktinfo.phpt b/tests/swoole_runtime/sockets/basic/socket_set_option_in6_pktinfo.phpt index 2ce3690ea9..40ec1276de 100644 --- a/tests/swoole_runtime/sockets/basic/socket_set_option_in6_pktinfo.phpt +++ b/tests/swoole_runtime/sockets/basic/socket_set_option_in6_pktinfo.phpt @@ -1,7 +1,10 @@ --TEST-- swoole_runtime/sockets/basic: socket_set_option() with IPV6_PKTINFO --SKIPIF-- - + --FILE-- +--FILE-- +\n"; + } else { + $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; + fwrite($fp, $http); + $content = ''; + while (!feof($fp)) { + $content .= fread($fp, 1024); + } + fclose($fp); + Assert::assert(strpos($content,'map.baidu.com') !== false); + } +}); +Swoole\Event::wait(); +?> +--EXPECT-- diff --git a/tests/swoole_runtime/ssl/enable_crypto.phpt b/tests/swoole_runtime/ssl/enable_crypto.phpt new file mode 100644 index 0000000000..8b96ebf7b7 --- /dev/null +++ b/tests/swoole_runtime/ssl/enable_crypto.phpt @@ -0,0 +1,62 @@ +--TEST-- +swoole_runtime/ssl: stream_socket_enable_crypto +--SKIPIF-- + +--FILE-- +\n"; + } else { + $ready->push(true); + $conn = stream_socket_accept($socket); + + fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); + fclose($conn); + fclose($socket); + echo "OK\n"; + } +}); + +go(function () use ($ready) { + $ready->pop(); + + $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); + if (!$fp) { + echo "$errstr ($errno)
    \n"; + } else { + stream_context_set_option($fp, ["ssl" => [ + "local_cert" => SSL_FILE_DIR . '/client.crt', + "local_pk" => SSL_FILE_DIR . '/client.key', + ]]); + // Enable SSL encryption after the connection is established + Assert::assert(stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)); + $data = fread($fp, 8192); + fclose($fp); + Assert::assert(strpos($data, 'local time') !== false); + echo "OK\n"; + } +}); + +Swoole\Event::wait(); +?> +--EXPECT-- +OK +OK diff --git a/tests/swoole_runtime/ssl/server.phpt b/tests/swoole_runtime/ssl/server.phpt new file mode 100644 index 0000000000..35414d40ef --- /dev/null +++ b/tests/swoole_runtime/ssl/server.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_runtime/ssl: ssl server +--SKIPIF-- + +--FILE-- +\n"; + } else { + $ready->push(true); + $conn = stream_socket_accept($socket); + fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); + fclose($conn); + fclose($socket); + echo "OK\n"; + } +}); + +go(function () use ($ready) { + $ready->pop(); + $fp = stream_socket_client("ssl://127.0.0.1:8000", $errno, $errstr, 30); + if (!$fp) { + echo "$errstr ($errno)
    \n"; + } else { + $data = fread($fp, 8192); + fclose($fp); + Assert::assert(strpos($data,'local time') !== false); + echo "OK\n"; + } +}); + +Swoole\Event::wait(); +?> +--EXPECT-- +OK +OK diff --git a/tests/swoole_runtime/ssl_client.phpt b/tests/swoole_runtime/ssl_client.phpt deleted file mode 100644 index f866818ea8..0000000000 --- a/tests/swoole_runtime/ssl_client.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -swoole_runtime: ssl client ---SKIPIF-- - ---FILE-- -\n"; - } else { - $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; - fwrite($fp, $http); - $content = ''; - while (!feof($fp)) { - $content .= fread($fp, 1024); - } - fclose($fp); - Assert::assert(strpos($content,'map.baidu.com') !== false); - } -}); -Swoole\Event::wait(); -?> ---EXPECT-- diff --git a/tests/swoole_runtime/ssl_server.phpt b/tests/swoole_runtime/ssl_server.phpt deleted file mode 100644 index a921cf89e7..0000000000 --- a/tests/swoole_runtime/ssl_server.phpt +++ /dev/null @@ -1,52 +0,0 @@ ---TEST-- -swoole_runtime: ssl server ---SKIPIF-- - ---FILE-- -\n"; - } else { - $ready->push(true); - $conn = stream_socket_accept($socket); - fwrite($conn, 'The local time is ' . date('n/j/Y g:i a')); - fclose($conn); - fclose($socket); - echo "OK\n"; - } -}); - -go(function () use ($ready) { - $ready->pop(); - $fp = stream_socket_client("ssl://127.0.0.1:8000", $errno, $errstr, 30); - if (!$fp) { - echo "$errstr ($errno)
    \n"; - } else { - $data = fread($fp, 8192); - fclose($fp); - Assert::assert(strpos($data,'local time') !== false); - echo "OK\n"; - } -}); - -Swoole\Event::wait(); -?> ---EXPECT-- -OK -OK diff --git a/tests/swoole_runtime/stdin.phpt b/tests/swoole_runtime/stdin.phpt index a14a0565a5..9c8f29a8bc 100644 --- a/tests/swoole_runtime/stdin.phpt +++ b/tests/swoole_runtime/stdin.phpt @@ -8,16 +8,15 @@ require __DIR__ . '/../include/skipif.inc'; -1, ]); +co::set(['socket_read_timeout' => -1]); $proc = new Process(function ($p) { - Co\run(function () use($p) { - $p->write('start'.PHP_EOL); - go(function() { + Co\run(function () use ($p) { + $p->write('start' . PHP_EOL); + go(function () { co::sleep(0.05); echo "sleep\n"; }); @@ -29,12 +28,12 @@ $proc->start(); echo $proc->read(); usleep(100000); -$proc->write('hello world'.PHP_EOL); +$proc->write('hello world' . PHP_EOL); echo $proc->read(); echo $proc->read(); -Process::wait() +Process::wait(); ?> --EXPECT-- start diff --git a/tests/swoole_runtime/stream_copy_to_stream_socket.phpt b/tests/swoole_runtime/stream_copy_to_stream_socket.phpt index 5d9deba168..beb8935953 100644 --- a/tests/swoole_runtime/stream_copy_to_stream_socket.phpt +++ b/tests/swoole_runtime/stream_copy_to_stream_socket.phpt @@ -1,18 +1,23 @@ --TEST-- swoole_runtime: stream_copy_to_stream() with socket as $source --SKIPIF-- - + --FILE-- \n"; + echo "{$errstr} ({$errno})
    \n"; } else { $local = stream_socket_accept($socket); - $remote = stream_socket_client("tcp://www.baidu.com:80", $errno, $errstr, 30, STREAM_CLIENT_CONNECT); + $remote = stream_socket_client('tcp://' . TEST_DOMAIN_3 . ':80', $errno, $errstr, 30, STREAM_CLIENT_CONNECT); go(function () use ($local, $remote) { stream_copy_to_stream($local, $remote); }); @@ -35,16 +40,16 @@ run(function () { }); $fp = stream_socket_client($uri, $errno, $errstr, 30); if (!$fp) { - echo "$errstr ($errno)
    \n"; + echo "{$errstr} ({$errno})
    \n"; } else { - $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Close\r\n\r\n"; + $http = "GET / HTTP/1.0\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: " . TEST_DOMAIN_3 . "\r\nConnection: Close\r\n\r\n"; fwrite($fp, $http); $content = ''; while (!feof($fp)) { $content .= fread($fp, 1024); } fclose($fp); - Assert::assert(strpos($content,'map.baidu.com') !== false); + Assert::assert(strpos($content, 'Location: https://www.gov.cn/') !== false); } echo "DONE\n"; }); diff --git a/tests/swoole_runtime/stream_select/base.phpt b/tests/swoole_runtime/stream_select/base.phpt index 7870b3b26a..00a0f3b76f 100644 --- a/tests/swoole_runtime/stream_select/base.phpt +++ b/tests/swoole_runtime/stream_select/base.phpt @@ -7,13 +7,12 @@ swoole_runtime/stream_select: base require __DIR__ . '/../../include/bootstrap.php'; Swoole\Runtime::enableCoroutine(); go(function () { - Swoole\Runtime::enableCoroutine(); - $fp1 = stream_socket_client("tcp://www.baidu.com:80", $errno, $errstr, 30); - $fp2 = stream_socket_client("tcp://www.qq.com:80", $errno, $errstr, 30); + $fp1 = stream_socket_client("tcp://" . TEST_DOMAIN_1 . ":80", $errno, $errstr, 30); + $fp2 = stream_socket_client("tcp://" . TEST_DOMAIN_2 . ":80", $errno, $errstr, 30); if (!$fp1) { echo "$errstr ($errno)
    \n"; } else { - fwrite($fp1, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); + fwrite($fp1, "GET / HTTP/1.0\r\nHost: " . TEST_DOMAIN_1 . "\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, 10); @@ -28,5 +27,6 @@ go(function () { fclose($fp1); } }); +Swoole\Runtime::enableCoroutine(0); ?> --EXPECT-- diff --git a/tests/swoole_runtime/stream_select/blocked.phpt b/tests/swoole_runtime/stream_select/blocked.phpt index a684299be8..61bd607763 100644 --- a/tests/swoole_runtime/stream_select/blocked.phpt +++ b/tests/swoole_runtime/stream_select/blocked.phpt @@ -6,12 +6,12 @@ swoole_runtime/stream_select: blocked \n"; } else { - fwrite($fp1, "GET / HTTP/1.0\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); + fwrite($fp1, "GET / HTTP/1.0\r\nHost: " . TEST_DOMAIN_1 . "\r\nUser-Agent: curl/7.58.0\r\nAccept: */*\r\n\r\n"); $r_array = [$fp1, $fp2]; $w_array = $e_array = null; $n = stream_select($r_array, $w_array, $e_array, 10); diff --git a/tests/swoole_runtime/stream_select/bug60120.phpt b/tests/swoole_runtime/stream_select/bug60120.phpt index e2b1c04802..7354618495 100644 --- a/tests/swoole_runtime/stream_select/bug60120.phpt +++ b/tests/swoole_runtime/stream_select/bug60120.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_runtime/stream_select: Bug #60120 proc_open hangs with stdin/out with 2048+ bytes --SKIPIF-- - + --FILE-- + --FILE-- = $stdinLen) { @@ -56,14 +63,14 @@ go(function () { $type = array_search($pipe, $pipes); $data = fread($pipe, 8192); var_dump($data); - if (false === $data || feof($pipe)) { + if ($data === false || feof($pipe)) { fclose($pipe); unset($pipes[$type]); } } } }); -Swoole\Event::wait(); +Event::wait(); ?> --EXPECTF-- string(4097) "%s" diff --git a/tests/swoole_runtime/stream_select/conflict.phpt b/tests/swoole_runtime/stream_select/conflict.phpt new file mode 100644 index 0000000000..7e64a56bd1 --- /dev/null +++ b/tests/swoole_runtime/stream_select/conflict.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_runtime/stream_select: conflict +--SKIPIF-- + +--FILE-- + true]); + +$n = new Atomic(1); + +go(function () use ($n) { + $server = stream_socket_server('tcp://0.0.0.0:8000', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); + while ($n->get()) { + $conn = @stream_socket_accept($server, 0.1); + if ($conn) { + go(function () use ($conn) { + Assert::eq(fread($conn, 8192), ''); + fclose($conn); + echo "Server done\n"; + }); + break; + } + } +}); + +go(function () use ($n) { + $fp1 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); + go(function () use ($fp1) { + Co::sleep(0.01); + $read = null; + $write = [$fp1]; + $except = null; + $rs = stream_select($read, $write, $except, 5); + Assert::eq($rs, 0); + stream_socket_shutdown($fp1, STREAM_SHUT_RDWR); + echo "shutdown\n"; + }); + $rs = fread($fp1, 8192); + Assert::eq($rs, ''); + echo "Client done\n"; +}); + +Event::wait(); +?> +--EXPECTF-- +[%s] WARNING ReactorEpoll::add(): [Reactor#0] epoll_ctl(epfd=%d, EPOLL_CTL_ADD, fd=%d, fd_type=%d, events=1024) failed, Error: File exists[17] +shutdown +Server done +Client done diff --git a/tests/swoole_runtime/stream_select/rw_events.phpt b/tests/swoole_runtime/stream_select/rw_events.phpt index 7a7acf604a..fbbb2bdbf5 100644 --- a/tests/swoole_runtime/stream_select/rw_events.phpt +++ b/tests/swoole_runtime/stream_select/rw_events.phpt @@ -1,18 +1,22 @@ --TEST-- swoole_runtime/stream_select: rw events --SKIPIF-- - + --FILE-- get()) { + $server = stream_socket_server('tcp://0.0.0.0:8000', $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN); + while ($n->get()) { $conn = @stream_socket_accept($server, 0.1); if ($conn) { go(function () use ($conn) { @@ -25,11 +29,12 @@ go(function () use ($n) { }); go(function () use ($n) { - $fp1 = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); - $fp2 = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30); + $fp1 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); + $fp2 = stream_socket_client('tcp://127.0.0.1:8000', $errno, $errstr, 30); $r_array = [$fp1, $fp2]; $w_array = [$fp1, $fp2]; $e_array = null; + usleep(1000); $retval = stream_select($r_array, $w_array, $e_array, 10); Assert::same($retval, 2); Assert::same(count($r_array), 2); diff --git a/tests/swoole_runtime/unix.phpt b/tests/swoole_runtime/unix.phpt index eed84dbaa9..4409a76fd1 100644 --- a/tests/swoole_runtime/unix.phpt +++ b/tests/swoole_runtime/unix.phpt @@ -8,42 +8,51 @@ require __DIR__ . '/../include/skipif.inc'; bind(__DIR__ . '/test.sock'); - $socket->listen(); + $socket = new Socket(AF_UNIX, SOCK_STREAM, 0); + Assert::true($socket->bind(SOCK_FILE), 'bind error: ' . $socket->errCode); + Assert::true($socket->listen(), 'listen error: ' . $socket->errCode); $client = $socket->accept(); + Assert::notNull($client); - for ($i = 0; $i < N; $i++) - { + for ($i = 0; $i < N; $i++) { $data = $client->recv(); - $client->send("Swoole: $data"); + $client->send("Swoole: {$data}"); } usleep(1000); }); go(function () { - $fp = stream_socket_client("unix://".__DIR__."/test.sock", $errno, $errstr, 30); + $fp = stream_socket_client('unix://' . SOCK_FILE, $errno, $errstr, 30); if (!$fp) { - echo "$errstr ($errno)
    \n"; + echo "{$errstr} ({$errno})
    \n"; } else { for ($i = 0; $i < N; $i++) { fwrite($fp, "hello-{$i}"); $data = fread($fp, 1024); - list($address) = explode(':', (stream_socket_get_name($fp, true))); + [$address] = explode(':', stream_socket_get_name($fp, true)); $address = basename($address); - echo "[Client] recvfrom[{$address}] : $data\n"; + echo "[Client] recvfrom[{$address}] : {$data}\n"; } fclose($fp); } }); -Swoole\Event::wait(); +Event::wait(); ?> --EXPECT-- [Client] recvfrom[test.sock] : Swoole: hello-0 diff --git a/tests/swoole_server/addProcess_with_error.phpt b/tests/swoole_server/addProcess_with_error.phpt index c1751f6277..f191a071ee 100644 --- a/tests/swoole_server/addProcess_with_error.phpt +++ b/tests/swoole_server/addProcess_with_error.phpt @@ -50,17 +50,54 @@ $pm->run(); --EXPECTF-- sleep start then sleep end -Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line 31 +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end -Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line 31 +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end -Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line 31 +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end -Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line 31 +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d sleep start then sleep end -Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line 31 +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +DONE +--EXPECTF_85-- +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process4->{closure:Process4::run():%d}() +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process4->{closure:Process4::run():%d}() +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process4->{closure:Process4::run():%d}() +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process4->{closure:Process4::run():%d}() +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_error.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process4->{closure:Process4::run():%d}() +#2 {main} DONE diff --git a/tests/swoole_server/addProcess_with_tick.phpt b/tests/swoole_server/addProcess_with_tick.phpt index c18e7c8948..d1aef2752f 100644 --- a/tests/swoole_server/addProcess_with_tick.phpt +++ b/tests/swoole_server/addProcess_with_tick.phpt @@ -70,3 +70,40 @@ sleep start then sleep end Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d DONE +--EXPECTF_85-- +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process3->{closure:Process3::run():%d}(%d) +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process3->{closure:Process3::run():%d}(%d) +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process3->{closure:Process3::run():%d}(%d) +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process3->{closure:Process3::run():%d}(%d) +#2 {main} +sleep start then sleep end + +Fatal error: ERROR in %s/tests/swoole_server/addProcess_with_tick.php on line %d +Stack trace: +#0 %s(%d): trigger_error('ERROR', 256) +#1 [internal function]: Process3->{closure:Process3::run():%d}(%d) +#2 {main} +DONE diff --git a/tests/swoole_server/base/reload_1.phpt b/tests/swoole_server/base/reload_1.phpt new file mode 100644 index 0000000000..af6f3edbd0 --- /dev/null +++ b/tests/swoole_server/base/reload_1.phpt @@ -0,0 +1,78 @@ +--TEST-- +swoole_server/base: reload all workers +--SKIPIF-- + +--FILE-- + new Swoole\Atomic(), + 'task_worker' => new Swoole\Atomic() +]; +$pm = new SwooleTest\ProcessManager; +$pm->parentFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { + while (!file_exists(TEST_PID_FILE)) { + usleep(100 * 1000); + } + $pid = file_get_contents(TEST_PID_FILE); + $random = mt_rand(1, 5); + usleep(100 * 1000); + for ($n = $random; $n--;) { + Swoole\Process::kill($pid, SIGUSR1); + usleep(200 * 1000); + } + + /**@var $counter Swoole\Atomic[] */ + $total = $counter['worker']->get() - $worker_num; + $expect = $random * $worker_num; + Assert::same($total, $expect, "[worker reload {$total} but expect {$expect}]"); + + $total = $counter['task_worker']->get() - $task_worker_num; + $expect = $random * $task_worker_num; + Assert::same($total, $expect, "[task worker reload {$total} but expect {$random}]"); + + $log = file_get_contents(TEST_LOG_FILE); + $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); + if (!Assert::assert(empty($log))) { + var_dump($log); + } + $pm->kill(); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { + @unlink(TEST_LOG_FILE); + @unlink(TEST_PID_FILE); + $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'log_file' => TEST_LOG_FILE, + 'pid_file' => TEST_PID_FILE, + 'worker_num' => $worker_num, + 'task_worker_num' => $task_worker_num, + ]); + $server->on('ManagerStart', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { + /**@var $counter Swoole\Atomic[] */ + global $counter; + $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; + $atomic->add(1); + }); + $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { + }); + $server->on('Task', function () { + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_server/base/reload_2.phpt b/tests/swoole_server/base/reload_2.phpt new file mode 100644 index 0000000000..a94161c4f9 --- /dev/null +++ b/tests/swoole_server/base/reload_2.phpt @@ -0,0 +1,77 @@ +--TEST-- +swoole_server/base: reload task workers +--SKIPIF-- + +--FILE-- + new Swoole\Atomic(), + 'task_worker' => new Swoole\Atomic() +]; +$pm = new SwooleTest\ProcessManager; +$pm->parentFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { + while (!file_exists(TEST_PID_FILE)) { + usleep(100 * 1000); + } + $pid = file_get_contents(TEST_PID_FILE); + $random = mt_rand(1, 5); + usleep(100 * 1000); + for ($n = $random; $n--;) { + Swoole\Process::kill($pid, SIGUSR2); + usleep(200 * 1000); + } + + /**@var $counter Swoole\Atomic[] */ + $total = $counter['worker']->get() - $worker_num; + Assert::same($total, 0, "[worker reload {$total} but expect 0]"); + + $total = $counter['task_worker']->get() - $task_worker_num; + $expect = $random * $task_worker_num; + Assert::same($total, $expect, "[task worker reload {$total} but expect {$expect}]"); + + $log = file_get_contents(TEST_LOG_FILE); + $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); + if (!Assert::assert(empty($log))) { + var_dump($log); + } + $pm->kill(); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm, $counter, $worker_num, $task_worker_num) { + @unlink(TEST_LOG_FILE); + @unlink(TEST_PID_FILE); + $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'log_file' => TEST_LOG_FILE, + 'pid_file' => TEST_PID_FILE, + 'worker_num' => $worker_num, + 'task_worker_num' => $task_worker_num, + ]); + $server->on('ManagerStart', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { + /**@var $counter Swoole\Atomic[] */ + global $counter; + $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; + $atomic->add(1); + }); + $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { + }); + $server->on('Task', function () { + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_server/base/shutdown.phpt b/tests/swoole_server/base/shutdown.phpt new file mode 100644 index 0000000000..034b883481 --- /dev/null +++ b/tests/swoole_server/base/shutdown.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_server/base: shutdown +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $client = new Co\Client(SWOOLE_SOCK_TCP); + Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); + Assert::assert($client->send($pm->getRandomData()) > 0); + }); + $pm->wait(-1); +}; +$pm->childFunc = function () use ($pm) { + $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => mt_rand(2, 4), + 'log_file' => '/dev/null', + ]); + $server->on('start', function () use ($pm) { + echo "START\n"; + }); + $server->on('managerStart', function () use ($pm) { + $pm->wakeup(); + }); + $server->on('receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { + Assert::same($data, $pm->getRandomData()); + $server->shutdown(); + }); + $server->on('shutdown', function () use ($pm) { + echo "SHUTDOWN\n"; + $pm->wakeup(); + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +$pm->expectExitCode(0); +?> +--EXPECT-- +START +SHUTDOWN diff --git a/tests/swoole_server/base/shutdown_single.phpt b/tests/swoole_server/base/shutdown_single.phpt new file mode 100644 index 0000000000..f6b39ec42a --- /dev/null +++ b/tests/swoole_server/base/shutdown_single.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_server/base: shutdown [single process] +--SKIPIF-- + +--FILE-- +initRandomData(1); +$pm->parentFunc = function () use ($pm) { + go(function () use ($pm) { + $client = new Co\Client(SWOOLE_SOCK_TCP); + Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); + Assert::assert($client->send($pm->getRandomData()) > 0); + }); +}; +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); + $server->on('start', function (Server $server) use ($pm) { + echo "START\n"; + Assert::eq($server->getManagerPid(), 0); + Assert::eq($server->getMasterPid(), posix_getpid()); + $pm->wakeup(); + }); + $server->on('receive', function (Server $server, int $fd, int $rid, string $data) use ($pm) { + Assert::same($data, $pm->getRandomData()); + $server->shutdown(); + }); + $server->on('shutdown', function () { + echo "SHUTDOWN\n"; + }); + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +$pm->expectExitCode(0); +?> +--EXPECT-- +START +SHUTDOWN diff --git a/tests/swoole_server/big_udp_packet.phpt b/tests/swoole_server/big_udp_packet.phpt index da677844ae..e9e04bfc4c 100644 --- a/tests/swoole_server/big_udp_packet.phpt +++ b/tests/swoole_server/big_udp_packet.phpt @@ -12,29 +12,24 @@ $port = get_one_free_port(); $pm = new SwooleTest\ProcessManager; -$pm->parentFunc = function ($pid) use ($port) -{ +$pm->parentFunc = function ($pid) use ($port) { $client = new Swoole\Client(SWOOLE_SOCK_UDP, SWOOLE_SOCK_SYNC); - if (!$client->connect('127.0.0.1', $port)) - { + if (!$client->connect('127.0.0.1', $port)) { exit("connect failed\n"); } - $client->send(str_repeat('A', N)); + $client->send(str_repeat('A', N)); $data = $client->recv(); Assert::same(strlen($data), N); Swoole\Process::kill($pid); }; -$pm->childFunc = function () use ($pm, $port) -{ +$pm->childFunc = function () use ($pm, $port) { $serv = new Swoole\Server('127.0.0.1', $port, SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set(['worker_num' => 1, 'log_file' => '/dev/null']); - $serv->on("workerStart", function ($serv) use ($pm) - { + $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); }); - $serv->on('packet', function ($serv, $data, $client) - { + $serv->on('packet', function ($serv, $data, $client) { $serv->sendto($client['address'], $client['port'], str_repeat('B', strlen($data))); }); $serv->start(); diff --git a/tests/swoole_server/bug_11000_01.phpt b/tests/swoole_server/bug_11000_01.phpt index dce4b2901c..fbb59ec447 100644 --- a/tests/swoole_server/bug_11000_01.phpt +++ b/tests/swoole_server/bug_11000_01.phpt @@ -5,6 +5,8 @@ swoole_server: bug_11000_01 --FILE-- childFunc = function () { $port = get_one_free_port(); $serv = new Server(TCP_SERVER_HOST, $port, SWOOLE_PROCESS); - $process = new \Swoole\Process(function ($process) use ($serv) { + $process = new Process(function ($process) use ($serv) { usleep(10000); $stats = $serv->stats(); Assert::isArray($stats); Assert::keyExists($stats, 'connection_num'); Assert::keyExists($stats, 'request_count'); + usleep(200000); $serv->shutdown(); }); $serv->set(['worker_num' => 2, 'log_file' => '/dev/null']); diff --git a/tests/swoole_server/bug_1864.phpt b/tests/swoole_server/bug_1864.phpt index bf0db9a65d..49e7f5d9f2 100644 --- a/tests/swoole_server/bug_1864.phpt +++ b/tests/swoole_server/bug_1864.phpt @@ -12,8 +12,7 @@ const M = 512; use Swoole\Server; $pm = new SwooleTest\ProcessManager; -$pm->parentFunc = function ($pid) use ($pm) -{ +$pm->parentFunc = function ($pid) use ($pm) { function run() { global $pm; @@ -35,14 +34,14 @@ $pm->parentFunc = function ($pid) use ($pm) } } } - for ($i = 0; $i < N; $i ++) { + + for ($i = 0; $i < N; $i++) { run(); } $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $ss = [ 'daemonize' => 0, 'dispatch_mode' => 1, @@ -66,14 +65,12 @@ $pm->childFunc = function () use ($pm) $tcp->set($ss); $tcp->on('receive', function (Server $server, $fd, $reactorID, $data) use ($status) { $size = unpack('N', substr($data, 4, 4))[1]; - if ($size !== strlen($data) - 8) - { + if ($size !== strlen($data) - 8) { $server->shutdown(); $status->set(1); } }); - $tcp->on("WorkerStart", function (Server $serv) use ($pm) - { + $tcp->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); $tcp->on("shutdown", function (Server $serv) use ($status) { diff --git a/tests/swoole_server/bug_2308.phpt b/tests/swoole_server/bug_2308.phpt index 233635d4dc..253bb68def 100644 --- a/tests/swoole_server/bug_2308.phpt +++ b/tests/swoole_server/bug_2308.phpt @@ -19,10 +19,11 @@ $pm->childFunc = function () use ($pm) { 'worker_num' => MAX_PROCESS_NUM, 'log_file' => '/dev/null', 'enable_coroutine' => false, + 'hook_flags' => SWOOLE_HOOK_ALL, ]); $server->on('start', function () { Swoole\Coroutine::create(function () { - $redis = new Swoole\Coroutine\Redis(); + $redis = new \Redis(); $redis->connect(REDIS_SERVER_HOST, REDIS_SERVER_PORT); $ret = $redis->set('foo', 'bar'); Assert::assert($ret); diff --git a/tests/swoole_server/bug_2639.phpt b/tests/swoole_server/bug_2639.phpt index 9d74aa7ab8..425d75ba90 100644 --- a/tests/swoole_server/bug_2639.phpt +++ b/tests/swoole_server/bug_2639.phpt @@ -15,8 +15,8 @@ $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set([ - 'ssl_cert_file' => dirname(__DIR__) . '/include/api/ssl-ca/client-cert.pem', - 'ssl_key_file' => dirname(__DIR__) . '/include/api/ssl-ca/client-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem', ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); @@ -37,12 +37,12 @@ $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', - 'ssl_cert_file' => dirname(__DIR__) . '/include/api/ssl-ca/server-cert.pem', - 'ssl_key_file' => dirname(__DIR__) . '/include/api/ssl-ca/server-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, 'task_worker_num' => 1, - 'ssl_client_cert_file' => dirname(__DIR__) . '/include/api/ssl-ca/ca-cert.pem', + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); diff --git a/tests/swoole_server/bug_2736.phpt b/tests/swoole_server/bug_2736.phpt index e159a28cb8..51a76f4cca 100644 --- a/tests/swoole_server/bug_2736.phpt +++ b/tests/swoole_server/bug_2736.phpt @@ -37,8 +37,8 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ - 'ssl_cert_file' => dirname(__DIR__) . '/include/api/ssl-ca/server-cert.pem', - 'ssl_key_file' => dirname(__DIR__) . '/include/api/ssl-ca/server-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'open_length_check' => true, 'package_max_length' => 2097152, 'package_length_type' => 'N', diff --git a/tests/swoole_server/bug_aio.phpt b/tests/swoole_server/bug_aio.phpt index 84319992f0..7d16e24a8d 100644 --- a/tests/swoole_server/bug_aio.phpt +++ b/tests/swoole_server/bug_aio.phpt @@ -22,6 +22,7 @@ $pm->childFunc = function () use ($pm) { Assert::same(Co::readFile(__FILE__), __FILE_CONTENTS__); echo 'read file ok' . PHP_EOL; $pm->wakeup(); + usleep(100000); $server->shutdown(); }); $server->on('Receive', function () { diff --git a/tests/swoole_server/close_max_fd.phpt b/tests/swoole_server/close_max_fd.phpt index eb8dda2694..c8491f6a4b 100644 --- a/tests/swoole_server/close_max_fd.phpt +++ b/tests/swoole_server/close_max_fd.phpt @@ -14,11 +14,11 @@ $pm->parentFunc = function () use ($pm) { go(function() use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); - Assert::assert($client->send('test')); + Assert::assert($client->send('test 1')); $client->recv(); - Co::sleep(2); - $client->send('ping'); - Co::sleep(2); + Co::sleep(1); + $client->send('ping 1'); + Co::sleep(1); $pm->kill(); }); go(function() use ($pm) { @@ -27,60 +27,55 @@ $pm->parentFunc = function () use ($pm) { go(function() use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); - $client->send('test2'); + $client->send('test 2'); + Co::sleep(1); }); }); }; $pm->childFunc = function () use ($pm) { + $log_file = __DIR__ . '/close_max_fd.log'; + $fp = fopen($log_file, 'a'); $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'log_level' => SWOOLE_LOG_ERROR, ]); - $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { - var_dump($data); - var_dump($serv->getClientList()); + $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) use ($fp) { + fputs($fp, "recv: $data\n"); + fputs($fp, 'getClientList: ' . implode(';', $serv->getClientList()) . "\n"); Assert::true(!empty($serv->getClientList())); foreach ($serv->connections as $_fd) { - var_dump("fd:{$_fd}"); + fputs($fp, "foreach: fd-{$_fd}\n"); } $serv->send($fd, "Server: " . $data); }); - $server->on('Close', function ($server, $fd) { - echo "Client:{$fd} Close.\n"; + $server->on('close', function ($server, $fd) use ($fp) { + fputs($fp, "close: fd-{$fd}\n"); }); $server->start(); + + fclose($fp); + $cnt = file_get_contents($log_file); + + Assert::eq(substr_count($cnt, 'recv: test 1'), 1); + Assert::eq(substr_count($cnt, 'recv: test 2'), 1); + Assert::eq(substr_count($cnt, 'recv: ping 1'), 1); + + Assert::eq(substr_count($cnt, 'close: fd-2'), 1); + Assert::eq(substr_count($cnt, 'close: fd-1'), 1); + + Assert::eq(substr_count($cnt, 'foreach: fd-1'), 3); + Assert::eq(substr_count($cnt, 'foreach: fd-2'), 2); + + Assert::eq(substr_count($cnt, 'getClientList: 1;2'), 2); + Assert::eq(substr_count($cnt, "getClientList: 1\n"), 1); + + unlink($log_file); }; $pm->childFirst(); $pm->run(); ?> --EXPECT-- -string(4) "test" -array(2) { - [0]=> - int(1) - [1]=> - int(2) -} -string(4) "fd:1" -string(4) "fd:2" -string(5) "test2" -array(2) { - [0]=> - int(1) - [1]=> - int(2) -} -string(4) "fd:1" -string(4) "fd:2" -Client:2 Close. -string(4) "ping" -array(1) { - [0]=> - int(1) -} -string(4) "fd:1" -Client:1 Close. diff --git a/tests/swoole_server/dispatch_mode_10.phpt b/tests/swoole_server/dispatch_mode_10.phpt index 7f2940af73..f95929233e 100644 --- a/tests/swoole_server/dispatch_mode_10.phpt +++ b/tests/swoole_server/dispatch_mode_10.phpt @@ -19,7 +19,7 @@ $table = new Table(64); $table->column('count', Table::TYPE_INT); $table->create(); -const N = 1024; +const N = IS_MAC_OS ? 256 : 1024; const EOF = "\r\n\r\n"; $pm = new SwooleTest\ProcessManager; diff --git a/tests/swoole_server/dispatch_mode_3.phpt b/tests/swoole_server/dispatch_mode_3.phpt index ebb8b801f9..a91e2212c4 100644 --- a/tests/swoole_server/dispatch_mode_3.phpt +++ b/tests/swoole_server/dispatch_mode_3.phpt @@ -4,25 +4,27 @@ swoole_server: dispatch_mode = 3 --FILE-- parentFunc = function ($pid) use ($port) { global $count, $stats; for ($i = 0; $i < MAX_CONCURRENCY; $i++) { @@ -55,7 +57,7 @@ $pm->parentFunc = function ($pid) use ($port) { }); } Event::wait(); - Swoole\Process::kill($pid); + Process::kill($pid); phpt_var_dump($stats); Assert::eq(count($stats), WORKER_N); Assert::lessThan($stats[5], MAX_REQUESTS); @@ -66,14 +68,14 @@ $pm->parentFunc = function ($pid) use ($port) { $pm->childFunc = function () use ($pm, $port, $barrier) { $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); - $serv->set(array( + $serv->set([ 'worker_num' => WORKER_N, 'dispatch_mode' => 3, 'package_eof' => "\r\n\r\n", 'enable_coroutine' => false, 'open_eof_split' => true, 'log_file' => '/dev/null', - )); + ]); $serv->on('WorkerStart', function (Server $serv) use ($pm, $barrier) { if ($barrier->add() == WORKER_N) { $pm->wakeup(); diff --git a/tests/swoole_server/enable_coroutine.phpt b/tests/swoole_server/enable_coroutine.phpt index aff2ea7dac..67cacb2def 100644 --- a/tests/swoole_server/enable_coroutine.phpt +++ b/tests/swoole_server/enable_coroutine.phpt @@ -6,20 +6,30 @@ swoole_server: reload with enable_coroutine parentFunc = function () use ($pm) { - go(function () use ($pm) { + $list = []; + go(function () use (&$list, $pm) { for ($i = 2; $i--;) { for ($n = 5; $n--;) { - echo "cid-" . httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/task?n={$n}") . "\n"; + echo 'cid-' . httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/task?n={$n}") . "\n"; } if ($i == 1) { - Swoole\Process::kill(file_get_contents(TEST_PID_FILE), SIGUSR1); - usleep(100 * 1000); + Process::kill(file_get_contents(TEST_PID_FILE), SIGUSR1); + $pm->wait(); } } }); - Swoole\Event::wait(); + Event::wait(); $pm->kill(); echo "DONE\n"; }; @@ -30,11 +40,12 @@ $pm->childFunc = function () use ($pm) { 'pid_file' => TEST_PID_FILE, 'worker_num' => 1, ]); - $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { + $server->on('WorkerStart', function (Server $server, int $worker_id) use ($pm) { $pm->wakeup(); }); - $server->on('request', function (\Swoole\Http\Request $request, \Swoole\Http\Response $response) { - $response->end(\Co::getuid()); + $server->on('request', function (Request $request, Response $response) { + System::sleep(0.01); + $response->end(co::getuid()); }); $server->start(); }; diff --git a/tests/swoole_server/enable_delay_receive.phpt b/tests/swoole_server/enable_delay_receive.phpt index 6b0bafa9d5..19e9ac30b3 100644 --- a/tests/swoole_server/enable_delay_receive.phpt +++ b/tests/swoole_server/enable_delay_receive.phpt @@ -14,7 +14,7 @@ $pm->parentFunc = function () use ($pm) { Assert::assert($client->send('world')); $s = microtime(true); Assert::eq($client->recv(), "hello world"); - time_approximate(0.3, microtime(true) - $s); + time_approximate(0.3, microtime(true) - $s, 0.5); $pm->kill(); }); }; diff --git a/tests/swoole_server/enable_reuse_port.phpt b/tests/swoole_server/enable_reuse_port.phpt index 1f4f86c68c..e654771387 100644 --- a/tests/swoole_server/enable_reuse_port.phpt +++ b/tests/swoole_server/enable_reuse_port.phpt @@ -8,7 +8,7 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Server; -const N = IS_IN_TRAVIS ? 32 : 128; +const N = IS_IN_CI ? 32 : 128; const W = 4; $pm = new SwooleTest\ProcessManager; diff --git a/tests/swoole_server/eof_server.phpt b/tests/swoole_server/eof_server.phpt index eb0ff555b6..a588bc21ea 100644 --- a/tests/swoole_server/eof_server.phpt +++ b/tests/swoole_server/eof_server.phpt @@ -6,73 +6,64 @@ swoole_server: eof server parentFunc = function ($pid) use ($pm) -{ - $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) - { - echo "Over flow. errno=" . $client->errCode; - die("\n"); +$pm->parentFunc = function ($pid) use ($pm) { + $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + if (!$client->connect('127.0.0.1', $pm->getFreePort(), 0.5, 0)) { + echo 'Over flow. errno=' . $client->errCode; + exit("\n"); } - $data = array( + $data = [ 'name' => __FILE__, 'sid' => 1000236, 'content' => str_repeat('A', 8192 * rand(1, 3)), - ); + ]; - $_serialize_data = serialize($data). "\r\n\r\n";; + $_serialize_data = serialize($data) . "\r\n\r\n"; $chunk_size = 2048; $len = strlen($_serialize_data); $chunk_num = intval($len / $chunk_size) + 1; - for ($i = 0; $i < $chunk_num; $i++) - { - if ($len < ($i + 1) * $chunk_size) - { + for ($i = 0; $i < $chunk_num; $i++) { + if ($len < ($i + 1) * $chunk_size) { $sendn = $len - ($i * $chunk_size); - } - else - { + } else { $sendn = $chunk_size; } $client->send(substr($_serialize_data, $i * $chunk_size, $sendn)); usleep(10000); } echo $client->recv(); - Swoole\Process::kill($pid); + Process::kill($pid); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $serv->set(array( + $serv->set([ 'package_eof' => "\r\n\r\n", 'open_eof_check' => true, 'open_eof_split' => true, 'dispatch_mode' => 3, - 'package_max_length' => 1024 * 1024 * 2, //2M - "worker_num" => 1, + 'package_max_length' => 1024 * 1024 * 2, // 2M + 'worker_num' => 1, 'log_file' => '/dev/null', - )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $_data = unserialize(rtrim($data)); - if ($_data and is_array($_data) and $_data['sid'] == 1000236) - { - $serv->send($fd, "SUCCESS"); - } - else - { - $serv->send($fd, "ERROR"); + if ($_data and is_array($_data) and $_data['sid'] == 1000236) { + $serv->send($fd, 'SUCCESS'); + } else { + $serv->send($fd, 'ERROR'); } }); $serv->start(); diff --git a/tests/swoole_server/event/worker_exit.phpt b/tests/swoole_server/event/worker_exit.phpt index 5ede55edd0..3c4b406cee 100644 --- a/tests/swoole_server/event/worker_exit.phpt +++ b/tests/swoole_server/event/worker_exit.phpt @@ -6,13 +6,14 @@ swoole_server/event: onWorkerExit setWaitTimeout(5); const FILE = __DIR__ . '/tmp_result.txt'; @@ -36,18 +37,18 @@ $pm->childFunc = function () use ($pm, $atomic) { 'log_file' => '/dev/null', ]); - $serv->on("start", function (Server $serv) use ($atomic, $pm) { + $serv->on('start', function (Server $serv) use ($atomic, $pm) { $pm->writeLog('master start'); }); $serv->on(Constant::EVENT_MANAGER_START, function (Server $serv) use ($atomic, $pm) { - usleep(1000); + usleep(10000); $pm->writeLog('manager start'); }); $serv->on(Constant::EVENT_WORKER_START, function (Server $serv) use ($atomic, $pm) { if ($atomic->get() == 0) { - usleep(2000); + usleep(20000); } $pm->writeLog('worker start, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus()); @@ -55,9 +56,10 @@ $pm->childFunc = function () use ($pm, $atomic) { usleep(10000); $serv->shutdown(); } else { - $serv->timer = Timer::tick(100, function () use ($serv, $pm) { + $GLOBALS['timer'] = Timer::tick(100, function () use ($serv, $pm) { $pm->writeLog( - 'tick, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus()); + 'tick, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus() + ); $pm->wakeup(); }); } @@ -65,15 +67,16 @@ $pm->childFunc = function () use ($pm, $atomic) { $serv->on(Constant::EVENT_WORKER_EXIT, function (Server $serv) use ($atomic, $pm) { $pm->writeLog( - 'worker exit, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus()); - Timer::clear($serv->timer); + 'worker exit, id=' . $serv->getWorkerId() . ', status=' . $serv->getWorkerStatus() + ); + Timer::clear($GLOBALS['timer']); }); $serv->on(Constant::EVENT_WORKER_STOP, function (Server $serv) use ($pm) { $pm->writeLog('worker stop'); }); - $serv->on("Receive", function () { }); + $serv->on('Receive', function () {}); $serv->start(); }; diff --git a/tests/swoole_server/force_reload.phpt b/tests/swoole_server/force_reload.phpt index cdee75c09b..c0ff39a971 100644 --- a/tests/swoole_server/force_reload.phpt +++ b/tests/swoole_server/force_reload.phpt @@ -11,17 +11,22 @@ if (swoole_cpu_num() === 1) { parentFunc = function ($pid) use ($pm) { $n = WORKER_NUM; $clients = []; while ($n--) { - $client = new Swoole\Client(SWOOLE_SOCK_TCP); + $client = new Client(SWOOLE_SOCK_TCP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } @@ -30,26 +35,28 @@ $pm->parentFunc = function ($pid) use ($pm) { } switch_process(); // reload - Swoole\Process::kill($pid, SIGUSR1); + Process::kill($pid, SIGUSR1); sleep(3); $pm->kill(); }; $pm->childFunc = function () use ($pm, $atomic) { - $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => WORKER_NUM, 'max_wait_time' => 1, 'enable_coroutine' => false, ]); - $server->on('workerStart', function (Swoole\Server $server, $worker_id) use ($pm, $atomic) { + $server->on('workerStart', function (Server $server, $worker_id) use ($pm, $atomic) { $atomic->add(1); if ($atomic->get() === WORKER_NUM) { $pm->wakeup(); } }); $server->on('receive', function ($serv, $fd, $tid, $data) { - sleep(100); + while (true) { + sleep(100); + } }); $server->start(); }; @@ -60,12 +67,11 @@ Assert::eq($atomic->get(), WORKER_NUM * 2); ?> --EXPECTF-- [%s] INFO Server is reloading all workers now -[%s] WARNING Manager::kill_timeout_process() (ERRNO 9101): worker(pid=%d, id=%d) exit timeout, force kill the process -[%s] WARNING Manager::kill_timeout_process() (ERRNO 9101): worker(pid=%d, id=%d) exit timeout, force kill the process -[%s] WARNING Manager::kill_timeout_process() (ERRNO 9101): worker(pid=%d, id=%d) exit timeout, force kill the process -[%s] WARNING Manager::kill_timeout_process() (ERRNO 9101): worker(pid=%d, id=%d) exit timeout, force kill the process -[%s] WARNING Server::check_worker_exit_status(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 -[%s] WARNING Server::check_worker_exit_status(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 -[%s] WARNING Server::check_worker_exit_status(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 -[%s] WARNING Server::check_worker_exit_status(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 -[%s] INFO Server is shutdown now +[%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) +[%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) +[%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) +[%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) +[%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 +[%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 +[%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 +[%s] WARNING Worker::report_error(): worker(pid=%d, id=%d) abnormal exit, status=0, signal=9 diff --git a/tests/swoole_server/force_reload2.phpt b/tests/swoole_server/force_reload2.phpt index 3ac58630ce..c1258ddf9e 100644 --- a/tests/swoole_server/force_reload2.phpt +++ b/tests/swoole_server/force_reload2.phpt @@ -7,42 +7,49 @@ swoole_server: force reload in base mode error_reporting(0); require __DIR__ . '/../include/bootstrap.php'; +use Swoole\Atomic; use Swoole\Server; use Swoole\Timer; +use SwooleTest\ProcessManager; -$atomic = new Swoole\Atomic(1); -$pm = new SwooleTest\ProcessManager; +$atomic = new Atomic(1); +$pm = new ProcessManager(); +$pm->setWaitTimeout(1000); $pm->parentFunc = function ($pid) use ($pm) { - sleep(2); $pm->kill(); }; -$pm->childFunc = function () use ($pm,$atomic) { - $flag = 0; - $flag1 = 0; +$pm->childFunc = function () use ($pm, $atomic) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set([ - "worker_num" => 2, - "max_wait_time" => 1, + 'worker_num' => 2, + 'max_wait_time' => 1, 'enable_coroutine' => false, ]); - $serv->on("WorkerStart", function (Server $server, $worker_id) use ($pm, $atomic) { - $pm->wakeup(); - echo "$worker_id [".$server->worker_pid."] start \n"; - if ($worker_id == 0 and $atomic->get() == 1) { - $flag = 1; - sleep(10); + $serv->on('WorkerStart', function (Server $server, $worker_id) use ($pm, $atomic) { + echo "{$worker_id} [" . $server->worker_pid . "] start\n"; + if ($worker_id == 0) { + if ($atomic->get() == 1) { + while (true) { + sleep(10); + } + } else { + $pm->wakeup(); + } } + if ($worker_id == 1 and $atomic->get() == 1) { Timer::after(1, function () use ($server, $worker_id, $atomic) { $atomic->add(1); - echo "$worker_id [" . $server->worker_pid . "] start to reload\n"; + echo "{$worker_id} [" . $server->worker_pid . "] reload\n"; $server->reload(); }); } }); - $serv->on('receive', function ($serv, $fd, $tid, $data) { + $serv->on('WorkerStop', function (Server $server, $worker_id) use ($pm, $atomic) { + echo "{$worker_id} [" . $server->worker_pid . "] stop\n"; }); + $serv->on('receive', function ($serv, $fd, $tid, $data) {}); $serv->start(); }; @@ -50,14 +57,14 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -%s -%s -1 [%s] start to reload -[%s] INFO reload workers -[%s] WARNING ProcessPool::kill_timeout_worker(): force kill worker process(pid=%d, id=%d) -[%s] WARNING ProcessPool::kill_timeout_worker(): force kill worker process(pid=%d, id=%d) -[%s] WARNING ProcessPool::wait(): worker#%d abnormal exit, status=0, signal=9 -[%s] WARNING ProcessPool::wait(): worker#%d abnormal exit, status=0, signal=9 -%s -%s -[%s] INFO Server is shutdown now +%d [%d] start +%d [%d] start +%d [%d] reload +[%s] INFO Server is reloading all workers now +%d [%d] stop +%d [%d] start +[%s] WARNING ReloadTask::kill_all(): force kill worker process(pid=%d, id=%d) +[%s] WARNING Worker::report_error(): worker(pid=%d, id=0) abnormal exit, status=0, signal=9 +%d [%d] start +%d [%d] stop +%d [%d] stop diff --git a/tests/swoole_server/force_reload4.phpt b/tests/swoole_server/force_reload4.phpt index 064543708b..2867bd845d 100644 --- a/tests/swoole_server/force_reload4.phpt +++ b/tests/swoole_server/force_reload4.phpt @@ -40,6 +40,5 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -[%s] INFO Server is shutdown now [%s] WARNING %s (ERRNO 9101): worker exit timeout, forced termination OK diff --git a/tests/swoole_server/getClientInfo_in_callback_of_close.phpt b/tests/swoole_server/getClientInfo_in_callback_of_close.phpt new file mode 100644 index 0000000000..10e9a80d05 --- /dev/null +++ b/tests/swoole_server/getClientInfo_in_callback_of_close.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_server: getClientInfo in callback of close event +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { + exit("connect failed\n"); + } + $client->send("close"); + $data = $client->recv(); + Assert::eq($data, ""); + echo "DONE\n"; + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, + 'log_file' => TEST_LOG_FILE, + ]); + $serv->on('workerStart', function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { + $serv->close($fd); + }); + $serv->on('close', function (Server $serv, $fd, $wid) { + $info = $serv->getClientInfo($fd); + Assert::isArray($info); + Assert::eq($info['close_errno'], 0); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_server/getClientInfo_in_callback_of_close_2.phpt b/tests/swoole_server/getClientInfo_in_callback_of_close_2.phpt new file mode 100644 index 0000000000..21d0364a17 --- /dev/null +++ b/tests/swoole_server/getClientInfo_in_callback_of_close_2.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_server: getClientInfo in callback of close event [2] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP ); + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { + exit("connect failed\n"); + } + $client->close(); + echo "DONE\n"; + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, + 'log_file' => TEST_LOG_FILE, + ]); + $serv->on('workerStart', function ($serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('receive', function (Server $serv, $fd, $reactor_id, $data) { + $serv->close($fd); + }); + $serv->on('close', function (Server $serv, $fd, $wid) { + $info = $serv->getClientInfo($fd); + Assert::isArray($info); + Assert::eq($info['close_errno'], 0); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_server/heartbeat_with_base.phpt b/tests/swoole_server/heartbeat_with_base.phpt index c984de02a5..a067505c6b 100644 --- a/tests/swoole_server/heartbeat_with_base.phpt +++ b/tests/swoole_server/heartbeat_with_base.phpt @@ -8,13 +8,13 @@ skip_if_in_valgrind(); --FILE-- parentFunc = function ($pid) use ($pm) -{ +$pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) - { + if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { echo "Over flow. errno=" . $client->errCode; die("\n"); } @@ -25,19 +25,17 @@ $pm->parentFunc = function ($pid) use ($pm) Swoole\Process::kill($pid); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( 'heartbeat_check_interval' => 1, 'heartbeat_idle_time' => 2, + 'worker_num' => 1, )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); }; diff --git a/tests/swoole_server/invalid_fd.phpt b/tests/swoole_server/invalid_fd.phpt index fb4ab39b0c..7681042c50 100644 --- a/tests/swoole_server/invalid_fd.phpt +++ b/tests/swoole_server/invalid_fd.phpt @@ -12,10 +12,8 @@ $pm->parentFunc = function () use ($pm) { go(function () use ($pm) { $client = new Co\Client(SWOOLE_SOCK_TCP); Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); - Assert::assert($client->send('null' . EOF)); - Assert::assert($client->send('-1' . EOF)); - Assert::assert($client->send('100' . EOF)); - Assert::assert($client->send(PHP_INT_MAX . EOF)); + Assert::notEmpty($client->send("TEST" . EOF)); + Assert::notEmpty($client->recv()); switch_process(); $pm->kill(); }); @@ -31,9 +29,11 @@ $pm->childFunc = function () use ($pm) { $pm->wakeup(); }); $server->on('receive', function (Swoole\Server $serv, int $fd, int $rid, string $data) { - $to_fd = null; - eval("\$to_fd = ${data};"); - Assert::false($serv->send($to_fd, "hello {$fd}" . EOF)); + Assert::false($serv->send(null, "hello {$fd}")); + Assert::false($serv->send(-1, "hello {$fd}")); + Assert::false($serv->send(100, "hello {$fd}")); + Assert::false($serv->send(PHP_INT_MAX, "hello {$fd}")); + Assert::true($serv->send($fd, "DONE\n")); }); $server->start(); }; diff --git a/tests/swoole_server/invalid_option.phpt b/tests/swoole_server/invalid_option.phpt index 9c7e040497..8cd5b434dd 100644 --- a/tests/swoole_server/invalid_option.phpt +++ b/tests/swoole_server/invalid_option.phpt @@ -23,7 +23,7 @@ try { ?> --EXPECTF-- -Warning: unsupported option [invalid_option] in @swoole-src/library/core/Server/Helper.php on line %d +Warning: unsupported option [invalid_option] in @swoole/library/core/Server/Helper.php on line %d %A %A %A diff --git a/tests/swoole_server/max_concurrency.phpt b/tests/swoole_server/max_concurrency.phpt deleted file mode 100644 index 19faebee37..0000000000 --- a/tests/swoole_server/max_concurrency.phpt +++ /dev/null @@ -1,74 +0,0 @@ ---TEST-- -swoole_server: max_concurrency ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) { - for ($i=0; $i < 5; $i++) { - go(function () use ($pm, $i) { - $client = new Client(SWOOLE_SOCK_TCP); - $client->set([ - "open_eof_check" => true, - "open_eof_split" => true, - "package_eof" => "\r\n\r\n", - ]); - $r = $client->connect('127.0.0.1', $pm->getFreePort(), -1); - $data = "$i\r\n\r\n"; - $client->send($data); - $ret = $client->recv(); - var_dump(trim($ret)); - $client->close(); - }); - } - - Event::wait(); - - Swoole\Process::kill($pid); -}; - -$pm->childFunc = function () use ($pm) -{ - Co::set(['max_concurrency' => 1]); - $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $serv->set([ - 'worker_num' => 1, - 'dispatch_mode' => 1, - 'open_eof_split' => true, - 'package_eof' => "\r\n\r\n", - 'log_file' => '/dev/null', - ]); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { - $pm->wakeup(); - }); - $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) - { - global $count; - $count = 0; - co::sleep(0.05); - $count += 1; - $serv->send($fd, "$count\r\n\r\n"); - }); - $serv->start(); -}; - -$pm->childFirst(); -$pm->run(); - -?> ---EXPECT-- -string(1) "1" -string(1) "1" -string(1) "1" -string(1) "1" -string(1) "1" diff --git a/tests/swoole_server/max_idle_time_1.phpt b/tests/swoole_server/max_idle_time_1.phpt index 9c335e4c1e..c637eb5595 100644 --- a/tests/swoole_server/max_idle_time_1.phpt +++ b/tests/swoole_server/max_idle_time_1.phpt @@ -30,7 +30,7 @@ $pm->parentFunc = function ($pid) use ($pm, $time1, $time2) { $s = microtime(true); sleep(1); usleep(200000); - Assert::greaterThan($time2->get() - $time1->get(), 1000); + Assert::greaterThanEq($time2->get() - $time1->get(), 1000); $result = ''; while(true) { $data = $client->recv(); @@ -39,7 +39,7 @@ $pm->parentFunc = function ($pid) use ($pm, $time1, $time2) { } $result .= $data; } - Assert::greaterThan(strlen($result), 65536); + Assert::greaterThanEq(strlen($result), 8192); $pm->kill(); }; diff --git a/tests/swoole_server/max_idle_time_2.phpt b/tests/swoole_server/max_idle_time_2.phpt index c3247ed0fb..7d11301fb4 100644 --- a/tests/swoole_server/max_idle_time_2.phpt +++ b/tests/swoole_server/max_idle_time_2.phpt @@ -25,7 +25,7 @@ $pm->parentFunc = function ($pid) use ($pm, $time1, $time2) { } sleep(1); usleep(200000); - Assert::greaterThan($time2->get() - $time1->get(), 1000); + Assert::greaterThanEq($time2->get() - $time1->get(), 1000); $data = $client->recv(); Assert::isEmpty($data); $pm->kill(); diff --git a/tests/swoole_server/memory_leak/length.phpt b/tests/swoole_server/memory_leak/length.phpt new file mode 100644 index 0000000000..4464a36ff7 --- /dev/null +++ b/tests/swoole_server/memory_leak/length.phpt @@ -0,0 +1,124 @@ +--TEST-- +swoole_server/memory_leak: length +--SKIPIF-- + +--FILE-- +setWaitTimeout(-1); + +$pm->parentFunc = function ($pid) use ($pm, $chunks, $total) { + $clients = []; + for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { + go(function () use ($pm, $i, $chunks, &$clients, $total) { + $cli = new Client(SWOOLE_SOCK_TCP); + $cli->set([ + 'open_length_check' => true, + 'package_max_length' => 4 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + ]); + if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { + echo "ERROR\n"; + return; + } + $count = 0; + foreach ($chunks as $data) { + $count += $cli->send($data); + usleep(10); + } + Assert::eq($count, $total); + $clients[] = $cli; + }); + } + Swoole\Event::wait(); + $pm->wait(); + $pm->kill(); +}; + +phpt_var_dump( + 'total all: ' . number_format(MAX_CONCURRENCY_MID * $total) . + ', n packets: ' . MAX_REQUESTS . + ', n clients: ' . MAX_CONCURRENCY_MID . + ', total: ' . number_format($total) +); + +$pm->childFunc = function () use ($pm, $counter1, $total, $counter2) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set(array( + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'open_length_check' => true, + 'package_max_length' => 4 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + )); + $serv->on("WorkerStart", function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('connect', function (Server $serv, $fd, $rid) { + $GLOBALS['bytes_' . $fd] = 0; + $GLOBALS['count_' . $fd] = 0; + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm, $counter1, $total, $counter2) { + if ($counter1->get() == 0) { + $GLOBALS['memory_usage_1'] = memory_get_usage(); + } + $counter1->add(strlen($data)); + $counter2->add(); + $GLOBALS['bytes_' . $fd] += strlen($data); + $GLOBALS['count_' . $fd]++; + + if ($GLOBALS['count_' . $fd] == MAX_REQUESTS) { + phpt_var_dump( + 'bytes: ' . number_format($counter1->get()) . + ', count: ' . $counter2->get() . + ', data: ' . strlen($data) . + ', client bytes: ' . number_format($GLOBALS['bytes_' . $fd]) . + ', client count: ' . $GLOBALS['count_' . $fd] + ); + } + + if ($counter1->get() == MAX_CONCURRENCY_MID * $total) { + $pm->wakeup(); + } + }); + $serv->on('close', function (Server $serv, $fd, $rid) { + }); + $serv->on('WorkerStop', function () use ($total, $counter2) { + $GLOBALS['memory_usage_2'] = memory_get_usage(); + Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); + Assert::eq($counter2->get(), MAX_CONCURRENCY_MID * MAX_REQUESTS); + echo "DONE\n"; + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_server/memory_leak/pipe_message.phpt b/tests/swoole_server/memory_leak/pipe_message.phpt new file mode 100644 index 0000000000..9829f464c4 --- /dev/null +++ b/tests/swoole_server/memory_leak/pipe_message.phpt @@ -0,0 +1,69 @@ +--TEST-- +swoole_server/memory_leak: task +--SKIPIF-- + +--FILE-- +setWaitTimeout(-1); + +$pm->parentFunc = function ($pid) use ($pm, $chunks) { + $pm->kill(); + echo "DONE\n"; +}; + +$pm->childFunc = function () use ($counter1, $counter2, $pm, $total, $chunks) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $serv->set(array( + 'worker_num' => 2, + 'log_file' => '/dev/null', + )); + $serv->on("WorkerStart", function (Server $serv, $wid) use ($pm, $chunks) { + $GLOBALS['memory_usage_1'] = memory_get_usage(); + foreach ($chunks as $ch) { + Assert::greaterThan($serv->sendMessage($ch, 1 - $wid), 0); + usleep(10); + } + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $_data) use ($chunks) { + + }); + $serv->on('pipeMessage', function (Server $serv, $wid, $data) use ($counter2, $counter1, $pm, $total, $chunks) { + $counter1->add(); + $counter2->add(strlen($data)); + if ($counter2->get() == $total * 2) { + $pm->wakeup(); + } + }); + $serv->on('WorkerStop', function (Server $serv) use ($counter2, $total) { + $GLOBALS['memory_usage_2'] = memory_get_usage(); + Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_server/memory_leak/task.phpt b/tests/swoole_server/memory_leak/task.phpt new file mode 100644 index 0000000000..aaa238f0d0 --- /dev/null +++ b/tests/swoole_server/memory_leak/task.phpt @@ -0,0 +1,99 @@ +--TEST-- +swoole_server/memory_leak: task +--SKIPIF-- + +--FILE-- +setWaitTimeout(-1); + +$pm->parentFunc = function ($pid) use ($pm, $chunks) { + go(function () use ($pm, $chunks) { + $cli = new Client(SWOOLE_SOCK_TCP); + if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { + echo "ERROR\n"; + return; + } + $cli->send("start\n"); + }); + Swoole\Event::wait(); + $pm->wait(); + $pm->kill(); +}; + +$GLOBALS['test_fn'] = function ($taskId, $data, $chunks) { + if ($GLOBALS['counter1'] == 0) { + $GLOBALS['memory_usage_1'] = memory_get_usage(); + } + $GLOBALS['counter1']++; + $GLOBALS['counter2'] += (strlen($data)); + Assert::eq($chunks[$taskId], $data); +}; + +$pm->childFunc = function () use ($pm, $total, $chunks) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $serv->set(array( + 'worker_num' => 1, + 'task_worker_num' => 1, + 'log_file' => '/dev/null', + )); + $serv->on('WorkerStart', function (Server $serv, $wid) use ($pm) { + if ($wid == 0) { + $pm->wakeup(); + } + $GLOBALS['atomic']->add(); + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $_data) use ($chunks) { + foreach ($chunks as $ch) { + Assert::greaterThanEq($serv->task($ch), 0); + usleep(100); + } + }); + $serv->on('finish', function (Server $serv, $taskId, $data) use ($pm, $total, $chunks) { + $GLOBALS['test_fn']($taskId, $data, $chunks); + if ($GLOBALS['counter2'] == $total) { + $pm->wakeup(); + } + }); + $serv->on('task', function (Server $serv, $taskId, $srcWorkerId, $data) use ($pm, $total, $chunks) { + $GLOBALS['test_fn']($taskId, $data, $chunks); + return $data; + }); + $serv->on('WorkerStop', function (Server $serv) use ($total) { + $GLOBALS['memory_usage_2'] = memory_get_usage(); + Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); + Assert::eq($GLOBALS['counter2'], $total); + $GLOBALS['atomic']->add(); + echo "DONE\n"; + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +Assert::eq($GLOBALS['atomic']->get(), 4); +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_server/memory_leak/tcp.phpt b/tests/swoole_server/memory_leak/tcp.phpt new file mode 100644 index 0000000000..f86ad96f3d --- /dev/null +++ b/tests/swoole_server/memory_leak/tcp.phpt @@ -0,0 +1,82 @@ +--TEST-- +swoole_server/memory_leak: tcp +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $chunks) { + $clients = []; + for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { + go(function () use ($pm, $i, &$total, $chunks, &$clients) { + $cli = new Client(SWOOLE_SOCK_TCP); + if ($cli->connect('127.0.0.1', $pm->getFreePort(), 100) == false) { + echo "ERROR\n"; + return; + } + foreach ($chunks as $data) { + $cli->send($data); + usleep(100); + } + $clients[] = $cli; + }); + } + Swoole\Event::wait(); + $pm->wait(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $counter, $total) { + $serv = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); + $serv->set(array( + 'worker_num' => 1, + 'log_file' => '/dev/null', + )); + $serv->on("WorkerStart", function (Server $serv) use ($pm) { + $pm->wakeup(); + }); + $serv->on('connect', function (Server $serv, $fd, $rid) { + + }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($pm, $counter, $total) { + if ($counter->get() == 0) { + $GLOBALS['memory_usage_1'] = memory_get_usage(); + } + if ($counter->add(strlen($data)) == MAX_CONCURRENCY_MID * $total) { + $pm->wakeup(); + } + }); + $serv->on('close', function (Server $serv, $fd, $rid) { + }); + $serv->on('WorkerStop', function () use ($total) { + $GLOBALS['memory_usage_2'] = memory_get_usage(); + Assert::lessThan($GLOBALS['memory_usage_2'] - $GLOBALS['memory_usage_1'], 8192); + echo "DONE\n"; + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_server/object/packet.phpt b/tests/swoole_server/object/packet.phpt index 73b410fa13..a029504cb9 100644 --- a/tests/swoole_server/object/packet.phpt +++ b/tests/swoole_server/object/packet.phpt @@ -6,21 +6,24 @@ swoole_server/object: packet object parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { - $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_UDP); + $client = new Client(SWOOLE_SOCK_UDP); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { - echo "Over flow. errno=" . $client->errCode; - die("\n"); + echo 'Over flow. errno=' . $client->errCode; + exit("\n"); } - $data = base64_encode(random_bytes(rand(1024, 8192))) . "\r\n\r\n";; + $data = base64_encode(random_bytes(rand(1024, DGRAM_MAX_SIZE))) . "\r\n\r\n"; $client->send($data); $recv_data = $client->recv(); Assert::eq($recv_data, $data); @@ -31,11 +34,11 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP); $serv->set( - array( - "worker_num" => 1, + [ + 'worker_num' => 1, 'event_object' => true, 'log_file' => '/dev/null', - ) + ] ); $serv->on( 'WorkerStart', diff --git a/tests/swoole_server/object/pipe_message.phpt b/tests/swoole_server/object/pipe_message.phpt index ee4cd2ccec..71df29a7ee 100644 --- a/tests/swoole_server/object/pipe_message.phpt +++ b/tests/swoole_server/object/pipe_message.phpt @@ -49,6 +49,7 @@ $pm->childFunc = function () use ($pm) { $serv->on('pipeMessage', function (Server $serv, PipeMessage $msg) { Assert::eq($msg->worker_id, 1 - $serv->getWorkerId()); + Assert::eq($msg->source_worker_id, 1 - $serv->getWorkerId()); $object = $msg->data; $serv->sendto($object->address, $object->port, $object->data, $object->server_socket); }); diff --git a/tests/swoole_server/object/status_info.phpt b/tests/swoole_server/object/status_info.phpt index c2d754ef58..3450052f9f 100644 --- a/tests/swoole_server/object/status_info.phpt +++ b/tests/swoole_server/object/status_info.phpt @@ -83,12 +83,12 @@ $pm->run(); Fatal error: Uncaught RuntimeException: error in %s:%d Stack trace: -#0 [internal function]: {closure}(Object(Swoole\Server), Object(Swoole\Server\PipeMessage)) +#0 [internal function]: {closure%S}(Object(Swoole\Server), Object(Swoole\Server\PipeMessage)) #1 %s(%d): Swoole\Server->start() -#2 [internal function]: {closure}() +#2 [internal function]: {closure%S}() #3 %s(%d): call_user_func(Object(Closure)) #4 %s(%d): SwooleTest\ProcessManager->runChildFunc() -#5 [internal function]: SwooleTest\ProcessManager->SwooleTest\{closure}(Object(Swoole\Process)) +#5 [internal function]: SwooleTest\ProcessManager->%s(Object(Swoole\Process)) #6 %s(%d): Swoole\Process->start() #7 %s(%d): SwooleTest\ProcessManager->run() #8 {main} diff --git a/tests/swoole_server/parse_option_to_size.phpt b/tests/swoole_server/parse_option_to_size.phpt new file mode 100644 index 0000000000..1a4ff49e47 --- /dev/null +++ b/tests/swoole_server/parse_option_to_size.phpt @@ -0,0 +1,30 @@ +--TEST-- +swoole_server: parse option value to size +--SKIPIF-- + +--FILE-- +set([ + 'buffer_output_size' => '2M', +]); +$server->set([ + 'buffer_output_size' => 2 * 1024 * 1024, +]); +$server->set([ + 'buffer_output_size' => 'xxx--2M', +]); +?> +--EXPECTF-- +Fatal error: Swoole\Server::set(): failed to parse 'xxx--2M' to size, Error: Invalid quantity "xxx--2M": no valid leading digits, interpreting as "0" for backwards compatibility in %s on line %d +--EXPECTF_85-- +Fatal error: Swoole\Server::set(): failed to parse 'xxx--2M' to size, Error: Invalid quantity "xxx--2M": no valid leading digits, interpreting as "0" for backwards compatibility in %s on line %d +Stack trace: +#0 %s(%d): Swoole\Server->set(Array) +#1 {main} diff --git a/tests/swoole_server/pid_file.phpt b/tests/swoole_server/pid_file.phpt index fe0b42a618..41dd782f01 100644 --- a/tests/swoole_server/pid_file.phpt +++ b/tests/swoole_server/pid_file.phpt @@ -5,34 +5,28 @@ swoole_server: pid_file --FILE-- parentFunc = function ($pid) -{ +$pm->parentFunc = function ($pid) { Assert::assert(is_file(PID_FILE)); Swoole\Process::kill($pid); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $serv->set(array( - "worker_num" => 1, + 'worker_num' => 1, 'pid_file' => PID_FILE, 'log_file' => '/dev/null', )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + $serv->on("Start", function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); diff --git a/tests/swoole_server/reload_base.phpt b/tests/swoole_server/reload_base.phpt deleted file mode 100644 index fcd7a84477..0000000000 --- a/tests/swoole_server/reload_base.phpt +++ /dev/null @@ -1,78 +0,0 @@ ---TEST-- -swoole_server: reload in base mode ---SKIPIF-- - ---FILE-- - new Swoole\Atomic(), - 'task_worker' => new Swoole\Atomic() -]; -$pm = new SwooleTest\ProcessManager; -$pm->parentFunc = function () use ($pm) { - global $counter, $worker_num; - while (!file_exists(TEST_PID_FILE)) { - usleep(100 * 1000); - } - $pid = file_get_contents(TEST_PID_FILE); - $random = mt_rand(1, 12); - usleep(100 * 1000); - for ($n = $random; $n--;) { - Swoole\Process::kill($pid, SIGUSR1); - usleep(100 * 1000); - // Swoole\Process::kill($pid, SIGUSR2); - // usleep(100 * 1000); - } - - /**@var $counter Swoole\Atomic[] */ - $total = $counter['worker']->get() - $worker_num; - $expect = $random * $worker_num; - Assert::same($total, $expect, "[worker reload {$total} but expect {$expect}]"); - - // $total = $counter['task_worker']->get() - 1; - // Assert::same($total, $random * 2, "[task worker reload {$total} but expect {$random}]"); - - $log = file_get_contents(TEST_LOG_FILE); - $log = trim(preg_replace('/.+?\s+?INFO\s+?.+/', '', $log)); - if (!Assert::assert(empty($log))){ - var_dump($log); - } - $pm->kill(); - echo "DONE\n"; -}; -$pm->childFunc = function () use ($pm) { - global $worker_num; - @unlink(TEST_LOG_FILE); - @unlink(TEST_PID_FILE); - $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->set([ - 'log_file' => TEST_LOG_FILE, - 'pid_file' => TEST_PID_FILE, - 'worker_num' => $worker_num - // 'task_worker_num' => 1 - ]); - $server->on('ManagerStart', function () use ($pm) { - $pm->wakeup(); - }); - $server->on('WorkerStart', function (Swoole\Server $server, int $worker_id) use ($pm) { - /**@var $counter Swoole\Atomic[] */ - global $counter; - $atomic = $server->taskworker ? $counter['task_worker'] : $counter['worker']; - $atomic->add(1); - }); - $server->on('Receive', function (Swoole\Server $server, $fd, $reactor_id, $data) { }); - // $server->on('Task', function () { }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -?> ---EXPECTF-- -Notice: SWOOLE_BASE not support reload task workers. in %s on line %d -DONE diff --git a/tests/swoole_server/sendMessage.phpt b/tests/swoole_server/sendMessage.phpt deleted file mode 100644 index a0a7a3febf..0000000000 --- a/tests/swoole_server/sendMessage.phpt +++ /dev/null @@ -1,38 +0,0 @@ ---TEST-- -swoole_server: send message ---SKIPIF-- - ---FILE-- -send(opcode_encode("sendMessage", ["SUCCESS", 1])); - Assert::assert($r !== false); -}, function(Client $cli, $recv) { - list($op, $msg) = opcode_decode($recv); - echo $msg; - global $timer; - $cli->close(); - Timer::clear($timer); -}); - -?> ---EXPECT-- -SUCCESS diff --git a/tests/swoole_server/sendMessage_02.phpt b/tests/swoole_server/sendMessage_02.phpt deleted file mode 100644 index 92e4e676ac..0000000000 --- a/tests/swoole_server/sendMessage_02.phpt +++ /dev/null @@ -1,95 +0,0 @@ ---TEST-- -swoole_server: send message [02] ---SKIPIF-- - ---FILE-- -parentFunc = function ($pid) use ($pm) -{ - $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $client->set([ - 'package_eof' => "\r\n", - 'open_eof_check' => true, - 'open_eof_split' => true, - ]); - if (!$client->connect('127.0.0.1', $pm->getFreePort())) - { - exit("connect failed\n"); - } - $list = []; - for ($i = 0; $i < 7; $i++) - { - $data = $client->recv(); - if ($data === false or $data === '') - { - echo "ERROR\n"; - break; - } - $list[] = intval($data); - } - sort($list); - Assert::same($list, range(0, 6)); - $pm->kill(); -}; - -$pm->childFunc = function () use ($pm) -{ - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP ); - $serv->set([ - 'log_file' => '/dev/null', - 'worker_num' => 4, - 'task_worker_num' => 3, - ]); - - $lock = new Swoole\Lock(); - - $process = new \Swoole\Process(function ($process) use ($serv) { - while (true) - { - $r = $process->read(); - if (!$r) - { - continue; - } - $cmd = json_decode($r, true); - for ($i = 0; $i < ($serv->setting['worker_num'] + $serv->setting['task_worker_num']); $i++) - { - $serv->sendMessage(['worker_id' => $i, 'fd' => $cmd['fd']], $i); - } - } - }); - - $serv->addProcess($process); - $serv->on("workerStart", function ($serv, $wid) use ($pm) { - if ($wid == 0) { - $pm->wakeup(); - } - }); - $serv->on('connect', function (Swoole\Server $serv, $fd) use ($process) { - $process->write(json_encode(["fd" => $fd])); - }); - $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { - - }); - - $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) use ($lock) { - //$lock->lock(); - $serv->send($data['fd'], $data['worker_id']."\r\n"); - //$lock->unlock(); - }); - - $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) - { - - }); - - $serv->start(); -}; - -$pm->childFirst(); -$pm->run(); -?> ---EXPECT-- diff --git a/tests/swoole_server/sendMessage_1.phpt b/tests/swoole_server/sendMessage_1.phpt new file mode 100644 index 0000000000..3d076b79e7 --- /dev/null +++ b/tests/swoole_server/sendMessage_1.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_server: send message [1] +--SKIPIF-- + +--FILE-- +send(opcode_encode("sendMessage", ["SUCCESS", 1])); + Assert::assert($r !== false); +}, function(Client $cli, $recv) { + list($op, $msg) = opcode_decode($recv); + echo $msg; + global $timer; + $cli->close(); + Timer::clear($timer); +}); + +?> +--EXPECT-- +SUCCESS diff --git a/tests/swoole_server/sendMessage_2.phpt b/tests/swoole_server/sendMessage_2.phpt new file mode 100644 index 0000000000..6d59834265 --- /dev/null +++ b/tests/swoole_server/sendMessage_2.phpt @@ -0,0 +1,82 @@ +--TEST-- +swoole_server: send message [02] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client->set([ + 'package_eof' => "\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + ]); + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { + exit("connect failed\n"); + } + $list = []; + for ($i = 0; $i < 7; $i++) { + $data = $client->recv(); + if ($data === false or $data === '') { + echo "ERROR\n"; + break; + } + $list[] = intval($data); + } + sort($list); + Assert::same($list, range(0, 6)); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); + $serv->set([ + 'log_file' => '/dev/null', + 'worker_num' => 4, + 'task_worker_num' => 3, + ]); + + $process = new \Swoole\Process(function ($process) use ($serv) { + while (true) { + $r = $process->read(); + if (!$r) { + continue; + } + $cmd = json_decode($r, true); + for ($i = 0; $i < ($serv->setting['worker_num'] + $serv->setting['task_worker_num']); $i++) { + $serv->sendMessage(['worker_id' => $i, 'fd' => $cmd['fd']], $i); + } + } + }); + + $serv->addProcess($process); + $serv->on("workerStart", function ($serv, $wid) use ($pm) { + if ($wid == 0) { + $pm->wakeup(); + } + }); + $serv->on('connect', function (Swoole\Server $serv, $fd) use ($process) { + $process->write(json_encode(["fd" => $fd])); + }); + $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { + + }); + + $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { + $serv->send($data['fd'], $data['worker_id'] . "\r\n"); + }); + + $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { + + }); + + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_server/sendMessage_3.phpt b/tests/swoole_server/sendMessage_3.phpt new file mode 100644 index 0000000000..f4a359c0a9 --- /dev/null +++ b/tests/swoole_server/sendMessage_3.phpt @@ -0,0 +1,64 @@ +--TEST-- +swoole_server: send message [3] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client->set([ + 'package_eof' => "\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + ]); + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { + exit("connect failed\n"); + } + echo $client->recv(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); + $serv->set([ + 'log_file' => '/dev/null', + 'worker_num' => 2, + ]); + $serv->on("workerStart", function ($serv, $wid) use ($pm) { + if ($wid == 0) { + $pm->wakeup(); + } + }); + $serv->on('connect', function (Swoole\Server $serv, $fd) { + $wid = $serv->getWorkerId(); + $serv->sendMessage([ + 'fd' => $fd, + 'worker_id' => $wid, + 'data' => random_bytes(random_int(1024 * 1024, 2 * 1024 * 1024)), + ], 1 - $wid); + }); + $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { + + }); + + $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { + $serv->send($data['fd'], "OK\r\n"); + }); + + $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { + + }); + + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +OK diff --git a/tests/swoole_server/sendMessage_4.phpt b/tests/swoole_server/sendMessage_4.phpt new file mode 100644 index 0000000000..dc9545da9a --- /dev/null +++ b/tests/swoole_server/sendMessage_4.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_server: send message [4] +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $client->set([ + 'package_eof' => "\r\n", + 'open_eof_check' => true, + 'open_eof_split' => true, + ]); + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { + exit("connect failed\n"); + } + echo $client->recv(); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP); + $serv->set([ + 'log_file' => '/dev/null', + 'worker_num' => 2, + ]); + $serv->on("workerStart", function ($serv, $wid) use ($pm) { + if ($wid == 0) { + $pm->wakeup(); + } + }); + $serv->on('connect', function (Swoole\Server $serv, $fd) { + $wid = $serv->getWorkerId(); + $serv->sendMessage([ + 'fd' => $fd, + 'worker_id' => $wid, + 'exception' => new \Exception(__METHOD__), + ], 1 - $wid); + }); + $serv->on('receive', function ($serv, $fd, $reactor_id, $data) { + + }); + $serv->on('pipeMessage', function (Swoole\Server $serv, $worker_id, $data) { + $serv->send($data['fd'], "OK\r\n"); + }); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +OK diff --git a/tests/swoole_server/send_2.phpt b/tests/swoole_server/send_2.phpt index 72fd9da61b..7821ef940b 100644 --- a/tests/swoole_server/send_2.phpt +++ b/tests/swoole_server/send_2.phpt @@ -8,13 +8,17 @@ require __DIR__ . '/../include/skipif.inc'; parentFunc = function ($pid) use ($pm) { $total = 0; - for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { + for ($i = 0; $i < CONCURRENCY; $i++) { go(function () use ($pm, $i, &$total) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ @@ -28,7 +32,7 @@ $pm->parentFunc = function ($pid) use ($pm) { echo "ERROR\n"; return; } - $n = MAX_REQUESTS; + $n = SEND_N; while ($n--) { $data = $cli->recv(); Assert::assert($data); @@ -67,9 +71,9 @@ $pm->childFunc = function () use ($pm) { if (VERBOSE) { echo "new client, fd=$fd\n"; } - $n = MAX_REQUESTS; + $n = SEND_N; while ($n--) { - $len = rand(8192, 1024 * 1024); + $len = rand(PKT_MIN_SIZE, PKT_MAX_SIZE); $send_data = str_repeat(chr(ord('A') + $n % 10), $len); if (VERBOSE) { echo "[Server] c=$fd, n=$n, len=" . (strlen($send_data) + 4) . "\n---------------------------------------------------------------------\n"; diff --git a/tests/swoole_server/send_2m_in_task_worker.phpt b/tests/swoole_server/send_2m_in_task_worker.phpt index 5d7583a46b..935b8e5a5c 100644 --- a/tests/swoole_server/send_2m_in_task_worker.phpt +++ b/tests/swoole_server/send_2m_in_task_worker.phpt @@ -15,7 +15,8 @@ use Swoole\Server; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { - for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { + $c = IS_MAC_OS ? 8 : MAX_CONCURRENCY_MID; + for ($i = 0; $i < $c; $i++) { go(function () use ($pm, $i) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ diff --git a/tests/swoole_server/send_2m_in_user_process.phpt b/tests/swoole_server/send_2m_in_user_process.phpt index d7c41d485c..a4203dfe20 100644 --- a/tests/swoole_server/send_2m_in_user_process.phpt +++ b/tests/swoole_server/send_2m_in_user_process.phpt @@ -3,6 +3,7 @@ swoole_server: send 2M data in user process --SKIPIF-- --FILE-- parentFunc = function ($pid) use ($pm) { for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { go(function () use ($pm, $i) { - $cli = new Co\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); $cli->set([ 'open_length_check' => true, 'package_max_length' => 4 * 1024 * 1024, @@ -39,14 +44,14 @@ $pm->parentFunc = function ($pid) use ($pm) { } }); } - Swoole\Event::wait(); + Event::wait(); $pm->kill(); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $serv->set(array( - "worker_num" => 2, + $serv->set([ + 'worker_num' => 2, 'task_worker_num' => 3, 'log_level' => SWOOLE_LOG_ERROR, 'open_length_check' => true, @@ -54,9 +59,9 @@ $pm->childFunc = function () use ($pm) { 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, - )); + ]); - $proc = new Swoole\Process(function ($process) use ($serv) { + $proc = new Process(function ($process) use ($serv) { while (true) { $pkt = $process->read(); if (!$pkt) { @@ -69,7 +74,7 @@ $pm->childFunc = function () use ($pm) { }, false, 2); $serv->addProcess($proc); - $serv->on("WorkerStart", function (Server $serv) use ($pm) { + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); diff --git a/tests/swoole_server/send_3.phpt b/tests/swoole_server/send_3.phpt index dd8e0f79b6..806e386b47 100644 --- a/tests/swoole_server/send_3.phpt +++ b/tests/swoole_server/send_3.phpt @@ -8,11 +8,16 @@ require __DIR__ . '/../include/skipif.inc'; parentFunc = function ($pid) use ($pm) { $total = 0; - for ($i = 0; $i < MAX_CONCURRENCY_MID; $i++) { + for ($i = 0; $i < CONCURRENCY; $i++) { go(function () use ($pm, $i, &$total) { $cli = new Co\Client(SWOOLE_SOCK_TCP); $cli->set([ @@ -26,7 +31,7 @@ $pm->parentFunc = function ($pid) use ($pm) { echo "ERROR\n"; return; } - $n = MAX_REQUESTS; + $n = SEND_N; while ($n--) { $data = $cli->recv(); Assert::assert($data); @@ -62,9 +67,9 @@ $pm->childFunc = function () use ($pm) { $pm->wakeup(); }); $serv->on('connect', function (Swoole\Server $serv, $fd, $rid) { - $n = MAX_REQUESTS; + $n = SEND_N; while ($n--) { - $len = rand(65536, 1024 * 1024); + $len = rand(PKT_MIN_SIZE, PKT_MAX_SIZE); $send_data = str_repeat(chr(ord('A') + $n % 10), $len); $retval = $serv->send($fd, pack('N', $len) . $send_data); if ($retval === false) { diff --git a/tests/swoole_server/shutdown.phpt b/tests/swoole_server/shutdown.phpt index eb2d921e43..bf7e1d7a18 100644 --- a/tests/swoole_server/shutdown.phpt +++ b/tests/swoole_server/shutdown.phpt @@ -3,7 +3,6 @@ swoole_server: shutdown --SKIPIF-- --FILE-- ---FILE-- -initRandomData(1); -$pm->parentFunc = function () use ($pm) { - go(function () use ($pm) { - $client = new Co\Client(SWOOLE_SOCK_TCP); - Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); - Assert::assert($client->send($pm->getRandomData()) > 0); - }); -}; -$pm->childFunc = function () use ($pm) { - $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->set(['worker_num' => mt_rand(2, 4), 'log_file' => '/dev/null']); - $server->on('start', function () use ($pm) { - echo "START\n"; - $pm->wakeup(); - }); - $server->on('receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { - Assert::same($data, $pm->getRandomData()); - $server->shutdown(); - }); - $server->on('shutdown', function () { - echo "SHUTDOWN\n"; - }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -$pm->expectExitCode(0); -?> ---EXPECT-- -START -SHUTDOWN diff --git a/tests/swoole_server/shutdown_in_base_single.phpt b/tests/swoole_server/shutdown_in_base_single.phpt deleted file mode 100644 index fbe18bc9be..0000000000 --- a/tests/swoole_server/shutdown_in_base_single.phpt +++ /dev/null @@ -1,39 +0,0 @@ ---TEST-- -swoole_server: shutdown in base single ---SKIPIF-- - ---FILE-- -initRandomData(1); -$pm->parentFunc = function () use ($pm) { - go(function () use ($pm) { - $client = new Co\Client(SWOOLE_SOCK_TCP); - Assert::assert($client->connect('127.0.0.1', $pm->getFreePort())); - Assert::assert($client->send($pm->getRandomData()) > 0); - }); -}; -$pm->childFunc = function () use ($pm) { - $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $server->set(['worker_num' => 1, 'log_file' => '/dev/null']); - $server->on('start', function () use ($pm) { - echo "START\n"; - $pm->wakeup(); - }); - $server->on('receive', function (Swoole\Server $server, int $fd, int $rid, string $data) use ($pm) { - Assert::same($data, $pm->getRandomData()); - $server->shutdown(); - }); - $server->on('shutdown', function () { - echo "SHUTDOWN\n"; - }); - $server->start(); -}; -$pm->childFirst(); -$pm->run(); -$pm->expectExitCode(0); -?> ---EXPECT-- -START -SHUTDOWN diff --git a/tests/swoole_server/single_thread/heartbeat.phpt b/tests/swoole_server/single_thread/heartbeat.phpt new file mode 100644 index 0000000000..e829bb78af --- /dev/null +++ b/tests/swoole_server/single_thread/heartbeat.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_server/single_thread: heartbeat +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + Assert::isEmpty(@file_get_contents('http://127.0.0.1:' . $pm->getFreePort() . '/')); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); + $http->set([ + 'single_thread' => true, + 'worker_num' => 1, + 'heartbeat_idle_time' => 1, + 'heartbeat_check_interval' => 1, + ]); + $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $http->on('Request', function ($request, $response) use ($http) { + sleep(3); + Assert::false($response->end('hello')); + Assert::eq($http->getLastError(), SWOOLE_ERROR_SESSION_NOT_EXIST); + }); + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_server/single_thread/large_packet.phpt b/tests/swoole_server/single_thread/large_packet.phpt new file mode 100644 index 0000000000..845243369c --- /dev/null +++ b/tests/swoole_server/single_thread/large_packet.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_server/single_thread: large packet +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm) { + $url = 'http://127.0.0.1:' . $pm->getFreePort() . '/'; + $filePath = tempnam('/tmp', 'swoole_test_'); + $rdata = random_bytes(1024 * 1024); + file_put_contents($filePath, $rdata); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: text/html', + 'Content-Type: multipart/form-data' + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'file' => new CURLFile($filePath, 'text/html') + ]); + $response = curl_exec($ch); + if (curl_errno($ch)) { + echo 'ERROR: ' . curl_error($ch); + } else { + Assert::eq($response, md5($rdata)); + } + curl_close($ch); + unlink($filePath); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); + $http->set([ + 'single_thread' => true, + 'worker_num' => 1, + 'dispatch_mode' => 10, + 'package_max_length' => '128m', + ]); + $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $http->on('Request', function ($request, $response) { + $response->end(md5_file($request->files['file']['tmp_name'])); + }); + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_server/single_thread/user_setting.phpt b/tests/swoole_server/single_thread/user_setting.phpt new file mode 100644 index 0000000000..fbd83e0c92 --- /dev/null +++ b/tests/swoole_server/single_thread/user_setting.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_server/single_thread: user setting +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $log_file) { + $url = 'http://127.0.0.1:' . $pm->getFreePort() . '/'; + posix_kill($pid, SIGUSR1); + sleep(1); + $output = file_get_contents($log_file); + Assert::contains($output, 'reloading all workers'); + $pm->kill(); + unlink($log_file); +}; + +$pm->childFunc = function () use ($pm, $log_file) { + $http = new Swoole\Http\Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); + $http->set([ + 'single_thread' => true, + 'worker_num' => 1, + 'user' => 'www-data', + 'group' => 'www-data', + 'log_file' => $log_file, + ]); + $http->on('WorkerStart', function (Swoole\Http\Server $serv) use ($pm) { + $pm->wakeup(); + }); + $http->on('Request', function ($request, $response) { + $response->end('hello'); + }); + $http->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_server/sleep.phpt b/tests/swoole_server/sleep.phpt index dc6b5aa3c5..102c5a1cc5 100644 --- a/tests/swoole_server/sleep.phpt +++ b/tests/swoole_server/sleep.phpt @@ -19,12 +19,14 @@ $pm = new SwooleTest\ProcessManager; const N = 8; $pm->parentFunc = function () use ($pm) { - $s = microtime(true); Co::set([Constant::OPTION_HOOK_FLAGS => SWOOLE_HOOK_ALL]); run(function () use ($pm) { $n = N; + $s = microtime(true); + $list = []; while($n--) { - go(function() use ($pm) { + $list[] = go(function() use ($pm) { + $s = microtime(true); $ch = curl_init(); $code = uniqid('swoole_'); $url = "http://127.0.0.1:".$pm->getFreePort()."/?code=".urlencode($code); @@ -44,10 +46,13 @@ $pm->parentFunc = function () use ($pm) { curl_close($ch); }); } + + Co::join($list); + + Assert::lessThan(microtime(true) - $s, 0.5); + echo "Done\n"; }); - Assert::lessThan(microtime(true) - $s, 0.5); $pm->kill(); - echo "Done\n"; }; $pm->childFunc = function () use ($pm) { $http = new Swoole\Http\Server("127.0.0.1", $pm->getFreePort(), SWOOLE_BASE); diff --git a/tests/swoole_server/slow_master.phpt b/tests/swoole_server/slow_master.phpt index f7dca07e8a..7845dd8784 100644 --- a/tests/swoole_server/slow_master.phpt +++ b/tests/swoole_server/slow_master.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_server: slow master --SKIPIF-- - + --FILE-- childFunc = function () use ($pm, $counter_server, $counter_client, $data_c $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) use ($counter_server, $counter_client, $data_chunks) { + $serv->on(Constant::EVENT_RECEIVE, function (Server $serv, $fd, $rid, $data) use ($counter_server, $counter_client, $data_chunks) { $serv->timer = Timer::tick(50, function () use ($counter_server) { $counter_server->add(1); }); diff --git a/tests/swoole_server/ssl/bad_client.phpt b/tests/swoole_server/ssl/bad_client.phpt index 751936651e..524f973095 100644 --- a/tests/swoole_server/ssl/bad_client.phpt +++ b/tests/swoole_server/ssl/bad_client.phpt @@ -9,15 +9,15 @@ require __DIR__ . '/../../include/bootstrap.php'; define('ERROR_FILE', __DIR__.'/ssl_error'); $pm = new SwooleTest\ProcessManager; +$rdata = "hello world"; -$pm->parentFunc = function ($pid) use ($pm) { +$pm->parentFunc = function ($pid) use ($pm, $rdata) { $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); //同步阻塞 - if (!$client->connect('127.0.0.1', $pm->getFreePort())) - { + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } - $client->send("hello world"); - Assert::same($client->recv(), ""); + $client->send($rdata); + Assert::notEq($client->recv(), "Swoole $rdata"); $pm->kill(); }; diff --git a/tests/swoole_server/ssl/dtls_big_packet.phpt b/tests/swoole_server/ssl/dtls_big_packet.phpt index b0a594df4f..4bd76263be 100644 --- a/tests/swoole_server/ssl/dtls_big_packet.phpt +++ b/tests/swoole_server/ssl/dtls_big_packet.phpt @@ -1,37 +1,42 @@ --TEST-- swoole_server/ssl: big dtls packet --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm) { - $client = new Swoole\Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 - if (!$client->connect('127.0.0.1', $pm->getFreePort())) - { + $client = new Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); // 同步阻塞 + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } - //TLS max record size = 16K - $client->send("hello world".str_repeat('A', 16000)); - Assert::same($client->recv(65535), "Swoole hello world".str_repeat('A', 16000)); + // TLS max record size = 16K + $client->send('hello world' . str_repeat('A', PKT_LEN)); + Assert::same($client->recv(65535), 'Swoole hello world' . str_repeat('A', PKT_LEN)); $pm->kill(); }; $pm->childFunc = function () use ($pm) { - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); $serv->set([ 'log_file' => '/dev/null', 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', 'ssl_key_file' => SSL_FILE_DIR . '/server.key', ]); - $serv->on("workerStart", function ($serv) use ($pm) { + $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); $serv->on('receive', function ($serv, $fd, $tid, $data) { - $serv->send($fd, "Swoole $data"); + $serv->send($fd, "Swoole {$data}"); }); $serv->start(); }; diff --git a/tests/swoole_server/ssl/dtls_with_length_protocol.phpt b/tests/swoole_server/ssl/dtls_with_length_protocol.phpt index c0e2e1ee3b..ad8cc02a93 100644 --- a/tests/swoole_server/ssl/dtls_with_length_protocol.phpt +++ b/tests/swoole_server/ssl/dtls_with_length_protocol.phpt @@ -1,34 +1,32 @@ --TEST-- swoole_server/ssl: dtls with length protocol --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm, $req, $resp) { $cli = new Client(SWOOLE_SOCK_UDP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); - $cli->set( - [ - 'open_length_check' => true, - 'package_max_length' => 16 * 1024 * 1024, - 'package_length_type' => 'N', - 'package_length_offset' => 0, - 'package_body_offset' => 4, - ] - ); - $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 1); + $cli->set([ + 'open_length_check' => true, + 'package_max_length' => 16 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + 'socket_buffer_size' => 1024 * 1024, + ]); + $cli->connect(TCP_SERVER_HOST, $pm->getFreePort(), 5); $cli->send(pack('N', strlen($req)) . $req); $data = $cli->recv(); Assert::eq(bin2hex($resp), bin2hex(substr($data, 4))); @@ -37,36 +35,35 @@ $pm->parentFunc = function ($pid) use ($pm, $req, $resp) { $pm->childFunc = function () use ($pm, $size, $req, $resp) { $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_UDP | SWOOLE_SSL); - $serv->set( - [ - "worker_num" => 1, - 'log_file' => '/dev/null', - 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', - 'ssl_key_file' => SSL_FILE_DIR . '/server.key', - 'open_length_check' => true, - 'package_max_length' => 16 * 1024 * 1024, - 'package_length_type' => 'N', - 'package_length_offset' => 0, - 'package_body_offset' => 4, - ] - ); + $serv->set([ + 'worker_num' => 1, + 'log_file' => '/dev/null', + 'ssl_cert_file' => SSL_FILE_DIR . '/server.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/server.key', + 'open_length_check' => true, + 'package_max_length' => 16 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + 'socket_buffer_size' => 1024 * 1024, + ]); $serv->on( - "WorkerStart", + 'WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); } ); $serv->on( - "connect", + 'connect', function ($serv, $fd, $rid) { - //echo "connect\n"; + // echo "connect\n"; } ); $serv->on( - "receive", + 'receive', function ($serv, $fd, $rid, $data) use ($req, $resp) { Assert::eq(bin2hex($req), bin2hex(substr($data, 4))); - $serv->send($fd, pack('N', strlen($resp)) . $resp); + Assert::assert($serv->send($fd, pack('N', strlen($resp)) . $resp)); } ); $serv->start(); diff --git a/tests/swoole_server/ssl/heartbeat_1.phpt b/tests/swoole_server/ssl/heartbeat_1.phpt index 182c12c97a..436360843d 100644 --- a/tests/swoole_server/ssl/heartbeat_1.phpt +++ b/tests/swoole_server/ssl/heartbeat_1.phpt @@ -4,41 +4,44 @@ swoole_server/ssl: heartbeat with bad client --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); if (!$client->connect('127.0.0.1', $pm->getFreePort(), 5, 0)) { - echo "Over flow. errno=" . $client->errCode; - die("\n"); + echo 'Over flow. errno=' . $client->errCode; + exit("\n"); } $s1 = time(); Assert::same($client->recv(), ''); $s2 = time(); Assert::assert($s2 - $s1 > 1); - Swoole\Process::kill($pid); + Process::kill($pid); }; $pm->childFunc = function () use ($pm) { $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); - $serv->set(array( + $serv->set([ 'heartbeat_check_interval' => 1, 'log_file' => '/dev/null', 'heartbeat_idle_time' => 1, - 'ssl_cert_file' => __DIR__ . '/../../include/api/ssl-ca/server-cert.pem', - 'ssl_key_file' => __DIR__ . '/../../include/api/ssl-ca/server-key.pem', - )); - $serv->on("workerStart", function ($serv) use ($pm) { + 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', + ]); + $serv->on('workerStart', function ($serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) { - }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; diff --git a/tests/swoole_server/ssl/heartbeat_2.phpt b/tests/swoole_server/ssl/heartbeat_2.phpt index 42e2cf202e..85168acaad 100644 --- a/tests/swoole_server/ssl/heartbeat_2.phpt +++ b/tests/swoole_server/ssl/heartbeat_2.phpt @@ -32,8 +32,8 @@ $pm->childFunc = function () use ($pm) { 'heartbeat_check_interval' => 1, 'log_file' => '/dev/null', 'heartbeat_idle_time' => 1, - 'ssl_cert_file' => __DIR__ . '/../../include/api/ssl-ca/server-cert.pem', - 'ssl_key_file' => __DIR__ . '/../../include/api/ssl-ca/server-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', )); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); diff --git a/tests/swoole_server/ssl/nodejs.phpt b/tests/swoole_server/ssl/nodejs.phpt index effbc006bc..8c2d0b5702 100644 --- a/tests/swoole_server/ssl/nodejs.phpt +++ b/tests/swoole_server/ssl/nodejs.phpt @@ -12,7 +12,7 @@ $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { Co\run(function () use ($pm) { - $result = Co::exec('node '.__DIR__.'/code/connect.js '.$pm->getFreePort()); + $result = Co::exec('node '.__DIR__.'/code/client.js '.$pm->getFreePort()); Assert::eq($result['code'], 0); Assert::contains($result['output'], 'swoole-http-server'); }); diff --git a/tests/swoole_server/ssl/verify_01.phpt b/tests/swoole_server/ssl/verify_01.phpt index 9ab0bba8f4..c2f19d1d44 100644 --- a/tests/swoole_server/ssl/verify_01.phpt +++ b/tests/swoole_server/ssl/verify_01.phpt @@ -15,8 +15,8 @@ $pm->parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { $client = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL); $client->set([ - 'ssl_cert_file' => __DIR__ . '/../../include/api/ssl-ca/client-cert.pem', - 'ssl_key_file' => __DIR__ . '/../../include/api/ssl-ca/client-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/client-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/client-key.pem', ]); if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); @@ -32,11 +32,11 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE, SWOOLE_SOCK_TCP | SWOOLE_SSL); $serv->set([ - 'ssl_cert_file' => __DIR__ . '/../../include/api/ssl-ca/server-cert.pem', - 'ssl_key_file' => __DIR__ . '/../../include/api/ssl-ca/server-key.pem', + 'ssl_cert_file' => SSL_FILE_DIR . '/server-cert.pem', + 'ssl_key_file' => SSL_FILE_DIR . '/server-key.pem', 'ssl_verify_peer' => true, 'ssl_allow_self_signed' => true, - 'ssl_client_cert_file' => __DIR__ . '/../../include/api/ssl-ca/ca-cert.pem', + 'ssl_client_cert_file' => SSL_FILE_DIR . '/ca-cert.pem', ]); $serv->on("workerStart", function ($serv) use ($pm) { $pm->wakeup(); diff --git a/tests/swoole_server/ssl/verify_02.phpt b/tests/swoole_server/ssl/verify_02.phpt index a4dfbb80d5..79ce4ed241 100644 --- a/tests/swoole_server/ssl/verify_02.phpt +++ b/tests/swoole_server/ssl/verify_02.phpt @@ -13,11 +13,10 @@ $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); $client->set([ - 'ssl_cert_file' => SSL_FILE_DIR . '/client.crt', - 'ssl_key_file' => SSL_FILE_DIR . '/client.key', + 'ssl_cert_file' => SSL_FILE_DIR . '/client-expired.crt', + 'ssl_key_file' => SSL_FILE_DIR . '/client-expired.key', ]); - if (!$client->connect('127.0.0.1', $pm->getFreePort())) - { + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); diff --git a/tests/swoole_server/start_twice.phpt b/tests/swoole_server/start_twice.phpt index 60011601bc..acc6c75b77 100644 --- a/tests/swoole_server/start_twice.phpt +++ b/tests/swoole_server/start_twice.phpt @@ -8,9 +8,6 @@ require __DIR__ . '/../include/skipif.inc'; childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( - "worker_num" => 1, + 'worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null', )); @@ -44,4 +41,4 @@ $pm->childFirst(); $pm->run(); ?> --EXPECTF-- -Warning: Swoole\Server::start(): server have been shutdown, unable to execute Swoole\Server->start() in %s on line %d +Warning: Swoole\Server::start(): The server have been shutdown, unable to execute Swoole\Server->start() in %s on line %d diff --git a/tests/swoole_server/stop_in_workerStart.phpt b/tests/swoole_server/stop_in_workerStart.phpt index 58c3a4341f..d6abf10355 100644 --- a/tests/swoole_server/stop_in_workerStart.phpt +++ b/tests/swoole_server/stop_in_workerStart.phpt @@ -10,18 +10,27 @@ use Swoole\Server; $server = new Server('127.0.0.1', get_one_free_port(), SWOOLE_PROCESS); +$atomic = new Swoole\Atomic(0); + $server->set([ 'worker_num' => 1 ]); -$server->on('Receive', function(Server $server, int $fd, int $reactorId, string $data){ -}); -$server->on('WorkerStart', function(Server $server, int $workid){ + +$server->on('Receive', function (Server $server, int $fd, int $reactorId, string $data) {}); + +$server->on('start', function () use ($atomic) {}); + +$server->on('WorkerStart', function (Server $server, int $workerId) { + usleep(50000); $server->stop(); }); -$server->on('WorkerStop',function(Server $server, int $workid){ - $server->shutdown(); + +$server->on('WorkerStop', function (Server $server, int $workerId) use ($atomic) { + usleep(200000); + if ($atomic->add(1) == 1) { + $server->shutdown(); + } }); $server->start(); ?> --EXPECTF-- -[%s] INFO Server is shutdown now diff --git a/tests/swoole_server/systemd_fds.phpt b/tests/swoole_server/systemd_fds.phpt index e7b3469189..e36d5c3b27 100644 --- a/tests/swoole_server/systemd_fds.phpt +++ b/tests/swoole_server/systemd_fds.phpt @@ -1,7 +1,9 @@ --TEST-- swoole_server: systemd fds --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm) { $client = new Client($type); Assert::notEmpty($client->connect($host, $port)); $client->send("SUCCESS"); - Assert::eq($client->recv(), 'SUCCESS'.PHP_EOL); + Assert::eq($client->recv(), 'SUCCESS' . PHP_EOL); $client->close(); }; @@ -53,19 +55,19 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { $sockets = []; $start_fd = swoole_array(scandir('/proc/self/fd'))->sort()->last(); - putenv('LISTEN_FDS_START='. $start_fd); + putenv('LISTEN_FDS_START=' . $start_fd); - $sockets[] = stream_socket_server('tcp://127.0.0.1:'.$pm->getFreePort(0), $errno, $errstr); - $sockets[] = stream_socket_server('udp://0.0.0.0:'.$pm->getFreePort(1), $errno, $errstr, STREAM_SERVER_BIND); + $sockets[] = stream_socket_server('tcp://127.0.0.1:' . $pm->getFreePort(0), $errno, $errstr); + $sockets[] = stream_socket_server('udp://0.0.0.0:' . $pm->getFreePort(1), $errno, $errstr, STREAM_SERVER_BIND); if (HAVE_IPV6) { - $sockets[] = stream_socket_server('tcp://[::1]:'.$pm->getFreePort(2), $errno, $errstr); - $sockets[] = stream_socket_server('udp://[::]:'.$pm->getFreePort(3), $errno, $errstr, STREAM_SERVER_BIND); + $sockets[] = stream_socket_server('tcp://[::1]:' . $pm->getFreePort(2), $errno, $errstr); + $sockets[] = stream_socket_server('udp://[::]:' . $pm->getFreePort(3), $errno, $errstr, STREAM_SERVER_BIND); } - $sockets[] = stream_socket_server('unix://'.UNIX_SOCK_1, $errno, $errstr); - $sockets[] = stream_socket_server('udg://'.UNIX_SOCK_2, $errno, $errstr, STREAM_SERVER_BIND); + $sockets[] = stream_socket_server('unix://' . UNIX_SOCK_1, $errno, $errstr); + $sockets[] = stream_socket_server('udg://' . UNIX_SOCK_2, $errno, $errstr, STREAM_SERVER_BIND); - putenv('LISTEN_PID='. posix_getpid()); - putenv('LISTEN_FDS='. count($sockets)); + putenv('LISTEN_PID=' . posix_getpid()); + putenv('LISTEN_FDS=' . count($sockets)); $serv = new Server('SYSTEMD', 0, SWOOLE_BASE); @@ -76,11 +78,11 @@ $pm->childFunc = function () use ($pm) { $serv->on("packet", function (Server $serv, $data, $addr) { // var_dump($addr); - $serv->sendto($addr['address'], isset($addr['port']) ? $addr['port'] : 0, 'SUCCESS'.PHP_EOL); + $serv->sendto($addr['address'], $addr['port'] ?? 0, 'SUCCESS' . PHP_EOL); }); $serv->on("receive", function (Server $serv, $fd, $tid, $data) { - $serv->send($fd, 'SUCCESS'.PHP_EOL); + $serv->send($fd, 'SUCCESS' . PHP_EOL); }); $serv->start(); diff --git a/tests/swoole_server/task/bug_2585.phpt b/tests/swoole_server/task/bug_2585.phpt index 7b31114bf8..da35ab009e 100644 --- a/tests/swoole_server/task/bug_2585.phpt +++ b/tests/swoole_server/task/bug_2585.phpt @@ -3,6 +3,7 @@ swoole_server/task: bug Github#2585 --SKIPIF-- --FILE-- + --FILE-- parentFunc = function ($pid) use ($pm) { $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or die("ERROR"); - $cli->send("task-01") or die("ERROR"); - Assert::same($cli->recv(), "hello world"); + $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or exit('ERROR'); + $cli->send('task-01') or exit('ERROR'); + Assert::same($cli->recv(), 'hello world'); $cli->close(); + usleep(1000000); echo file_get_contents(TMP_LOG_FILE); $pm->kill(); }; @@ -26,16 +30,15 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm) { swoole_ignore_error(SWOOLE_ERROR_SERVER_NO_IDLE_WORKER); $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $server->set( - [ - 'log_file' => TMP_LOG_FILE, - 'log_level' => SWOOLE_LOG_NOTICE, - 'task_worker_num' => 1, - 'socket_send_timeout' => 1.0, - 'worker_num' => 1, - 'enable_coroutine' => false, - ] - ); + $server->set([ + 'log_file' => TMP_LOG_FILE, + 'log_level' => SWOOLE_LOG_NOTICE, + 'task_worker_num' => 1, + 'socket_send_timeout' => 1.0, + 'socket_buffer_size' => 128 * 1024, + 'worker_num' => 1, + 'enable_coroutine' => false, + ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); }); @@ -43,20 +46,20 @@ $pm->childFunc = function () use ($pm) { $server->task($fd); usleep(1100000); }); - $server->on( - 'task', - function ($server, $task_id, $worker_id, string $fd) { - $n = 200; - while ($n--) { - if (!$server->finish(str_repeat('A', 8000))) { - break; - } + $server->on('task', function ($server, $task_id, $worker_id, string $fd) { + $n = 200; + $size = IS_MAC_OS ? 2000 : 8000; + while ($n--) { + if (!$server->finish(str_repeat('A', $size))) { + break; } - $server->send($fd, "hello world"); } - ); - $server->on('finish', function () { }); - $server->on('close', function () { }); + $server->send($fd, 'hello world'); + }); + $server->on('finish', function ($server, $task_id, $data) { + + }); + $server->on('close', function () {}); $server->start(); }; $pm->childFirst(); @@ -64,5 +67,4 @@ $pm->run(); unlink(TMP_LOG_FILE); ?> --EXPECTF-- -[%s] WARNING Socket::send_blocking(): send %d bytes failed, Error: Resource temporarily unavailable[11] -[%s] WARNING Server::reply_task_result() (ERRNO %d): send result to worker timed out +[%s] WARNING Server::finish() (ERRNO %d): send result to worker timed out diff --git a/tests/swoole_server/task/huge_data.phpt b/tests/swoole_server/task/huge_data.phpt index 8a718edcd8..a9776df10a 100644 --- a/tests/swoole_server/task/huge_data.phpt +++ b/tests/swoole_server/task/huge_data.phpt @@ -32,7 +32,7 @@ $pm->childFunc = function () use ($pm) { ]); $http->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($http) { Assert::assert($response->detach()); - $scope = IS_IN_TRAVIS ? [4, 16] : [16, 64]; + $scope = IS_IN_CI ? [4, 16] : [16, 64]; $repeat = mt_rand(...$scope); $http->task([ 'fd' => $response->fd, diff --git a/tests/swoole_server/task/invalid_packet.phpt b/tests/swoole_server/task/invalid_packet.phpt index 184d9f479c..95d6e9eccf 100644 --- a/tests/swoole_server/task/invalid_packet.phpt +++ b/tests/swoole_server/task/invalid_packet.phpt @@ -9,20 +9,24 @@ skip_if_function_not_exist('msg_get_queue'); require __DIR__ . '/../../include/bootstrap.php'; const MSGQ_KEY = 0x70001001; -$file = __DIR__.'/tmp.log'; +$file = __DIR__ . '/tmp.log'; +use Swoole\Atomic; +use Swoole\Exception; use Swoole\Server; +use Swoole\Server\Task; +use SwooleTest\ProcessManager; -$result = new Swoole\Atomic(0); -$pm = new SwooleTest\ProcessManager; +$result = new Atomic(0); +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { - $data = '{"tid":17732683638813521,"out_trade_no":"dm5601993521","runMethod":"\\Action\\Mpay\\Uni\\UniApiV3Act:jsonDrive"}'; + $data = '{"tid":17732683638813521,"out_trade_no":"dm5601993521","runMethod":"\Action\Mpay\Uni\UniApiV3Act:jsonDrive"}'; $queueId = msg_get_queue(MSGQ_KEY); if ($queueId === false) { - throw new \Swoole\Exception("msg_get_queue() failed."); + throw new Exception('msg_get_queue() failed.'); } - Assert::true(msg_send($queueId, 1, $data)); - Assert::true(msg_send($queueId, 1, Swoole\Server\Task::pack($data), false)); + Assert::true(msg_send($queueId, 1, str_repeat('\n', 64) . 'hello' . $data)); + Assert::true(msg_send($queueId, 1, Task::pack($data), false)); $pm->wait(); $pm->kill(); }; @@ -30,19 +34,17 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->childFunc = function () use ($pm, $file, $result) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); - $serv->set(array( - "worker_num" => 2, + $serv->set([ + 'worker_num' => 2, 'task_worker_num' => 1, 'task_ipc_mode' => 3, 'message_queue_key' => MSGQ_KEY, 'log_file' => $file, - )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) { - - }); + $serv->on('receive', function (Server $serv, $fd, $rid, $data) {}); $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) use ($pm, $result) { $pm->wakeup(); $result->add(1); @@ -54,8 +56,10 @@ $pm->childFunc = function () use ($pm, $file, $result) { $pm->childFirst(); $pm->run(); +usleep(100000); // echo file_get_contents($file); -Assert::true(swoole_string(file_get_contents($file))->contains('ProcessPool_worker_loop: bad task packet')); + +Assert::true(swoole_string(file_get_contents($file))->contains('bad task packet')); unlink($file); Assert::eq($result->get(), 1); ?> diff --git a/tests/swoole_server/task/kill_01.phpt b/tests/swoole_server/task/kill_01.phpt index 7c5a83d0d0..e6e9693886 100644 --- a/tests/swoole_server/task/kill_01.phpt +++ b/tests/swoole_server/task/kill_01.phpt @@ -14,9 +14,8 @@ const PROC_NAME = 'swoole_unittest_server_task_worker'; $pm = new SwooleTest\ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { - for ($i = 0; $i < 5; $i++) - { - //杀死进程 + for ($i = 0; $i < 5; $i++) { + // 杀死进程 kill_process_by_name(PROC_NAME); usleep(10000); //判断进程是否存在 diff --git a/tests/swoole_server/task/task_ipc_mode_2.phpt b/tests/swoole_server/task/task_ipc_mode_2.phpt index f0013f390b..bb92da15b0 100644 --- a/tests/swoole_server/task/task_ipc_mode_2.phpt +++ b/tests/swoole_server/task/task_ipc_mode_2.phpt @@ -5,31 +5,40 @@ swoole_server/task: task_ipc_mode = 2 --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { echo httpGetBody("http://127.0.0.1:{$pm->getFreePort()}/"); $pm->kill(); }); }; -$pm->childFunc = function () use ($pm) { +$pm->childFunc = function () use ($pm, $atomic) { $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'log_file' => '/dev/null', 'open_tcp_nodelay' => true, 'task_worker_num' => 4, + 'worker_num' => 2, 'task_ipc_mode' => 2, 'dispatch_mode' => 2 ]); - $server->on('workerStart', function () use ($pm) { - $pm->wakeup(); + $server->on('workerStart', function () use ($pm, $atomic) { + if ($atomic->add() == 6) { + $pm->wakeup(); + } }); - $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { + $server->on('request', function (Request $request, Response $response) use ($server) { $response->detach(); $server->task($response->fd); }); $server->on('task', function ($server, $task_id, $worker_id, string $fd) { - $response = Swoole\Http\Response::create($fd); + $response = Response::create($fd); $response->end("Hello Swoole!\n"); }); $server->on('finish', function () { }); diff --git a/tests/swoole_server/task/task_ipc_mode_3.phpt b/tests/swoole_server/task/task_ipc_mode_3.phpt index 438a98d920..a19914e57f 100644 --- a/tests/swoole_server/task/task_ipc_mode_3.phpt +++ b/tests/swoole_server/task/task_ipc_mode_3.phpt @@ -5,6 +5,11 @@ swoole_server/task: task_ipc_mode = 3 --FILE-- parentFunc = function ($pid) use ($pm) { go(function () use ($pm) { @@ -13,24 +18,27 @@ $pm->parentFunc = function ($pid) use ($pm) { Swoole\Event::wait(); $pm->kill(); }; -$pm->childFunc = function () use ($pm) { - $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); +$pm->childFunc = function () use ($pm, $atomic) { + $server = new Swoole\Http\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'log_file' => '/dev/null', 'open_tcp_nodelay' => true, + 'worker_num' => 3, 'task_worker_num' => 4, 'task_ipc_mode' => 3, 'dispatch_mode' => 2 ]); - $server->on('workerStart', function () use ($pm) { - $pm->wakeup(); + $server->on('workerStart', function () use ($pm, $atomic) { + if ($atomic->add() == 7) { + $pm->wakeup(); + } }); - $server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { + $server->on('request', function (Request $request, Response $response) use ($server) { $response->detach(); $server->task($response->fd); }); $server->on('task', function ($server, $task_id, $worker_id, string $fd) { - $response = Swoole\Http\Response::create($fd); + $response = Response::create($server, $fd); $response->end("Hello Swoole!\n"); }); $server->on('finish', function () { }); diff --git a/tests/swoole_server/task/task_max_request.phpt b/tests/swoole_server/task/task_max_request.phpt index 173779c06a..9be764d711 100644 --- a/tests/swoole_server/task/task_max_request.phpt +++ b/tests/swoole_server/task/task_max_request.phpt @@ -8,50 +8,45 @@ require __DIR__ . '/../../include/bootstrap.php'; const N = 4000; -use Swoole\Server; use Swoole\Atomic; +use Swoole\Process; +use Swoole\Server; $counter1 = new Atomic(); // onTask $counter2 = new Atomic(); // onFinish $counter3 = new Atomic(); // task num -$process = new Swoole\Process(function() { - +$process = new Process(function () { $serv = new Server('127.0.0.1', get_one_free_port(), SWOOLE_PROCESS); $serv->set([ - "worker_num" => 1, + 'worker_num' => 1, 'task_max_request' => 200, 'task_worker_num' => 4, 'log_file' => TEST_LOG_FILE, ]); - $serv->on("WorkerStart", function (Server $serv, $worker_id) - { + $serv->on('WorkerStart', function (Server $serv, $worker_id) { if (!$serv->taskworker) { - for($i = 0; $i< N; $i++) { - $serv->task(array('type' => 'php', 'data' => RandStr::gen(100))); + for ($i = 0; $i < N; $i++) { + $serv->task(['type' => 'php', 'data' => RandStr::gen(100)]); } } else { - //Task 进程启动数量 global $counter3; $counter3->add(1); } }); - $serv->on("Receive", function (Server $serv, $fd, $reactorId, $data) - { - $serv->send($fd, "Server: $data"); + $serv->on('Receive', function (Server $serv, $fd, $reactorId, $data) { + $serv->send($fd, "Server: {$data}"); }); - $serv->on('Task', function ($swooleServer, $task_id, $workerId, $data) - { + $serv->on('Task', function (Server $serv, $task_id, $workerId, $data) { global $counter1; $counter1->add(1); return json_encode($data); }); - $serv->on('Finish', function (Server $swooleServer, $workerId, $task_data) - { + $serv->on('Finish', function (Server $swooleServer, $workerId, $task_data) { global $counter2; $counter2->add(1); if ($counter2->get() == N) { @@ -60,10 +55,10 @@ $process = new Swoole\Process(function() { }); $serv->start(); -},false, false); +}, false, false); $process->start(); -Swoole\Process::wait(); +Process::wait(); Assert::same($counter1->get(), 4000); Assert::same($counter2->get(), 4000); Assert::assert($counter3->get() > 15); diff --git a/tests/swoole_server/task/task_queue.phpt b/tests/swoole_server/task/task_queue.phpt index 634422f417..0b2869b458 100644 --- a/tests/swoole_server/task/task_queue.phpt +++ b/tests/swoole_server/task/task_queue.phpt @@ -8,60 +8,48 @@ require __DIR__ . '/../../include/bootstrap.php'; const N = 2048; use Swoole\Server; $pm = new SwooleTest\ProcessManager; -$pm->parentFunc = function ($pid) use ($pm) -{ +$pm->parentFunc = function ($pid) use ($pm) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $cli->connect('127.0.0.1', $pm->getFreePort(), 10) or die("ERROR"); + $cli->connect('127.0.0.1', $pm->getFreePort(), 30) or die("ERROR"); $cli->send("task-01") or die("ERROR"); echo $cli->recv(); $cli->close(); $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); $serv->set(array( - "worker_num" => 1, + 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { - for ($i = 0; $i < 2048; $i++) - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + for ($i = 0; $i < 2048; $i++) { $data = array('id' => $i, 'fd' => $fd, 'data' => RandStr::getBytes(rand(2048, 4096))); - if ($serv->task($data) === false) - { + if ($serv->task($data) === false) { $serv->send($fd, "ERROR\n"); return; } } }); - - $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) - { - if ($task_id == 0) - { + $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { + if ($task_id == 0) { sleep(1); } - if ($task_id != $data['id']) - { + if ($task_id != $data['id']) { echo "ERROR, $task_id, {$data['id']}\n"; } - if ($data['id'] == N - 1) - { + if ($data['id'] == N - 1) { $serv->send($data['fd'], "OK"); } }); - $serv->on('finish', function (Server $serv, $fd, $rid, $data) - { + $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); diff --git a/tests/swoole_server/task/timer.phpt b/tests/swoole_server/task/timer.phpt index 25bf7146eb..f55606931c 100644 --- a/tests/swoole_server/task/timer.phpt +++ b/tests/swoole_server/task/timer.phpt @@ -3,6 +3,7 @@ swoole_server/task: timer --SKIPIF-- --FILE-- parentFunc = function (int $pid) use ($pm) { run(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); - $cli->set(['websocket_compression' => true, ]); + $cli->set(['websocket_compression' => true]); $cli->upgrade('/'); $cli->push('Hello Swoole'); $data = $cli->recv(5); @@ -41,20 +44,20 @@ $pm->childFunc = function () use ($pm) { }); $http->on('WorkerStart', function (Server $server, int $workerId) { if ($server->taskworker) { - Swoole\Timer::after(1, function () use ($server, $workerId) { - var_dump("after1 : " . time()); + Timer::after(1, function () use ($server, $workerId) { + var_dump('after1 : ' . time()); }); // never callback - Swoole\Timer::after(10000, function () use ($server, $workerId) { - var_dump("after2 : " . time()); + Timer::after(10000, function () use ($server, $workerId) { + var_dump('after2 : ' . time()); }); } }); $http->on('task', function (Server $server, Task $task) { var_dump('begin : ' . time()); - Swoole\Timer::after(2000, function () use ($server, $task) { + Timer::after(2000, function () use ($server, $task) { var_dump('end : ' . time()); - Assert::true($server->push($task->data['fd'], "OK")); + Assert::true($server->push($task->data['fd'], 'OK')); }); }); $http->start(); diff --git a/tests/swoole_server/task/unpack.phpt b/tests/swoole_server/task/unpack.phpt new file mode 100644 index 0000000000..3f34472e77 --- /dev/null +++ b/tests/swoole_server/task/unpack.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_server/task: unpack +--SKIPIF-- + +--FILE-- + random_bytes(random_int(16, 2000)), + 'msg' => 'data 3', + 'int' => random_int(1, 9999999), + 'uniq' => uniqid(), +]; +$packed3 = Task::pack($data3); +Assert::same($data3, Task::unpack($packed3)); + +$data4 = [ + 'data' => random_bytes(random_int(9000, 2 * 1024 * 1024)), + 'msg' => 'data 4', + 'int' => random_int(1, 9999999), + 'uniq' => uniqid(), +]; +$packed4 = Task::pack($data4); +Assert::same($data4, Task::unpack($packed4)); +?> +--EXPECT-- diff --git a/tests/swoole_server/taskWaitMulti.phpt b/tests/swoole_server/taskWaitMulti.phpt index 18a19cc4d9..5c2de43439 100644 --- a/tests/swoole_server/taskWaitMulti.phpt +++ b/tests/swoole_server/taskWaitMulti.phpt @@ -6,10 +6,11 @@ swoole_server: taskWaitMulti parentFunc = function ($pid) use ($port) -{ +$pm->parentFunc = function ($pid) use ($port) { $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $cli->connect('127.0.0.1', $port, 0.5) or die("ERROR"); @@ -21,31 +22,26 @@ $pm->parentFunc = function ($pid) use ($port) Swoole\Process::kill($pid); }; -$pm->childFunc = function () use ($pm, $port) -{ +$pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); $serv->set(array( - "worker_num" => 1, + 'worker_num' => 1, 'task_worker_num' => 1, + 'enable_coroutine' => random_int(0, 100) > 50, 'log_file' => '/dev/null', )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + $serv->on("WorkerStart", function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { - if ($data == 'task-01') - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + if ($data == 'task-01') { $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $results = $serv->taskWaitMulti($tasks, 2); - } - else - { + } else { $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); $tasks[] = mt_rand(1000, 9999); @@ -53,27 +49,21 @@ $pm->childFunc = function () use ($pm, $port) $tasks[] = 0; $results = $serv->taskWaitMulti($tasks, 0.2); } - if (count($results) == 4) - { + if (count($results) == 4) { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } }); - $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) - { - if ($data == 0) - { + $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { + if ($data == 0) { usleep(300000); } return $data; }); - $serv->on('finish', function (Server $serv, $fd, $rid, $data) - { + $serv->on('finish', function (Server $serv, $fd, $rid, $data) { }); $serv->start(); diff --git a/tests/swoole_server/taskwait_01.phpt b/tests/swoole_server/taskwait_01.phpt index 6898c1fb50..722998b799 100644 --- a/tests/swoole_server/taskwait_01.phpt +++ b/tests/swoole_server/taskwait_01.phpt @@ -1,109 +1,84 @@ --TEST-- swoole_server: taskwait [blocking] --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($port) -{ - $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $cli->connect('127.0.0.1', $port, 0.5) or die("ERROR"); +$pm = new ProcessManager(); +$pm->parentFunc = function ($pid) use ($port) { + $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $cli->connect('127.0.0.1', $port, 0.5) or exit('ERROR'); - $cli->send("array-01") or die("ERROR"); + $cli->send('array-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("array-02") or die("ERROR"); + $cli->send('array-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("string-01") or die("ERROR"); + $cli->send('string-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("string-02") or die("ERROR"); + $cli->send('string-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("timeout") or die("ERROR"); + $cli->send('timeout') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - Swoole\Process::kill($pid); + Process::kill($pid); }; -$pm->childFunc = function () use ($pm, $port) -{ +$pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); - $serv->set(array( - "worker_num" => 1, + $serv->set([ + 'worker_num' => 1, 'task_worker_num' => 1, 'enable_coroutine' => false, 'log_file' => '/dev/null', - )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { - if ($data == 'array-01') - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + if ($data == 'array-01') { $res = $serv->taskwait(['type' => 'array', 'value' => $data]); - if (!empty($res['name'])) - { + if (!empty($res['name'])) { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'array-02') - { + } elseif ($data == 'array-02') { $res = $serv->taskwait(['type' => 'string', 'value' => $data]); - if ($res == "hello world\n") - { + if ($res == "hello world\n") { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'string-01') - { + } elseif ($data == 'string-01') { $res = $serv->taskwait('array'); - if (!empty($res['name'])) - { + if (!empty($res['name'])) { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'string-02') - { + } elseif ($data == 'string-02') { $res = $serv->taskwait('string'); - if ($res == "hello world\n") - { + if ($res == "hello world\n") { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'timeout') - { + } elseif ($data == 'timeout') { $res = $serv->taskwait('timeout', 0.2); - if ($res === false) - { + if ($res === false) { $res = $serv->taskwait('string', 0.2); - if ($res === "hello world\n") - { + if ($res === "hello world\n") { $serv->send($fd, 'OK'); return; } @@ -112,41 +87,26 @@ $pm->childFunc = function () use ($pm, $port) } }); - $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) - { - if (is_array($data)) - { - if ($data['type'] == 'array') - { - return array('name' => 'rango', 'year' => 1987); - } - else - { - return "hello world\n"; + $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { + if (is_array($data)) { + if ($data['type'] == 'array') { + return ['name' => 'rango', 'year' => 1987]; } + return "hello world\n"; } - else - { - if ($data == 'array') - { - return array('name' => 'rango', 'year' => 1987); - } - elseif ($data == 'string') - { - return "hello world\n"; - } - elseif ($data == 'timeout') - { - usleep(300000); - return "task timeout\n"; - } + if ($data == 'array') { + return ['name' => 'rango', 'year' => 1987]; + } + if ($data == 'string') { + return "hello world\n"; + } + if ($data == 'timeout') { + usleep(300000); + return "task timeout\n"; } }); - $serv->on('finish', function (Server $serv, $fd, $rid, $data) - { - - }); + $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; diff --git a/tests/swoole_server/taskwait_02.phpt b/tests/swoole_server/taskwait_02.phpt index 3d2aba01f3..88e1a43a7c 100644 --- a/tests/swoole_server/taskwait_02.phpt +++ b/tests/swoole_server/taskwait_02.phpt @@ -1,108 +1,85 @@ --TEST-- swoole_server: taskwait [coroutine] --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($port) -{ - $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $cli->connect('127.0.0.1', $port, 0.5) or die("ERROR"); +use SwooleTest\ProcessManager; + +$pm = new ProcessManager(); +$pm->parentFunc = function ($pid) use ($port) { + $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $cli->connect('127.0.0.1', $port, 0.5) or exit('ERROR'); - $cli->send("array-01") or die("ERROR"); + $cli->send('array-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("array-02") or die("ERROR"); + $cli->send('array-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("string-01") or die("ERROR"); + $cli->send('string-01') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("string-02") or die("ERROR"); + $cli->send('string-02') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - $cli->send("timeout") or die("ERROR"); + $cli->send('timeout') or exit('ERROR'); Assert::same($cli->recv(), 'OK'); - Swoole\Process::kill($pid); + Process::kill($pid); }; -$pm->childFunc = function () use ($pm, $port) -{ +$pm->childFunc = function () use ($pm, $port) { ini_set('swoole.display_errors', 'Off'); ini_set('display_errors', 'Off'); $serv = new Server('127.0.0.1', $port, SWOOLE_PROCESS); - $serv->set(array( - "worker_num" => 1, + $serv->set([ + 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', 'enable_coroutine' => true, - )); - $serv->on("WorkerStart", function (Server $serv) use ($pm) - { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Server $serv, $fd, $rid, $data) - { - if ($data == 'array-01') - { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { + if ($data == 'array-01') { $res = $serv->taskwait(['type' => 'array', 'value' => $data]); - if (!empty($res['name'])) - { + if (!empty($res['name'])) { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'array-02') - { + } elseif ($data == 'array-02') { $res = $serv->taskwait(['type' => 'string', 'value' => $data]); - if ($res == "hello world\n") - { + if ($res == "hello world\n") { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'string-01') - { + } elseif ($data == 'string-01') { $res = $serv->taskwait('array'); - if (!empty($res['name'])) - { + if (!empty($res['name'])) { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'string-02') - { + } elseif ($data == 'string-02') { $res = $serv->taskwait('string'); - if ($res == "hello world\n") - { + if ($res == "hello world\n") { $serv->send($fd, 'OK'); - } - else - { + } else { $serv->send($fd, 'ERR'); } - } - elseif ($data == 'timeout') - { + } elseif ($data == 'timeout') { $res = $serv->taskwait('timeout', 0.2); - if ($res === false) - { + if ($res === false) { $res = $serv->taskwait('string', 0.2); - if ($res === "hello world\n") - { + if ($res === "hello world\n") { $serv->send($fd, 'OK'); return; } @@ -111,41 +88,26 @@ $pm->childFunc = function () use ($pm, $port) } }); - $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) - { - if (is_array($data)) - { - if ($data['type'] == 'array') - { - return array('name' => 'rango', 'year' => 1987); - } - else - { - return "hello world\n"; + $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { + if (is_array($data)) { + if ($data['type'] == 'array') { + return ['name' => 'rango', 'year' => 1987]; } + return "hello world\n"; } - else - { - if ($data == 'array') - { - return array('name' => 'rango', 'year' => 1987); - } - elseif ($data == 'string') - { - return "hello world\n"; - } - elseif ($data == 'timeout') - { - usleep(300000); - return "task timeout\n"; - } + if ($data == 'array') { + return ['name' => 'rango', 'year' => 1987]; + } + if ($data == 'string') { + return "hello world\n"; + } + if ($data == 'timeout') { + usleep(300000); + return "task timeout\n"; } }); - $serv->on('finish', function (Server $serv, $fd, $rid, $data) - { - - }); + $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; diff --git a/tests/swoole_server/unregistered_signal.phpt b/tests/swoole_server/unregistered_signal.phpt index 8abab97ec2..9be79b2b1c 100644 --- a/tests/swoole_server/unregistered_signal.phpt +++ b/tests/swoole_server/unregistered_signal.phpt @@ -13,10 +13,11 @@ $pm->parentFunc = function ($pid) use ($pm) { $pid = file_get_contents(TEST_PID_FILE); usleep(1000); Swoole\Process::kill($pid, SIGPIPE); - usleep(1000); + usleep(50000); + $pm->kill(); + usleep(50000); $log = file_get_contents(TEST_LOG_FILE); echo $log, "\n"; - $pm->kill(); }; $pm->childFunc = function () use ($pm) { @unlink(TEST_LOG_FILE); diff --git a/tests/swoole_server/user_process_pid.phpt b/tests/swoole_server/user_process_pid.phpt new file mode 100644 index 0000000000..1884af1b63 --- /dev/null +++ b/tests/swoole_server/user_process_pid.phpt @@ -0,0 +1,48 @@ +--TEST-- +swoole_server: user process +--SKIPIF-- + +--FILE-- +parentFunc = function ($pid) use ($pm, $atomic) { + $url = "http://127.0.0.1:" . $pm->getFreePort() . "/"; + Assert::eq(file_get_contents($url), $atomic->get()); + $pm->wait(); + Assert::eq(file_get_contents($url), $atomic->get()); + usleep(100000); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm, $atomic) { + $serv = new Server(TCP_SERVER_HOST, $pm->getFreePort(), SWOOLE_PROCESS); + $process = new Process(function ($process) use ($serv, $pm, $atomic) { + $atomic->set(posix_getpid()); + usleep(100000); + $pm->wakeup(); + }); + $serv->set([ + 'worker_num' => 2, + 'task_worker_num' => 3, + 'log_file' => '/dev/null', + ]); + $serv->on('Request', function ($req, $resp) use ($serv) { + $resp->end($serv->getWorkerPid(5)); + }); + $serv->on('task', function (){}); + $serv->addProcess($process); + $serv->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_server/wrong_eof_setting.phpt b/tests/swoole_server/wrong_eof_setting.phpt index 7860214431..d3d2dcfe1a 100644 --- a/tests/swoole_server/wrong_eof_setting.phpt +++ b/tests/swoole_server/wrong_eof_setting.phpt @@ -52,3 +52,23 @@ $pm->run(); Fatal error: %s: package_eof cannot be an empty string in %s on line %d Fatal error: %s: package_eof cannot be an empty string in %s on line %d + +--EXPECTF_85-- +Fatal error: %s: package_eof cannot be an empty string in %s on line %d +Stack trace: +#0 [internal function]: Swoole\Server\Port->set(Array) +#1 %s(%d): Swoole\Server->set(Array) +#2 [internal function]: {closure:%s:%d}() +#3 %s/tests/include/lib/src/ProcessManager.php(%d): call_user_func(Object(Closure)) +#4 %s/tests/include/lib/src/ProcessManager.php(%d): SwooleTest\ProcessManager->runChildFunc() +#5 [internal function]: SwooleTest\ProcessManager->{closure:SwooleTest\ProcessManager::run():298}(Object(Swoole\Process)) +#6 %s/tests/include/lib/src/ProcessManager.php(%d): Swoole\Process->start() +#7 %s(%d): SwooleTest\ProcessManager->run() +#8 {main} + +Fatal error: %s: package_eof cannot be an empty string in %s on line %d +Stack trace: +#0 %s(%d): Swoole\Coroutine\Client->connect('127.0.0.1', %d) +#1 [internal function]: {closure:{closure:%s:%d}:%d}() +#2 {main} + diff --git a/tests/swoole_server_coro/length_1.phpt b/tests/swoole_server_coro/length_1.phpt index e1353c3637..45c04c65d0 100644 --- a/tests/swoole_server_coro/length_1.phpt +++ b/tests/swoole_server_coro/length_1.phpt @@ -26,7 +26,7 @@ class TestServer_5 extends LengthServer } TestServer_5::$random_bytes = true; -TestServer_5::$pkg_num = IS_IN_TRAVIS ? 1000 : 10000; +TestServer_5::$pkg_num = IS_IN_CI ? 1000 : 10000; $pm = new ProcessManager; $pm->parentFunc = function ($pid) use ($pm) diff --git a/tests/swoole_server_coro/reuse_port.phpt b/tests/swoole_server_coro/reuse_port.phpt index eca9c2c182..4e2c606c20 100644 --- a/tests/swoole_server_coro/reuse_port.phpt +++ b/tests/swoole_server_coro/reuse_port.phpt @@ -7,19 +7,23 @@ swoole_server_coro: reuse port parentFunc = function ($pid) use ($pm) { - $sch = new Swoole\Coroutine\Scheduler(); + $sch = new Scheduler(); $pids = []; $sch->parallel(10, function () use ($pm, &$pids) { - $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); if (!$cli->connect('127.0.0.1', $pm->getFreePort())) { echo "ERROR [1]\n"; return; @@ -41,18 +45,17 @@ $pm->parentFunc = function ($pid) use ($pm) { $pids[$result['wid']] = 1; }); $sch->start(); - Assert::eq(count($pids), 2); + Assert::eq(count($pids), IS_MAC_OS ? 1 : 2); echo "DONE\n"; $pm->kill(); }; $pm->childFunc = function () use ($pm) { - - $atomic = new \Swoole\Atomic(); - $pool = new Swoole\Process\Pool(2); + $atomic = new Atomic(); + $pool = new Pool(2); $pool->set(['enable_coroutine' => true]); $pool->on(Constant::EVENT_WORKER_START, function ($pool, $id) use ($pm, $atomic) { - $server = new Swoole\Coroutine\Server('127.0.0.1', $pm->getFreePort(), false, true); + $server = new Server('127.0.0.1', $pm->getFreePort(), false, true); if ($atomic->add() == 2) { $pm->wakeup(); } @@ -60,7 +63,7 @@ $pm->childFunc = function () use ($pm) { $server->shutdown(); }); $server->handle(function (Connection $conn) { - Co::sleep(0.005); + co::sleep(0.005); $data = $conn->recv(); if (empty($data)) { $conn->close(); diff --git a/tests/swoole_server_coro/ssl.phpt b/tests/swoole_server_coro/ssl.phpt index ea747b9a5c..497fed9915 100644 --- a/tests/swoole_server_coro/ssl.phpt +++ b/tests/swoole_server_coro/ssl.phpt @@ -13,8 +13,7 @@ $pm = new ProcessManager; $pm->parentFunc = function ($pid) use ($pm) { $client = new Swoole\Client(SWOOLE_SOCK_TCP | SWOOLE_SSL, SWOOLE_SOCK_SYNC); //同步阻塞 - if (!$client->connect('127.0.0.1', $pm->getFreePort())) - { + if (!$client->connect('127.0.0.1', $pm->getFreePort())) { exit("connect failed\n"); } $client->send("hello world"); diff --git a/tests/swoole_server_port/connections.phpt b/tests/swoole_server_port/connections.phpt index 6f68228d98..1338e19b53 100644 --- a/tests/swoole_server_port/connections.phpt +++ b/tests/swoole_server_port/connections.phpt @@ -1,100 +1,113 @@ --TEST-- swoole_server_port: connections --SKIPIF-- - + --FILE-- initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { - $sch = new \Co\Scheduler(); + $sch = new Scheduler(); + $conns_1 = []; + $conns_2 = []; + $sch->parallel( 3, - function () use ($pm) { - $c = new Swoole\Coroutine\Http\Client(TCP_SERVER_HOST, $pm->getFreePort(0)); + function () use ($pm, &$conns_1) { + $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); + $conns_1[] = $c->recv()->data; $c->recv(); } ); $sch->parallel( 2, - function () use ($pm) { - $c = new Swoole\Coroutine\Http\Client(TCP_SERVER_HOST, $pm->getFreePort(1)); + function () use ($pm, &$conns_2) { + $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(1)); $c->upgrade('/'); + $conns_2[] = $c->recv()->data; $c->recv(); } ); - //all + // all $sch->add( - function () use ($pm) { - $c = new Swoole\Coroutine\Http\Client(TCP_SERVER_HOST, $pm->getFreePort(0)); + function () use ($pm, &$conns_1, &$conns_2) { + $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); + $conns_1[] = $c->recv()->data; + $c->push('all'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 8); - Assert::eq($json->list, range(1, 8)); + + $list1 = array_arrange($json->list); + $list2 = array_arrange(array_merge($conns_1, $conns_2)); + + Assert::eq($list1, $list2); } ); - //port-0 + // port-0 $sch->add( - function () use ($pm) { - $c = new Swoole\Coroutine\Http\Client(TCP_SERVER_HOST, $pm->getFreePort(0)); + function () use ($pm, &$conns_1) { + $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(0)); $c->upgrade('/'); + $conns_1[] = $c->recv()->data; $c->push('port-0'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 5); - Assert::eq($json->list, [1,2,3,6,7]); + Assert::eq($json->list, $conns_1); } ); - //port-1 + // port-1 $sch->add( - function () use ($pm) { - $c = new Swoole\Coroutine\Http\Client(TCP_SERVER_HOST, $pm->getFreePort(1)); + function () use ($pm, &$conns_2) { + $c = new Client(TCP_SERVER_HOST, $pm->getFreePort(1)); $c->upgrade('/'); + $conns_2[] = $c->recv()->data; $c->push('port-1'); $frame = $c->recv(); Assert::assert($frame); $json = json_decode($frame->data); Assert::eq($json->count, 3); - Assert::eq($json->list, [4,5,8]); + Assert::eq($json->list, $conns_2); } ); $sch->add( function () use ($pm) { - \Co\System::sleep(.5); + System::sleep(.5); $pm->kill(); } ); $sch->start(); }; -$pm->childFunc = function () use ($pm) -{ - $server = new Swoole\WebSocket\Server("0.0.0.0", $pm->getFreePort(0), SWOOLE_PROCESS); - $server->set( - [ - Constant::OPTION_LOG_FILE => '/dev/null', - Constant::OPTION_WORKER_NUM => 1, - ] - ); - $server->on( - 'open', - function (Swoole\WebSocket\Server $server, $request) { - } - ); +$pm->childFunc = function () use ($pm) { + $server = new Server('0.0.0.0', $pm->getFreePort(0), SWOOLE_PROCESS); + $server->set([ + Constant::OPTION_LOG_FILE => '/dev/null', + Constant::OPTION_WORKER_NUM => 1, + ]); + $server->on('open', function (Server $server, $request) { + $server->push($request->fd, $request->fd); + }); $server->on( Constant::EVENT_WORKER_START, function () use ($pm) { @@ -106,7 +119,7 @@ $pm->childFunc = function () use ($pm) $server->on( 'message', - function (Swoole\WebSocket\Server $server, $frame) { + function (Server $server, $frame) { if ($frame->data == 'all') { $iterator = $server->connections; } elseif ($frame->data == 'port-0') { @@ -123,8 +136,7 @@ $pm->childFunc = function () use ($pm) } ); - $server->on('close', function ($ser, $fd) { - }); + $server->on('close', function ($ser, $fd) {}); $server->start(); }; diff --git a/tests/swoole_server_port/heartbeat_2.phpt b/tests/swoole_server_port/heartbeat_2.phpt index 2e74ef628e..6a4a471996 100644 --- a/tests/swoole_server_port/heartbeat_2.phpt +++ b/tests/swoole_server_port/heartbeat_2.phpt @@ -23,23 +23,22 @@ $pm->parentFunc = function ($pid) use ($pm) { return $cli->recv(0.01); }; go(function () use ($test_func) { - Assert::same($test_func(0, 1.3), ''); + Assert::same($test_func(0, 2), ''); echo "DONE 0\n"; }); go(function () use ($test_func) { - Assert::same($test_func(1, 2.3), ''); + Assert::same($test_func(1, 3), ''); echo "DONE 1\n"; }); go(function () use ($test_func) { - Assert::same($test_func(2, 3.3), false); + Assert::same($test_func(2, 3.5), false); echo "DONE 2\n"; }); }); $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->set([ 'heartbeat_check_interval' => 1, diff --git a/tests/swoole_server_port/heartbeat_3.phpt b/tests/swoole_server_port/heartbeat_3.phpt index 0225856b8c..cc60588b02 100644 --- a/tests/swoole_server_port/heartbeat_3.phpt +++ b/tests/swoole_server_port/heartbeat_3.phpt @@ -1,23 +1,27 @@ --TEST-- swoole_server_port: heartbeat 3 --SKIPIF-- - + --FILE-- initFreePorts(3); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $test_func = function ($port_index, $sleep_seconds) use ($pm) { - $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $cli = new Client(SWOOLE_SOCK_TCP); $cli->connect('127.0.0.1', $pm->getFreePort($port_index)); System::sleep($sleep_seconds); return $cli->recv(0.01); @@ -34,13 +38,12 @@ $pm->parentFunc = function ($pid) use ($pm) { $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ - $server = new Swoole\Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(0), SWOOLE_BASE); $server->on('receive', function ($server, $fd, $reactorId, $data) { $server->send($fd, 'ok'); }); - $server->on("WorkerStart", function (Server $serv) use ($pm) { + $server->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); diff --git a/tests/swoole_server_port/http.phpt b/tests/swoole_server_port/http.phpt index ccbca8aeb7..edde6353a0 100644 --- a/tests/swoole_server_port/http.phpt +++ b/tests/swoole_server_port/http.phpt @@ -8,29 +8,27 @@ require __DIR__ . '/../include/bootstrap.php'; ini_set("swoole.display_errors", "Off"); +const TEST_STR = "hello swooler\n"; + $pm = new ProcessManager; $pm->initFreePorts(2); $pm->parentFunc = function ($pid) use ($pm) { - go(function () use ($pm) - { + go(function () use ($pm) { $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); - if (!$cli->connect('127.0.0.1', $pm->getFreePort(0), 0.5)) - { + if (!$cli->connect('127.0.0.1', $pm->getFreePort(0), 0.5)) { fail: echo "ERROR 1\n"; return; } //no eof, should be timeout here - if (!$cli->send("hello\r\n\r\n")) - { + if (!$cli->send("hello\r\n\r\n")) { goto fail; } $ret = $cli->recv(); - if (!$ret) - { + if (!$ret) { goto fail; } echo "OK\n"; @@ -39,7 +37,7 @@ $pm->parentFunc = function ($pid) use ($pm) go(function () use ($pm) { $cli = new Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort(1)); if ($cli->get("/")) { - echo $cli->body; + Assert::same($cli->body, TEST_STR); Assert::same($cli->statusCode, 200); } else { echo "ERROR 2\n"; @@ -56,19 +54,18 @@ $pm->childFunc = function () use ($pm) $server->set([ 'open_eof_check' => true, - "package_eof" => "\r\n\r\n", - 'log_file' => '/dev/null' + "package_eof" => "\r\n\r\n", + 'log_file' => '/dev/null' ]); - $server->on('Receive', function ($serv, $fd, $rid, $data) - { + $server->on('Receive', function ($serv, $fd, $rid, $data) { $serv->send($fd, "Swoole: $data\r\n\r\n"); }); $port2 = $server->listen('127.0.0.1', $pm->getFreePort(1), SWOOLE_SOCK_TCP); $port2->set(['open_http_protocol' => true,]); $port2->on("request", function ($req, $resp) { - $resp->end("hello swooler\n"); + $resp->end(TEST_STR); }); $server->on("WorkerStart", function (Swoole\Server $serv) { @@ -89,4 +86,3 @@ $pm->run(); ?> --EXPECT-- OK -hello swooler diff --git a/tests/swoole_server_port/multi_port.phpt b/tests/swoole_server_port/multi_port.phpt index ede28d4c95..f9fbe7e074 100644 --- a/tests/swoole_server_port/multi_port.phpt +++ b/tests/swoole_server_port/multi_port.phpt @@ -15,7 +15,7 @@ $port1 = get_one_free_port(); $port2 = get_one_free_port(); $port3 = get_one_free_port(); -function makeTcpClient_without_protocol($host, $port, callable $onConnect = null, callable $onReceive = null) +function makeTcpClient_without_protocol($host, $port, ?callable $onConnect = null, ?callable $onReceive = null) { go(function () use ($host, $port, $onConnect, $onReceive) { $cli = new Client(SWOOLE_SOCK_TCP); diff --git a/tests/swoole_socket_coro/bound_error.phpt b/tests/swoole_socket_coro/bound_error.phpt new file mode 100644 index 0000000000..e276e3cdc7 --- /dev/null +++ b/tests/swoole_socket_coro/bound_error.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_socket_coro: bound error +--SKIPIF-- + +--FILE-- +bind('127.0.0.1', $port)); + Assert::assert($server->listen()); +}); +go(function () use ($port) { + $cli = new Client(SWOOLE_SOCK_TCP); + $ret = $cli->connect('127.0.0.1', $port); + Assert::true($ret); + go(function () use ($cli) { + $cli->recv(); + }); + $cli->recv(); +}); +Event::wait(); +?> +--EXPECTF-- +Fatal error: Uncaught Swoole\Error: Socket#%d has already been bound to another coroutine#%d, reading of the same socket in coroutine#%d at the same time is not allowed in %s:%d +Stack trace: +#0 %s(%d): Swoole\Coroutine\Client->recv() +#1 [internal function]: {%s}() +#2 {main} + thrown in %s on line %d + + [Coroutine-3] Stack trace: + ------------------------------------------------------------------- +#0 %s(%d): Swoole\Coroutine\Client->recv() +#1 [internal function]: {%s}() diff --git a/tests/swoole_socket_coro/complete_test.phpt b/tests/swoole_socket_coro/complete_test.phpt index 45918e1b8a..eab9e1e483 100644 --- a/tests/swoole_socket_coro/complete_test.phpt +++ b/tests/swoole_socket_coro/complete_test.phpt @@ -1,21 +1,26 @@ --TEST-- swoole_socket_coro: complete test server&&client&&timeout(millisecond) --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm, $port) { - $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); - Assert::isInstanceOf($socket, Swoole\Coroutine\Socket::class); + $socket = new Socket(AF_INET, SOCK_STREAM, 0); + Assert::isInstanceOf($socket, Socket::class); Assert::same($socket->errCode, 0); go(function () use ($socket, $port) { Assert::assert($socket->connect('localhost', $port)); $i = 0.000; while (true) { - $socket->send("hello"); + $socket->send('hello'); $server_reply = $socket->recv(1024, 0.1); Assert::same($server_reply, 'swoole'); co::sleep($i += .001); // after 10 times we sleep 0.01s to trigger server timeout @@ -24,37 +29,39 @@ $pm->parentFunc = function ($pid) use ($pm, $port) { } } co::sleep(0.5); - echo("client exit\n"); + echo "client exit\n"; $socket->close(); }); - Swoole\Event::wait(); + Event::wait(); }; $pm->childFunc = function () use ($pm, $port) { - $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); + $socket = new Socket(AF_INET, SOCK_STREAM, 0); Assert::assert($socket->bind('127.0.0.1', $port)); Assert::assert($socket->listen(128)); go(function () use ($socket, $pm) { + $pm->wakeup(); $client = $socket->accept(); - Assert::isInstanceOf($client, Swoole\Coroutine\Socket::class); + Assert::assert($client, 'error: ' . swoole_last_error()); + Assert::isInstanceOf($client, Socket::class); $i = 0; while (true) { $client_data = $client->recv(1024, 0.1); if ($client->errCode > 0) { Assert::same($client->errCode, SOCKET_ETIMEDOUT); break; - } else { - $i++; - Assert::same($client_data, 'hello'); - $client->send('swoole'); } + $i++; + Assert::same($client_data, 'hello'); + $client->send('swoole'); } - echo "$i\n"; - echo("sever exit\n"); + echo "{$i}\n"; + echo "sever exit\n"; usleep(1); $client->close(); $socket->close(); }); + Event::wait(); }; $pm->childFirst(); diff --git a/tests/swoole_socket_coro/getBoundCid.phpt b/tests/swoole_socket_coro/getBoundCid.phpt new file mode 100644 index 0000000000..a68590cf4a --- /dev/null +++ b/tests/swoole_socket_coro/getBoundCid.phpt @@ -0,0 +1,65 @@ +--TEST-- +swoole_socket_coro: getBoundCid +--SKIPIF-- + +--FILE-- +parentFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $sock = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, 0); + Assert::assert($sock->connect('127.0.0.1', $pm->getFreePort())); + set_socket_coro_buffer_size($sock, 8192); + $write_co = $read_co = -1; + go(function () use ($pm, $sock, &$write_co, &$read_co) { + Co::sleep(0.001); + echo "CLOSE\n"; + Assert::eq($sock->getBoundCid(SWOOLE_EVENT_READ), $read_co); + Assert::eq($sock->getBoundCid(SWOOLE_EVENT_WRITE), $write_co); + $sock->close(); + $pm->kill(); + echo "DONE\n"; + }); + $write_co = go(function () use ($sock) { + echo "SEND\n"; + $size = 16 * 1024 * 1024; + Assert::lessThan($sock->sendAll(str_repeat('S', $size)), $size); + Assert::eq($sock->errCode, SOCKET_ECANCELED); + echo "SEND CLOSED\n"; + }); + $read_co = go(function () use ($sock) { + echo "RECV\n"; + Assert::false($sock->recv(-1)); + Assert::eq($sock->errCode, SOCKET_ECANCELED); + echo "RECV CLOSED\n"; + }); + }); +}; +$pm->childFunc = function () use ($pm) { + Co\run(function () use ($pm) { + $server = new Co\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + Assert::assert($server->bind('127.0.0.1', $pm->getFreePort())); + Assert::assert($server->listen()); + go(function () use ($pm, $server) { + if (Assert::assert(($conn = $server->accept()) && $conn instanceof Co\Socket)) { + switch_process(); + co::sleep(5); + $conn->close(); + } + $server->close(); + }); + $pm->wakeup(); + }); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +SEND +RECV +CLOSE +SEND CLOSED +RECV CLOSED +DONE diff --git a/tests/swoole_socket_coro/getopt/tcpinfo.phpt b/tests/swoole_socket_coro/getopt/tcpinfo.phpt new file mode 100644 index 0000000000..9a5dc6eebf --- /dev/null +++ b/tests/swoole_socket_coro/getopt/tcpinfo.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_socket_coro/getopt: tcp info +--SKIPIF-- + +--FILE-- +getOption(SOL_TCP, TCP_INFO); + Assert::greaterThan($info['rcv_space'], 0); + Assert::greaterThan($info['rto'], 0); + Assert::greaterThan($info['rtt'], 0); + Assert::greaterThan($info['snd_mss'], 0); + Assert::greaterThan($info['rcv_mss'], 0); + echo "DONE\n"; + }); + Assert::assert(strpos($content, 'map.baidu.com') !== false); +}); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_socket_coro/icmp.phpt b/tests/swoole_socket_coro/icmp.phpt new file mode 100644 index 0000000000..6a942b9e4b --- /dev/null +++ b/tests/swoole_socket_coro/icmp.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_socket_coro: icmp +--SKIPIF-- + +--FILE-- +connect($host)); + $socket->send($package, strlen($package)); + $pkt = $socket->recv(256); + Assert::notEmpty($pkt); +}); +?> +--EXPECT-- diff --git a/tests/swoole_socket_coro/import_4.phpt b/tests/swoole_socket_coro/import_4.phpt index b4c5a79205..d2ceb54676 100644 --- a/tests/swoole_socket_coro/import_4.phpt +++ b/tests/swoole_socket_coro/import_4.phpt @@ -102,3 +102,33 @@ socket_set_block 0 socket_get_option 0 Done. +--EXPECTF_85-- +normal +stream_set_blocking 1 +socket_set_block 1 +socket_get_option 2 + + +unset stream +socket_set_block 1 +socket_get_option 2 + + +unset socket +stream_set_blocking 1 + + +close stream +stream_set_blocking TypeError: stream_set_blocking(): Argument #1 ($stream) must be an open stream resource + +socket_set_block 1 +socket_get_option 2 + + +close socket +stream_set_blocking TypeError: stream_set_blocking(): Argument #1 ($stream) must be an open stream resource + +socket_set_block 0 +socket_get_option 0 + +Done. diff --git a/tests/swoole_socket_coro/import_5.phpt b/tests/swoole_socket_coro/import_5.phpt new file mode 100644 index 0000000000..135704d507 --- /dev/null +++ b/tests/swoole_socket_coro/import_5.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_socket_coro: import 5 +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_socket_coro/readVectorAll.phpt b/tests/swoole_socket_coro/readVectorAll.phpt index a6625fdc06..290999d9c5 100644 --- a/tests/swoole_socket_coro/readVectorAll.phpt +++ b/tests/swoole_socket_coro/readVectorAll.phpt @@ -15,7 +15,6 @@ $totalLength = 0; $iovector = []; $packedStr = ''; - for ($i = 0; $i < 10; $i++) { $iovector[$i] = str_repeat(get_safe_random(1024), 128); $totalLength += strlen($iovector[$i]); @@ -31,7 +30,7 @@ run(function () { Assert::assert($server->bind('127.0.0.1', $port)); Assert::assert($server->listen(512)); $conn = $server->accept(); - Assert::assert($conn instanceof Socket); + Assert::assert($conn instanceof Socket); Assert::assert($conn->fd > 0); global $packedStr, $iovector, $totalLength2; @@ -60,7 +59,14 @@ run(function () { $ret = $conn->sendAll(substr($packedStr, 0, $totalLength2)); Assert::eq($ret, $totalLength2); - $server->close(); + /** + *The TCP three-way handshake is handled by the kernel. After connect() succeeds, + * the server coroutine's accept() function does not return immediately. + * The client coroutine continues to execute sendAll() until an IO block occurs; + * only when a writable event is detected does it switch to the server coroutine, at which point accept() returns and proceeds to execute readVectorAll(). + * If $server->close() is called, it may cause the server coroutine's accept() to return false. + */ + $conn->close(); }); }); diff --git a/tests/swoole_socket_coro/readVectorAll_ssl.phpt b/tests/swoole_socket_coro/readVectorAll_ssl.phpt index 8aeca4d79d..e1816cb4a7 100644 --- a/tests/swoole_socket_coro/readVectorAll_ssl.phpt +++ b/tests/swoole_socket_coro/readVectorAll_ssl.phpt @@ -7,7 +7,6 @@ swoole_socket_coro: readVectorAll with ssl require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine\Socket; -use Swoole\Server; use function Swoole\Coroutine\run; @@ -22,7 +21,7 @@ for ($i = 0; $i < 10; $i++) { } $totalLength2 = rand(strlen($packedStr) / 2, strlen($packedStr) - 1024 * 128); -$pm = new ProcessManager; +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; @@ -51,8 +50,10 @@ $pm->childFunc = function () use ($pm) { Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); + $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); + Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); $iov = []; diff --git a/tests/swoole_socket_coro/readVector_ssl.phpt b/tests/swoole_socket_coro/readVector_ssl.phpt index 77a2d6f5b3..648c6079e6 100644 --- a/tests/swoole_socket_coro/readVector_ssl.phpt +++ b/tests/swoole_socket_coro/readVector_ssl.phpt @@ -7,11 +7,10 @@ swoole_socket_coro: readVector with ssl require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine\Socket; -use Swoole\Server; use function Swoole\Coroutine\run; -$pm = new ProcessManager; +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { $conn = new Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); @@ -40,8 +39,10 @@ $pm->childFunc = function () use ($pm) { Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); + $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); + Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Assert::eq($conn->readVector([5, 5]), ['hello', 'world']); diff --git a/tests/swoole_socket_coro/readVector_ssl_eagain.phpt b/tests/swoole_socket_coro/readVector_ssl_eagain.phpt index 28cd27f5fe..4f87616cc6 100644 --- a/tests/swoole_socket_coro/readVector_ssl_eagain.phpt +++ b/tests/swoole_socket_coro/readVector_ssl_eagain.phpt @@ -8,7 +8,6 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine; use Swoole\Coroutine\Socket; -use Swoole\Server; use function Swoole\Coroutine\run; @@ -23,7 +22,7 @@ for ($i = 0; $i < 10; $i++) { } $totalLength2 = rand(strlen($packedStr) / 2, strlen($packedStr) - 1024 * 128); -$pm = new ProcessManager; +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $packedStr; @@ -53,8 +52,10 @@ $pm->childFunc = function () use ($pm) { Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); + $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); + Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); $iov = []; diff --git a/tests/swoole_socket_coro/sendto_large_packet.phpt b/tests/swoole_socket_coro/sendto_large_packet.phpt index 4ea966fa1b..7d8dd7a3fd 100644 --- a/tests/swoole_socket_coro/sendto_large_packet.phpt +++ b/tests/swoole_socket_coro/sendto_large_packet.phpt @@ -1,38 +1,45 @@ --TEST-- swoole_socket_coro: send large packet --SKIPIF-- - + --FILE-- bind('127.0.0.1', 9601); - for ($i = 0; $i < N; $i++) - { + for ($i = 0; $i < N; $i++) { $peer = null; $data = $socket->recvfrom($peer); - $socket->sendto($peer['address'], $peer['port'], "Swoole: $data"); - Assert::assert(strlen($data) >= 30000); + Assert::assert($data); + Assert::assert($socket->sendto($peer['address'], $peer['port'], "Swoole: {$data}")); + Assert::assert(strlen($data) >= MIN_PACKET_SIZE); Assert::assert(is_array($peer)); } }); -//Client +// Client go(function () { - $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0); - for ($i = 0; $i < N; $i++) - { - $socket->sendto('127.0.0.1', 9601, str_repeat('A', rand(30000, 65000))); + $socket = new Socket(AF_INET, SOCK_DGRAM, 0); + for ($i = 0; $i < N; $i++) { + Assert::assert($socket->sendto('127.0.0.1', 9601, str_repeat('A', rand(MIN_PACKET_SIZE, MAX_PACKET_SIZE))), 'error: ' . swoole_strerror($socket->errCode)); $peer = null; $data = $socket->recvfrom($peer); + Assert::assert($data); Assert::assert(is_array($peer)); - Assert::assert(strlen($data) >= 30000); + Assert::assert(strlen($data) >= MIN_PACKET_SIZE); } }); -Swoole\Event::wait(); +Event::wait(); ?> --EXPECTF-- diff --git a/tests/swoole_socket_coro/setopt/ipv6_pktinfo.phpt b/tests/swoole_socket_coro/setopt/ipv6_pktinfo.phpt index acbcb12185..4eae429bf1 100644 --- a/tests/swoole_socket_coro/setopt/ipv6_pktinfo.phpt +++ b/tests/swoole_socket_coro/setopt/ipv6_pktinfo.phpt @@ -1,7 +1,10 @@ --TEST-- swoole_socket_coro/setopt: setOption IPV6_PKTINFO --SKIPIF-- - + --FILE-- + --FILE-- setProtocol(['open_ssl' => true,]); - - if (!$cli->connect('www.baidu.com', 443)) { - echo "ERROR\n"; - } - - $http = "GET / HTTP/1.1\r\nAccept: */*User-Agent: Lowell-Agent\r\nHost: www.baidu.com\r\nConnection: Keep-Alive\r\n" - . "Keep-Alive: on\r\n\r\n"; - if (!$cli->send($http)) { - echo "ERROR\n"; - } - - $content = ''; - $length = 0; - while (true) { - $read = $cli->recv(); - if (empty($read)) { - var_dump($read); - break; - } - $content .= $read; - if ($length == 0) { - if (preg_match('#Content-Length: (\d+)#i', $content, $match)) { - $length = intval($match[1]); - } - } - $header_length = strpos($content, "\r\n\r\n"); - if (strlen($content) == $length + $header_length + 4) { - break; - } - } - $cli->close(); + $content = http_get_with_co_socket('www.baidu.com'); Assert::assert(strpos($content, 'map.baidu.com') !== false); }); ?> diff --git a/tests/swoole_socket_coro/unix_stream.phpt b/tests/swoole_socket_coro/unix_stream.phpt new file mode 100644 index 0000000000..ff9fa3de79 --- /dev/null +++ b/tests/swoole_socket_coro/unix_stream.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_socket_coro: unix stream +--SKIPIF-- + +--FILE-- +bind(SOCK_FILE); + $server->listen(); + + go(function () use ($server) { + while (!$server->isClosed()) { + $conn = $server->accept(); + while ($data = $conn->recv()) { + Assert::same($data, 'hello'); + $conn->send('world'); + } + } + }); + + go(function () use ($server) { + $client = new Swoole\Coroutine\Socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP); + $client->connect(SOCK_FILE); + for ($n = MAX_REQUESTS; $n--;) { + $client->send('hello'); + $data = $client->recv(); + Assert::notEmpty($data); + if (empty($data)) { + break; + } + Assert::same($data, 'world'); + } + $client->close(); + $server->close(); + }); +}); +echo "DONE\n"; +?> +--EXPECT-- +DONE diff --git a/tests/swoole_socket_coro/writeVectorAll.phpt b/tests/swoole_socket_coro/writeVectorAll.phpt index 2beeb04232..e37fa1d8c2 100644 --- a/tests/swoole_socket_coro/writeVectorAll.phpt +++ b/tests/swoole_socket_coro/writeVectorAll.phpt @@ -44,7 +44,8 @@ run(function () { Assert::assert($conn->connect('127.0.0.1', $port)); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); - $server->close(); + + $conn->close(); }); }); diff --git a/tests/swoole_socket_coro/writeVectorAll_ssl.phpt b/tests/swoole_socket_coro/writeVectorAll_ssl.phpt index d2d69e795c..8dd75283e6 100644 --- a/tests/swoole_socket_coro/writeVectorAll_ssl.phpt +++ b/tests/swoole_socket_coro/writeVectorAll_ssl.phpt @@ -7,7 +7,6 @@ swoole_socket_coro: writeVector with ssl require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine\Socket; -use Swoole\Server; use function Swoole\Coroutine\run; @@ -15,7 +14,6 @@ $totalLength = 0; $iovector = []; $packedStr = ''; - for ($i = 0; $i < 10; $i++) { $iovector[$i] = str_repeat(get_safe_random(1024), 128); $totalLength += strlen($iovector[$i]); @@ -23,7 +21,7 @@ for ($i = 0; $i < 10; $i++) { } $totalLength2 = rand(strlen($packedStr) / 2, strlen($packedStr) - 1024 * 128); -$pm = new ProcessManager; +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; @@ -52,8 +50,10 @@ $pm->childFunc = function () use ($pm) { Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); + $pm->wakeup(); /** @var Socket */ $conn = $socket->accept(); + Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Assert::eq($conn->recvAll($totalLength), $packedStr, -1); diff --git a/tests/swoole_socket_coro/writeVector_ssl_eagain.phpt b/tests/swoole_socket_coro/writeVector_ssl_eagain.phpt index 682c9ab9ac..2c3ad9755f 100644 --- a/tests/swoole_socket_coro/writeVector_ssl_eagain.phpt +++ b/tests/swoole_socket_coro/writeVector_ssl_eagain.phpt @@ -8,7 +8,6 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Coroutine; use Swoole\Coroutine\Socket; -use Swoole\Server; use function Swoole\Coroutine\run; @@ -16,7 +15,6 @@ $totalLength = 0; $iovector = []; $packedStr = ''; - for ($i = 0; $i < 10; $i++) { $iovector[$i] = str_repeat(get_safe_random(1024), 128); $totalLength += strlen($iovector[$i]); @@ -24,7 +22,7 @@ for ($i = 0; $i < 10; $i++) { } $totalLength2 = rand(strlen($packedStr) / 2, strlen($packedStr) - 1024 * 128); -$pm = new ProcessManager; +$pm = new ProcessManager(); $pm->parentFunc = function ($pid) use ($pm) { run(function () use ($pm) { global $totalLength, $iovector; @@ -53,8 +51,11 @@ $pm->childFunc = function () use ($pm) { Assert::assert($socket->bind('127.0.0.1', $pm->getFreePort())); Assert::assert($socket->listen(MAX_CONCURRENCY)); + $pm->wakeup(); + /** @var Socket */ $conn = $socket->accept(); + Assert::assert($conn, 'error: ' . swoole_last_error()); $conn->sslHandshake(); Coroutine::sleep(0.5); diff --git a/tests/swoole_socket_coro/writev_eagain.phpt b/tests/swoole_socket_coro/writev_eagain.phpt index 92322e73e1..1f4b194fa9 100644 --- a/tests/swoole_socket_coro/writev_eagain.phpt +++ b/tests/swoole_socket_coro/writev_eagain.phpt @@ -48,7 +48,7 @@ run(function () { Assert::assert($conn->connect('127.0.0.1', $port)); $ret = $conn->writeVectorAll($iovector); Assert::eq($ret, $totalLength); - $server->close(); + $conn->close(); }); }); diff --git a/tests/swoole_ssh2/bug63480.phpt b/tests/swoole_ssh2/bug63480.phpt new file mode 100644 index 0000000000..dd2e85133c --- /dev/null +++ b/tests/swoole_ssh2/bug63480.phpt @@ -0,0 +1,23 @@ +--TEST-- +Bug #63480 (Warning on using the SSH2 Session resource in the uri) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +yada yada diff --git a/tests/swoole_ssh2/bug79631.phpt b/tests/swoole_ssh2/bug79631.phpt new file mode 100644 index 0000000000..d6d798fd0e --- /dev/null +++ b/tests/swoole_ssh2/bug79631.phpt @@ -0,0 +1,21 @@ +--TEST-- +Bug 79631 (SSH disconnect segfault with SFTP (assertion failed)) +--SKIPIF-- + +--FILE-- + +--EXPECT-- +done diff --git a/tests/swoole_ssh2/ssh2_auth.phpt b/tests/swoole_ssh2/ssh2_auth.phpt new file mode 100644 index 0000000000..6f1b30dee5 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_auth.phpt @@ -0,0 +1,15 @@ +--TEST-- +ssh2_auth_FOO() - Attempt to authenticate to a remote host +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) diff --git a/tests/swoole_ssh2/ssh2_auth_pubkey.phpt b/tests/swoole_ssh2/ssh2_auth_pubkey.phpt new file mode 100644 index 0000000000..03e1481c59 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_auth_pubkey.phpt @@ -0,0 +1,29 @@ +--TEST-- +ssh2_auth_pubkey() - Tests authentication with a key +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +testing echo with key auth + diff --git a/tests/swoole_ssh2/ssh2_auth_pubkey_file.phpt b/tests/swoole_ssh2/ssh2_auth_pubkey_file.phpt new file mode 100644 index 0000000000..506f0e5ea6 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_auth_pubkey_file.phpt @@ -0,0 +1,29 @@ +--TEST-- +ssh2_auth_pubkey_file() - Tests authentication with a key +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +testing echo with key auth + diff --git a/tests/swoole_ssh2/ssh2_connect.phpt b/tests/swoole_ssh2/ssh2_connect.phpt new file mode 100644 index 0000000000..82cebf54ef --- /dev/null +++ b/tests/swoole_ssh2/ssh2_connect.phpt @@ -0,0 +1,68 @@ +--TEST-- +ssh2_connect() Basic connection and pre-authentication +--SKIPIF-- + +--FILE-- + 0); + } + + echo "**Negotiation\n"; + $mn = ssh2_methods_negotiated($ssh); + var_dump(ssh2t_strset($mn['kex'])); + var_dump(ssh2t_strset($mn['hostkey'])); + foreach (['client_to_server', 'server_to_client'] as $direction) { + $mnd = $mn[$direction]; + var_dump(ssh2t_strset($mnd['crypt'])); + var_dump(ssh2t_strset($mnd['comp'])); + var_dump(ssh2t_strset($mnd['mac'])); + } + + Assert::true(ssh2_disconnect($ssh)); +}); +?> +--EXPECT-- +**Connect +bool(true) +string(12) "SSH2 Session" +**Fingerprint MD5 +bool(true) +int(32) +bool(true) +**Fingerprint SHA1 +bool(true) +int(40) +bool(true) +**Negotiation +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) + diff --git a/tests/swoole_ssh2/ssh2_exec.phpt b/tests/swoole_ssh2/ssh2_exec.phpt new file mode 100644 index 0000000000..87ff12faa5 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_exec.phpt @@ -0,0 +1,25 @@ +--TEST-- +ssh2_shell_test() - Tests opening a shell +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +testing echo + diff --git a/tests/swoole_ssh2/ssh2_scp_recv.phpt b/tests/swoole_ssh2/ssh2_scp_recv.phpt new file mode 100644 index 0000000000..ff524011f0 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_scp_recv.phpt @@ -0,0 +1,46 @@ +--TEST-- +ssh2_scp_recv() - Tests receiving a file via SCP +--CREDITS-- +Chris MacPherson +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_ssh2/ssh2_scp_send.phpt b/tests/swoole_ssh2/ssh2_scp_send.phpt new file mode 100644 index 0000000000..77de689ad5 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_scp_send.phpt @@ -0,0 +1,41 @@ +--TEST-- +ssh2_scp_send() - Tests sending a file via SCP +--CREDITS-- +Chris MacPherson +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_ssh2/ssh2_send_eof.phpt b/tests/swoole_ssh2/ssh2_send_eof.phpt new file mode 100644 index 0000000000..360e9106b7 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_send_eof.phpt @@ -0,0 +1,34 @@ +--TEST-- +ssh2_send_eof() - Tests closing standard input +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +bool(true) +foo + diff --git a/tests/swoole_ssh2/ssh2_sftp_001.phpt b/tests/swoole_ssh2/ssh2_sftp_001.phpt new file mode 100644 index 0000000000..ec0bd04a6f --- /dev/null +++ b/tests/swoole_ssh2/ssh2_sftp_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +ssh2_sftp - SFTP tests +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/swoole_ssh2/ssh2_sftp_002.phpt b/tests/swoole_ssh2/ssh2_sftp_002.phpt new file mode 100644 index 0000000000..ded1104a71 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_sftp_002.phpt @@ -0,0 +1,31 @@ +--TEST-- +ssh2_sftp - SFTP tests +--SKIPIF-- + +--FILE-- + +--EXPECT-- +Hello World +Goodbye Planet +bool(true) diff --git a/tests/swoole_ssh2/ssh2_shell.phpt b/tests/swoole_ssh2/ssh2_shell.phpt new file mode 100644 index 0000000000..058dae4b35 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_shell.phpt @@ -0,0 +1,35 @@ +--TEST-- +ssh2_shell_test() - Tests opening a shell +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +%a +%a diff --git a/tests/swoole_ssh2/ssh2_skip.inc b/tests/swoole_ssh2/ssh2_skip.inc new file mode 100644 index 0000000000..f7a8107dba --- /dev/null +++ b/tests/swoole_ssh2/ssh2_skip.inc @@ -0,0 +1,32 @@ + +--FILE-- + 0) { + while($line = fgets($shell)) { + echo $line; + if (str_ends_with($line, "howdy\r\n")) { + break; + } + } + } + $elapsed = time() - $start; + var_dump(($elapsed < $timeout)); +}); +?> +--EXPECTF-- +bool(true) +resource(%d) of type (stream) +%a +%a diff --git a/tests/swoole_ssh2/ssh2_test.inc b/tests/swoole_ssh2/ssh2_test.inc new file mode 100644 index 0000000000..4b61a876c9 --- /dev/null +++ b/tests/swoole_ssh2/ssh2_test.inc @@ -0,0 +1,49 @@ + +--FILE-- +'); +$array[0] = 1; +$array[1] = 2; +$array[2] = 3; +$array[3] = 999; +Assert::false($array->isEmpty()); +Assert::eq($array->count(), count($array)); +Assert::eq($array->slice(0, 2), [1, 2]); +Assert::true($array->contains(999)); +Assert::eq($array->search(999), 3); +Assert::true([]->isEmpty()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/array_method/1.phpt b/tests/swoole_stdext/array_method/1.phpt new file mode 100644 index 0000000000..db974ec88e --- /dev/null +++ b/tests/swoole_stdext/array_method/1.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_stdext/array_method: 1 +--SKIPIF-- + +--FILE-- +', ["lemon", "orange", "banana", "apple"]); +$sorted_array = array("apple", "banana", "lemon", "orange",); + +$ref = &$array; +$ref->sort(SORT_NATURAL | SORT_FLAG_CASE); + +Assert::same($array, $sorted_array); +Assert::same($ref, $sorted_array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/array_method/2.phpt b/tests/swoole_stdext/array_method/2.phpt new file mode 100644 index 0000000000..f85aa1e5cd --- /dev/null +++ b/tests/swoole_stdext/array_method/2.phpt @@ -0,0 +1,27 @@ +--TEST-- +swoole_stdext/array_method: 2 +--SKIPIF-- + +--FILE-- +count(), 4); +$ref = &$stack; +$fruit = $ref->shift(); +Assert::eq($fruit, "orange"); +Assert::eq($stack->count(), 3); +Assert::eq($array->count(), 4); + +$ref->unshift("mango"); +Assert::eq($stack->count(), 4); + +$stack2 = array("orange", "banana", "apple", "raspberry"); +Assert::eq($stack2->count(), 4); +$stack2->shift(); +?> +--EXPECTF-- +Warning: array_shift(): Argument #1 ($array) must be passed by reference, value given in %s on line %d diff --git a/tests/swoole_stdext/array_method/method.phpt b/tests/swoole_stdext/array_method/method.phpt new file mode 100644 index 0000000000..565244cc22 --- /dev/null +++ b/tests/swoole_stdext/array_method/method.phpt @@ -0,0 +1,215 @@ +--TEST-- +swoole_stdext/array_method: all array methods test +--SKIPIF-- + +--FILE-- += 80400) { + $array = [ + 'a' => 'dog', + 'b' => 'cat', + 'c' => 'cow', + 'd' => 'duck', + 'e' => 'goose', + 'f' => 'elephant' + ]; + Assert::eq( + $array->all(fn(string $value) => $value->length() > 12), + array_all($array, fn(string $value) => $value->length() > 12) + ); + + Assert::eq( + $array->any(fn(string $value) => $value->length() > 5), + array_any($array, fn(string $value) => $value->length() > 5) + ); +} + +$array = array("FirSt" => 1, "SecOnd" => 4); +Assert::eq($array->changeKeyCase(CASE_UPPER), array_change_key_case($array, CASE_UPPER)); + +$array = array('a', 'b', 'c', 'd', 'e'); +Assert::eq($array->chunk(2), array_chunk($array, 2)); + +$records = [ + [ + 'id' => 2135, + 'first_name' => 'John', + 'last_name' => 'Doe', + ], + [ + 'id' => 3245, + 'first_name' => 'Sally', + 'last_name' => 'Smith', + ], + [ + 'id' => 5342, + 'first_name' => 'Jane', + 'last_name' => 'Jones', + ], + [ + 'id' => 5623, + 'first_name' => 'Peter', + 'last_name' => 'Doe', + ] +]; +Assert::eq($records->column('id'), array_column($records, 'id')); + +$array = array(1, "hello", 1, "world", "hello"); +Assert::eq($array->countValues(), array_count_values($array)); + +$array1 = array("a" => "green", "red", "blue", "red"); +$array2 = array("b" => "green", "yellow", "red"); +Assert::eq($array1->diff($array2), array_diff($array1, $array2)); + +$array1 = array("a" => "green", "b" => "brown", "c" => "blue", "red"); +$array2 = array("a" => "green", "yellow", "red"); +Assert::eq($array1->diffAssoc($array2), array_diff_assoc($array1, $array2)); + +$array1 = array('blue' => 1, 'red' => 2, 'green' => 3, 'purple' => 4); +$array2 = array('green' => 5, 'yellow' => 7, 'cyan' => 8); +Assert::eq($array1->diffKey($array2), array_diff_key($array1, $array2)); + +function odd($var) +{ + return $var & 1; +} +$array = [6, 7, 8, 9, 10, 11, 12]; +Assert::eq($array->filter('odd'), array_filter($array, 'odd')); + +if (PHP_VERSION_ID >= 80400) { + $array = [ + 'a' => 'dog', + 'b' => 'cat', + 'c' => 'cow', + 'd' => 'duck', + 'e' => 'goose', + 'f' => 'elephant' + ]; + function compare(string $value) { + return strlen($value) > 4; + } + Assert::eq($array->find('compare'), array_find($array, 'compare')); +} + +$input = array("oranges", "apples", "pears"); +Assert::eq($input->flip(), array_flip($input)); + +$array1 = array("a" => "green", "red", "blue"); +$array2 = array("b" => "green", "yellow", "red"); +Assert::eq($array1->intersect($array2), array_intersect($array1, $array2)); +Assert::eq($array1->intersectAssoc($array2), array_intersect_assoc($array1, $array2)); + +$array = ['apple', 2, 3]; +Assert::eq($array->isList(), array_is_list($array)); + +$array = ['first' => 1, 'second' => 4]; +Assert::eq($array->keyExists('first'), array_key_exists('first', $array)); + +$array = ['a' => 1, 'b' => 2, 'c' => 3]; +Assert::eq($array->keyFirst(), array_key_first($array)); +Assert::eq($array->keyLast(), array_key_last($array)); +Assert::eq($array->keys(), array_keys($array)); +Assert::eq($array->values(), array_values($array)); + +function cube($n) +{ + return ($n * $n * $n); +} + +$a = [1, 2, 3, 4, 5]; +Assert::eq($a->map('cube'), array_map('cube', $a)); + +$array = array(12, 10, 9); +Assert::eq($array->pad(5, 0), array_pad($array, 5, 0)); + +$a = array(2, 4, 6, 8); +Assert::eq($a->product(), array_product($a)); + +$array = array("Neo", "Morpheus", "Trinity", "Cypher", "Tank"); +Assert::notEq($array->rand(2), array_rand($array, 2)); + +function sum($carry, $item) +{ + $carry += $item; + return $carry; +} +$array = array(1, 2, 3, 4, 5); +Assert::eq($array->reduce('sum'), array_reduce($array, 'sum')); + +$base = array("orange", "banana", "apple", "raspberry"); +$replacements = array(0 => "pineapple", 4 => "cherry"); +$replacements2 = array(0 => "grape"); +Assert::eq($base->replace($replacements, $replacements2), array_replace($base, $replacements, $replacements2)); + +$array = array("php", 4.0, array("green", "red")); +Assert::eq($array->reverse()->reverse(), array_reverse(array_reverse($array))); + +$array = array(0 => 'blue', 1 => 'red', 2 => 'green', 3 => 'red'); +Assert::eq($array->search('green'), array_search('green', $array)); + +$array = array("a", "b", "c", "d", "e"); +Assert::eq($array->slice(-2, 1), array_slice($array, -2, 1)); + +$a = array(2, 4, 6, 8); +Assert::eq($a->sum(), array_sum($a)); + +$array = ["a" => "green", "red", "b" => "green", "blue", "red"]; +Assert::eq($array->unique(), array_unique($array)); +Assert::eq($array->count(), count($array)); + +$array = array("Mac", "NT", "Irix", "Linux"); +Assert::eq($array->contains("Mac"), in_array("Mac", $array)); +Assert::eq($array->join(","), implode(",", $array)); + +$array = []; +Assert::true($array->isEmpty()); + +$array = typed_array('', ["lemon", "orange", "banana", "apple"]); +Assert::true($array->isTyped()); + +$fruits1 = array("lemon", "orange", "banana", "apple"); +$fruits2 = array("lemon", "orange", "banana", "apple"); +$result = &$fruits1; +sort($fruits2); +Assert::eq($result->sort(), $fruits2); +Assert::eq($fruits1, $fruits2); + +$stack = array("orange", "banana", "apple", "raspberry"); +$result = &$stack; +Assert::eq($result->pop(), 'raspberry'); +Assert::eq(array_pop($stack), 'apple'); + +$array = array("red","green"); +$result = &$array; +$result->push("blue"); +Assert::eq($array, ["red","green", "blue"]); +array_push($array, "yellow"); +Assert::eq($array, ["red","green", "blue", "yellow"]); + +$stack = array("orange", "banana", "apple", "raspberry"); +$result = &$stack; +Assert::eq($result->shift(), 'orange'); +Assert::eq(array_shift($stack), 'banana'); + +$queue = ["orange", "banana"]; +$result = &$queue; +$result->unshift("orange"); +Assert::eq($queue, ["orange", "orange", "banana"]); +array_unshift($queue, "orange"); +Assert::eq($queue, ["orange", "orange", "orange", "banana"]); + +$array1 = array("red", "green", "blue", "yellow"); +$array2 = array("red", "green", "blue", "yellow"); + +$result = &$array1; +Assert::eq($result->splice(2), array_splice($array2, 2)); +Assert::eq($array1, $array2); + +$find = array("Hello","world"); +$replace = array("B"); +$arr = array("Hello","world","!"); +Assert::eq($arr->replaceStr($find, $replace), str_replace($find, $replace, $arr)); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/stream_method/0.phpt b/tests/swoole_stdext/stream_method/0.phpt new file mode 100644 index 0000000000..9aab805acb --- /dev/null +++ b/tests/swoole_stdext/stream_method/0.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_stdext/stream_method: 0 +--SKIPIF-- + +--FILE-- +write($rdata->base64Encode()), $rdata->length()); +$fp->seek(0); +Assert::eq($rdata, $fp->read(8192)->base64Decode()); +Assert::eq($fp->stat(), fstat($fp)); +Assert::true($fp->sync()); +Assert::true($fp->dataSync()); +$fp->seek(100); +Assert::true($fp->tell() == 100); +Assert::true($fp->lock(LOCK_SH) == true); +Assert::true($fp->lock(LOCK_UN) == true); +Assert::true($fp->eof() == feof($fp)); + +$fp->seek(0); +$char = $fp->getChar(); +$fp->seek(0); +Assert::eq($char, fgetc($fp)); +$fp->seek(0); +$line = $fp->getLine(); +$fp->seek(0); +Assert::eq($line, fgets($fp)); +Assert::true($fp->truncate(1000)); + +Assert::true($fp->close()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/0.phpt b/tests/swoole_stdext/string_method/0.phpt new file mode 100644 index 0000000000..b7b77f11b4 --- /dev/null +++ b/tests/swoole_stdext/string_method/0.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_stdext/string_method: 0 +--SKIPIF-- + +--FILE-- +isEmpty()); +Assert::eq($string->length(), strlen($string)); +Assert::eq($string->substr(0, 5), 'hello'); +Assert::eq($string->contains('world'), true); +Assert::eq($string->indexOf('test'), strpos($string, 'test')); +Assert::eq($string->split(' '), explode(' ', $string)); +Assert::true(''->isEmpty()); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/1.phpt b/tests/swoole_stdext/string_method/1.phpt new file mode 100644 index 0000000000..1df0e03e14 --- /dev/null +++ b/tests/swoole_stdext/string_method/1.phpt @@ -0,0 +1,16 @@ +--TEST-- +swoole_stdext/string_method: 1 +--SKIPIF-- + +--FILE-- +parseStr(); +Assert::eq($output['first'], 'value'); +Assert::eq($output['arr'][0], 'foo bar'); +Assert::eq($output['arr'][1], 'baz'); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/json.phpt b/tests/swoole_stdext/string_method/json.phpt new file mode 100644 index 0000000000..f6db806459 --- /dev/null +++ b/tests/swoole_stdext/string_method/json.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_stdext/string_method: json +--SKIPIF-- + +--FILE-- + random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; + +$str = $array->jsonEncode(); +Assert::notEmpty($str); +Assert::eq($str->jsonDecode(), $array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/marshal.phpt b/tests/swoole_stdext/string_method/marshal.phpt new file mode 100644 index 0000000000..4a80f1ab20 --- /dev/null +++ b/tests/swoole_stdext/string_method/marshal.phpt @@ -0,0 +1,15 @@ +--TEST-- +swoole_stdext/string_method: marshal +--SKIPIF-- + +--FILE-- + random_bytes(128)->base64Encode(), 'b' => random_int(1, PHP_INT_MAX), 'c' => php_uname()]; + +$str = $array->marshal(); +Assert::notEmpty($str); +Assert::eq($str->unmarshal(), $array); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/match.phpt b/tests/swoole_stdext/string_method/match.phpt new file mode 100644 index 0000000000..e2a729a325 --- /dev/null +++ b/tests/swoole_stdext/string_method/match.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_stdext/string_method: match +--SKIPIF-- + +--FILE-- +match($regex1, PREG_OFFSET_CAPTURE); + +preg_match($regex1, $str, $matches2, PREG_OFFSET_CAPTURE); +Assert::eq($matches, $matches2); + + +$html = "bold textclick me"; +$regex2 = "/(<([\w]+)[^>]*>)(.*?)(<\/\\2>)/"; +preg_match_all($regex2, $html, $matches2, PREG_SET_ORDER); + +$matches = $html->matchAll($regex2, PREG_SET_ORDER); +Assert::eq($matches, $matches2); + +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/mbstring.phpt b/tests/swoole_stdext/string_method/mbstring.phpt new file mode 100644 index 0000000000..1cc129d4da --- /dev/null +++ b/tests/swoole_stdext/string_method/mbstring.phpt @@ -0,0 +1,62 @@ +--TEST-- +swoole_stdext/string_method: all mbstring methods test +--SKIPIF-- + +--FILE-- +mbUpperFirst(), mb_ucfirst($text)); + +$text = 'Bbbb大声向宇宙呐喊bbbb'; +Assert::eq($text->mbLowerFirst(), mb_lcfirst($text)); + +$text = "\t\t大声向宇宙呐喊 :) ... "; +Assert::eq($text->mbTrim(), mb_trim($text)); +Assert::eq($text->mbLTrim(), mb_ltrim($text)); +Assert::eq($text->mbRTrim(), mb_rtrim($text)); + +$text = '大声大声大声向宇宙呐喊'; +Assert::eq($text->mbSubstrCount('大声'), mb_substr_count($text, '大声')); + +$text = "大声大声大声向宇宙呐喊"; +Assert::eq($text->mbSubstr(0, -1), mb_substr($text, 0, -1)); + +$text = 'b大声大声大声向宇宙呐喊'; +Assert::eq($text->mbUpper(), mb_strtoupper($text)); + +$text = 'BBBBBB大声大声大声向宇宙呐喊'; +Assert::eq($text->mbLower(), mb_strtolower($text)); + +$email = 'name我哦知道@example.com'; +Assert::eq($email->mbFind('哦'), mb_strstr($email, '哦')); +Assert::eq($email->mbIFind('道'), mb_stristr($email, '道')); + +$mystring1 = '当地特产'; +$mystring2 = '现在只是谈谈'; +Assert::true($mystring1->mbIndexOf('地') === mb_strpos($mystring1, '地')); +Assert::true($mystring2->mbIIndexOf('A') === mb_stripos($mystring2, 'a')); + +$mystring = '啦啦啦啦啦啦啦'; +Assert::true($mystring->mbLastIndexOf('好') === false); +Assert::true(mb_strrpos($mystring, '好') === false); +Assert::eq($mystring->mbILastIndexOf('啦'), mb_strripos($mystring, '啦')); +Assert::eq($mystring->mbLastCharIndexOf('啦'), mb_strrchr($mystring, '啦')); +Assert::eq($mystring->mbILastCharIndex('啦'), mb_strrichr($mystring, '啦')); + +$text = '啦啦啦啦啦啦啦'; +Assert::eq($text->mbLength(), mb_strlen($text)); +Assert::eq($text->mbCut(0, 2), mb_strcut($text, 0, 2)); +Assert::eq($text->mbDetectEncoding(), mb_detect_encoding($text)); +Assert::eq($text->mbConvertEncoding('GBK'), mb_convert_encoding($text, 'GBK')); + +$text = 'bbbbba啦啦啦啦啦啦啦'; +Assert::eq( + $text->mbConvertCase(MB_CASE_UPPER)->mbConvertCase(MB_CASE_LOWER), + mb_convert_case(mb_convert_case($text, MB_CASE_UPPER), MB_CASE_LOWER) + ); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/string_method/method.phpt b/tests/swoole_stdext/string_method/method.phpt new file mode 100644 index 0000000000..739d8aa43e --- /dev/null +++ b/tests/swoole_stdext/string_method/method.phpt @@ -0,0 +1,163 @@ +--TEST-- +swoole_stdext/string_method: all string methods test +--SKIPIF-- + +--FILE-- +length(), strlen($text)); + +$text = ''; +Assert::eq($text->isEmpty(), true); + +$text = 'A'; +Assert::eq($text->lower(), strtolower($text)); +Assert::eq($text->lowerFirst(), lcfirst($text)); + +$text = 'b'; +Assert::eq($text->upper(), strtoupper($text)); +Assert::eq($text->upperFirst(), ucfirst($text)); + +$text = 'hello world!'; +Assert::eq($text->upperWords(), ucwords($text)); + +$text = "PHP isThirty\nYears Old!\tYay to the Elephant!\n"; +$characters = "\0..\37!@\177..\377"; +Assert::eq($text->addCSlashes($characters), addCSlashes($text, $characters)); + +$text = "O'Reilly?"; +Assert::eq($text->addSlashes(), addslashes($text)); + +$text = 'This is quite a long string, which will get broken up because the line is going to be too long after base64 encoding it.'; +Assert::eq($text->base64Encode()->chunkSplit(), chunk_split(base64_encode($text))); + +$text = "Two Ts and one F."; +Assert::eq($text->countChars(1), count_chars($text, 1)); + +$text = "I'll \"walk\" the dog now"; +Assert::eq($text->htmlEntityEncode()->htmlEntityDecode(), html_entity_decode(htmlentities($text))); + +$text = "Test"; +Assert::eq($text->htmlSpecialCharsEncode(ENT_QUOTES)->htmlSpecialCharsDecode(), htmlspecialchars_decode(htmlspecialchars($text, ENT_QUOTES))); + +$text = "\t\tThese are a few words :) ... "; +Assert::eq($text->trim(), trim($text)); +Assert::eq($text->lTrim(), ltrim($text)); +Assert::eq($text->rTrim(), rtrim($text)); + +$text = "first=value&arr[]=foo+bar&arr[]=baz"; +parse_str($text, $result); +Assert::eq($text->parseStr(), $result); + +$url = 'http://username:password@hostname:9090/path?arg=value#anchor'; +Assert::eq($url->parseUrl(), parse_url($url)); + +$text = 'The lazy fox jumped over the fence'; +Assert::eq($text->contains('lazy'), str_contains($text, 'lazy')); + +if (PHP_VERSION_ID >= 80300) { + $text = 'ABC'; + Assert::eq($text->incr(), str_increment($text)); + Assert::eq($text->decr(), str_decrement($text)); +} + +$text = ""; +Assert::eq($text->replace("%BODY%", "black"), str_replace("%BODY%", "black", $text)); +Assert::eq($text->iReplace("%body%", "black"), str_ireplace("%body%", "black", $text)); + +$text = "Alien"; +Assert::eq($text->pad(10), str_pad($text, 10)); + +$text = "-="; +Assert::eq($text->repeat(10), str_repeat($text, 10)); + +$text = 'abcdef'; +Assert::notEq($text->shuffle(), str_shuffle($text)); + +$text = "piece1 piece2 piece3 piece4 piece5 piece6"; +Assert::eq($text->split(' '), explode(' ', $text)); + +$text = 'The lazy fox jumped over the fence'; +Assert::eq($text->startsWith('The'), str_starts_with($text, 'The')); +Assert::eq($text->endsWith('fence'), str_ends_with($text, 'fence')); + +$text = "Hello fri3nd, you're + looking good today!"; +Assert::eq($text->wordCount(0), str_word_count($text, 0)); +Assert::eq($text->wordCount(1), str_word_count($text, 1)); +Assert::eq($text->wordCount(2), str_word_count($text, 2)); + +$var1 = "Hello"; +$var2 = "hello"; +Assert::eq($var1->iCompare($var2), strcasecmp($var1, $var2)); +Assert::eq($var1->compare($var2), strcmp($var1, $var2)); + +$email = 'name@example.com'; +Assert::eq($email->find('@'), strstr($email, '@')); +Assert::eq($email->iFind('N'), stristr($email, 'N')); + +$text = '

    Test paragraph.

    Other text'; +Assert::eq($text->stripTags(), strip_tags($text)); + +$text = 'I\'d have a coffee.\nNot a problem.'; +Assert::eq($text->stripCSlashes(), stripcslashes($text)); + +$str = "Is your name O\'reilly?"; +Assert::eq($str->stripSlashes(), stripslashes($str)); + +$mystring1 = 'xyz'; +$mystring2 = 'ABC'; +Assert::true($mystring1->iIndexOf('A') === stripos($mystring1, 'a')); +Assert::true($mystring2->iIndexOf('A') === stripos($mystring2, 'a')); +Assert::eq($mystring2->indexOf('a'), strpos($mystring2, 'a')); + +$mystring = 'Elephpant'; +Assert::true($mystring->lastIndexOf('b') === false); +Assert::true(strrpos($mystring, 'b') === false); +Assert::eq($mystring->iLastIndexOf('E'), strripos($mystring, 'E')); +Assert::eq($mystring->lastCharIndexOf('E'), strrchr($mystring, 'E')); + +$text = "abcdef"; +Assert::eq($text->substr(0, -1), substr($text, 0, -1)); +Assert::eq($text->substrCompare("bc", 1, 2), substr_compare($text, "bc", 1, 2)); + +$text = 'This is a test'; +Assert::eq($text->substrCount('is'), substr_count($text, 'is')); + +$var = 'ABCDEFGH:/MNRPQR/'; +Assert::eq($var->substrReplace('bob', 0), substr_replace($var, 'bob', 0)); + +$var = 'abcdefghijklmn'; +Assert::eq($var->reverse(), strrev($var)); +Assert::eq($var->md5(), md5($var)); +Assert::eq($var->sha1(), sha1($var)); +Assert::eq($var->crc32(), crc32($var)); +Assert::eq($var->hash('sha256'), hash('sha256', $var)); + +$str = 'This is an encoded string'; +Assert::eq($str->base64Encode()->base64Decode(), base64_decode(base64_encode($str))); + +$text = 'Data123!@-_ +'; +Assert::eq($text->urlEncode()->urlDecode(), urldecode(urlencode($text))); + +$text = 'foo @+%/'; +Assert::eq($text->rawUrlEncode()->rawUrlDecode(), rawurldecode(rawurlencode($text))); + +$pattern = "/php/i"; +$text = "PHP is the web scripting language of choice."; +$result1 = $text->match("/php/i"); +preg_match("/php/i", $text, $result2); +Assert::eq($result1, $result2); + +$pattern = "/\(? (\d{3})? \)? (?(1) [\-\s] ) \d{3}-\d{4}/x"; +$subject = "Call 555-1212 or 1-800-555-1212"; +$result1 = $subject->matchAll($pattern); +preg_match_all($pattern, $subject, $result2); +Assert::eq($result1, $result2); + +$text = '112'; +Assert::eq($text->isNumeric(), is_numeric($text)); +?> +--EXPECT-- diff --git a/tests/swoole_stdext/typed_array/0.phpt b/tests/swoole_stdext/typed_array/0.phpt new file mode 100644 index 0000000000..4e6a01ec28 --- /dev/null +++ b/tests/swoole_stdext/typed_array/0.phpt @@ -0,0 +1,19 @@ +--TEST-- +swoole_stdext/typed_array: 0 +--SKIPIF-- + +--FILE-- +'); + +try { + $array = typed_array('getMessage()->contains("must start with '<' and end with '>'")); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/1.phpt b/tests/swoole_stdext/typed_array/1.phpt new file mode 100644 index 0000000000..bd46c6c46a --- /dev/null +++ b/tests/swoole_stdext/typed_array/1.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_stdext/typed_array: 1 +--SKIPIF-- + +--FILE-- +'); +$array[] = 123; + +try { + $array[] = '456'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = true; +$array[] = false; +try { + $array[] = '456'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = '456'; +try { + $array[] = true; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = 4556.56; +try { + $array[] = 456; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +$array = typed_array(''); +$array[] = new stdClass(); +try { + $array[] = new ArrayObject(); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); +} + +?> +--EXPECTF-- diff --git a/tests/swoole_stdext/typed_array/10.phpt b/tests/swoole_stdext/typed_array/10.phpt new file mode 100644 index 0000000000..aa5b850e19 --- /dev/null +++ b/tests/swoole_stdext/typed_array/10.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_stdext/typed_array: 10 +--SKIPIF-- + +--FILE-- +'); +$array[999] = $bytes; +$copy = typed_array('', $array); +Assert::eq($copy[999], $bytes); + +try { + $copy2 = typed_array('', $array); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not match the initial values')); + echo "DONE\n"; +} + + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/11.phpt b/tests/swoole_stdext/typed_array/11.phpt new file mode 100644 index 0000000000..94c7ec8803 --- /dev/null +++ b/tests/swoole_stdext/typed_array/11.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_stdext/typed_array: 11 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); +foreach($array as &$v) { + var_dump($v); + echo "each\n"; +} +echo "end\n"; +?> +--EXPECTF-- +Fatal error: Uncaught Error: The type array do not support using references for element value during iteration in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/swoole_stdext/typed_array/2.phpt b/tests/swoole_stdext/typed_array/2.phpt new file mode 100644 index 0000000000..5811d4e4a3 --- /dev/null +++ b/tests/swoole_stdext/typed_array/2.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_stdext/typed_array: 2 +--SKIPIF-- + +--FILE-- +>'); +$array[999] = typed_array(''); + +try { + $array[888] = typed_array(''); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +try { + $array[777] = []; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/3.phpt b/tests/swoole_stdext/typed_array/3.phpt new file mode 100644 index 0000000000..a97dc7279d --- /dev/null +++ b/tests/swoole_stdext/typed_array/3.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_stdext/typed_array: 3 +--SKIPIF-- + +--FILE-- +'); +$map['a'] = 1; +$map['b'] = 2; +$map['c'] = 3; +unset($map['b']); + +Assert::true(isset($map['a'])); +Assert::true(isset($map['c'])); +Assert::false(isset($map['b'])); + +$array = typed_array(''); +$array[] = 3; +$array[] = 5; +$array[] = 7; + +try { + unset($array[0]); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not support')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/4.phpt b/tests/swoole_stdext/typed_array/4.phpt new file mode 100644 index 0000000000..9d4e71bab6 --- /dev/null +++ b/tests/swoole_stdext/typed_array/4.phpt @@ -0,0 +1,29 @@ +--TEST-- +swoole_stdext/typed_array: 4 +--SKIPIF-- + +--FILE-- +'); +$array[999] = 'test'; + +try { + $array['hello'] = 'world'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array key type mismatch')); + echo "DONE\n"; +} + +$key = str_repeat('a', 1000); +try { + $array[$key] = 'world'; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array key type mismatch')); + echo "DONE\n"; +} +?> +--EXPECTF-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/5.phpt b/tests/swoole_stdext/typed_array/5.phpt new file mode 100644 index 0000000000..def36f88be --- /dev/null +++ b/tests/swoole_stdext/typed_array/5.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_stdext/typed_array: 5 +--SKIPIF-- + +--FILE-- +'); +$array[] = 3; +$array[] = 5; +$array[] = 7; +$array[3] = random_int(1, 100); + +try { + $array[5] = 999; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('out of the permitted range')); + echo "DONE\n"; +} + +try { + $array["hello"] = 999; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('must be undef or int')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/6.phpt b/tests/swoole_stdext/typed_array/6.phpt new file mode 100644 index 0000000000..b823dcf51d --- /dev/null +++ b/tests/swoole_stdext/typed_array/6.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 6 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); +Assert::eq($array->count(), 3); +Assert::eq($array[1], $num); + +try { + $array = typed_array('', [1, "hello", 3]); +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/7.phpt b/tests/swoole_stdext/typed_array/7.phpt new file mode 100644 index 0000000000..04f19e1604 --- /dev/null +++ b/tests/swoole_stdext/typed_array/7.phpt @@ -0,0 +1,45 @@ +--TEST-- +swoole_stdext/typed_array: 7 +--SKIPIF-- + +--FILE-- +', [1, $num, 3]); + +$copy = []; +foreach($array as $k => $v) { + $copy[] = $v; + $array[$k] = $v * 2; +} + +Assert::eq($copy[1], $num); +Assert::eq($array[1], $num * 2); +Assert::true($array->isTyped()); + +try { + $array[] = 'hello'; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +foreach($copy as &$v) { + $v = 0; +} +Assert::eq($copy->sum(), 0); + +try { + foreach ($array as &$v) { + var_dump($v); + } +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('not support using references for element value')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE +DONE diff --git a/tests/swoole_stdext/typed_array/8.phpt b/tests/swoole_stdext/typed_array/8.phpt new file mode 100644 index 0000000000..19bb697166 --- /dev/null +++ b/tests/swoole_stdext/typed_array/8.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: 8 +--SKIPIF-- + +--FILE-- +'); + +$arr[] = 1; +$arr[0] += 10; +Assert::eq($arr[0], 11); + +try { + $arr[0] .= "hello world"; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/9.phpt b/tests/swoole_stdext/typed_array/9.phpt new file mode 100644 index 0000000000..d41386d948 --- /dev/null +++ b/tests/swoole_stdext/typed_array/9.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 9 +--SKIPIF-- + +--FILE-- +'); +$array[999] = random_bytes(128); + +try { + $a = typed_array(''); + $array[888] = $a; +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} + +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_pop.phpt b/tests/swoole_stdext/typed_array/array_pop.phpt new file mode 100644 index 0000000000..fb4ce9e808 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_pop.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: array_pop +--SKIPIF-- + +--FILE-- +', [1, 3, 5, 7]); +$v = array_pop($arr); +Assert::eq($v, 7); +Assert::eq($arr[2], 5); +Assert::eq($arr->count(), 3); +Assert::true($arr->isList()); + +try { + array_push($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_push.phpt b/tests/swoole_stdext/typed_array/array_push.phpt new file mode 100644 index 0000000000..e60693717b --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_push.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 9 +--SKIPIF-- + +--FILE-- +', [1, 3, 5]); +array_push($arr, 7); +Assert::eq($arr[3], 7); +Assert::true($arr->isList()); + +try { + array_push($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_shift.phpt b/tests/swoole_stdext/typed_array/array_shift.phpt new file mode 100644 index 0000000000..85ea228efa --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_shift.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_stdext/typed_array: array_shift +--SKIPIF-- + +--FILE-- +', [1, 3, 5, 7]); +$v = array_shift($arr); +Assert::eq($v, 1); +Assert::eq($arr[1], 5); +Assert::eq($arr->count(), 3); +Assert::true($arr->isList()); + +try { + array_unshift($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_splice.phpt b/tests/swoole_stdext/typed_array/array_splice.phpt new file mode 100644 index 0000000000..31524ac356 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_splice.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_stdext/typed_array: array_splice +--SKIPIF-- + +--FILE-- +', array("red", "green", "blue", "yellow")); +array_splice($arr, 2); +Assert::eq($arr->count(), 2); +array_push($arr, "purple", "orange"); +Assert::eq($arr->count(), 4); +Assert::true($arr->isList()); + +array_splice($arr, 1, count($arr), "black"); +Assert::true($arr->contains("black")); +Assert::eq($arr->count(), 2); +Assert::true($arr->isList()); +Assert::true($arr->isTyped()); + +array_splice($arr, -1, 1, array("gray", "maroon")); +Assert::eq($arr->count(), 3); +Assert::true($arr->contains("maroon")); +Assert::true($arr->isTyped()); +Assert::true($arr->isList()); + +try { + array_splice($arr, -1, 1, array(9999, false)); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/array_unshift.phpt b/tests/swoole_stdext/typed_array/array_unshift.phpt new file mode 100644 index 0000000000..9d79b95395 --- /dev/null +++ b/tests/swoole_stdext/typed_array/array_unshift.phpt @@ -0,0 +1,22 @@ +--TEST-- +swoole_stdext/typed_array: 10 +--SKIPIF-- + +--FILE-- +', [1, 3, 5]); +array_unshift($arr, 999); +Assert::eq($arr[0], 999); +Assert::true($arr->isList()); + +try { + array_unshift($arr, 9, 'hello world', true); +} catch (TypeError $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/resource.phpt b/tests/swoole_stdext/typed_array/resource.phpt new file mode 100644 index 0000000000..de2add6a0e --- /dev/null +++ b/tests/swoole_stdext/typed_array/resource.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_stdext/typed_array: resource +--SKIPIF-- + +--FILE-- +'); +$map['a'] = fopen(__FILE__, 'r'); +Assert::eq(get_resource_type($map['a']), 'stream'); + +try { + $map['b'] = 1; +} catch (Throwable $e) { + Assert::true($e->getMessage()->contains('Array value type mismatch')); + echo "DONE\n"; +} +?> +--EXPECT-- +DONE diff --git a/tests/swoole_stdext/typed_array/sort.phpt b/tests/swoole_stdext/typed_array/sort.phpt new file mode 100644 index 0000000000..0b92db4135 --- /dev/null +++ b/tests/swoole_stdext/typed_array/sort.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_stdext/typed_array: sort +--SKIPIF-- + +--FILE-- +', [1243, 3434, 5453, 4532, 2, 3454, 5233, 655, 234, 6, 2356, 4554]); +Assert::true($arr->isList()); +Assert::true($arr->isTyped()); + +sort($arr); +Assert::true($arr->isTyped()); + +shuffle($arr); +Assert::true($arr->isTyped()); + +?> +--EXPECT-- diff --git a/tests/swoole_table/getMemorySize_1.phpt b/tests/swoole_table/getMemorySize_1.phpt new file mode 100644 index 0000000000..0f316f37a4 --- /dev/null +++ b/tests/swoole_table/getMemorySize_1.phpt @@ -0,0 +1,17 @@ +--TEST-- +swoole_table: getMemorySize +--SKIPIF-- + +--FILE-- +getMemorySize(), 0); +$table->column('name', Table::TYPE_STRING, 32); +$table->create(); +Assert::greaterThan($table->getMemorySize(), 0); +?> +--EXPECTF-- diff --git a/tests/swoole_table/gh-5789.phpt b/tests/swoole_table/gh-5789.phpt new file mode 100644 index 0000000000..c0526c81c8 --- /dev/null +++ b/tests/swoole_table/gh-5789.phpt @@ -0,0 +1,23 @@ +--TEST-- +swoole_table: bug_5789 +--SKIPIF-- + +--FILE-- +column('test', Table::TYPE_INT); +$table->create(); + +$value = random_int(1, PHP_INT_MAX); +$table->set('firstrow', ['test' => $value]); + +Assert::eq($table->get('firstrow', 'test'), $value); +Assert::eq($table->get('firstrow', null), ['test' => $value]); +Assert::same($table->get('not-exists', null), false); +Assert::same($table->get('not-exists', 'test'), false); +?> +--EXPECT-- diff --git a/tests/swoole_table/random_bytes.phpt b/tests/swoole_table/random_bytes.phpt index a34e652444..a628e1eb45 100644 --- a/tests/swoole_table/random_bytes.phpt +++ b/tests/swoole_table/random_bytes.phpt @@ -5,11 +5,11 @@ swoole_table: read/write random data --FILE-- column('string', \Swoole\Table::TYPE_STRING, 256 * 1024); $table->create(); -$n = IS_IN_TRAVIS ? 100 : 1000; +$n = IS_IN_CI ? 100 : 1000; // $n = 100; $map = []; diff --git a/tests/swoole_table/stats.phpt b/tests/swoole_table/stats.phpt index 2c1887b70e..6c8097e635 100644 --- a/tests/swoole_table/stats.phpt +++ b/tests/swoole_table/stats.phpt @@ -8,7 +8,7 @@ require __DIR__ . '/../include/bootstrap.php'; use Swoole\Table; -define('N', IS_IN_TRAVIS ? 10000 : 100000); +define('N', IS_IN_CI ? 10000 : 100000); $table = new Table(N); $table->column('string', Table::TYPE_STRING, 256); diff --git a/tests/swoole_thread/add_update.phpt b/tests/swoole_thread/add_update.phpt new file mode 100644 index 0000000000..ace8f1da9e --- /dev/null +++ b/tests/swoole_thread/add_update.phpt @@ -0,0 +1,51 @@ +--TEST-- +swoole_thread: add/update +--SKIPIF-- + +--FILE-- +update(KEY_NOT_EXISTS, $value)); +Assert::true($m->add(KEY_NOT_EXISTS, $value)); +Assert::eq($m[KEY_NOT_EXISTS], $value); + +unset($m[KEY_NOT_EXISTS]); +Assert::eq($m[KEY_NOT_EXISTS], null); + +Assert::false($m->add(KEY_EXISTS, $value)); +Assert::true($m->update(KEY_EXISTS, $value)); +Assert::eq($m[KEY_EXISTS], $value); + +$m2 = new Map(); +$m2[INDEX_EXISTS] = $value; + +Assert::false($m2->update(INDEX_NOT_EXISTS, $value)); +Assert::true($m2->add(INDEX_NOT_EXISTS, $value)); +Assert::eq($m2[INDEX_NOT_EXISTS], $value); + +unset($m2[INDEX_NOT_EXISTS]); +Assert::eq($m2[INDEX_NOT_EXISTS], null); + +Assert::false($m2->add(INDEX_EXISTS, $value)); +Assert::true($m2->update(INDEX_EXISTS, $value)); +Assert::eq($m2[INDEX_EXISTS], $value); + +?> +--EXPECTF-- diff --git a/tests/swoole_thread/affinity.phpt b/tests/swoole_thread/affinity.phpt new file mode 100644 index 0000000000..7516b1b637 --- /dev/null +++ b/tests/swoole_thread/affinity.phpt @@ -0,0 +1,36 @@ +--TEST-- +swoole_thread: Affinity +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $thread = new Thread(__FILE__, 'child'); + $r = Thread::getAffinity(); + Assert::eq(count($r), swoole_cpu_num()); + Assert::assert(Thread::setAffinity([1])); + Assert::eq(Thread::getAffinity(), [1]); + $thread->join(); +}; + +$tm->childFunc = function () { + $r = Thread::getAffinity(); + Assert::eq(count($r), swoole_cpu_num()); + Assert::assert(Thread::setAffinity([0])); + Assert::eq(Thread::getAffinity(), [0]); +}; + +$tm->run(); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/arraylist.phpt b/tests/swoole_thread/arraylist.phpt new file mode 100644 index 0000000000..e7ffe10aa9 --- /dev/null +++ b/tests/swoole_thread/arraylist.phpt @@ -0,0 +1,58 @@ +--TEST-- +swoole_thread: arraylist +--SKIPIF-- + +--FILE-- +toArray(), $array); +Assert::eq($l->find($uuid), 2); + +for ($i = 0; $i < count($array); $i++) { + Assert::eq($l[$i], $array[$i]); +} + +$array2 = [ + 'key' => 'value', + 'hello' => 'world', +]; +$l[] = $array2; + +Assert::eq(count($l), 5); +Assert::eq($l[4]->toArray(), $array2); + +try { + $l2 = new ArrayList($array2); + echo "never here\n"; +} catch (Throwable $e) { + Assert::contains($e->getMessage(), 'must be an array of type list'); +} + +$uuid2 = uniqid(); +$l[] = $uuid2; +$count = count($l); + +unset($l[1]); +Assert::eq(count($l), $count - 1); +Assert::eq($l[1], $uuid); +Assert::eq($l->find($uuid), 1); +Assert::eq($l->find($uuid2), $count - 2); + +?> +--EXPECTF-- diff --git a/tests/swoole_thread/async-io.phpt b/tests/swoole_thread/async-io.phpt new file mode 100644 index 0000000000..ed12ca48bd --- /dev/null +++ b/tests/swoole_thread/async-io.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_thread: async-io +--SKIPIF-- + +--FILE-- +join(); + } + Assert::eq($atomic->get(), C * N); + Assert::eq($atomicLong->get(), C * N * M); +} else { + $id = $args[0]; + $atomic = $args[1]; + $atomicLong = $args[2]; + Co\run(function () use ($atomic, $atomicLong, $md5) { + $n = N; + while ($n--) { + $atomic->add(); + $atomicLong->add(M); + $rs = \Swoole\Coroutine\System::readFile(__FILE__); + Assert::eq(md5($rs), $md5); + } + }); + exit(0); +} +echo "DONE\n"; +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_thread/atomic_ctor.phpt b/tests/swoole_thread/atomic_ctor.phpt new file mode 100644 index 0000000000..e4bbbb5b74 --- /dev/null +++ b/tests/swoole_thread/atomic_ctor.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_thread: atomic ctor +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $lock = new Lock; + $lock->lock(); + $num1 = random_int(1, 1 << 31); + $num2 = random_int(1 << 31, PHP_INT_MAX); + $atomic1 = new Swoole\Thread\Atomic($num1); + $atomic2 = new Swoole\Thread\Atomic\Long($num2); + $thread = new Thread(__FILE__, $lock, $atomic1, $atomic2, $num1, $num2); + $lock->lock(); + echo "main thread\n"; + $thread->join(); +}; + +$tm->childFunc = function ($lock, $atomic1, $atomic2, $num1, $num2) { + echo "child thread\n"; + usleep(200_000); + $lock->unlock(); + Assert::eq($atomic1->get(), $num1); + Assert::eq($atomic2->get(), $num2); + exit(0); +}; + +$tm->run(); +?> +--EXPECTF-- +child thread +main thread diff --git a/tests/swoole_thread/barrier.phpt b/tests/swoole_thread/barrier.phpt new file mode 100644 index 0000000000..1129ab99b5 --- /dev/null +++ b/tests/swoole_thread/barrier.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_thread: barrier +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $barrier = new Barrier(2); + $s = microtime(true); + $thread = new Thread(__FILE__, $barrier); + $barrier->wait(); + Assert::greaterThanEq(microtime(true) - $s, 0.2); + echo "main thread\n"; + $thread->join(); +}; + +$tm->childFunc = function ($barrier) { + echo "child thread\n"; + usleep(200_000); + $barrier->wait(); + exit(0); +}; + +$tm->run(); +?> +--EXPECTF-- +child thread +main thread diff --git a/tests/swoole_thread/co-stream.phpt b/tests/swoole_thread/co-stream.phpt new file mode 100644 index 0000000000..96d02ec575 --- /dev/null +++ b/tests/swoole_thread/co-stream.phpt @@ -0,0 +1,57 @@ +--TEST-- +swoole_thread: co stream +--SKIPIF-- + +--FILE-- +initFreePorts(increment: crc32(__FILE__) % 1000); + +$tm->parentFunc = function () use ($tm) { + Runtime::enableCoroutine(SWOOLE_HOOK_ALL); + Co\run(function () use ($tm) { + $queue = new Queue(); + $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + $queue->push($fp); + $thread = new Thread(__FILE__, $queue); + var_dump('main thread'); + $thread->join(); + }); +}; + +$tm->childFunc = function ($queue) use ($tm) { + var_dump('child thread'); + $fp = $queue->pop(); + Co\run(function () use ($fp, $tm) { + var_dump('child thread, co 0'); + Co\go(function () use ($tm) { + var_dump('child thread, co 1'); + $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + Assert::notEmpty($client); + $data = fread($client, 8192); + Assert::eq($data, "hello world\n"); + fclose($client); + }); + $conn = stream_socket_accept($fp, -1); + fwrite($conn, "hello world\n"); + fclose($conn); + fclose($fp); + }); +}; + +$tm->run(); +?> +--EXPECT-- +string(11) "main thread" +string(12) "child thread" +string(18) "child thread, co 0" +string(18) "child thread, co 1" diff --git a/tests/swoole_thread/co-user-yield.phpt b/tests/swoole_thread/co-user-yield.phpt new file mode 100644 index 0000000000..25159124ff --- /dev/null +++ b/tests/swoole_thread/co-user-yield.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_thread: co user yield +--SKIPIF-- + +--FILE-- + false]); + +if (empty($args)) { + $threads = []; + $atomic = new Swoole\Thread\Atomic(); + for ($i = 0; $i < C; $i++) { + $threads[] = new Thread(__FILE__, $i, $atomic); + } + for ($i = 0; $i < C; $i++) { + $threads[$i]->join(); + } + Assert::eq($atomic->get(), C * N); +} else { + $id = $args[0]; + $atomic = $args[1]; + Co\run(function () use ($atomic) { + $n = N; + $cid = Co::getCid(); + while ($n--) { + Timer::after(10, function () use ($cid) { + Co::resume($cid); + }); + Co::yield(); + $atomic->add(); + } + }); + exit(0); +} +echo "DONE\n"; +?> +--EXPECT-- +DONE diff --git a/tests/swoole_thread/empty_args.phpt b/tests/swoole_thread/empty_args.phpt new file mode 100644 index 0000000000..8733dbfc04 --- /dev/null +++ b/tests/swoole_thread/empty_args.phpt @@ -0,0 +1,17 @@ +--TEST-- +swoole_thread: info +--SKIPIF-- + +--FILE-- + +--EXPECTF-- diff --git a/tests/swoole_thread/exit.phpt b/tests/swoole_thread/exit.phpt new file mode 100644 index 0000000000..5e12dc4b7a --- /dev/null +++ b/tests/swoole_thread/exit.phpt @@ -0,0 +1,39 @@ +--TEST-- +swoole_thread: lock +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $lock = new Lock; + $lock->lock(); + $thread = new Thread(__FILE__, $lock); + $lock->unlock(); + $thread->join(); + Assert::eq($thread->getExitStatus(), CODE); + echo 'DONE' . PHP_EOL; +}; + +$tm->childFunc = function ($lock) { + $lock->lock(); + usleep(100_000); + exit(CODE); +}; + +$tm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_thread/fatal_error_1.inc b/tests/swoole_thread/fatal_error_1.inc new file mode 100644 index 0000000000..e332a8b508 --- /dev/null +++ b/tests/swoole_thread/fatal_error_1.inc @@ -0,0 +1,18 @@ +join(); + echo "stop child thread\n"; +} else { + Co\run(function () { + (function () { + swoole_implicit_fn('fatal_error'); + })(); + }); +} +echo "DONE\n"; diff --git a/tests/swoole_thread/fatal_error_1.phpt b/tests/swoole_thread/fatal_error_1.phpt new file mode 100644 index 0000000000..1d9204366b --- /dev/null +++ b/tests/swoole_thread/fatal_error_1.phpt @@ -0,0 +1,20 @@ +--TEST-- +swoole_thread: fatal error +--SKIPIF-- + +--FILE-- +getChildOutput(); +Assert::contains($output, "start child thread\n"); +Assert::contains($output, "stop child thread\n"); +Assert::contains($output, "Fatal error: Uncaught Swoole\Error: test"); +?> +--EXPECT-- diff --git a/tests/swoole_thread/fatal_error_2.inc b/tests/swoole_thread/fatal_error_2.inc new file mode 100644 index 0000000000..c0b731d1f1 --- /dev/null +++ b/tests/swoole_thread/fatal_error_2.inc @@ -0,0 +1,16 @@ +join(); + echo "stop child thread\n"; +} else { + (function () { + swoole_implicit_fn('fatal_error'); + })(); +} +echo "DONE\n"; diff --git a/tests/swoole_thread/fatal_error_2.phpt b/tests/swoole_thread/fatal_error_2.phpt new file mode 100644 index 0000000000..2da63c12bb --- /dev/null +++ b/tests/swoole_thread/fatal_error_2.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_thread: fatal error +--SKIPIF-- + +--FILE-- +getChildOutput(); +Assert::contains($output, "start child thread\n"); +Assert::contains($output, "stop child thread\n"); +Assert::contains($output, "Fatal error: Uncaught Swoole\Error: test"); +?> +--EXPECT-- diff --git a/tests/swoole_thread/fatal_error_3.phpt b/tests/swoole_thread/fatal_error_3.phpt new file mode 100644 index 0000000000..c0d4b7c035 --- /dev/null +++ b/tests/swoole_thread/fatal_error_3.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_thread: fatal error 3 +--SKIPIF-- + +--FILE-- +parentFunc = function () { + register_shutdown_function(function () { + echo "shutdown\n"; + }); + Assert::eq(Thread::getInfo()['thread_num'], 1); + $thread = new Thread(__FILE__, 'child'); + usleep(100000); + echo "main thread\n"; + Assert::eq(Thread::getInfo()['thread_num'], 2); + $thread->detach(); +}; + +$tm->childFunc = function () { + echo "child thread\n"; + sleep(1000); + exit(0); +}; + +$tm->run(); +?> +--EXPECTF-- +child thread +main thread +shutdown +[%s] WARNING PHPCoroutine::enable_hook(): The runtime hook can only set on the main thread and no child threads have been created +[%s] WARNING php_swoole_thread_rshutdown(): Fatal Error: 2 active threads are running, cannot exit safely. diff --git a/tests/swoole_thread/incr.phpt b/tests/swoole_thread/incr.phpt new file mode 100644 index 0000000000..9c93093f7d --- /dev/null +++ b/tests/swoole_thread/incr.phpt @@ -0,0 +1,78 @@ +--TEST-- +swoole_thread: incr/decr +--SKIPIF-- + +--FILE-- +incr(KEY_NOT_EXISTS_LVAL), 1); +Assert::eq($m[KEY_NOT_EXISTS_LVAL], 1); + +Assert::eq($m->incr(KEY_NOT_EXISTS_DVAL, $add_dval), $add_dval); +Assert::eq($m[KEY_NOT_EXISTS_DVAL], $add_dval); + +Assert::eq($m->incr(KEY_EXISTS_LVAL), $init_lval + 1); +Assert::eq($m[KEY_EXISTS_LVAL], $init_lval + 1); + +Assert::eq($m->incr(KEY_EXISTS_DVAL), $init_dval + 1); +Assert::eq($m[KEY_EXISTS_DVAL], $init_dval + 1); + +// clean +$m[KEY_EXISTS_LVAL] = $init_lval; +$m[KEY_EXISTS_DVAL] = $init_dval; +unset($m[KEY_NOT_EXISTS_DVAL], $m[KEY_NOT_EXISTS_LVAL]); + +Assert::eq($m->incr(KEY_EXISTS_LVAL, $add_lval), $init_lval + $add_lval); +Assert::eq($m[KEY_EXISTS_LVAL], $init_lval + $add_lval); + +Assert::eq($m->incr(KEY_EXISTS_DVAL, $add_lval), $init_dval + $add_lval); +Assert::eq($m[KEY_EXISTS_DVAL], $init_dval + $add_lval); + +Assert::eq($m->decr(KEY_NOT_EXISTS_LVAL), -1); +Assert::eq($m[KEY_NOT_EXISTS_LVAL], -1); + +$m[KEY_EXISTS_LVAL] = $init_lval; +$m[KEY_EXISTS_DVAL] = $init_dval; + +Assert::eq($m->decr(KEY_EXISTS_LVAL, $add_lval), $init_lval - $add_lval); +Assert::eq($m[KEY_EXISTS_LVAL], $init_lval - $add_lval); + +Assert::eq($m->decr(KEY_EXISTS_DVAL, $add_lval), $init_dval - $add_lval); +Assert::eq($m[KEY_EXISTS_DVAL], $init_dval - $add_lval); + +Assert::eq($l->incr(0), 1); +Assert::eq($l[0], 1); + +Assert::eq($l->incr(1, $add_lval), $add_lval); +Assert::eq($l[1], $add_lval); + +$l[0] = 0; +$l[1] = 0; + +Assert::eq($l->incr(0, $add_dval), intval($add_dval)); + +?> +--EXPECTF-- diff --git a/tests/swoole_thread/info.phpt b/tests/swoole_thread/info.phpt new file mode 100644 index 0000000000..08070ae460 --- /dev/null +++ b/tests/swoole_thread/info.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_thread: info +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $thread = new Thread(__FILE__, 'child'); + $info = Thread::getInfo(); + Assert::true($info['is_main_thread']); + $thread->join(); +}; + +$tm->childFunc = function () { + $info = Thread::getInfo(); + Assert::false($info['is_main_thread']); +}; + +$tm->run(); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/lock.phpt b/tests/swoole_thread/lock.phpt new file mode 100644 index 0000000000..03e6301d12 --- /dev/null +++ b/tests/swoole_thread/lock.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_thread: lock +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $lock = new Lock; + $lock->lock(); + $thread = new Thread(__FILE__, $lock); + $lock->lock(); + echo "main thread\n"; + $thread->join(); +}; + +$tm->childFunc = function ($lock) { + echo "child thread\n"; + usleep(200_000); + $lock->unlock(); + exit(0); +}; + +$tm->run(); +?> +--EXPECTF-- +child thread +main thread diff --git a/tests/swoole_thread/map.phpt b/tests/swoole_thread/map.phpt new file mode 100644 index 0000000000..7abb024f29 --- /dev/null +++ b/tests/swoole_thread/map.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_thread: map +--SKIPIF-- + +--FILE-- + random_int(1, 999999999999999999), + 'b' => random_bytes(128), + 'c' => $uuid, + 'd' => time(), +]; + +$m = new Map($array); +Assert::eq($m->toArray(), $array); +Assert::eq(count($m), count($array)); +Assert::eq($m->find($uuid), 'c'); + +foreach ($array as $k => $v) { + Assert::eq($m[$k], $array[$k]); +} + +$array2 = [ + 'key' => 'value', + 'hello' => 'world', +]; +$m['map'] = $array2; +Assert::eq(count($m), 5); +Assert::eq($m['map']->toArray(), $array2); +Assert::eq(count($m['map']), count($array2)); +Assert::eq($m['map']->values(), array_values($array2)); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/map2array.phpt b/tests/swoole_thread/map2array.phpt new file mode 100644 index 0000000000..6682d65986 --- /dev/null +++ b/tests/swoole_thread/map2array.phpt @@ -0,0 +1,24 @@ +--TEST-- +swoole_thread: map to array +--SKIPIF-- + +--FILE-- + 1111]); +$ls = $LURDATE[$time]->toArray(); +foreach ($ls as $k => $v) { + unset($LURDATE[$time][$k]); +} +unset($LURDATE[$time]); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/name.phpt b/tests/swoole_thread/name.phpt new file mode 100644 index 0000000000..09ce20fb30 --- /dev/null +++ b/tests/swoole_thread/name.phpt @@ -0,0 +1,33 @@ +--TEST-- +swoole_thread: name +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $thread = new Thread(__FILE__, 'child'); + Thread::setName('master thread'); + Assert::eq(get_thread_name(), 'master thread'); + $thread->join(); +}; + +$tm->childFunc = function () { + Thread::setName('child thread'); + Assert::eq(get_thread_name(), 'child thread'); +}; + +$tm->run(); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/numeric_strkey.phpt b/tests/swoole_thread/numeric_strkey.phpt new file mode 100644 index 0000000000..e2a0a452bf --- /dev/null +++ b/tests/swoole_thread/numeric_strkey.phpt @@ -0,0 +1,46 @@ +--TEST-- +swoole_thread: numeric key +--SKIPIF-- + +--FILE-- + 2222, 'test' => $uuid]); +Assert::eq($arr[S_KEY], 2222); +Assert::eq($arr[6666], 2222); +Assert::eq($arr['test'], $uuid); + +unset($arr[S_KEY]); +Assert::false(isset($arr[S_KEY])); +Assert::keyNotExists($arr->toArray(), I_KEY); + +$uuid2 = uniqid(); +$arr[6666.66] = $uuid2; +$arr['6666.66'] = $uuid2; +Assert::eq($arr[6666], $uuid2); + +$arr[true] = $uuid2; +$arr[false] = $uuid2; +$arr[null] = $uuid2; + +$stream = fopen('php://stdin', 'r+'); +@$arr[$stream] = $uuid2; + +Assert::eq($arr[true], $uuid2); +Assert::eq($arr[false], $uuid2); +Assert::eq($arr[null], $uuid2); +Assert::eq(@$arr[$stream], $uuid2); + +?> +--EXPECTF-- diff --git a/tests/swoole_thread/php_socket.phpt b/tests/swoole_thread/php_socket.phpt new file mode 100644 index 0000000000..bd0d34d8ce --- /dev/null +++ b/tests/swoole_thread/php_socket.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_thread: php_socket +--SKIPIF-- + +--FILE-- +initFreePorts(increment: crc32(__FILE__) % 1000); + +$tm->parentFunc = function () use ($tm) { + $queue = new Queue(); + $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); + socket_bind($sock, '127.0.0.1', $tm->getFreePort()); + $queue->push($sock); + $thread = new Thread(__FILE__, $queue, 0); + var_dump('main thread'); + $thread->join(); +}; + +$tm->childFunc = function ($queue, $id) use ($tm) { + if ($id === 0) { + var_dump('child thread 0'); + $svr_sock = $queue->pop(); + socket_listen($svr_sock, 128); + $thread = new Thread(__FILE__, $queue, 1); + $conn = socket_accept($svr_sock); + socket_write($conn, "Swoole: hello world\n"); + socket_close($conn); + socket_close($svr_sock); + $thread->join(); + } else { + var_dump('child thread 1'); + $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + socket_connect($sock, '127.0.0.1', $tm->getFreePort()); + socket_send($sock, "hello world", 0, 0); + socket_recv($sock, $buf, 1024, 0); + Assert::eq($buf, "Swoole: hello world\n"); + socket_close($sock); + } + exit(0); +}; + +$tm->run(); +echo "Done\n"; +?> +--EXPECT-- +string(11) "main thread" +string(14) "child thread 0" +string(14) "child thread 1" +Done diff --git a/tests/swoole_thread/pipe.phpt b/tests/swoole_thread/pipe.phpt new file mode 100644 index 0000000000..10875bffe3 --- /dev/null +++ b/tests/swoole_thread/pipe.phpt @@ -0,0 +1,37 @@ +--TEST-- +swoole_thread: pipe +--SKIPIF-- + +--FILE-- +recv(8192), $rdata); + $thread->join(); + echo "DONE\n"; + }); +} else { + $socket = $args[0]; + $rdata = $args[1]; + Co\run(function () use ($socket, $rdata, $argv) { + usleep(100); + shell_exec('sleep 0.01'); + $socket->send($rdata); + }); + exit(0); +} +?> +--EXPECTF-- +DONE diff --git a/tests/swoole_thread/priority.phpt b/tests/swoole_thread/priority.phpt new file mode 100644 index 0000000000..8b6354924a --- /dev/null +++ b/tests/swoole_thread/priority.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_thread: priority +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $thread = new Thread(__FILE__, 'child'); + test_thread_priority(10, Thread::SCHED_FIFO); + $thread->join(); +}; + +$tm->childFunc = function () { + test_thread_priority(5, Thread::SCHED_RR); +}; + +$tm->run(); +?> +--EXPECTF-- diff --git a/tests/swoole_thread/putenv.phpt b/tests/swoole_thread/putenv.phpt new file mode 100644 index 0000000000..d681efb310 --- /dev/null +++ b/tests/swoole_thread/putenv.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_thread: putenv +--SKIPIF-- + +--FILE-- +join(); +} + +for ($i = 0; $i < $c; $i++) { + $env = getenv('TEST_THREAD_' . $i); + Assert::notEmpty($env); +} +?> +--EXPECT-- diff --git a/tests/swoole_thread/queue.phpt b/tests/swoole_thread/queue.phpt new file mode 100644 index 0000000000..3cc2d73ad0 --- /dev/null +++ b/tests/swoole_thread/queue.phpt @@ -0,0 +1,61 @@ +--TEST-- +swoole_thread: queue +--SKIPIF-- + +--FILE-- +push($rdata, Queue::NOTIFY_ONE); + usleep(random_int(100, 1000)); + } + $n = 4; + while ($n--) { + $queue->push('', Queue::NOTIFY_ONE); + } + for ($i = 0; $i < C; $i++) { + $threads[$i]->join(); + $total_child += $map[$i]; + } + Assert::eq($total_parent, $total_child); +} else { + $i = $args[0]; + $queue = $args[1]; + $map = $args[2]; + $map[$i] = 0; + while (1) { + $job = $queue->pop(-1); + if (!$job) { + break; + } + $map[$i] += strlen($job); + Assert::assert(strlen($job), 16); + } + exit(0); +} +?> +--EXPECTF-- diff --git a/tests/swoole_thread/queue_notify_all.phpt b/tests/swoole_thread/queue_notify_all.phpt new file mode 100644 index 0000000000..f208b72d8d --- /dev/null +++ b/tests/swoole_thread/queue_notify_all.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_thread: queue notify all +--SKIPIF-- + +--FILE-- +wait(); + usleep(10000); + for ($i = 0; $i < C; $i++) { + $queue->push($uuid, Queue::NOTIFY_ALL); + } + for ($i = 0; $i < C; $i++) { + $threads[$i]->join(); + } + Assert::eq($queue->count(), 0); +} else { + $i = $args[0]; + $queue = $args[1]; + $uuid = $args[2]; + $barrier = $args[3]; + $barrier->wait(); + $job = $queue->pop(-1); + if ($job !== null) { + Assert::eq($job, $uuid); + } else { + Assert::eq(swoole_last_error(), SWOOLE_ERROR_NO_PAYLOAD); + } + exit(0); +} +?> +--EXPECTF-- diff --git a/tests/swoole_thread/server/base.phpt b/tests/swoole_thread/server/base.phpt new file mode 100644 index 0000000000..351c496994 --- /dev/null +++ b/tests/swoole_thread/server/base.phpt @@ -0,0 +1,64 @@ +--TEST-- +swoole_thread/server: base +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'open_eof_check' => true, + 'package_eof' => "\r\n", + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $json = json_decode(rtrim($data)); + if ($json->type == 'eof') { + $serv->send($fd, "EOF\r\n"); + } +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + thread_server_test_eof_client($port); + }); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/bug_5662.phpt b/tests/swoole_thread/server/bug_5662.phpt new file mode 100644 index 0000000000..e1fd17cc36 --- /dev/null +++ b/tests/swoole_thread/server/bug_5662.phpt @@ -0,0 +1,44 @@ +--TEST-- +swoole_thread/server: Github #5662 +--SKIPIF-- + +--FILE-- +set([ + 'log_file' => '/dev/null', + 'worker_num' => 2, + 'max_request' => 5, + 'init_arguments' => function () { + global $queue; + $queue = new Queue(); + return [$queue]; + } +]); +$server->on('WorkerStart', function (Swoole\Server $server, $workerId) { + [$queue] = Thread::getArguments(); + $queue->push('start', Queue::NOTIFY_ALL); +}); +$server->addProcess(new Swoole\Process(function ($process) use ($server, $port) { + [$queue] = Thread::getArguments(); + Assert::true($queue->pop(-1) == 'start'); + for ($i = 0; $i < 20; $i++) { + Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); + } + $server->shutdown(); +})); +$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) { + $response->end('OK'); +}); +$server->start(); +?> +--EXPECT-- diff --git a/tests/swoole_thread/server/bug_5694.phpt b/tests/swoole_thread/server/bug_5694.phpt new file mode 100644 index 0000000000..50d191441e --- /dev/null +++ b/tests/swoole_thread/server/bug_5694.phpt @@ -0,0 +1,60 @@ +--TEST-- +swoole_thread/server: Github #5694 +--SKIPIF-- + +--FILE-- +set([ + 'log_file' => '/dev/null', + 'worker_num' => 1, + 'task_worker_num' => 1, + 'max_request' => 1, + 'heartbeat_check_interval'=> 1, + 'heartbeat_idle_time'=> 2, + 'enable_coroutine' => true, + 'hook_flags' => SWOOLE_HOOK_ALL, + 'init_arguments' => function () { + global $queue; + $queue = new Queue(); + return [$queue]; + } +]); + +$server->on('WorkerStart', function (Swoole\Server $server, $workerId) { + [$queue] = Thread::getArguments(); + $queue->push('start', Queue::NOTIFY_ALL); +}); + +$server->on('Task', function (Swoole\Server $server, int $task_id, int $src_worker_id, mixed $data) { + var_dump($data); +}); + +$server->addProcess(new Swoole\Process(function ($process) use ($server, $port) { + [$queue] = Thread::getArguments(); + Assert::true($queue->pop(-1) == 'start'); + Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); + sleep(1); + Assert::true(file_get_contents("http://127.0.0.1:{$port}/") == 'OK'); + sleep(2); + $server->shutdown(); +})); + +$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) use ($server) { + $server->task('12313'); + $response->end('OK'); +}); +$server->start(); +?> +--EXPECT-- +string(5) "12313" +string(5) "12313" diff --git a/tests/swoole_thread/server/create_response.phpt b/tests/swoole_thread/server/create_response.phpt new file mode 100644 index 0000000000..59e8f02c15 --- /dev/null +++ b/tests/swoole_thread/server/create_response.phpt @@ -0,0 +1,69 @@ +--TEST-- +swoole_thread/server: create response +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'task_worker_num' => 3, + 'log_level' => SWOOLE_LOG_ERROR, + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == 5) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { + $resp->detach(); + $serv->task(['fd' => $resp->fd, 'uid' => $req->get['uid']]); +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { + $response = Swoole\Http\Response::create($data['fd']); + $response->end($data['uid']); + $response->close(); +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + Assert::eq($atomic1->get(), 5); + Assert::eq($atomic2->get(), 5); + echo "shutdown\n"; +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + $reqUid = uniqid(); + Assert::eq(file_get_contents('http://127.0.0.1:' . $port . '/?uid=' . $reqUid), $reqUid); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECTF-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/exit.phpt b/tests/swoole_thread/server/exit.phpt new file mode 100644 index 0000000000..db62bd78b9 --- /dev/null +++ b/tests/swoole_thread/server/exit.phpt @@ -0,0 +1,78 @@ +--TEST-- +swoole_thread/server: exit +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_file' => '/dev/null', + 'log_level' => SWOOLE_LOG_ERROR, + 'enable_coroutine' => false, + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == 2) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } + echo 'worker start', PHP_EOL; +}); +$serv->on('workerError', function (Server $serv, $workerId, $workerPid, $status, $signal) { + Assert::eq($status, CODE); + echo 'worker error', PHP_EOL; +}); +$serv->on('WorkerStop', function (Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { + if ($req->server['request_uri'] == '/exit') { + swoole_implicit_fn('bailout', CODE); + } +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 3); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + + $rs = @file_get_contents('http://127.0.0.1:' . $port . '/exit'); + Assert::false($rs); + + usleep(200_000); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECTF-- +worker start +worker start +begin +worker error +worker start +done +shutdown diff --git a/tests/swoole_thread/server/fatal_error.phpt b/tests/swoole_thread/server/fatal_error.phpt new file mode 100644 index 0000000000..a1995fe87e --- /dev/null +++ b/tests/swoole_thread/server/fatal_error.phpt @@ -0,0 +1,80 @@ +--TEST-- +swoole_thread/server: fatal error +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == 2) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { + if ($req->server['request_uri'] == '/error') { + trigger_error('user fatal error', E_USER_ERROR); + } +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 3); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + + $rs = @file_get_contents('http://127.0.0.1:' . $port . '/error'); + Assert::false($rs); + + usleep(100_000); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECTF-- +begin + +Fatal error: user fatal error in %s on line %d +done +shutdown + +--EXPECTF_85-- +begin + +Fatal error: user fatal error in %s on line %d +Stack trace: +#0 %s(%d): trigger_error('%s', %d) +#1 [internal function]: {closure:%s:%d}(Object(Swoole\Http\Request), Object(Swoole\Http\Response)) +#2 {main} +done +shutdown diff --git a/tests/swoole_thread/server/functions.inc b/tests/swoole_thread/server/functions.inc new file mode 100644 index 0000000000..8ffe248537 --- /dev/null +++ b/tests/swoole_thread/server/functions.inc @@ -0,0 +1,12 @@ +set([ + 'open_eof_check' => true, + 'package_eof' => "\r\n", + ]); + Assert::assert($cli->connect('127.0.0.1', $port, 2)); + $cli->send(json_encode(['type' => 'eof']) . "\r\n"); + Assert::eq($cli->recv(), "EOF\r\n"); +} diff --git a/tests/swoole_thread/server/heartbeat.phpt b/tests/swoole_thread/server/heartbeat.phpt new file mode 100644 index 0000000000..d53826cb93 --- /dev/null +++ b/tests/swoole_thread/server/heartbeat.phpt @@ -0,0 +1,67 @@ +--TEST-- +swoole_thread/server: heartbeat +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 1, + 'log_level' => SWOOLE_LOG_ERROR, + 'heartbeat_check_interval' => 1, + 'heartbeat_idle_time' => 2, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + + $client = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + if (!$client->connect('127.0.0.1', $port, 5, 0)) { + echo "Error: " . $client->errCode; + die("\n"); + } + $s1 = time(); + Assert::same(@$client->recv(), ''); + $s2 = time(); + Assert::assert($s2 - $s1 > 1); + + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/hook_flags.phpt b/tests/swoole_thread/server/hook_flags.phpt new file mode 100644 index 0000000000..d20e1ce2f2 --- /dev/null +++ b/tests/swoole_thread/server/hook_flags.phpt @@ -0,0 +1,61 @@ +--TEST-- +swoole_thread/server: base +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 4, + 'log_level' => SWOOLE_LOG_ERROR, + 'hook_flags' => SWOOLE_HOOK_ALL, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Queue(); + $atomic = new Atomic(0); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + Assert::eq(Runtime::getHookFlags(), SWOOLE_HOOK_ALL); + $output = file_get_contents("http://127.0.0.1:$port/"); + $queue->push($output, Queue::NOTIFY_ALL); +}); +$serv->on('Request', function ($req, $resp) { + usleep(100000); + $resp->end('DONE'); +}); +$serv->on('shutdown', function ($server) { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), $server->setting['worker_num']); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + for ($i = 0; $i < 4; $i++) { + echo $queue->pop(-1), PHP_EOL; + $atomic->add(1); + } + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +DONE +DONE +DONE +DONE +shutdown diff --git a/tests/swoole_thread/server/listen.phpt b/tests/swoole_thread/server/listen.phpt new file mode 100644 index 0000000000..408077f8a0 --- /dev/null +++ b/tests/swoole_thread/server/listen.phpt @@ -0,0 +1,71 @@ +--TEST-- +swoole_thread/server: listen +--SKIPIF-- + +--FILE-- +listen('127.0.0.1', $port, SWOOLE_SOCK_TCP); +$serv->set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'open_eof_check' => true, + 'package_eof' => "\r\n", + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $json = json_decode(rtrim($data)); + if ($json->type == 'eof') { + $serv->send($fd, "EOF\r\n"); + } +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + Co::join([ + Co\go(function () use ($port) { + thread_server_test_eof_client($port); + }), + Co\go(function () use ($port) { + thread_server_test_eof_client($port + 1); + }) + ]); + }); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/manager_timer.phpt b/tests/swoole_thread/server/manager_timer.phpt new file mode 100644 index 0000000000..7bbbd01a03 --- /dev/null +++ b/tests/swoole_thread/server/manager_timer.phpt @@ -0,0 +1,72 @@ +--TEST-- +swoole_thread/server: reload +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic1->add(); +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { +}); +$serv->on('managerStart', function ($serv) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); + $count = 0; + Timer::tick(100, function () use ($queue, &$count) { + $count++; + if ($count == 5) { + $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); + } + }); +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 2); + Assert::eq($atomic2->get(), 2); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + echo $queue->pop(-1); + echo $queue->pop(-1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin 1 +begin 2 +done +shutdown diff --git a/tests/swoole_thread/server/reload.phpt b/tests/swoole_thread/server/reload.phpt new file mode 100644 index 0000000000..826411847f --- /dev/null +++ b/tests/swoole_thread/server/reload.phpt @@ -0,0 +1,77 @@ +--TEST-- +swoole_thread/server: reload +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 3, + 'task_worker_num' => 4, + 'log_level' => SWOOLE_LOG_ERROR, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == 14) { + $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { +}); +$serv->on('managerStart', function ($serv) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); +}); +$serv->on('beforeReload', function ($serv) { + echo 'beforeReload', PHP_EOL; +}); +$serv->on('afterReload', function ($serv) { + echo 'afterReload', PHP_EOL; + [$queue, $atomic1, $atomic2] = Thread::getArguments(); +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 14); + Assert::eq($atomic2->get(), 14); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + echo $queue->pop(-1); + $serv->reload(); + echo $queue->pop(-1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin 1 +beforeReload +afterReload +begin 2 +done +shutdown diff --git a/tests/swoole_thread/server/reload_task_workers.phpt b/tests/swoole_thread/server/reload_task_workers.phpt new file mode 100644 index 0000000000..26f2f94242 --- /dev/null +++ b/tests/swoole_thread/server/reload_task_workers.phpt @@ -0,0 +1,78 @@ +--TEST-- +swoole_thread/server: reload task workers +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 3, + 'task_worker_num' => 4, + 'log_level' => SWOOLE_LOG_ERROR, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $c = $atomic1->add(); + if ($c == 11) { + $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { +}); +$serv->on('managerStart', function ($serv) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); +}); +$serv->on('beforeReload', function ($serv) { + echo 'beforeReload', PHP_EOL; +}); +$serv->on('afterReload', function ($serv) { + echo 'afterReload', PHP_EOL; + [$queue, $atomic1, $atomic2] = Thread::getArguments(); +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 11); + Assert::eq($atomic2->get(), 11); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + echo $queue->pop(-1); + $serv->reload(true); + echo $queue->pop(-1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin 1 +beforeReload +afterReload +begin 2 +done +shutdown diff --git a/tests/swoole_thread/server/reload_task_workers_async.phpt b/tests/swoole_thread/server/reload_task_workers_async.phpt new file mode 100644 index 0000000000..22042937e0 --- /dev/null +++ b/tests/swoole_thread/server/reload_task_workers_async.phpt @@ -0,0 +1,79 @@ +--TEST-- +swoole_thread/server: reload task workers [async] +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 3, + 'task_worker_num' => 4, + 'log_level' => SWOOLE_LOG_ERROR, + 'task_enable_coroutine' => true, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $c = $atomic1->add(); + if ($c == 11) { + $queue->push("begin 2\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { +}); +$serv->on('managerStart', function ($serv) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $queue->push("begin 1\n", Thread\Queue::NOTIFY_ALL); +}); +$serv->on('beforeReload', function ($serv) { + echo 'beforeReload', PHP_EOL; +}); +$serv->on('afterReload', function ($serv) { + echo 'afterReload', PHP_EOL; + [$queue, $atomic1, $atomic2] = Thread::getArguments(); +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 11); + Assert::eq($atomic2->get(), 11); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + echo $queue->pop(-1); + $serv->reload(true); + echo $queue->pop(-1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin 1 +beforeReload +afterReload +begin 2 +done +shutdown diff --git a/tests/swoole_thread/server/reset_concurrency.phpt b/tests/swoole_thread/server/reset_concurrency.phpt new file mode 100644 index 0000000000..23fcc4099b --- /dev/null +++ b/tests/swoole_thread/server/reset_concurrency.phpt @@ -0,0 +1,118 @@ +--TEST-- +swoole_thread/server: reset concurrency [SWOOLE_THREAD] +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => WORKER_NUM, + 'max_concurrency' => 160, + 'log_level' => SWOOLE_LOG_ERROR, + 'log_file' => '/dev/null', + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == WORKER_NUM) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Server $serv, $workerId) { + echo 'WORKER STOP', PHP_EOL; +}); +$serv->on('pipeMessage', function (Server $serv, $wid, $msg) { + swoole_implicit_fn('bailout'); +}); +$serv->on('Request', function (Request $req, Response $resp) use ($serv) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $c = $atomic2->add(); + if ($c < N) { + Co::sleep(100); + } elseif ($c == N) { + $stats = $serv->stats(); + Assert::eq($stats['concurrency'], N); + $wid = $serv->getWorkerId(); + for ($i = 0; $i < WORKER_NUM; $i++) { + if ($i !== $wid) { + $serv->sendMessage('error', $i); + } + } + swoole_implicit_fn('bailout'); + } else { + $stats = $serv->stats(); + Assert::eq($stats['concurrency'], 1); + $resp->end(json_encode($stats)); + } +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'SHUTDOWN', PHP_EOL; + Assert::eq($atomic1->get(), WORKER_NUM * 2); + Assert::eq($atomic2->get(), N + 1); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv, $port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $queue->pop(-1); + run(function () use ($port, $serv, $atomic1, $queue) { + $n = N; + $coroutines = []; + while ($n--) { + $coroutines[] = go(function () use ($port) { + $client = new Client('127.0.0.1', $port); + $client->set(['timeout' => 10]); + Assert::eq($client->get('/'), false); + Assert::eq($client->getStatusCode(), SWOOLE_HTTP_CLIENT_ESTATUS_SERVER_RESET); + }); + } + + Co::join($coroutines); + + while (1) { + if ($atomic1->get() == WORKER_NUM * 2) { + break; + } + Co::sleep(0.1); + } + + $client = new Client('127.0.0.1', $port); + Assert::assert($client->get('/')); + $stats = json_decode($client->getBody()); + Assert::eq($stats->concurrency, 1); + $serv->shutdown(); + + echo "DONE\n"; + }); +})); +$serv->start(); +?> +--EXPECT-- +DONE +WORKER STOP +WORKER STOP +WORKER STOP +WORKER STOP +SHUTDOWN diff --git a/tests/swoole_thread/server/send_in_user_process.phpt b/tests/swoole_thread/server/send_in_user_process.phpt new file mode 100644 index 0000000000..5cd2890512 --- /dev/null +++ b/tests/swoole_thread/server/send_in_user_process.phpt @@ -0,0 +1,73 @@ +--TEST-- +swoole_thread/server: send in user process +--SKIPIF-- + +--FILE-- +pop(-1); + $reqUid = uniqid(); + Assert::eq(file_get_contents('http://127.0.0.1:' . $port . '/?uid=' . $reqUid), $reqUid); + echo "done\n"; + $serv->shutdown(); +}); +$serv->addProcess($proc); + +$proc2 = new Swoole\Process(function ($process) use ($serv) { + $json = $process->read(); + $data = json_decode($json, true); + $response = Swoole\Http\Response::create($data['fd']); + $response->end($data['uid']); + $response->close(); +}); +$serv->addProcess($proc2); + +$serv->set(array( + 'worker_num' => 1, + 'log_level' => SWOOLE_LOG_ERROR, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(0); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + $atomic->add(); + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic] = Thread::getArguments(); + $atomic->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv, $proc2) { + $resp->detach(); + $proc2->write(json_encode(['fd' => $resp->fd, 'uid' => $req->get['uid']])); +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + Assert::eq($atomic->get(), 2); + echo "shutdown\n"; +}); + +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/send_large_packet.phpt b/tests/swoole_thread/server/send_large_packet.phpt new file mode 100644 index 0000000000..2f277a7f5f --- /dev/null +++ b/tests/swoole_thread/server/send_large_packet.phpt @@ -0,0 +1,92 @@ +--TEST-- +swoole_thread/server: send large packet +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'open_length_check' => true, + 'package_max_length' => 4 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on("WorkerStop", function (Swoole\Server $serv, $workerId) { +}); +$serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $send_data = str_repeat('A', SIZE - 12) . substr($data, -8, 8); + $serv->send($fd, pack('N', strlen($send_data)) . $send_data); +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + + $c = MAX_CONCURRENCY_LOW; + $n = MAX_REQUESTS_LOW; + + for ($i = 0; $i < $c; $i++) { + go(function () use ($i, $n, $atomic, $port) { + $cli = new Co\Client(SWOOLE_SOCK_TCP); + $cli->set([ + 'open_length_check' => true, + 'package_max_length' => 4 * 1024 * 1024, + 'package_length_type' => 'N', + 'package_length_offset' => 0, + 'package_body_offset' => 4, + ]); + if ($cli->connect('127.0.0.1', $port, 2) == false) { + echo "ERROR\n"; + return; + } + for ($i = 0; $i < $n; $i++) { + $sid = strval(rand(10000000, 99999999)); + $send_data = str_repeat('A', 1000) . $sid; + $cli->send(pack('N', strlen($send_data)) . $send_data); + $data = $cli->recv(); + Assert::same(strlen($data), SIZE); + Assert::same($sid, substr($data, -8, 8)); + } + }); + } + Swoole\Event::wait(); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/setting.phpt b/tests/swoole_thread/server/setting.phpt new file mode 100644 index 0000000000..19f7b53159 --- /dev/null +++ b/tests/swoole_thread/server/setting.phpt @@ -0,0 +1,53 @@ +--TEST-- +swoole_thread/server: setting +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => N, + 'log_level' => SWOOLE_LOG_ERROR, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(0); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + Assert::isArray($serv->setting); + if ($atomic->add(1) == N) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), N); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + echo $queue->pop(-1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/server/stop_worker.phpt b/tests/swoole_thread/server/stop_worker.phpt new file mode 100644 index 0000000000..ff14d6b9f0 --- /dev/null +++ b/tests/swoole_thread/server/stop_worker.phpt @@ -0,0 +1,73 @@ +--TEST-- +swoole_thread/server: stop worker +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'task_worker_num' => 3, + 'log_level' => SWOOLE_LOG_ERROR, + 'init_arguments' => function () { + global $queue, $atomic1, $atomic2; + $queue = new Swoole\Thread\Queue(); + $atomic1 = new Swoole\Thread\Atomic(0); + $atomic2 = new Swoole\Thread\Atomic(0); + return [$queue, $atomic1, $atomic2]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + if ($atomic1->add() == 5) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('WorkerStop', function (Swoole\Server $serv, $workerId) { + [$queue, $atomic1, $atomic2] = Thread::getArguments(); + $atomic2->add(); +}); +$serv->on('Request', function ($req, $resp) use ($serv) { + if ($req->server['request_uri'] == '/stop') { + $serv->stop($req->get['worker'] ?? 0); + $resp->end("OK\n"); + } +}); +$serv->on('Task', function ($serv, $task_id, $worker_id, $data) { + +}); +$serv->on('shutdown', function () { + global $queue, $atomic1, $atomic2; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic1->get(), 7); + Assert::eq($atomic2->get(), 7); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + + echo file_get_contents('http://127.0.0.1:' . $port . '/stop?worker=' . random_int(0, 1)); + echo file_get_contents('http://127.0.0.1:' . $port . '/stop?worker=' . random_int(2, 4)); + + sleep(1); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECTF-- +begin +OK +OK +done +shutdown diff --git a/tests/swoole_thread/server/taskCo.phpt b/tests/swoole_thread/server/taskCo.phpt new file mode 100644 index 0000000000..40c8bff6cb --- /dev/null +++ b/tests/swoole_thread/server/taskCo.phpt @@ -0,0 +1,72 @@ +--TEST-- +swoole_thread/server: taskCo +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'task_worker_num' => 2, + 'enable_coroutine' => true, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('task', + function (Server $server, int $task_id, int $src_worker_id, mixed $data) { + $server->finish($data * 7); + } +); +$serv->on('Request', + function (Request $request, Response $response) use ($serv) { + $tasks = range(1, 16); + $result = $serv->taskCo($tasks); + $response->header('Content-Type', 'application/json'); + $response->end(json_encode(["count" => count($result)])); + } +); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; + }); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +{"count":16} +done +shutdown diff --git a/tests/swoole_thread/server/taskWaitMulti.phpt b/tests/swoole_thread/server/taskWaitMulti.phpt new file mode 100644 index 0000000000..39a5ea5665 --- /dev/null +++ b/tests/swoole_thread/server/taskWaitMulti.phpt @@ -0,0 +1,77 @@ +--TEST-- +swoole_thread/server: taskWaitMulti +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'task_worker_num' => 2, + 'enable_coroutine' => false, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); + + +$serv->on('task', + function (Server $server, int $task_id, int $src_worker_id, mixed $data) { + $server->finish($data * 7); + } +); + +$serv->on('Request', + function (Request $request, Response $response) use ($serv) { + $tasks = [9999, 8888]; + $result = $serv->taskWaitMulti($tasks); + $response->header('Content-Type', 'application/json'); + $_rs = array_values($result); + sort($_rs); + $response->end(json_encode(["data" => $_rs])); + } +); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + echo httpGetBody("http://127.0.0.1:{$port}/") . PHP_EOL; + }); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +{"data":[62216,69993]} +done +shutdown diff --git a/tests/swoole_thread/server/udp_port.phpt b/tests/swoole_thread/server/udp_port.phpt new file mode 100644 index 0000000000..f6f5c656cf --- /dev/null +++ b/tests/swoole_thread/server/udp_port.phpt @@ -0,0 +1,76 @@ +--TEST-- +swoole_thread/server: listen udp port +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'reload_async' => true, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(0); + return [$queue, $atomic]; + } +)); +$udp = $serv->addListener('127.0.0.1', $port + 1, SWOOLE_SOCK_UDP); +$udp->on('packet', function ($serv, $data, $addr) { + echo "udp packet\n"; + $serv->sendto($addr['address'], $addr['port'], $data); +}); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($atomic->add() == 1) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } + echo "worker start\n"; +}); +$serv->on('message', function (Server $server, $frame) { + echo "message\n"; +}); +$serv->on('workerExit', function (Server $server, $wid) { + var_dump('worker exit: ' . $wid); + Timer::clearAll(); +}); +$serv->on('shutdown', function (Server $server) { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), $server->setting['worker_num']); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + $udp_sock = stream_socket_client('udp://127.0.0.1:' . ($port + 1), $errno, $errstr); + $pkt = random_bytes(1024); + fwrite($udp_sock, $pkt); + $data = fread($udp_sock, 1024); + Assert::eq($pkt, $data); + }); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +worker start +worker start +begin +udp packet +done +shutdown diff --git a/tests/swoole_thread/server/websocket.phpt b/tests/swoole_thread/server/websocket.phpt new file mode 100644 index 0000000000..6409001bbe --- /dev/null +++ b/tests/swoole_thread/server/websocket.phpt @@ -0,0 +1,63 @@ +--TEST-- +swoole_thread/server: websocket +--SKIPIF-- + +--FILE-- +set(array( + 'worker_num' => 2, + 'log_level' => SWOOLE_LOG_ERROR, + 'init_arguments' => function () { + global $queue, $atomic; + $queue = new Swoole\Thread\Queue(); + $atomic = new Swoole\Thread\Atomic(1); + return [$queue, $atomic]; + } +)); +$serv->on('WorkerStart', function (Swoole\Server $serv, $workerId) use ($port) { + [$queue, $atomic] = Thread::getArguments(); + if ($workerId == 0) { + $queue->push("begin\n", Thread\Queue::NOTIFY_ALL); + } +}); +$serv->on('message', function (Server $server, $frame) { + $server->push($frame->fd, $frame->data); +}); +$serv->on('shutdown', function () { + global $queue, $atomic; + echo 'shutdown', PHP_EOL; + Assert::eq($atomic->get(), 0); +}); +$serv->addProcess(new Swoole\Process(function ($process) use ($serv) { + [$queue, $atomic] = Thread::getArguments(); + global $port; + echo $queue->pop(-1); + Co\run(function () use ($port) { + $cli = new Co\Http\Client('127.0.0.1', $port); + $data = base64_decode(random_bytes(2048)); + Assert::assert($cli->upgrade('/')); + $cli->push($data); + $frame = $cli->recv(); + Assert::eq($frame->data, $data); + }); + $atomic->set(0); + echo "done\n"; + $serv->shutdown(); +})); +$serv->start(); +?> +--EXPECT-- +begin +done +shutdown diff --git a/tests/swoole_thread/shell_exec.phpt b/tests/swoole_thread/shell_exec.phpt new file mode 100644 index 0000000000..25448a8a8c --- /dev/null +++ b/tests/swoole_thread/shell_exec.phpt @@ -0,0 +1,43 @@ +--TEST-- +swoole_thread: lock +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $lock = new Lock; + $lock->lock(); + $thread = new Thread(__FILE__, $lock); + $lock->unlock(); + $thread->join(); + Assert::eq($thread->getExitStatus(), 0); + echo 'DONE' . PHP_EOL; +}; + +$tm->childFunc = function ($lock) { + $lock->lock(); + usleep(100_000); + Co\run(function () { + shell_exec('ls /tmp'); + sleep(1); + gethostbyname('www.baidu.com'); + }); + exit(0); +}; + +$tm->run(); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_thread/signal.phpt b/tests/swoole_thread/signal.phpt new file mode 100644 index 0000000000..7b212326a7 --- /dev/null +++ b/tests/swoole_thread/signal.phpt @@ -0,0 +1,55 @@ +--TEST-- +swoole_thread: signal +--SKIPIF-- + +--FILE-- +send('exit'); + } + Co\go(function () use ($parent_pipe, $thread) { + // 从管道中读取子线程退出的信息 + echo $parent_pipe->recv(8192), PHP_EOL; + // 回收子线程 + $thread->join(); + }); + }); +} else { + $child_pipe = $args[0]; + Co\run(function () use ($child_pipe) { + // 收到父线程的指令,开始退出 + echo $child_pipe->recv(8192), PHP_EOL; + // 通知父线程已退出 + $child_pipe->send('child exit'); + }); + exit(0); +} +?> +--EXPECTF-- +timer +signal term +exit +child exit diff --git a/tests/swoole_thread/sort.phpt b/tests/swoole_thread/sort.phpt new file mode 100644 index 0000000000..941d89452b --- /dev/null +++ b/tests/swoole_thread/sort.phpt @@ -0,0 +1,41 @@ +--TEST-- +swoole_thread: sort +--SKIPIF-- + +--FILE-- + "lemon", + "o" => "orange", + "O" => "Orange", + "O1" => "Orange1", + "o2" => "orange2", + "O3" => "Orange3", + "o20" => "orange20", + "b" => "banana", +); + +$unsorted_map = new Map($original_map); +$unsorted_map->sort(); + +$copied_map = $original_map; +asort($copied_map); +Assert::eq($unsorted_map->toArray(), $copied_map); + +$original_list = array( 100, 33, 555, 22 ); +$copied_list = $original_list; + +$unsorted_list = new ArrayList($original_list); +$unsorted_list->sort(); +sort($copied_list); +Assert::eq($unsorted_list->toArray(), $copied_list); +?> +--EXPECT-- diff --git a/tests/swoole_thread/stdio.phpt b/tests/swoole_thread/stdio.phpt new file mode 100644 index 0000000000..318b0c2aff --- /dev/null +++ b/tests/swoole_thread/stdio.phpt @@ -0,0 +1,42 @@ +--TEST-- +swoole_thread: stdio +--SKIPIF-- + +--FILE-- +parentFunc = function () { + $lock = new Lock; + $lock->lock(); + $thread = new Thread(__FILE__, $lock); + $lock->lock(); + $thread->join(); + echo "main thread\n"; +}; + +$tm->childFunc = function ($lock) { + echo "child thread\n"; + usleep(200_000); + $lock->unlock(); + fwrite(STDOUT, "hello swoole\n"); + Assert::notEmpty(STDIN); + exit(0); +}; + +$tm->run(); +echo "DONE\n"; +?> +--EXPECTF-- +child thread +hello swoole +main thread +DONE diff --git a/tests/swoole_thread/stream.phpt b/tests/swoole_thread/stream.phpt new file mode 100644 index 0000000000..e65861c18e --- /dev/null +++ b/tests/swoole_thread/stream.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_thread: stream +--SKIPIF-- + +--FILE-- +initFreePorts(increment: crc32(__FILE__) % 1000); + +$tm->parentFunc = function () use ($tm) { + $queue = new Queue(); + $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + $queue->push($fp); + $thread = new Thread(__FILE__, $queue, 0); + var_dump('main thread'); + $thread->join(); +}; + +$tm->childFunc = function ($queue, $id) use ($tm) { + if ($id === 0) { + var_dump('child thread 0'); + $fp = $queue->pop(); + $thread = new Thread(__FILE__, $queue, 1); + $conn = stream_socket_accept($fp, -1); + fwrite($conn, "hello world\n"); + fclose($conn); + fclose($fp); + $thread->join(); + } else { + var_dump('child thread 1'); + $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + Assert::notEmpty($client); + $data = fread($client, 8192); + Assert::eq($data, "hello world\n"); + fclose($client); + } +}; + +$tm->run(); +?> +--EXPECT-- +string(11) "main thread" +string(14) "child thread 0" +string(14) "child thread 1" diff --git a/tests/swoole_thread/stream_arg.phpt b/tests/swoole_thread/stream_arg.phpt new file mode 100644 index 0000000000..7d7f9d0c9c --- /dev/null +++ b/tests/swoole_thread/stream_arg.phpt @@ -0,0 +1,49 @@ +--TEST-- +swoole_thread: stream as a thread argument +--SKIPIF-- + +--FILE-- +initFreePorts(increment: crc32(__FILE__) % 1000); + +$tm->parentFunc = function () use ($tm) { + $fp = stream_socket_server('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + $thread = new Thread(__FILE__, $fp, 0); + var_dump('main thread'); + $thread->join(); +}; + +$tm->childFunc = function ($fp, $id) use ($tm) { + if ($id === 0) { + var_dump('child thread 0'); + $thread = new Thread(__FILE__, $fp, 1); + $conn = stream_socket_accept($fp, -1); + fwrite($conn, "hello world\n"); + fclose($conn); + fclose($fp); + $thread->join(); + } else { + var_dump('child thread 1'); + $client = stream_socket_client('tcp://127.0.0.1:' . $tm->getFreePort(), $errno, $errstr); + Assert::notEmpty($client); + $data = fread($client, 8192); + Assert::eq($data, "hello world\n"); + fclose($client); + } +}; + +$tm->run(); +?> +--EXPECT-- +string(11) "main thread" +string(14) "child thread 0" +string(14) "child thread 1" diff --git a/tests/swoole_thread/thread_status.phpt b/tests/swoole_thread/thread_status.phpt new file mode 100644 index 0000000000..464ed2b78e --- /dev/null +++ b/tests/swoole_thread/thread_status.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_thread: thread status +--SKIPIF-- + +--FILE-- +joinable()); +Assert::true($t1->isAlive()); +Assert::true($t1->join()); +Assert::false($t1->joinable()); +Assert::false($t1->isAlive()); + +$t2 = new Thread(TESTS_API_PATH . '/swoole_thread/sleep.php'); +$t2->detach(); +usleep(10); +Assert::false($t2->joinable()); +Assert::true($t2->isAlive()); +while (Thread::activeCount() > 1) { + usleep(10); +} +Assert::false($t2->isAlive()); +?> +--EXPECT-- diff --git a/tests/swoole_thread/yield.phpt b/tests/swoole_thread/yield.phpt new file mode 100644 index 0000000000..94b784d9f0 --- /dev/null +++ b/tests/swoole_thread/yield.phpt @@ -0,0 +1,25 @@ +--TEST-- +swoole_thread: thread status +--SKIPIF-- + +--FILE-- +isAlive()); +$t1->detach(); +Thread::yield(); +usleep(10); +Assert::true($t1->isAlive()); +while (Thread::activeCount() > 1) { + usleep(10); +} +Assert::false($t1->isAlive()); +?> +--EXPECT-- diff --git a/tests/swoole_timer/bug_4794.phpt b/tests/swoole_timer/bug_4794.phpt new file mode 100644 index 0000000000..4ff0b44690 --- /dev/null +++ b/tests/swoole_timer/bug_4794.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +push(['rand' => 9999]); + }); + go(function () use ($channel) { + $data = $channel->pop(0.00001); + var_dump($data); + }); +}); +?> +--EXPECT-- +array(1) { + ["rand"]=> + int(9999) +} diff --git a/tests/swoole_timer/bug_4794_2.phpt b/tests/swoole_timer/bug_4794_2.phpt new file mode 100644 index 0000000000..19749e5b77 --- /dev/null +++ b/tests/swoole_timer/bug_4794_2.phpt @@ -0,0 +1,59 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +start(); + +$processSlow = new Process(function () use ($atomic) { + $atomic->wait(10); + usleep(10 * 1000); +}); +$processSlow->start(); + +Coroutine\run(function () use ($processFast, $processSlow, $atomic) { + for ($n = MAX_REQUESTS; $n--;) { + $status = System::waitPid($processSlow->pid, 0.0001); + Assert::false($status); + Assert::same(swoole_last_error(), SOCKET_ETIMEDOUT); + } + $atomic->wakeup(); + $status = System::waitPid($processSlow->pid, 1); + Assert::same($status['pid'], $processSlow->pid); + var_dump($status); + $status = System::waitPid($processFast->pid); + Assert::same($status['pid'], $processFast->pid); + var_dump($status); +}); + +?> +--EXPECTF-- +array(3) { + ["pid"]=> + int(%d) + ["code"]=> + int(0) + ["signal"]=> + int(0) +} +array(3) { + ["pid"]=> + int(%d) + ["code"]=> + int(0) + ["signal"]=> + int(0) +} diff --git a/tests/swoole_timer/bug_4794_3.phpt b/tests/swoole_timer/bug_4794_3.phpt new file mode 100644 index 0000000000..b02852b727 --- /dev/null +++ b/tests/swoole_timer/bug_4794_3.phpt @@ -0,0 +1,28 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- + +--EXPECT-- +DONE diff --git a/tests/swoole_timer/bug_4794_4.phpt b/tests/swoole_timer/bug_4794_4.phpt new file mode 100644 index 0000000000..44be4623ad --- /dev/null +++ b/tests/swoole_timer/bug_4794_4.phpt @@ -0,0 +1,57 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +wait(); + echo "2\n"; + switch_process(); + Process::kill($pid, SIGUSR1); + $atomic->wait(); + echo "6\n"; + switch_process(); + Process::kill($pid, SIGUSR2); + echo "8\n"; +}); +$killer->start(); + +Coroutine\run(function () use ($atomic) { + Coroutine::sleep(0.001); + switch_process(); + $atomic->wakeup(); + echo "1\n"; + Assert::eq(System::waitSignal(SIGUSR1), SIGUSR1); + echo "3\n"; + Assert::false(System::waitSignal(SIGUSR2, 0.0001)); + echo "4\n"; + $atomic->wakeup(); + echo "5\n"; + Assert::eq(System::waitSignal(SIGUSR2), SIGUSR2); + echo "7\n"; + System::wait(0.0001); + echo "9\n"; +}); + +?> +--EXPECT-- +1 +2 +3 +4 +5 +6 +8 +7 +9 diff --git a/tests/swoole_timer/bug_4794_5.phpt b/tests/swoole_timer/bug_4794_5.phpt new file mode 100644 index 0000000000..4d056fdf19 --- /dev/null +++ b/tests/swoole_timer/bug_4794_5.phpt @@ -0,0 +1,18 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/swoole_timer/bug_4794_6.phpt b/tests/swoole_timer/bug_4794_6.phpt new file mode 100644 index 0000000000..8db622ec2f --- /dev/null +++ b/tests/swoole_timer/bug_4794_6.phpt @@ -0,0 +1,21 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +connect('www.baidu.com', 80); + $info = $conn->getpeername(); + Assert::eq($info['host'], System::gethostbyname('www.baidu.com'), 'AF_INET', 0.0001); + Assert::eq($info['port'], 80); + } +); +?> +--EXPECT-- diff --git a/tests/swoole_timer/bug_4794_7.phpt b/tests/swoole_timer/bug_4794_7.phpt new file mode 100644 index 0000000000..aa3cbed20a --- /dev/null +++ b/tests/swoole_timer/bug_4794_7.phpt @@ -0,0 +1,16 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +connect("11.11.11.11", 80, 0.0005); +Assert::false($r); +Assert::eq($cli->errCode, SOCKET_ETIMEDOUT); +?> +--EXPECT-- diff --git a/tests/swoole_timer/bug_4794_8.phpt b/tests/swoole_timer/bug_4794_8.phpt new file mode 100644 index 0000000000..dab6e17f88 --- /dev/null +++ b/tests/swoole_timer/bug_4794_8.phpt @@ -0,0 +1,32 @@ +--TEST-- +swoole_timer: #4794 Timer::add() (ERRNO 505): msec value[0] is invalid +--SKIPIF-- + +--FILE-- +bind('127.0.0.1', 9601)); + Assert::assert($sock->listen(512)); + $conn = $sock->accept(0.0001); + Assert::assert($conn); + Assert::isInstanceOf($conn, Swoole\Coroutine\Socket::class); + + $data = $conn->recv(0.0001); + $json = json_decode($data, true); + Assert::same($json['data'] ?? '', 'hello'); + $conn->send("world\n"); + $conn->close(); +}); + +go(function () { + $conn = new Swoole\Coroutine\Socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + Assert::assert($conn->connect('127.0.0.1', 9601)); + $conn->send(json_encode(['data' => 'hello'])); + echo $conn->recv(); +}); +?> +--EXPECT-- +world diff --git a/tests/swoole_timer/clearAll.phpt b/tests/swoole_timer/clearAll.phpt index 97b4994a89..dd81880b43 100644 --- a/tests/swoole_timer/clearAll.phpt +++ b/tests/swoole_timer/clearAll.phpt @@ -16,7 +16,7 @@ $server->on('workerStart', function (Swoole\Server $server, int $worker_id) { }); Swoole\Timer::clearAll(); if ($worker_id === 0) { - Swoole\Timer::after(10, function () use ($server) { + Swoole\Timer::after(200, function () use ($server) { $server->shutdown(); }); } diff --git a/tests/swoole_timer/enable_coroutine2.phpt b/tests/swoole_timer/enable_coroutine2.phpt index 44ad8d319e..18e0d10734 100644 --- a/tests/swoole_timer/enable_coroutine2.phpt +++ b/tests/swoole_timer/enable_coroutine2.phpt @@ -5,20 +5,23 @@ swoole_timer: enable_coroutine setting --FILE-- false ]); Swoole\Timer::after(1, function () { $uid = Co::getuid(); echo "#{$uid}\n"; - Swoole\Timer::set([ - 'enable_coroutine' => true - ]); - Swoole\Timer::after(1, function () { - $uid = Co::getuid(); - echo "#{$uid}\n"; - }); }); +Swoole\Event::wait(); + +swoole_async_set([ + 'enable_coroutine' => true +]); +Swoole\Timer::after(1, function () { + $uid = Co::getuid(); + echo "#{$uid}\n"; +}); +Swoole\Event::wait(); ?> --EXPECT-- #-1 diff --git a/tests/swoole_timer/function_alias.phpt b/tests/swoole_timer/function_alias.phpt index 61cf74fab8..870f6eca27 100644 --- a/tests/swoole_timer/function_alias.phpt +++ b/tests/swoole_timer/function_alias.phpt @@ -7,7 +7,6 @@ swoole_timer: function alias require __DIR__ . '/../include/bootstrap.php'; var_dump( - function_exists('swoole_timer_set') && function_exists('swoole_timer_after') && function_exists('swoole_timer_tick') && function_exists('swoole_timer_exists') && diff --git a/tests/swoole_timer/master.phpt b/tests/swoole_timer/master.phpt index 169e149e08..e47efefc01 100644 --- a/tests/swoole_timer/master.phpt +++ b/tests/swoole_timer/master.phpt @@ -1,20 +1,22 @@ --TEST-- swoole_timer: timer in master --SKIPIF-- - + --FILE-- setWaitTimeout(-1); $pm->parentFunc = function ($pid) use ($pm) { - $fp = fopen(RES_FILE, "rw"); + $fp = fopen(RES_FILE, 'rw'); while (!feof($fp)) { $line = fgets($fp); if ($line) { @@ -26,37 +28,38 @@ $pm->parentFunc = function ($pid) use ($pm) { } }; $pm->childFunc = function () use ($pm) { - $server = new Swoole\Server("0.0.0.0", $pm->getFreePort(), SWOOLE_PROCESS); + $server = new Server('0.0.0.0', $pm->getFreePort(), SWOOLE_PROCESS); $server->set([ 'worker_num' => 1, 'log_file' => '/dev/null', ]); - $server->on('start', function (Swoole\Server $server) use ($pm) { + $server->on('start', function (Server $server) use ($pm) { file_put_contents(RES_FILE, "start\n", FILE_APPEND); - $id = Swoole\Timer::tick(30, function () { + $id = Timer::tick(30, function () { file_put_contents(RES_FILE, "timer 1\n", FILE_APPEND); }); - Swoole\Timer::after(90, function () use ($id, $server, $pm) { + Timer::after(90, function () use ($id, $server, $pm) { file_put_contents(RES_FILE, "timer 2\n", FILE_APPEND); - Swoole\Timer::clear($id); - Swoole\Timer::tick(10, function ($id) use ($server, $pm) { + Timer::clear($id); + Timer::tick(10, function ($id) use ($server, $pm) { static $i = 0; file_put_contents(RES_FILE, "timer 3\n", FILE_APPEND); $i++; if ($i > 4) { file_put_contents(RES_FILE, "end\n", FILE_APPEND); - Swoole\Timer::clear($id); + Timer::clear($id); $pm->wakeup(); $server->shutdown(); } }); }); }); - $server->on('receive', function () { }); + $server->on('receive', function () {}); $server->start(); }; $pm->childFirst(); $pm->run(); +@unlink(RES_FILE); ?> --EXPECT-- start diff --git a/tests/swoole_timer/stats.phpt b/tests/swoole_timer/stats.phpt index e98a0aad86..50383ce334 100644 --- a/tests/swoole_timer/stats.phpt +++ b/tests/swoole_timer/stats.phpt @@ -18,7 +18,7 @@ Swoole\Timer::after(100, function () { var_dump(Swoole\Timer::stats()); }); Swoole\Event::wait(); -time_approximate(0.1, microtime(true) - $s); +time_approximate(0.1, microtime(true) - $s, 0.2); ?> --EXPECTF-- array(3) { diff --git a/tests/swoole_timer/swoole_timer_list_alias.phpt b/tests/swoole_timer/swoole_timer_list_alias.phpt new file mode 100644 index 0000000000..320e1a6bc6 --- /dev/null +++ b/tests/swoole_timer/swoole_timer_list_alias.phpt @@ -0,0 +1,11 @@ +--TEST-- +swoole_timer: function alias about swoole_timer_list +--SKIPIF-- + +--FILE-- + +--EXPECT-- +3 diff --git a/tests/swoole_timer/task_worker.phpt b/tests/swoole_timer/task_worker.phpt index 6078de33c4..715592b7fa 100644 --- a/tests/swoole_timer/task_worker.phpt +++ b/tests/swoole_timer/task_worker.phpt @@ -1,68 +1,66 @@ --TEST-- swoole_timer: call after in Task-Worker --SKIPIF-- - + --FILE-- parentFunc = function ($pid) use ($pm) -{ - $cli = new Swoole\Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); - $cli->set(['open_eof_check' => true, "package_eof" => "\r\n\r\n"]); - $cli->connect('127.0.0.1', $pm->getFreePort(), 5) or die("ERROR"); +use Swoole\Client; +use Swoole\Server; +use Swoole\Timer; + +$pm = new ProcessManager(); +$pm->parentFunc = function ($pid) use ($pm) { + $cli = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); + $cli->set(['open_eof_check' => true, 'package_eof' => "\r\n\r\n"]); + $cli->connect('127.0.0.1', $pm->getFreePort(), 5) or exit('ERROR'); - $cli->send("task-01") or die("ERROR"); - for ($i = 0; $i < 5; $i++) - { - echo trim($cli->recv())."\n"; + $cli->send('task-01') or exit('ERROR'); + for ($i = 0; $i < 5; $i++) { + echo trim($cli->recv()) . "\n"; } $pm->kill(); }; -$pm->childFunc = function () use ($pm) -{ +$pm->childFunc = function () use ($pm) { ini_set('swoole.display_errors', 'Off'); - $serv = new Swoole\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); - $serv->set(array( - "worker_num" => 1, + $serv = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_PROCESS); + $serv->set([ + 'worker_num' => 1, 'task_worker_num' => 1, 'log_file' => '/dev/null', - )); - $serv->on("WorkerStart", function (Swoole\Server $serv) use ($pm) - { + ]); + $serv->on('WorkerStart', function (Server $serv) use ($pm) { $pm->wakeup(); }); - $serv->on('receive', function (Swoole\Server $serv, $fd, $rid, $data) { + $serv->on('receive', function (Server $serv, $fd, $rid, $data) { $serv->task([$fd, 'timer']); }); - $serv->on('task', function (Swoole\Server $serv, $task_id, $worker_id, $data) { - list($fd) = $data; - Swoole\Timer::after(500, function () use ($serv, $fd) { + $serv->on('task', function (Server $serv, $task_id, $worker_id, $data) { + [$fd] = $data; + Timer::after(500, function () use ($serv, $fd) { $serv->send($fd, "500\r\n\r\n"); - Swoole\Timer::after(300, function () use ($serv, $fd) { + Timer::after(300, function () use ($serv, $fd) { $serv->send($fd, "800\r\n\r\n"); }); }); - Swoole\Timer::after(1000, function () use ($serv, $fd) { + Timer::after(1000, function () use ($serv, $fd) { $serv->send($fd, "1000[1]\r\n\r\n"); }); - Swoole\Timer::after(1000, function () use ($serv, $fd) { + Timer::after(1000, function () use ($serv, $fd) { $serv->send($fd, "1000[2]\r\n\r\n"); }); - Swoole\Timer::after(500, function () use ($serv, $fd) { + Timer::after(500, function () use ($serv, $fd) { $serv->send($fd, "500[2]\r\n\r\n"); }); - Swoole\Timer::after(2000, function () use ($serv, $fd) { + Timer::after(2000, function () use ($serv, $fd) { $serv->send($fd, "2000\r\n\r\n"); }); }); - $serv->on('finish', function (Swoole\Server $serv, $fd, $rid, $data) - { - - }); + $serv->on('finish', function (Server $serv, $fd, $rid, $data) {}); $serv->start(); }; diff --git a/tests/swoole_websocket_server/bug_1.phpt b/tests/swoole_websocket_server/bug_1.phpt new file mode 100644 index 0000000000..76e164c6fa --- /dev/null +++ b/tests/swoole_websocket_server/bug_1.phpt @@ -0,0 +1,52 @@ +--TEST-- +swoole_websocket_server: bug 1 +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + global $count; + $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli->set(['timeout' => 5]); + Assert::assert($cli->upgrade('/')); + $data1 = get_safe_random(random_int(1024, 8192)); + $cli->push($data1); + $frame1 = $cli->recv(); + Assert::eq($frame1->data, md5($data1)); + + $data2 = get_safe_random(random_int(65536, 65536 * 2)); + $pkt2 = Swoole\WebSocket\Server::pack($data2, WEBSOCKET_OPCODE_TEXT); + + $cli->socket->sendAll(substr($pkt2, 0, 4)); + usleep(1000); + $cli->socket->sendAll(substr($pkt2, 4)); + + $frame2 = $cli->recv(); + Assert::eq($frame2->data, md5($data2)); + }); + $pm->kill(); +}; +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort()); + $serv->set([ + 'open_http2_protocol' => true, +// 'log_file' => '/dev/null', + ]); + $serv->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { + $server->push($frame->fd, md5($frame->data)); + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_websocket_server/close_frame_full.phpt b/tests/swoole_websocket_server/close_frame_full.phpt index 94f810d3ca..65bae79828 100644 --- a/tests/swoole_websocket_server/close_frame_full.phpt +++ b/tests/swoole_websocket_server/close_frame_full.phpt @@ -10,25 +10,30 @@ $pm->parentFunc = function (int $pid) use ($pm) { for ($c = MAX_CONCURRENCY_LOW; $c--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); - $cli->set(['timeout' => 5]); + $cli->set([ + 'timeout' => 5, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); for ($n = MAX_REQUESTS; $n--;) { - $ret = $cli->upgrade('/'); - Assert::assert($ret); + Assert::true($cli->upgrade('/')); $code = mt_rand(0, 5000); $reason = md5($code); $close_frame = new Swoole\WebSocket\CloseFrame; $close_frame->code = $code; $close_frame->reason = $reason; - $cli->push($close_frame); + Assert::true($cli->push($close_frame)); // recv the last close frame $frame = $cli->recv(); Assert::isInstanceOf($frame, Swoole\WebSocket\CloseFrame::class); Assert::same($frame->opcode, WEBSOCKET_OPCODE_CLOSE); Assert::same(md5($frame->code), $frame->reason); // connection closed + Assert::true($cli->disconnect($frame->code, $frame->reason)); Assert::false($cli->recv()); Assert::false($cli->connected); - Assert::same($cli->errCode, 0); // connection close normally + Assert::same($cli->errCode, 5001); // connection close normally } }); } diff --git a/tests/swoole_websocket_server/compression.phpt b/tests/swoole_websocket_server/compression.phpt index bb999f6fed..175dbc07b7 100644 --- a/tests/swoole_websocket_server/compression.phpt +++ b/tests/swoole_websocket_server/compression.phpt @@ -6,21 +6,21 @@ swoole_websocket_server: compression initRandomData(MAX_REQUESTS); $pm->parentFunc = function (int $pid) use ($pm) { Co\run(function () use ($pm) { $cli = new Client('127.0.0.1', $pm->getFreePort()); $cli->set([ 'timeout' => 5, - 'websocket_compression' => true + 'websocket_compression' => true, ]); $ret = $cli->upgrade('/'); if (!Assert::assert($ret)) { @@ -49,7 +49,7 @@ $pm->childFunc = function () use ($pm) { $server = new Server('127.0.0.1', $pm->getFreePort(), SERVER_MODE_RANDOM); $server->set([ 'log_file' => '/dev/null', - 'websocket_compression' => true + 'websocket_compression' => true, ]); $server->on('workerStart', function () use ($pm) { $pm->wakeup(); diff --git a/tests/swoole_websocket_server/dynamic_property.phpt b/tests/swoole_websocket_server/dynamic_property.phpt new file mode 100644 index 0000000000..5023586f26 --- /dev/null +++ b/tests/swoole_websocket_server/dynamic_property.phpt @@ -0,0 +1,38 @@ +--TEST-- +swoole_websocket_server: Creation of dynamic property is deprecated. +--SKIPIF-- + +--FILE-- +initFreePorts(10); +$websocket = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort()); +$port1 = $websocket->listen("127.0.0.1", $pm->getFreePort(1), SWOOLE_SOCK_TCP); +$port2 = $websocket->listen("127.0.0.1", $pm->getFreePort(2), SWOOLE_SOCK_TCP); +$port3 = $websocket->listen("127.0.0.1", $pm->getFreePort(3), SWOOLE_SOCK_TCP); +$port1->on('handshake', function($request, $response) {}); +$port1->on('beforehandshakeresponse', function($request, $response) {}); +var_dump($port1->getCallback('handshake') != null); +var_dump($port1->getCallback('BeforeHandshakeResponse') != null); + +$port2->on('HANDSHAKE', function($request, $response) {}); +$port2->on('BEFOREHANDSHAKERESPONSE', function($request, $response) {}); +var_dump($port1->getCallback('HANDSHAKE') != null); +var_dump($port1->getCallback('BEFOREHANDSHAKERESPONSE') != null); + +$port3->on('handShake', function($request, $response) {}); +$port3->on('beforehandShakeResponse', function($request, $response) {}); +var_dump($port1->getCallback('handShake') != null); +var_dump($port1->getCallback('beforehandShakeResponse') != null); +echo "DONE\n"; +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +DONE diff --git a/tests/swoole_websocket_server/header_token.phpt b/tests/swoole_websocket_server/header_token.phpt new file mode 100644 index 0000000000..a5afa6a57b --- /dev/null +++ b/tests/swoole_websocket_server/header_token.phpt @@ -0,0 +1,72 @@ +--TEST-- +swoole_websocket_server: header token +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm, &$count) { + Co\run(function () use ($pm) { + $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); + Assert::assert($connected); + $cli->send("GET /chat HTTP/1.1\r\n" . + "Host: localhost\r\n" . + "Upgrade: websocket, abc\r\n" . + "Connection: Upgrade, abc\r\n" . + "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . + "Sec-WebSocket-Protocol: chat\r\n" . + "Sec-WebSocket-Version: 13\r\n\r\n"); + $res = $cli->recv(); + Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); + + $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); + Assert::assert($connected); + $cli->send("GET /chat HTTP/1.1\r\n" . + "Host: localhost\r\n" . + "Upgrade: abc, websocket\r\n" . + "Connection: abc, Upgrade\r\n" . + "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . + "Sec-WebSocket-Protocol: chat\r\n" . + "Sec-WebSocket-Version: 13\r\n\r\n"); + $res = $cli->recv(); + Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); + + $cli = new Swoole\Coroutine\Client(SWOOLE_SOCK_TCP); + $connected = $cli->connect('127.0.0.1', $pm->getFreePort()); + Assert::assert($connected); + $cli->send("GET /chat HTTP/1.1\r\n" . + "Host: localhost\r\n" . + "Upgrade: abc, websocket, def\r\n" . + "Connection: abc, Upgrade, def\r\n" . + "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n" . + "Sec-WebSocket-Protocol: chat\r\n" . + "Sec-WebSocket-Version: 13\r\n\r\n"); + $res = $cli->recv(); + Assert::contains($res, 'HTTP/1.1 101 Switching Protocols'); + }); + $pm->kill(); + echo "DONE\n"; +}; +$pm->childFunc = function () use ($pm) { + $serv = new Swoole\WebSocket\Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $serv->set([ + 'log_file' => LOG_FILE, + ]); + $serv->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + $serv->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) { + }); + $serv->start(); +}; +$pm->childFirst(); +$pm->run(); +unlink(LOG_FILE); +?> +--EXPECT-- +DONE diff --git a/tests/swoole_websocket_server/pack.phpt b/tests/swoole_websocket_server/pack.phpt index d8625e60dc..6ea60eda66 100644 --- a/tests/swoole_websocket_server/pack.phpt +++ b/tests/swoole_websocket_server/pack.phpt @@ -54,11 +54,16 @@ for ($i = 1000; $i--;) { if ($opcode === WEBSOCKET_OPCODE_CLOSE) { Assert::same($unpacked->code, $code); Assert::same($unpacked->reason, $data); - Assert::true($unpacked->finish); + Assert::true($unpacked->finish == SWOOLE_WEBSOCKET_FLAG_FIN); } else { Assert::same($unpacked->data, $data); Assert::same($unpacked->opcode, $opcode); - Assert::same($unpacked->finish, $finish); + if ($opcode === WEBSOCKET_OPCODE_PING || $opcode === WEBSOCKET_OPCODE_PONG) { + Assert::true($unpacked->finish == SWOOLE_WEBSOCKET_FLAG_FIN); + } else { + Assert::same($unpacked->finish, $finish); + } + } } ?> diff --git a/tests/swoole_websocket_server/ping.phpt b/tests/swoole_websocket_server/ping.phpt new file mode 100644 index 0000000000..077e12e605 --- /dev/null +++ b/tests/swoole_websocket_server/ping.phpt @@ -0,0 +1,51 @@ +--TEST-- +swoole_websocket_server: test ping function +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $client->set(['open_websocket_ping_frame' => true]); + Assert::true($client->upgrade('/')); + $client->push('123456'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + Assert::true($frame->data == 'Hello World'); + $frame = $client->recv(); + Assert::true($frame->opcode == SWOOLE_WEBSOCKET_OPCODE_PING); + Assert::true($frame->data == ''); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) { + $server->ping($frame->fd, 'Hello World'); + $server->ping($frame->fd); + }); + + $server->start(); +}; + +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- diff --git a/tests/swoole_websocket_server/pingloop.phpt b/tests/swoole_websocket_server/pingloop.phpt index fb20602e85..1eddfc5dc4 100644 --- a/tests/swoole_websocket_server/pingloop.phpt +++ b/tests/swoole_websocket_server/pingloop.phpt @@ -15,6 +15,11 @@ $pm->parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_MID; $i--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $ret = $cli->upgrade('/'); Assert::assert($ret); $loop = 0; diff --git a/tests/swoole_websocket_server/pingloop_open_ping_pong_frame.phpt b/tests/swoole_websocket_server/pingloop_open_ping_pong_frame.phpt index 2d62ad4960..6aec849125 100644 --- a/tests/swoole_websocket_server/pingloop_open_ping_pong_frame.phpt +++ b/tests/swoole_websocket_server/pingloop_open_ping_pong_frame.phpt @@ -15,6 +15,11 @@ $pm->parentFunc = function (int $pid) use ($pm) { for ($i = MAX_CONCURRENCY_MID; $i--;) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); + $cli->set([ + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $ret = $cli->upgrade('/'); Assert::assert($ret); $loop = 0; diff --git a/tests/swoole_websocket_server/pingpong.phpt b/tests/swoole_websocket_server/pingpong.phpt index 42a86b51a4..8d860d12cb 100644 --- a/tests/swoole_websocket_server/pingpong.phpt +++ b/tests/swoole_websocket_server/pingpong.phpt @@ -9,7 +9,12 @@ $pm = new ProcessManager; $pm->parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); - $cli->set(['timeout' => 5]); + $cli->set([ + 'timeout' => 5, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $ret = $cli->upgrade('/'); Assert::assert($ret); for ($i = 100; $i--;) { diff --git a/tests/swoole_websocket_server/pingpong_open_ping_pong_frame.phpt b/tests/swoole_websocket_server/pingpong_open_ping_pong_frame.phpt index 7aa4b157de..f39ae76513 100644 --- a/tests/swoole_websocket_server/pingpong_open_ping_pong_frame.phpt +++ b/tests/swoole_websocket_server/pingpong_open_ping_pong_frame.phpt @@ -9,7 +9,12 @@ $pm = new ProcessManager; $pm->parentFunc = function (int $pid) use ($pm) { go(function () use ($pm) { $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', $pm->getFreePort()); - $cli->set(['timeout' => 5]); + $cli->set([ + 'timeout' => 5, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_websocket_close_frame' => true, + ]); $ret = $cli->upgrade('/'); Assert::assert($ret); for ($i = 100; $i--;) { diff --git a/tests/swoole_websocket_server/send_close_frame.phpt b/tests/swoole_websocket_server/send_close_frame.phpt new file mode 100644 index 0000000000..8f53cf5a86 --- /dev/null +++ b/tests/swoole_websocket_server/send_close_frame.phpt @@ -0,0 +1,54 @@ +--TEST-- +swoole_websocket_server: send close frame will not close connection +--SKIPIF-- + +--FILE-- +parentFunc = function (int $pid) use ($pm) { + Co\run(function () use ($pm) { + $client = new Client('127.0.0.1', $pm->getFreePort()); + $ret = $client->upgrade('/'); + $client->push('aaa'); + $client->push('lalalala'); + }); + $pm->kill(); +}; + +$pm->childFunc = function () use ($pm) { + $server = new Server('127.0.0.1', $pm->getFreePort(), SWOOLE_BASE); + $server->set([ + 'worker_num' => 1, + 'package_max_length' => 100 * 1024 * 1024, + ]); + + $server->on('workerStart', function () use ($pm) { + $pm->wakeup(); + }); + + $server->on('message', function (Server $server, Frame $frame) use ($pm) { + if ($frame->data == 'aaa') { + $close = new CloseFrame(); + $close->opcode = SWOOLE_WEBSOCKET_OPCODE_CLOSE; + $close->code = SWOOLE_WEBSOCKET_CLOSE_NORMAL; + $close->reason = 'hahahhah'; + Assert::true($server->push($frame->fd, $close)); + } else { + var_dump($frame->data); + } + }); + + $server->start(); +}; +$pm->childFirst(); +$pm->run(); +?> +--EXPECT-- +string(8) "lalalala" diff --git a/thirdparty/boost/asm/combined.S b/thirdparty/boost/asm/combined.S index 35fd2098b8..99d2df0002 100644 --- a/thirdparty/boost/asm/combined.S +++ b/thirdparty/boost/asm/combined.S @@ -1,22 +1,16 @@ #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) - #if defined(__i386__) - #include "make_i386_sysv_elf_gas.S" - #include "jump_i386_sysv_elf_gas.S" - #elif defined(__x86_64__) + #if defined(__x86_64__) #include "make_x86_64_sysv_elf_gas.S" #include "jump_x86_64_sysv_elf_gas.S" - #elif defined(__ppc__) - #include "make_ppc32_sysv_elf_gas.S" - #include "jump_ppc32_sysv_elf_gas.S" #elif defined(__ppc64__) #include "make_ppc64_sysv_elf_gas.S" #include "jump_ppc64_sysv_elf_gas.S" - #elif defined(__arm__) - #include "make_arm_aapcs_elf_gas.S" - #include "jump_arm_aapcs_elf_gas.S" - #elif defined(__arm64__) + #elif defined(__arm64__) || defined(__aarch64__) #include "make_arm64_aapcs_elf_gas.S" #include "jump_arm64_aapcs_elf_gas.S" + #elif defined(__loongarch64) + #include "make_loongarch64_sysv_elf_gas.S" + #include "jump_loongarch64_sysv_elf_gas.S" #else #error "No arch's" #endif diff --git a/thirdparty/boost/asm/jump_arm64_aapcs_elf_gas.S b/thirdparty/boost/asm/jump_arm64_aapcs_elf_gas.S index 4780fb86fd..47282c18e9 100644 --- a/thirdparty/boost/asm/jump_arm64_aapcs_elf_gas.S +++ b/thirdparty/boost/asm/jump_arm64_aapcs_elf_gas.S @@ -1,5 +1,5 @@ /* - Copyright Edward Nevill 2015 + Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -51,7 +51,7 @@ * * *******************************************************/ -.cpu generic+fp+simd +.file "jump_arm64_aapcs_elf_gas.S" .text .align 2 .global swoole_jump_fcontext @@ -60,23 +60,12 @@ swoole_jump_fcontext: # prepare stack for GP + FPU sub sp, sp, #0xb0 -# Because gcc may save integer registers in fp registers across a -# function call we cannot skip saving the fp registers. -# -# Do not reinstate this test unless you fully understand what you -# are doing. -# -# # test if fpu env should be preserved -# cmp w3, #0 -# b.eq 1f - # save d8 - d15 stp d8, d9, [sp, #0x00] stp d10, d11, [sp, #0x10] stp d12, d13, [sp, #0x20] stp d14, d15, [sp, #0x30] -1: # save x19-x30 stp x19, x20, [sp, #0x40] stp x21, x22, [sp, #0x50] @@ -88,17 +77,11 @@ swoole_jump_fcontext: # save LR as PC str x30, [sp, #0xa0] - # store RSP (pointing to context-data) in first argument (x0). - # STR cannot have sp as a target register + # store RSP (pointing to context-data) in X0 mov x4, sp - str x4, [x0] - - # restore RSP (pointing to context-data) from A2 (x1) - mov sp, x1 -# # test if fpu env should be preserved -# cmp w3, #0 -# b.eq 2f + # restore RSP (pointing to context-data) from X1 + mov sp, x0 # load d8 - d15 ldp d8, d9, [sp, #0x00] @@ -106,7 +89,6 @@ swoole_jump_fcontext: ldp d12, d13, [sp, #0x20] ldp d14, d15, [sp, #0x30] -2: # load x19-x30 ldp x19, x20, [sp, #0x40] ldp x21, x22, [sp, #0x50] @@ -115,9 +97,10 @@ swoole_jump_fcontext: ldp x27, x28, [sp, #0x80] ldp x29, x30, [sp, #0x90] - # use third arg as return value after jump - # and as first arg in context function - mov x0, x2 + # return transfer_t from jump + # pass transfer_t as first arg in context function + # X0 == FCTX, X1 == DATA + mov x0, x4 # load pc ldr x4, [sp, #0xa0] @@ -127,7 +110,5 @@ swoole_jump_fcontext: ret x4 .size swoole_jump_fcontext,.-swoole_jump_fcontext -#ifndef __NetBSD__ # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/jump_arm64_aapcs_macho_gas.S b/thirdparty/boost/asm/jump_arm64_aapcs_macho_gas.S index 2cac127426..dc544e026e 100644 --- a/thirdparty/boost/asm/jump_arm64_aapcs_macho_gas.S +++ b/thirdparty/boost/asm/jump_arm64_aapcs_macho_gas.S @@ -1,3 +1,9 @@ +/* + Copyright Edward Nevill + Oliver Kowalke 2015 + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ /******************************************************* * * * ------------------------------------------------- * @@ -52,20 +58,12 @@ _swoole_jump_fcontext: ; prepare stack for GP + FPU sub sp, sp, #0xb0 -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - ; test if fpu env should be preserved - cmp w3, #0 - b.eq 1f - ; save d8 - d15 stp d8, d9, [sp, #0x00] stp d10, d11, [sp, #0x10] stp d12, d13, [sp, #0x20] stp d14, d15, [sp, #0x30] -1: -#endif - ; save x19-x30 stp x19, x20, [sp, #0x40] stp x21, x22, [sp, #0x50] @@ -77,18 +75,11 @@ _swoole_jump_fcontext: ; save LR as PC str lr, [sp, #0xa0] - ; store RSP (pointing to context-data) in first argument (x0). - ; STR cannot have sp as a target register + ; store RSP (pointing to context-data) in X0 mov x4, sp - str x4, [x0] - ; restore RSP (pointing to context-data) from A2 (x1) - mov sp, x1 - -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - ; test if fpu env should be preserved - cmp w3, #0 - b.eq 2f + ; restore RSP (pointing to context-data) from X1 + mov sp, x0 ; load d8 - d15 ldp d8, d9, [sp, #0x00] @@ -96,9 +87,6 @@ _swoole_jump_fcontext: ldp d12, d13, [sp, #0x20] ldp d14, d15, [sp, #0x30] -2: -#endif - ; load x19-x30 ldp x19, x20, [sp, #0x40] ldp x21, x22, [sp, #0x50] @@ -107,9 +95,10 @@ _swoole_jump_fcontext: ldp x27, x28, [sp, #0x80] ldp fp, lr, [sp, #0x90] - ; use third arg as return value after jump - ; and as first arg in context function - mov x0, x2 + ; return transfer_t from jump + ; pass transfer_t as first arg in context function + ; X0 == FCTX, X1 == DATA + mov x0, x4 ; load pc ldr x4, [sp, #0xa0] diff --git a/thirdparty/boost/asm/jump_arm_aapcs_elf_gas.S b/thirdparty/boost/asm/jump_arm_aapcs_elf_gas.S deleted file mode 100644 index ade82a2ada..0000000000 --- a/thirdparty/boost/asm/jump_arm_aapcs_elf_gas.S +++ /dev/null @@ -1,95 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * - * ------------------------------------------------- * - * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * - * ------------------------------------------------- * - * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * - * ------------------------------------------------- * - * | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | | * - * ------------------------------------------------- * - * | 0x60| 0x64| | * - * ------------------------------------------------- * - * | lr | pc | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl swoole_jump_fcontext -.align 2 -.type swoole_jump_fcontext,%function -swoole_jump_fcontext: - @ save LR as PC - push {lr} - @ save V1-V8,LR - push {v1-v8,lr} - - @ prepare stack for FPU - sub sp, sp, #64 - -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - @ test if fpu env should be preserved - cmp a4, #0 - beq 1f - - @ save S16-S31 - vstmia sp, {d8-d15} - -1: -#endif - - @ store RSP (pointing to context-data) in A1 - str sp, [a1] - - @ restore RSP (pointing to context-data) from A2 - mov sp, a2 - -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - @ test if fpu env should be preserved - cmp a4, #0 - beq 2f - - @ restore S16-S31 - vldmia sp, {d8-d15} -2: -#endif - - @ prepare stack for FPU - add sp, sp, #64 - - @ use third arg as return value after jump - @ and as first arg in context function - mov a1, a3 - - @ restore v1-V8,LR,PC - pop {v1-v8,lr,pc} -.size swoole_jump_fcontext,.-swoole_jump_fcontext - -#ifndef __NetBSD__ -@ Mark that we don't need executable stack. -.section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/jump_arm_aapcs_macho_gas.S b/thirdparty/boost/asm/jump_arm_aapcs_macho_gas.S deleted file mode 100644 index ef6e1c02f6..0000000000 --- a/thirdparty/boost/asm/jump_arm_aapcs_macho_gas.S +++ /dev/null @@ -1,103 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * - * ------------------------------------------------- * - * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * - * ------------------------------------------------- * - * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * - * ------------------------------------------------- * - * | sjlj| v1 | v2 | v3 | v4 | v5 | v6 | v7 | - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | | * - * ------------------------------------------------- * - * | 0x60| 0x64| 0x68| | * - * ------------------------------------------------- * - * | v8 | lr | pc | | * - * ------------------------------------------------- * - * * - * *****************************************************/ - -.text -.globl _swoole_jump_fcontext -.align 2 -_swoole_jump_fcontext: - @ save LR as PC - push {lr} - @ save V1-V8,LR - push {v1-v8,lr} - - @ locate TLS to save/restore SjLj handler - mrc p15, 0, v2, c13, c0, #3 - bic v2, v2, #3 - - @ load TLS[__PTK_LIBC_DYLD_Unwind_SjLj_Key] - ldr v1, [v2, #72] - @ save SjLj handler - push {v1} - - @ prepare stack for FPU - sub sp, sp, #64 - -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - @ test if fpu env should be preserved - cmp a4, #0 - beq 1f - - @ save S16-S31 - vstmia sp, {d8-d15} - -1: -#endif - - @ store RSP (pointing to context-data) in A1 - str sp, [a1] - - @ restore RSP (pointing to context-data) from A2 - mov sp, a2 - -#if (defined(__VFP_FP__) && !defined(__SOFTFP__)) - @ test if fpu env should be preserved - cmp a4, #0 - beq 2f - - @ restore S16-S31 - vldmia sp, {d8-d15} - -2: -#endif - - @ prepare stack for FPU - add sp, sp, #64 - - @ restore SjLj handler - pop {v1} - @ store SjLj handler in TLS - str v1, [v2, #72] - - @ use third arg as return value after jump - @ and as first arg in context function - mov a1, a3 - - @ restore v1-V8,LR,PC - pop {v1-v8,lr,pc} diff --git a/thirdparty/boost/asm/jump_arm_aapcs_pe_armasm.asm b/thirdparty/boost/asm/jump_arm_aapcs_pe_armasm.asm deleted file mode 100644 index ce5109e7d3..0000000000 --- a/thirdparty/boost/asm/jump_arm_aapcs_pe_armasm.asm +++ /dev/null @@ -1,112 +0,0 @@ -;/* -; Copyright Oliver Kowalke 2009. -; Distributed under the Boost Software License, Version 1.0. -; (See accompanying file LICENSE_1_0.txt or copy at -; http://www.boost.org/LICENSE_1_0.txt) -;*/ - -; ******************************************************* -; * * -; * ------------------------------------------------- * -; * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * -; * ------------------------------------------------- * -; * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * -; * ------------------------------------------------- * -; * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * -; * ------------------------------------------------- * -; * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * -; * ------------------------------------------------- * -; * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * -; * ------------------------------------------------- * -; * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * -; * ------------------------------------------------- * -; * |deall|limit| base| v1 | v2 | v3 | v4 | v5 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 24 | 25 | 26 | 27 | 28 | | * -; * ------------------------------------------------- * -; * | 0x60| 0x64| 0x68| 0x6c| 0x70| | * -; * ------------------------------------------------- * -; * | v6 | v7 | v8 | lr | pc | | * -; * ------------------------------------------------- * -; * * -; ******************************************************* - - AREA |.text|, CODE - ALIGN 4 - EXPORT swoole_jump_fcontext - -swoole_jump_fcontext PROC - @ save LR as PC - push {lr} - @ save V1-V8,LR - push {v1-v8,lr} - - @ prepare stack for FPU - sub sp, sp, #0x4c - - @ test if fpu env should be preserved - cmp a4, #0 - beq 1f - - @ save S16-S31 - vstmia sp, {d8-d15} - -1: - ; load TIB to save/restore thread size and limit. - ; we do not need preserve CPU flag and can use it's arg register - mrc p15, #0, v1, c13, c0, #2 - - ; save current stack base - ldr a5, [v1,#0x04] - str a5, [sp,#0x48] - ; save current stack limit - ldr a5, [v1,#0x08] - str a5, [sp,#0x44] - ; save current deallocation stack - ldr a5, [v1,#0xe0c] - str a5, [sp,#0x40] - - @ store RSP (pointing to context-data) in A1 - str sp, [a1] - - @ restore RSP (pointing to context-data) from A2 - mov sp, a2 - - @ test if fpu env should be preserved - cmp a4, #0 - beq 2f - - @ restore S16-S31 - vldmia sp, {d8-d15} - -2: - ; restore stack base - ldr a5, [sp,#0x48] - str a5, [v1,#0x04] - ; restore stack limit - ldr a5, [sp,#0x44] - str a5, [v1,#0x08] - ; restore deallocation stack - ldr a5, [sp,#0x40] - str a5, [v1,#0xe0c] - - @ prepare stack for FPU - add sp, sp, #0x4c - - ; use third arg as return value after jump - ; and as first arg in context function - mov a1, a3 - - @ restore v1-V8,LR - pop {v1-v8,lr} - pop {pc} - - ENDP - END diff --git a/thirdparty/boost/asm/jump_combined_sysv_macho_gas.S b/thirdparty/boost/asm/jump_combined_sysv_macho_gas.S index 34a32f785f..773e8345df 100644 --- a/thirdparty/boost/asm/jump_combined_sysv_macho_gas.S +++ b/thirdparty/boost/asm/jump_combined_sysv_macho_gas.S @@ -7,16 +7,10 @@ // Stub file for universal binary -#if defined(__i386__) - #include "jump_i386_sysv_macho_gas.S" -#elif defined(__x86_64__) +#if defined(__x86_64__) #include "jump_x86_64_sysv_macho_gas.S" -#elif defined(__ppc__) - #include "jump_ppc32_sysv_macho_gas.S" #elif defined(__ppc64__) #include "jump_ppc64_sysv_macho_gas.S" -#elif defined(__arm__) - #include "jump_arm_aapcs_macho_gas.S" #elif defined(__arm64__) #include "jump_arm64_aapcs_macho_gas.S" #else diff --git a/thirdparty/boost/asm/jump_i386_sysv_elf_gas.S b/thirdparty/boost/asm/jump_i386_sysv_elf_gas.S deleted file mode 100644 index f691f8c024..0000000000 --- a/thirdparty/boost/asm/jump_i386_sysv_elf_gas.S +++ /dev/null @@ -1,90 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/**************************************************************************************** - * * - * ---------------------------------------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ---------------------------------------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * - * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| EDI | ESI | EBX | EBP | EIP | EXIT | * - * ---------------------------------------------------------------------------------- * - * * - ****************************************************************************************/ - -.text -.globl swoole_jump_fcontext -.align 2 -.type swoole_jump_fcontext,@function -swoole_jump_fcontext: - /* fourth arg of swoole_jump_fcontext() == flag indicating preserving FPU */ - movl 0x10(%esp), %ecx - - pushl %ebp /* save EBP */ - pushl %ebx /* save EBX */ - pushl %esi /* save ESI */ - pushl %edi /* save EDI */ - - /* prepare stack for FPU */ - leal -0x8(%esp), %esp - - /* test for flag preserve_fpu */ - test %ecx, %ecx - je 1f - - /* save MMX control- and status-word */ - stmxcsr (%esp) - /* save x87 control-word */ - fnstcw 0x4(%esp) - -1: - /* first arg of swoole_jump_fcontext() == context jumping from */ - movl 0x1c(%esp), %eax - - /* store ESP (pointing to context-data) in EAX */ - movl %esp, (%eax) - - /* second arg of swoole_jump_fcontext() == context jumping to */ - movl 0x20(%esp), %edx - - /* third arg of swoole_jump_fcontext() == value to be returned after jump */ - movl 0x24(%esp), %eax - - /* restore ESP (pointing to context-data) from EDX */ - movl %edx, %esp - - /* test for flag preserve_fpu */ - test %ecx, %ecx - je 2f - - /* restore MMX control- and status-word */ - ldmxcsr (%esp) - /* restore x87 control-word */ - fldcw 0x4(%esp) -2: - /* prepare stack for FPU */ - leal 0x8(%esp), %esp - - popl %edi /* restore EDI */ - popl %esi /* restore ESI */ - popl %ebx /* restore EBX */ - popl %ebp /* restore EBP */ - - /* restore return-address */ - popl %edx - - /* use value in EAX as return-value after jump */ - /* use value in EAX as first arg in context function */ - movl %eax, 0x4(%esp) - - /* indirect jump to context */ - jmp *%edx -.size swoole_jump_fcontext,.-swoole_jump_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/jump_i386_sysv_macho_gas.S b/thirdparty/boost/asm/jump_i386_sysv_macho_gas.S deleted file mode 100644 index e9f09c246a..0000000000 --- a/thirdparty/boost/asm/jump_i386_sysv_macho_gas.S +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/**************************************************************************************** - * * - * ---------------------------------------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ---------------------------------------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * - * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| EDI | ESI | EBX | EBP | EIP | EXIT | * - * ---------------------------------------------------------------------------------- * - * * - ****************************************************************************************/ - -.text -.globl _swoole_jump_fcontext -.align 2 -_swoole_jump_fcontext: - /* fourth arg of swoole_jump_fcontext() == flag indicating preserving FPU */ - movl 0x10(%esp), %ecx - - pushl %ebp /* save EBP */ - pushl %ebx /* save EBX */ - pushl %esi /* save ESI */ - pushl %edi /* save EDI */ - - /* prepare stack for FPU */ - leal -0x8(%esp), %esp - - /* test for flag preserve_fpu */ - test %ecx, %ecx - je 1f - - /* save MMX control- and status-word */ - stmxcsr (%esp) - /* save x87 control-word */ - fnstcw 0x4(%esp) - -1: - /* first arg of swoole_jump_fcontext() == context jumping from */ - movl 0x1c(%esp), %eax - - /* store ESP (pointing to context-data) in EAX */ - movl %esp, (%eax) - - /* second arg of swoole_jump_fcontext() == context jumping to */ - movl 0x20(%esp), %edx - - /* third arg of swoole_jump_fcontext() == value to be returned after jump */ - movl 0x24(%esp), %eax - - /* restore ESP (pointing to context-data) from EDX */ - movl %edx, %esp - - /* test for flag preserve_fpu */ - test %ecx, %ecx - je 2f - - /* restore MMX control- and status-word */ - ldmxcsr (%esp) - /* restore x87 control-word */ - fldcw 0x4(%esp) -2: - /* prepare stack for FPU */ - leal 0x8(%esp), %esp - - popl %edi /* restore EDI */ - popl %esi /* restore ESI */ - popl %ebx /* restore EBX */ - popl %ebp /* restore EBP */ - - /* restore return-address */ - popl %edx - - /* use value in EAX as return-value after jump */ - /* use value in EAX as first arg in context function */ - movl %eax, 0x4(%esp) - - /* indirect jump to context */ - jmp *%edx diff --git a/thirdparty/boost/asm/jump_i386_x86_64_sysv_macho_gas.S b/thirdparty/boost/asm/jump_i386_x86_64_sysv_macho_gas.S deleted file mode 100644 index 959ddac16f..0000000000 --- a/thirdparty/boost/asm/jump_i386_x86_64_sysv_macho_gas.S +++ /dev/null @@ -1,16 +0,0 @@ -/* - Copyright Sergue E. Leontiev 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -// Stub file for universal binary - -#if defined(__i386__) - #include "jump_i386_sysv_macho_gas.S" -#elif defined(__x86_64__) - #include "jump_x86_64_sysv_macho_gas.S" -#else - #error "No arch's" -#endif diff --git a/thirdparty/boost/asm/jump_loongarch64_sysv_elf_gas.S b/thirdparty/boost/asm/jump_loongarch64_sysv_elf_gas.S new file mode 100644 index 0000000000..89a08821ca --- /dev/null +++ b/thirdparty/boost/asm/jump_loongarch64_sysv_elf_gas.S @@ -0,0 +1,121 @@ +/******************************************************* + * * + * ------------------------------------------------- * + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * + * ------------------------------------------------- * + * | 0 | 8 | 16 | 24 | * + * ------------------------------------------------- * + * | FS0 | FS1 | FS2 | FS3 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * + * ------------------------------------------------- * + * | 32 | 40 | 48 | 56 | * + * ------------------------------------------------- * + * | FS4 | FS5 | FS6 | FS7 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * + * ------------------------------------------------- * + * | 64 | 72 | 80 | 88 | * + * ------------------------------------------------- * + * | S0 | S1 | S2 | S3 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * + * ------------------------------------------------- * + * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * + * ------------------------------------------------- * + * | S4 | S5 | S6 | S7 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * + * ------------------------------------------------- * + * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * + * ------------------------------------------------- * + * | S8 | FP | RA | PC | * + * ------------------------------------------------- * + * * + * *****************************************************/ + +.file "jump_loongarch64_sysv_elf_gas.S" +.text +.globl swoole_jump_fcontext +.align 2 +.type swoole_jump_fcontext,@function +swoole_jump_fcontext: + # reserve space on stack + addi.d $sp, $sp, -160 + + # save fs0 - fs7 + fst.d $fs0, $sp, 0 + fst.d $fs1, $sp, 8 + fst.d $fs2, $sp, 16 + fst.d $fs3, $sp, 24 + fst.d $fs4, $sp, 32 + fst.d $fs5, $sp, 40 + fst.d $fs6, $sp, 48 + fst.d $fs7, $sp, 56 + + # save s0 - s8, fp, ra + st.d $s0, $sp, 64 + st.d $s1, $sp, 72 + st.d $s2, $sp, 80 + st.d $s3, $sp, 88 + st.d $s4, $sp, 96 + st.d $s5, $sp, 104 + st.d $s6, $sp, 112 + st.d $s7, $sp, 120 + st.d $s8, $sp, 128 + st.d $fp, $sp, 136 + st.d $ra, $sp, 144 + + # save RA as PC + st.d $ra, $sp, 152 + + # store SP (pointing to context-data) in A2 + move $a2, $sp + + # restore SP (pointing to context-data) from A0 + move $sp, $a0 + + # load fs0 - fs7 + fld.d $fs0, $sp, 0 + fld.d $fs1, $sp, 8 + fld.d $fs2, $sp, 16 + fld.d $fs3, $sp, 24 + fld.d $fs4, $sp, 32 + fld.d $fs5, $sp, 40 + fld.d $fs6, $sp, 48 + fld.d $fs7, $sp, 56 + + #load s0 - s7 + ld.d $s0, $sp, 64 + ld.d $s1, $sp, 72 + ld.d $s2, $sp, 80 + ld.d $s3, $sp, 88 + ld.d $s4, $sp, 96 + ld.d $s5, $sp, 104 + ld.d $s6, $sp, 112 + ld.d $s7, $sp, 120 + ld.d $s8, $sp, 128 + ld.d $fp, $sp, 136 + ld.d $ra, $sp, 144 + + # return transfer_t from jump + # pass transfer_t as first arg in context function + # a0 == FCTX, a1 == DATA + move $a0, $a2 + + # load PC + ld.d $a2, $sp, 152 + + # restore stack + addi.d $sp, $sp, 160 + + # jump to context + jr $a2 +.size swoole_jump_fcontext, .-swoole_jump_fcontext + +/* Mark that we don't need executable stack. */ +.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/jump_mips32_o32_elf_gas.S b/thirdparty/boost/asm/jump_mips32_o32_elf_gas.S deleted file mode 100644 index f5a08722bd..0000000000 --- a/thirdparty/boost/asm/jump_mips32_o32_elf_gas.S +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F20 | F22 | F24 | F26 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F28 | F30 | S0 | S1 | S2 | S3 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | | * - * ------------------------------------------------- * - * | S4 | S5 | S6 | S7 | FP | RA | PC | | * - * ------------------------------------------------- * - * * - * *****************************************************/ - -.text -.globl swoole_jump_fcontext -.align 2 -.type swoole_jump_fcontext,@function -.ent swoole_jump_fcontext -swoole_jump_fcontext: - # reserve space on stack - addiu $sp, $sp, -92 - - sw $s0, 48($sp) # save S0 - sw $s1, 52($sp) # save S1 - sw $s2, 56($sp) # save S2 - sw $s3, 60($sp) # save S3 - sw $s4, 64($sp) # save S4 - sw $s5, 68($sp) # save S5 - sw $s6, 72($sp) # save S6 - sw $s7, 76($sp) # save S7 - sw $fp, 80($sp) # save FP - sw $ra, 84($sp) # save RA - sw $ra, 88($sp) # save RA as PC - -#if defined(__mips_hard_float) - # test if fpu env should be preserved - beqz $a3, 1f - - s.d $f20, ($sp) # save F20 - s.d $f22, 8($sp) # save F22 - s.d $f24, 16($sp) # save F24 - s.d $f26, 24($sp) # save F26 - s.d $f28, 32($sp) # save F28 - s.d $f30, 40($sp) # save F30 - -1: -#endif - - # store SP (pointing to context-data) in A0 - sw $sp, ($a0) - - # restore SP (pointing to context-data) from A1 - move $sp, $a1 - - -#if defined(__mips_hard_float) - # test if fpu env should be preserved - beqz $a3, 2f - - l.d $f20, ($sp) # restore F20 - l.d $f22, 8($sp) # restore F22 - l.d $f24, 16($sp) # restore F24 - l.d $f26, 24($sp) # restore F26 - l.d $f28, 32($sp) # restore F28 - l.d $f30, 40($sp) # restore F30 - -2: -#endif - - lw $s0, 48($sp) # restore S0 - lw $s1, 52($sp) # restore S1 - lw $s2, 56($sp) # restore S2 - lw $s3, 60($sp) # restore S3 - lw $s4, 64($sp) # restore S4 - lw $s5, 68($sp) # restore S5 - lw $s6, 72($sp) # restore S6 - lw $s7, 76($sp) # restore S7 - lw $fp, 80($sp) # restore FP - lw $ra, 84($sp) # restore RA - - # load PC - lw $t9, 88($sp) - - # adjust stack - addiu $sp, $sp, 92 - - # use third arg as return value after jump - move $v0, $a2 - # use third arg as first arg in context function - move $a0, $a2 - - # jump to context - jr $t9 -.end swoole_jump_fcontext -.size swoole_jump_fcontext, .-swoole_jump_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/jump_mips64_n64_elf_gas.S b/thirdparty/boost/asm/jump_mips64_n64_elf_gas.S index 2c21bdaa97..edff6ec050 100644 --- a/thirdparty/boost/asm/jump_mips64_n64_elf_gas.S +++ b/thirdparty/boost/asm/jump_mips64_n64_elf_gas.S @@ -5,10 +5,6 @@ http://www.boost.org/LICENSE_1_0.txt) */ -/* - "backported" version of original jump_mips64_n64_elf_gas.S -*/ - /******************************************************* * * * ------------------------------------------------- * @@ -49,6 +45,7 @@ * * * *****************************************************/ +.file "jump_mips64_n64_elf_gas.S" .text .globl swoole_jump_fcontext .align 3 @@ -71,9 +68,6 @@ swoole_jump_fcontext: sd $ra, 152($sp) # save RA as PC #if defined(__mips_hard_float) - # test if fpu env should be preserved - beqz $a3, 1f - s.d $f24, 0($sp) # save F24 s.d $f25, 8($sp) # save F25 s.d $f26, 16($sp) # save F26 @@ -82,20 +76,15 @@ swoole_jump_fcontext: s.d $f29, 40($sp) # save F29 s.d $f30, 48($sp) # save F30 s.d $f31, 56($sp) # save F31 -1: #endif - # store SP (pointing to context-data) in A0 - sd $sp, ($a0) + # store SP (pointing to old context-data) in v0 as return + move $v0, $sp - # restore SP (pointing to context-data) from A1 - move $sp, $a1 + # get SP (pointing to new context-data) from a0 param + move $sp, $a0 #if defined(__mips_hard_float) - # test if fpu env should be preserved - beqz $a3, 2f - - l.d $f24, 0($sp) # restore F24 l.d $f25, 8($sp) # restore F25 l.d $f26, 16($sp) # restore F26 @@ -104,7 +93,6 @@ swoole_jump_fcontext: l.d $f29, 40($sp) # restore F29 l.d $f30, 48($sp) # restore F30 l.d $f31, 56($sp) # restore F31 -2: #endif ld $s0, 64($sp) # restore S0 @@ -124,10 +112,8 @@ swoole_jump_fcontext: # adjust stack daddiu $sp, $sp, 160 - # use third arg as return value after jump - move $v0, $a2 - # use third arg as first arg in context function - move $a0, $a2 + move $a0, $v0 # move old sp from v0 to a0 as param + move $v1, $a1 # move *data from a1 to v1 as return # jump to context jr $t9 @@ -136,4 +122,3 @@ swoole_jump_fcontext: /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits - diff --git a/thirdparty/boost/asm/jump_ppc32_ppc64_sysv_macho_gas.S b/thirdparty/boost/asm/jump_ppc32_ppc64_sysv_macho_gas.S deleted file mode 100644 index f175e31233..0000000000 --- a/thirdparty/boost/asm/jump_ppc32_ppc64_sysv_macho_gas.S +++ /dev/null @@ -1,16 +0,0 @@ -/* - Copyright Sergue E. Leontiev 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -// Stub file for universal binary - -#if defined(__ppc__) - #include "jump_ppc32_sysv_macho_gas.S" -#elif defined(__ppc64__) - #include "jump_ppc64_sysv_macho_gas.S" -#else - #error "No arch's" -#endif diff --git a/thirdparty/boost/asm/jump_ppc32_sysv_elf_gas.S b/thirdparty/boost/asm/jump_ppc32_sysv_elf_gas.S deleted file mode 100644 index b2f8b9e72f..0000000000 --- a/thirdparty/boost/asm/jump_ppc32_sysv_elf_gas.S +++ /dev/null @@ -1,210 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * - * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * - * ------------------------------------------------- * - * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * - * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * - * ------------------------------------------------- * - * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * - * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | R14 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * - * ------------------------------------------------- * - * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * - * ------------------------------------------------- * - * | R15 | R16 | R17 | R18 | R19 | R20 | R21 | R22 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * - * ------------------------------------------------- * - * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * - * ------------------------------------------------- * - * | R23 | R24 | R25 | R26 | R27 | R28 | R29 | R30 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 56 | 57 | 58 | 59 | | * - * ------------------------------------------------- * - * | 224 | 228 | 232 | 236 | | * - * ------------------------------------------------- * - * | R31 | CR | LR | PC | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl swoole_jump_fcontext -.align 2 -.type swoole_jump_fcontext,@function -swoole_jump_fcontext: - # reserve space on stack - subi %r1, %r1, 240 - - stw %r13, 152(%r1) # save R13 - stw %r14, 156(%r1) # save R14 - stw %r15, 160(%r1) # save R15 - stw %r16, 164(%r1) # save R16 - stw %r17, 168(%r1) # save R17 - stw %r18, 172(%r1) # save R18 - stw %r19, 176(%r1) # save R19 - stw %r20, 180(%r1) # save R20 - stw %r21, 184(%r1) # save R21 - stw %r22, 188(%r1) # save R22 - stw %r23, 192(%r1) # save R23 - stw %r24, 196(%r1) # save R24 - stw %r25, 200(%r1) # save R25 - stw %r26, 204(%r1) # save R26 - stw %r27, 208(%r1) # save R27 - stw %r28, 212(%r1) # save R28 - stw %r29, 216(%r1) # save R29 - stw %r30, 220(%r1) # save R30 - stw %r31, 224(%r1) # save R31 - - # save CR - mfcr %r0 - stw %r0, 228(%r1) - # save LR - mflr %r0 - stw %r0, 232(%r1) - # save LR as PC - stw %r0, 236(%r1) - - # test if fpu env should be preserved - cmpwi cr7, %r6, 0 - beq cr7, 1f - - stfd %f14, 0(%r1) # save F14 - stfd %f15, 8(%r1) # save F15 - stfd %f16, 16(%r1) # save F16 - stfd %f17, 24(%r1) # save F17 - stfd %f18, 32(%r1) # save F18 - stfd %f19, 40(%r1) # save F19 - stfd %f20, 48(%r1) # save F20 - stfd %f21, 56(%r1) # save F21 - stfd %f22, 64(%r1) # save F22 - stfd %f23, 72(%r1) # save F23 - stfd %f24, 80(%r1) # save F24 - stfd %f25, 88(%r1) # save F25 - stfd %f26, 96(%r1) # save F26 - stfd %f27, 104(%r1) # save F27 - stfd %f28, 112(%r1) # save F28 - stfd %f29, 120(%r1) # save F29 - stfd %f30, 128(%r1) # save F30 - stfd %f31, 136(%r1) # save F31 - mffs %f0 # load FPSCR - stfd %f0, 144(%r1) # save FPSCR - -1: - # store RSP (pointing to context-data) in R3 - stw %r1, 0(%r3) - - # restore RSP (pointing to context-data) from R4 - mr %r1, %r4 - - # test if fpu env should be preserved - cmpwi cr7, %r6, 0 - beq cr7, 2f - - lfd %f14, 0(%r1) # restore F14 - lfd %f15, 8(%r1) # restore F15 - lfd %f16, 16(%r1) # restore F16 - lfd %f17, 24(%r1) # restore F17 - lfd %f18, 32(%r1) # restore F18 - lfd %f19, 40(%r1) # restore F19 - lfd %f20, 48(%r1) # restore F20 - lfd %f21, 56(%r1) # restore F21 - lfd %f22, 64(%r1) # restore F22 - lfd %f23, 72(%r1) # restore F23 - lfd %f24, 80(%r1) # restore F24 - lfd %f25, 88(%r1) # restore F25 - lfd %f26, 96(%r1) # restore F26 - lfd %f27, 104(%r1) # restore F27 - lfd %f28, 112(%r1) # restore F28 - lfd %f29, 120(%r1) # restore F29 - lfd %f30, 128(%r1) # restore F30 - lfd %f31, 136(%r1) # restore F31 - lfd %f0, 144(%r1) # load FPSCR - mtfsf 0xff, %f0 # restore FPSCR - -2: - lwz %r13, 152(%r1) # restore R13 - lwz %r14, 156(%r1) # restore R14 - lwz %r15, 160(%r1) # restore R15 - lwz %r16, 164(%r1) # restore R16 - lwz %r17, 168(%r1) # restore R17 - lwz %r18, 172(%r1) # restore R18 - lwz %r19, 176(%r1) # restore R19 - lwz %r20, 180(%r1) # restore R20 - lwz %r21, 184(%r1) # restore R21 - lwz %r22, 188(%r1) # restore R22 - lwz %r23, 192(%r1) # restore R23 - lwz %r24, 196(%r1) # restore R24 - lwz %r25, 200(%r1) # restore R25 - lwz %r26, 204(%r1) # restore R26 - lwz %r27, 208(%r1) # restore R27 - lwz %r28, 212(%r1) # restore R28 - lwz %r29, 216(%r1) # restore R29 - lwz %r30, 220(%r1) # restore R30 - lwz %r31, 224(%r1) # restore R31 - - # restore CR - lwz %r0, 228(%r1) - mtcr %r0 - # restore LR - lwz %r0, 232(%r1) - mtlr %r0 - - # load PC - lwz %r0, 236(%r1) - # restore CTR - mtctr %r0 - - # adjust stack - addi %r1, %r1, 240 - - # use third arg as return value after jump - # use third arg as first arg in context function - mr %r3, %r5 - - # jump to context - bctr -.size swoole_jump_fcontext, .-swoole_jump_fcontext - -#ifndef __NetBSD__ -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/jump_ppc32_sysv_macho_gas.S b/thirdparty/boost/asm/jump_ppc32_sysv_macho_gas.S deleted file mode 100644 index ea6704af1f..0000000000 --- a/thirdparty/boost/asm/jump_ppc32_sysv_macho_gas.S +++ /dev/null @@ -1,203 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * - * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * - * ------------------------------------------------- * - * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * - * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * - * ------------------------------------------------- * - * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * - * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | R14 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * - * ------------------------------------------------- * - * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * - * ------------------------------------------------- * - * | R15 | R16 | R17 | R18 | R19 | R20 | R21 | R22 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * - * ------------------------------------------------- * - * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * - * ------------------------------------------------- * - * | R23 | R24 | R25 | R26 | R27 | R28 | R29 | R30 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 56 | 57 | 58 | 59 | | * - * ------------------------------------------------- * - * | 224 | 228 | 232 | 236 | | * - * ------------------------------------------------- * - * | R31 | CR | LR | PC | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl _swoole_jump_fcontext -.align 2 -_swoole_jump_fcontext: - ; reserve space on stack - subi r1, r1, 240 - - stw r13, 152(r1) ; save R13 - stw r14, 156(r1) ; save R14 - stw r15, 160(r1) ; save R15 - stw r16, 164(r1) ; save R16 - stw r17, 168(r1) ; save R17 - stw r18, 172(r1) ; save R18 - stw r19, 176(r1) ; save R19 - stw r20, 180(r1) ; save R20 - stw r21, 184(r1) ; save R21 - stw r22, 188(r1) ; save R22 - stw r23, 192(r1) ; save R23 - stw r24, 196(r1) ; save R24 - stw r25, 200(r1) ; save R25 - stw r26, 204(r1) ; save R26 - stw r27, 208(r1) ; save R27 - stw r28, 212(r1) ; save R28 - stw r29, 216(r1) ; save R29 - stw r30, 220(r1) ; save R30 - stw r31, 224(r1) ; save R31 - - ; save CR - mfcr r0 - stw r0, 228(r1) - ; save LR - mflr r0 - stw r0, 232(r1) - ; save LR as PC - stw r0, 236(r1) - - ; test if fpu env should be preserved - cmpwi cr7, r6, 0 - beq cr7, l1 - - stfd f14, 0(r1) ; save F14 - stfd f15, 8(r1) ; save F15 - stfd f16, 16(r1) ; save F16 - stfd f17, 24(r1) ; save F17 - stfd f18, 32(r1) ; save F18 - stfd f19, 40(r1) ; save F19 - stfd f20, 48(r1) ; save F20 - stfd f21, 56(r1) ; save F21 - stfd f22, 64(r1) ; save F22 - stfd f23, 72(r1) ; save F23 - stfd f24, 80(r1) ; save F24 - stfd f25, 88(r1) ; save F25 - stfd f26, 96(r1) ; save F26 - stfd f27, 104(r1) ; save F27 - stfd f28, 112(r1) ; save F28 - stfd f29, 120(r1) ; save F29 - stfd f30, 128(r1) ; save F30 - stfd f31, 136(r1) ; save F31 - mffs f0 ; load FPSCR - stfd f0, 144(r1) ; save FPSCR - -l1: - ; store RSP (pointing to context-data) in R3 - stw r1, 0(r3) - - ; restore RSP (pointing to context-data) from R4 - mr r1, r4 - - ; test if fpu env should be preserved - cmpwi cr7, r6, 0 - beq cr7, l2 - - lfd f14, 0(r1) ; restore F14 - lfd f15, 8(r1) ; restore F15 - lfd f16, 16(r1) ; restore F16 - lfd f17, 24(r1) ; restore F17 - lfd f18, 32(r1) ; restore F18 - lfd f19, 40(r1) ; restore F19 - lfd f20, 48(r1) ; restore F20 - lfd f21, 56(r1) ; restore F21 - lfd f22, 64(r1) ; restore F22 - lfd f23, 72(r1) ; restore F23 - lfd f24, 80(r1) ; restore F24 - lfd f25, 88(r1) ; restore F25 - lfd f26, 96(r1) ; restore F26 - lfd f27, 104(r1) ; restore F27 - lfd f28, 112(r1) ; restore F28 - lfd f29, 120(r1) ; restore F29 - lfd f30, 128(r1) ; restore F30 - lfd f31, 136(r1) ; restore F31 - lfd f0, 144(r1) ; load FPSCR - mtfsf 0xff, f0 ; restore FPSCR - -l2: - lwz r13, 152(r1) ; restore R13 - lwz r14, 156(r1) ; restore R14 - lwz r15, 160(r1) ; restore R15 - lwz r16, 164(r1) ; restore R16 - lwz r17, 168(r1) ; restore R17 - lwz r18, 172(r1) ; restore R18 - lwz r19, 176(r1) ; restore R19 - lwz r20, 180(r1) ; restore R20 - lwz r21, 184(r1) ; restore R21 - lwz r22, 188(r1) ; restore R22 - lwz r23, 192(r1) ; restore R23 - lwz r24, 196(r1) ; restore R24 - lwz r25, 200(r1) ; restore R25 - lwz r26, 204(r1) ; restore R26 - lwz r27, 208(r1) ; restore R27 - lwz r28, 212(r1) ; restore R28 - lwz r29, 216(r1) ; restore R29 - lwz r30, 220(r1) ; restore R30 - lwz r31, 224(r1) ; restore R31 - - ; restore CR - lwz r0, 228(r1) - mtcr r0 - ; restore LR - lwz r0, 232(r1) - mtlr r0 - - ; load PC - lwz r0, 236(r1) - ; restore CTR - mtctr r0 - - ; adjust stack - addi r1, r1, 240 - - ; use third arg as return value after jump - ; use third arg as first arg in context function - mr r3, r5 - - ; jump to context - bctr diff --git a/thirdparty/boost/asm/jump_ppc32_sysv_xcoff_gas.S b/thirdparty/boost/asm/jump_ppc32_sysv_xcoff_gas.S deleted file mode 100644 index a437633fe4..0000000000 --- a/thirdparty/boost/asm/jump_ppc32_sysv_xcoff_gas.S +++ /dev/null @@ -1,138 +0,0 @@ -.globl .swoole_jump_fcontext -.globl swoole_jump_fcontext[DS] -.align 2 -.csect swoole_jump_fcontext[DS] -swoole_jump_fcontext: - .long .swoole_jump_fcontext -.swoole_jump_fcontext: - # reserve space on stack - subi 1, 1, 240 - - stw 13, 152(1) # save R13 - stw 14, 156(1) # save R14 - stw 15, 160(1) # save R15 - stw 16, 164(1) # save R16 - stw 17, 168(1) # save R17 - stw 18, 172(1) # save R18 - stw 19, 176(1) # save R19 - stw 20, 180(1) # save R20 - stw 21, 184(1) # save R21 - stw 22, 188(1) # save R22 - stw 23, 192(1) # save R23 - stw 24, 196(1) # save R24 - stw 25, 200(1) # save R25 - stw 26, 204(1) # save R26 - stw 27, 208(1) # save R27 - stw 28, 212(1) # save R28 - stw 29, 216(1) # save R29 - stw 30, 220(1) # save R30 - stw 31, 224(1) # save R31 - - # save CR - mfcr 0 - stw 0, 228(1) - # save LR - mflr 0 - stw 0, 232(1) - # save LR as PC - stw 0, 236(1) - - # test if fpu env should be preserved - cmpwi 7, 6, 0 - beq 7, label1 - - stfd 14, 0(1) # save F14 - stfd 15, 8(1) # save F15 - stfd 16, 16(1) # save F16 - stfd 17, 24(1) # save F17 - stfd 18, 32(1) # save F18 - stfd 19, 40(1) # save F19 - stfd 20, 48(1) # save F20 - stfd 21, 56(1) # save F21 - stfd 22, 64(1) # save F22 - stfd 23, 72(1) # save F23 - stfd 24, 80(1) # save F24 - stfd 25, 88(1) # save F25 - stfd 26, 96(1) # save F26 - stfd 27, 104(1) # save F27 - stfd 28, 112(1) # save F28 - stfd 29, 120(1) # save F29 - stfd 30, 128(1) # save F30 - stfd 31, 136(1) # save F31 - mffs 0 # load FPSCR - stfd 0, 144(1) # save FPSCR - -label1: - # store RSP (pointing to context-data) in R3 - stw 1, 0(3) - - # restore RSP (pointing to context-data) from R4 - mr 1, 4 - - # test if fpu env should be preserved - cmpwi 7, 6, 0 - beq 7, label2 - - lfd 14, 0(1) # restore F14 - lfd 15, 8(1) # restore F15 - lfd 16, 16(1) # restore F16 - lfd 17, 24(1) # restore F17 - lfd 18, 32(1) # restore F18 - lfd 19, 40(1) # restore F19 - lfd 20, 48(1) # restore F20 - lfd 21, 56(1) # restore F21 - lfd 22, 64(1) # restore F22 - lfd 23, 72(1) # restore F23 - lfd 24, 80(1) # restore F24 - lfd 25, 88(1) # restore F25 - lfd 26, 96(1) # restore F26 - lfd 27, 104(1) # restore F27 - lfd 28, 112(1) # restore F28 - lfd 29, 120(1) # restore F29 - lfd 30, 128(1) # restore F30 - lfd 31, 136(1) # restore F31 - lfd 0, 144(1) # load FPSCR - mtfsf 0xff, 0 # restore FPSCR - -label2: - lwz 13, 152(1) # restore R13 - lwz 14, 156(1) # restore R14 - lwz 15, 160(1) # restore R15 - lwz 16, 164(1) # restore R16 - lwz 17, 168(1) # restore R17 - lwz 18, 172(1) # restore R18 - lwz 19, 176(1) # restore R19 - lwz 20, 180(1) # restore R20 - lwz 21, 184(1) # restore R21 - lwz 22, 188(1) # restore R22 - lwz 23, 192(1) # restore R23 - lwz 24, 196(1) # restore R24 - lwz 25, 200(1) # restore R25 - lwz 26, 204(1) # restore R26 - lwz 27, 208(1) # restore R27 - lwz 28, 212(1) # restore R28 - lwz 29, 216(1) # restore R29 - lwz 30, 220(1) # restore R30 - lwz 31, 224(1) # restore R31 - - # restore CR - lwz 0, 228(1) - mtcr 0 - # restore LR - lwz 0, 232(1) - mtlr 0 - - # load PC - lwz 0, 236(1) - # restore CTR - mtctr 0 - - # adjust stack - addi 1, 1, 240 - - # use third arg as return value after jump - # use third arg as first arg in context function - mr 3, 5 - - # jump to context - bctr diff --git a/thirdparty/boost/asm/jump_ppc64_sysv_elf_gas.S b/thirdparty/boost/asm/jump_ppc64_sysv_elf_gas.S index 5cc97550b6..a90ffbe168 100644 --- a/thirdparty/boost/asm/jump_ppc64_sysv_elf_gas.S +++ b/thirdparty/boost/asm/jump_ppc64_sysv_elf_gas.S @@ -12,82 +12,61 @@ * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * + * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * + * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * + * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * + * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * - * | F30 | F31 | fpscr | TOC | * + * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * - * | R14 | R15 | R16 | R17 | * + * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * - * | R18 | R19 | R20 | R21 | * + * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * - * | R22 | R23 | R24 | R25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | * - * ------------------------------------------------- * - * | 256 | 260 | 264 | 268 | 272 | 276 | 280 | 284 | * - * ------------------------------------------------- * - * | R26 | R27 | R28 | R29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | * - * ------------------------------------------------- * - * | 288 | 292 | 296 | 300 | 304 | 308 | 312 | 316 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | R30 | R31 | CR | LR | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 80 | 81 | | * - * ------------------------------------------------- * - * | 320 | 324 | | * - * ------------------------------------------------- * - * | PC | | * + * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ +.file "jump_ppc64_sysv_elf_gas.S" .globl swoole_jump_fcontext #if _CALL_ELF == 2 .text @@ -118,143 +97,118 @@ swoole_jump_fcontext: # endif #endif # reserve space on stack - subi %r1, %r1, 328 + subi %r1, %r1, 184 #if _CALL_ELF != 2 - std %r2, 152(%r1) # save TOC + std %r2, 0(%r1) # save TOC +#endif + std %r14, 8(%r1) # save R14 + std %r15, 16(%r1) # save R15 + std %r16, 24(%r1) # save R16 + std %r17, 32(%r1) # save R17 + std %r18, 40(%r1) # save R18 + std %r19, 48(%r1) # save R19 + std %r20, 56(%r1) # save R20 + std %r21, 64(%r1) # save R21 + std %r22, 72(%r1) # save R22 + std %r23, 80(%r1) # save R23 + std %r24, 88(%r1) # save R24 + std %r25, 96(%r1) # save R25 + std %r26, 104(%r1) # save R26 + std %r27, 112(%r1) # save R27 + std %r28, 120(%r1) # save R28 + std %r29, 128(%r1) # save R29 + std %r30, 136(%r1) # save R30 + std %r31, 144(%r1) # save R31 +#if _CALL_ELF != 2 + std %r3, 152(%r1) # save hidden #endif - std %r14, 160(%r1) # save R14 - std %r15, 168(%r1) # save R15 - std %r16, 176(%r1) # save R16 - std %r17, 184(%r1) # save R17 - std %r18, 192(%r1) # save R18 - std %r19, 200(%r1) # save R19 - std %r20, 208(%r1) # save R20 - std %r21, 216(%r1) # save R21 - std %r22, 224(%r1) # save R22 - std %r23, 232(%r1) # save R23 - std %r24, 240(%r1) # save R24 - std %r25, 248(%r1) # save R25 - std %r26, 256(%r1) # save R26 - std %r27, 264(%r1) # save R27 - std %r28, 272(%r1) # save R28 - std %r29, 280(%r1) # save R29 - std %r30, 288(%r1) # save R30 - std %r31, 296(%r1) # save R31 # save CR mfcr %r0 - std %r0, 304(%r1) + std %r0, 160(%r1) # save LR mflr %r0 - std %r0, 312(%r1) + std %r0, 168(%r1) # save LR as PC - std %r0, 320(%r1) - - # test if fpu env should be preserved - cmpwi cr7, %r6, 0 - beq cr7, 1f - - stfd %f14, 0(%r1) # save F14 - stfd %f15, 8(%r1) # save F15 - stfd %f16, 16(%r1) # save F16 - stfd %f17, 24(%r1) # save F17 - stfd %f18, 32(%r1) # save F18 - stfd %f19, 40(%r1) # save F19 - stfd %f20, 48(%r1) # save F20 - stfd %f21, 56(%r1) # save F21 - stfd %f22, 64(%r1) # save F22 - stfd %f23, 72(%r1) # save F23 - stfd %f24, 80(%r1) # save F24 - stfd %f25, 88(%r1) # save F25 - stfd %f26, 96(%r1) # save F26 - stfd %f27, 104(%r1) # save F27 - stfd %f28, 112(%r1) # save F28 - stfd %f29, 120(%r1) # save F29 - stfd %f30, 128(%r1) # save F30 - stfd %f31, 136(%r1) # save F31 - mffs %f0 # load FPSCR - stfd %f0, 144(%r1) # save FPSCR + std %r0, 176(%r1) -1: - # store RSP (pointing to context-data) in R3 - std %r1, 0(%r3) + # store RSP (pointing to context-data) in R6 + mr %r6, %r1 +#if _CALL_ELF == 2 + # restore RSP (pointing to context-data) from R3 + mr %r1, %r3 +#else # restore RSP (pointing to context-data) from R4 mr %r1, %r4 - # test if fpu env should be preserved - cmpwi cr7, %r6, 0 - beq cr7, 2f - - lfd %f14, 0(%r1) # restore F14 - lfd %f15, 8(%r1) # restore F15 - lfd %f16, 16(%r1) # restore F16 - lfd %f17, 24(%r1) # restore F17 - lfd %f18, 32(%r1) # restore F18 - lfd %f19, 40(%r1) # restore F19 - lfd %f20, 48(%r1) # restore F20 - lfd %f21, 56(%r1) # restore F21 - lfd %f22, 64(%r1) # restore F22 - lfd %f23, 72(%r1) # restore F23 - lfd %f24, 80(%r1) # restore F24 - lfd %f25, 88(%r1) # restore F25 - lfd %f26, 96(%r1) # restore F26 - lfd %f27, 104(%r1) # restore F27 - lfd %f28, 112(%r1) # restore F28 - lfd %f29, 120(%r1) # restore F29 - lfd %f30, 128(%r1) # restore F30 - lfd %f31, 136(%r1) # restore F31 - lfd %f0, 144(%r1) # load FPSCR - mtfsf 0xff, %f0 # restore FPSCR - -2: + ld %r2, 0(%r1) # restore TOC +#endif + ld %r14, 8(%r1) # restore R14 + ld %r15, 16(%r1) # restore R15 + ld %r16, 24(%r1) # restore R16 + ld %r17, 32(%r1) # restore R17 + ld %r18, 40(%r1) # restore R18 + ld %r19, 48(%r1) # restore R19 + ld %r20, 56(%r1) # restore R20 + ld %r21, 64(%r1) # restore R21 + ld %r22, 72(%r1) # restore R22 + ld %r23, 80(%r1) # restore R23 + ld %r24, 88(%r1) # restore R24 + ld %r25, 96(%r1) # restore R25 + ld %r26, 104(%r1) # restore R26 + ld %r27, 112(%r1) # restore R27 + ld %r28, 120(%r1) # restore R28 + ld %r29, 128(%r1) # restore R29 + ld %r30, 136(%r1) # restore R30 + ld %r31, 144(%r1) # restore R31 #if _CALL_ELF != 2 - ld %r2, 152(%r1) # restore TOC + ld %r3, 152(%r1) # restore hidden #endif - ld %r14, 160(%r1) # restore R14 - ld %r15, 168(%r1) # restore R15 - ld %r16, 176(%r1) # restore R16 - ld %r17, 184(%r1) # restore R17 - ld %r18, 192(%r1) # restore R18 - ld %r19, 200(%r1) # restore R19 - ld %r20, 208(%r1) # restore R20 - ld %r21, 216(%r1) # restore R21 - ld %r22, 224(%r1) # restore R22 - ld %r23, 232(%r1) # restore R23 - ld %r24, 240(%r1) # restore R24 - ld %r25, 248(%r1) # restore R25 - ld %r26, 256(%r1) # restore R26 - ld %r27, 264(%r1) # restore R27 - ld %r28, 272(%r1) # restore R28 - ld %r29, 280(%r1) # restore R29 - ld %r30, 288(%r1) # restore R30 - ld %r31, 296(%r1) # restore R31 # restore CR - ld %r0, 304(%r1) + ld %r0, 160(%r1) mtcr %r0 # restore LR - ld %r0, 312(%r1) + ld %r0, 168(%r1) mtlr %r0 # load PC - ld %r12, 320(%r1) + ld %r12, 176(%r1) # restore CTR mtctr %r12 # adjust stack - addi %r1, %r1, 328 + addi %r1, %r1, 184 - # use third arg as return value after jump - # use third arg as first arg in context function - mr %r3, %r5 +#if _CALL_ELF == 2 + # copy transfer_t into transfer_fn arg registers + mr %r3, %r6 + # arg pointer already in %r4 # jump to context bctr -#if _CALL_ELF == 2 .size swoole_jump_fcontext, .-swoole_jump_fcontext #else + # zero in r3 indicates first jump to context-function + cmpdi %r3, 0 + beq use_entry_arg + + # return transfer_t + std %r6, 0(%r3) + std %r5, 8(%r3) + + # jump to context + bctr + +use_entry_arg: + # copy transfer_t into transfer_fn arg registers + mr %r3, %r6 + mr %r4, %r5 + + # jump to context + bctr # ifdef _CALL_LINUX .size .swoole_jump_fcontext, .-.L.swoole_jump_fcontext # else @@ -263,7 +217,5 @@ swoole_jump_fcontext: #endif -#ifndef __NetBSD__ /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/jump_ppc64_sysv_macho_gas.S b/thirdparty/boost/asm/jump_ppc64_sysv_macho_gas.S index d8d9aa4f51..abea794062 100644 --- a/thirdparty/boost/asm/jump_ppc64_sysv_macho_gas.S +++ b/thirdparty/boost/asm/jump_ppc64_sysv_macho_gas.S @@ -12,215 +12,153 @@ * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * + * | R13 | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * + * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * + * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * + * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | * + * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * - * | R14 | R15 | R16 | R17 | * + * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * - * | R18 | R19 | R20 | R21 | * + * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * - * | R22 | R23 | R24 | R25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | * - * ------------------------------------------------- * - * | 256 | 260 | 264 | 268 | 272 | 276 | 280 | 284 | * - * ------------------------------------------------- * - * | R26 | R27 | R28 | R29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | * - * ------------------------------------------------- * - * | 288 | 292 | 296 | 300 | 304 | 308 | 312 | 316 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | R30 | R31 | CR | LR | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 80 | 81 | | * - * ------------------------------------------------- * - * | 320 | 324 | | * - * ------------------------------------------------- * - * | PC | | * + * | FCTX | DATA | | | * * ------------------------------------------------- * * * *******************************************************/ .text .align 2 -.globl swoole_jump_fcontext +.globl _swoole_jump_fcontext _swoole_jump_fcontext: ; reserve space on stack - subi r1, r1, 328 - - std r13, 152(r1) ; save R13 - std r14, 160(r1) ; save R14 - std r15, 168(r1) ; save R15 - std r16, 176(r1) ; save R16 - std r17, 184(r1) ; save R17 - std r18, 192(r1) ; save R18 - std r19, 200(r1) ; save R19 - std r20, 208(r1) ; save R20 - std r21, 216(r1) ; save R21 - std r22, 224(r1) ; save R22 - std r23, 232(r1) ; save R23 - std r24, 240(r1) ; save R24 - std r25, 248(r1) ; save R25 - std r26, 256(r1) ; save R26 - std r27, 264(r1) ; save R27 - std r28, 272(r1) ; save R28 - std r29, 280(r1) ; save R29 - std r30, 288(r1) ; save R30 - std r31, 296(r1) ; save R31 + subi r1, r1, 184 + + std r14, 8(r1) ; save R14 + std r15, 16(r1) ; save R15 + std r16, 24(r1) ; save R16 + std r17, 32(r1) ; save R17 + std r18, 40(r1) ; save R18 + std r19, 48(r1) ; save R19 + std r20, 56(r1) ; save R20 + std r21, 64(r1) ; save R21 + std r22, 72(r1) ; save R22 + std r23, 80(r1) ; save R23 + std r24, 88(r1) ; save R24 + std r25, 96(r1) ; save R25 + std r26, 104(r1) ; save R26 + std r27, 112(r1) ; save R27 + std r28, 120(r1) ; save R28 + std r29, 128(r1) ; save R29 + std r30, 136(r1) ; save R30 + std r31, 144(r1) ; save R31 + std r3, 152(r1) ; save hidden ; save CR mfcr r0 - std r0, 304(r1) + std r0, 160(r1) ; save LR mflr r0 - std r0, 312(r1) + std r0, 168(r1) ; save LR as PC - std r0, 320(r1) - - ; test if fpu env should be preserved - cmpwi cr7, r6, 0 - beq cr7, l1 - - stfd f14, 0(r1) ; save F14 - stfd f15, 8(r1) ; save F15 - stfd f16, 16(r1) ; save F16 - stfd f17, 24(r1) ; save F17 - stfd f18, 32(r1) ; save F18 - stfd f19, 40(r1) ; save F19 - stfd f20, 48(r1) ; save F20 - stfd f21, 56(r1) ; save F21 - stfd f22, 64(r1) ; save F22 - stfd f23, 72(r1) ; save F23 - stfd f24, 80(r1) ; save F24 - stfd f25, 88(r1) ; save F25 - stfd f26, 96(r1) ; save F26 - stfd f27, 104(r1) ; save F27 - stfd f28, 112(r1) ; save F28 - stfd f29, 120(r1) ; save F29 - stfd f30, 128(r1) ; save F30 - stfd f31, 136(r1) ; save F31 - mffs f0 ; load FPSCR - stfd f0, 144(r1) ; save FPSCR - -l1: - ; store RSP (pointing to context-data) in R3 - stw r1, 0(r3) + std r0, 176(r1) + + ; store RSP (pointing to context-data) in R6 + mr r6, r1 ; restore RSP (pointing to context-data) from R4 mr r1, r4 - ; test if fpu env should be preserved - cmpwi cr7, r6, 0 - beq cr7, l2 - - lfd f14, 0(r1) ; restore F14 - lfd f15, 8(r1) ; restore F15 - lfd f16, 16(r1) ; restore F16 - lfd f17, 24(r1) ; restore F17 - lfd f18, 32(r1) ; restore F18 - lfd f19, 40(r1) ; restore F19 - lfd f20, 48(r1) ; restore F20 - lfd f21, 56(r1) ; restore F21 - lfd f22, 64(r1) ; restore F22 - lfd f23, 72(r1) ; restore F23 - lfd f24, 80(r1) ; restore F24 - lfd f25, 88(r1) ; restore F25 - lfd f26, 96(r1) ; restore F26 - lfd f27, 104(r1) ; restore F27 - lfd f28, 112(r1) ; restore F28 - lfd f29, 120(r1) ; restore F29 - lfd f30, 128(r1) ; restore F30 - lfd f31, 136(r1) ; restore F31 - lfd f0, 144(r1) ; load FPSCR - mtfsf 0xff, f0 ; restore FPSCR - -2: - ld r13, 152(r1) ; restore R13 - ld r14, 160(r1) ; restore R14 - ld r15, 168(r1) ; restore R15 - ld r16, 176(r1) ; restore R16 - ld r17, 184(r1) ; restore R17 - ld r18, 192(r1) ; restore R18 - ld r19, 200(r1) ; restore R19 - ld r20, 208(r1) ; restore R20 - ld r21, 216(r1) ; restore R21 - ld r22, 224(r1) ; restore R22 - ld r23, 232(r1) ; restore R23 - ld r24, 240(r1) ; restore R24 - ld r25, 248(r1) ; restore R25 - ld r26, 256(r1) ; restore R26 - ld r27, 264(r1) ; restore R27 - ld r28, 272(r1) ; restore R28 - ld r29, 280(r1) ; restore R29 - ld r30, 288(r1) ; restore R30 - ld r31, 296(r1) ; restore R31 + ld r14, 8(r1) ; restore R14 + ld r15, 16(r1) ; restore R15 + ld r16, 24(r1) ; restore R16 + ld r17, 32(r1) ; restore R17 + ld r18, 40(r1) ; restore R18 + ld r19, 48(r1) ; restore R19 + ld r20, 56(r1) ; restore R20 + ld r21, 64(r1) ; restore R21 + ld r22, 72(r1) ; restore R22 + ld r23, 80(r1) ; restore R23 + ld r24, 88(r1) ; restore R24 + ld r25, 96(r1) ; restore R25 + ld r26, 104(r1) ; restore R26 + ld r27, 112(r1) ; restore R27 + ld r28, 120(r1) ; restore R28 + ld r29, 128(r1) ; restore R29 + ld r30, 136(r1) ; restore R30 + ld r31, 144(r1) ; restore R31 + ld r3, 152(r1) ; restore hidden ; restore CR - ld r0, 304(r1) + ld r0, 160(r1) mtcr r0 ; restore LR - ld r0, 312(r1) + ld r0, 168(r1) mtlr r0 ; load PC - ld r0, 320(r1) + ld r12, 176(r1) ; restore CTR - mtctr r0 + mtctr r12 ; adjust stack - addi r1, r1, 328 + addi r1, r1, 184 + + ; zero in r3 indicates first jump to context-function + cmpdi r3, 0 + beq use_entry_arg + + ; return transfer_t + std r6, 0(r3) + std r5, 8(r3) + + ; jump to context + bctr - ; use third arg as return value after jump - ; use third arg as first arg in context function - mr r3, r5 +use_entry_arg: + ; copy transfer_t into transfer_fn arg registers + mr r3, r6 + mr r4, r5 ; jump to context bctr diff --git a/thirdparty/boost/asm/jump_ppc64_sysv_xcoff_gas.S b/thirdparty/boost/asm/jump_ppc64_sysv_xcoff_gas.S index e00720b0a5..a125f681b5 100644 --- a/thirdparty/boost/asm/jump_ppc64_sysv_xcoff_gas.S +++ b/thirdparty/boost/asm/jump_ppc64_sysv_xcoff_gas.S @@ -1,134 +1,173 @@ -.align 2 -.globl .swoole_jump_fcontext + +/* + Copyright Oliver Kowalke 2009. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +/******************************************************* + * * + * ------------------------------------------------- * + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * + * ------------------------------------------------- * + * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * + * ------------------------------------------------- * + * | TOC | R14 | R15 | R16 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * + * ------------------------------------------------- * + * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * + * ------------------------------------------------- * + * | R17 | R18 | R19 | R20 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * + * ------------------------------------------------- * + * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * + * ------------------------------------------------- * + * | R21 | R22 | R23 | R24 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * + * ------------------------------------------------- * + * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * + * ------------------------------------------------- * + * | R25 | R26 | R27 | R28 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * + * ------------------------------------------------- * + * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * + * ------------------------------------------------- * + * | R29 | R30 | R31 | hidden | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * + * ------------------------------------------------- * + * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * + * ------------------------------------------------- * + * | CR | LR | PC | back-chain| * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * + * ------------------------------------------------- * + * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * + * ------------------------------------------------- * + * | cr saved | lr saved | compiler | linker | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * + * ------------------------------------------------- * + * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * + * ------------------------------------------------- * + * | TOC saved | FCTX | DATA | | * + * ------------------------------------------------- * + * * + *******************************************************/ + + .file "jump_ppc64_sysv_xcoff_gas.S" + .toc + .csect .text[PR], 5 + .align 2 + .globl swoole_jump_fcontext[DS] + .globl .swoole_jump_fcontext + .csect swoole_jump_fcontext[DS], 3 +swoole_jump_fcontext: + .llong .swoole_jump_fcontext[PR], TOC[tc0], 0 + .csect .text[PR], 5 .swoole_jump_fcontext: # reserve space on stack - subi 1, 1, 328 - - std 13, 152(1) # save R13 - std 14, 160(1) # save R14 - std 15, 168(1) # save R15 - std 16, 176(1) # save R16 - std 17, 184(1) # save R17 - std 18, 192(1) # save R18 - std 19, 200(1) # save R19 - std 20, 208(1) # save R20 - std 21, 216(1) # save R21 - std 22, 224(1) # save R22 - std 23, 232(1) # save R23 - std 24, 240(1) # save R24 - std 25, 248(1) # save R25 - std 26, 256(1) # save R26 - std 27, 264(1) # save R27 - std 28, 272(1) # save R28 - std 29, 280(1) # save R29 - std 30, 288(1) # save R30 - std 31, 296(1) # save R31 + subi 1, 1, 184 + + std 2, 0(1) # save TOC + std 14, 8(1) # save R14 + std 15, 16(1) # save R15 + std 16, 24(1) # save R16 + std 17, 32(1) # save R17 + std 18, 40(1) # save R18 + std 19, 48(1) # save R19 + std 20, 56(1) # save R20 + std 21, 64(1) # save R21 + std 22, 72(1) # save R22 + std 23, 80(1) # save R23 + std 24, 88(1) # save R24 + std 25, 96(1) # save R25 + std 26, 104(1) # save R26 + std 27, 112(1) # save R27 + std 28, 120(1) # save R28 + std 29, 128(1) # save R29 + std 30, 136(1) # save R30 + std 31, 144(1) # save R31 + std 3, 152(1) # save hidden # save CR mfcr 0 - std 0, 304(1) + std 0, 160(1) # save LR mflr 0 - std 0, 312(1) + std 0, 168(1) # save LR as PC - std 0, 320(1) - - # test if fpu env should be preserved - cmpwi 7, 6, 0 - beq 7, label1 - - stfd 14, 0(1) # save F14 - stfd 15, 8(1) # save F15 - stfd 16, 16(1) # save F16 - stfd 17, 24(1) # save F17 - stfd 18, 32(1) # save F18 - stfd 19, 40(1) # save F19 - stfd 20, 48(1) # save F20 - stfd 21, 56(1) # save F21 - stfd 22, 64(1) # save F22 - stfd 23, 72(1) # save F23 - stfd 24, 80(1) # save F24 - stfd 25, 88(1) # save F25 - stfd 26, 96(1) # save F26 - stfd 27, 104(1) # save F27 - stfd 28, 112(1) # save F28 - stfd 29, 120(1) # save F29 - stfd 30, 128(1) # save F30 - stfd 31, 136(1) # save F31 - mffs 0 # load FPSCR - stfd 0, 144(1) # save FPSCR - -label1: - # store RSP (pointing to context-data) in R3 - stw 1, 0(3) + std 0, 176(1) + + # store RSP (pointing to context-data) in R6 + mr 6, 1 # restore RSP (pointing to context-data) from R4 mr 1, 4 - # test if fpu env should be preserved - cmpwi 7, 6, 0 - beq 7, label2 - - lfd 14, 0(1) # restore F14 - lfd 15, 8(1) # restore F15 - lfd 16, 16(1) # restore F16 - lfd 17, 24(1) # restore F17 - lfd 18, 32(1) # restore F18 - lfd 19, 40(1) # restore F19 - lfd 20, 48(1) # restore F20 - lfd 21, 56(1) # restore F21 - lfd 22, 64(1) # restore F22 - lfd 23, 72(1) # restore F23 - lfd 24, 80(1) # restore F24 - lfd 25, 88(1) # restore F25 - lfd 26, 96(1) # restore F26 - lfd 27, 104(1) # restore F27 - lfd 28, 112(1) # restore F28 - lfd 29, 120(1) # restore F29 - lfd 30, 128(1) # restore F30 - lfd 31, 136(1) # restore F31 - lfd 0, 144(1) # load FPSCR - mtfsf 0xff, 0 # restore FPSCR - -label2: - ld 13, 152(1) # restore R13 - ld 14, 160(1) # restore R14 - ld 15, 168(1) # restore R15 - ld 16, 176(1) # restore R16 - ld 17, 184(1) # restore R17 - ld 18, 192(1) # restore R18 - ld 19, 200(1) # restore R19 - ld 20, 208(1) # restore R20 - ld 21, 216(1) # restore R21 - ld 22, 224(1) # restore R22 - ld 23, 232(1) # restore R23 - ld 24, 240(1) # restore R24 - ld 25, 248(1) # restore R25 - ld 26, 256(1) # restore R26 - ld 27, 264(1) # restore R27 - ld 28, 272(1) # restore R28 - ld 29, 280(1) # restore R29 - ld 30, 288(1) # restore R30 - ld 31, 296(1) # restore R31 + ld 2, 0(1) # restore TOC + ld 14, 8(1) # restore R14 + ld 15, 16(1) # restore R15 + ld 16, 24(1) # restore R16 + ld 17, 32(1) # restore R17 + ld 18, 40(1) # restore R18 + ld 19, 48(1) # restore R19 + ld 20, 56(1) # restore R20 + ld 21, 64(1) # restore R21 + ld 22, 72(1) # restore R22 + ld 23, 80(1) # restore R23 + ld 24, 88(1) # restore R24 + ld 25, 96(1) # restore R25 + ld 26, 104(1) # restore R26 + ld 27, 112(1) # restore R27 + ld 28, 120(1) # restore R28 + ld 29, 128(1) # restore R29 + ld 30, 136(1) # restore R30 + ld 31, 144(1) # restore R31 + ld 3, 152(1) # restore hidden # restore CR - ld 0, 304(1) + ld 0, 160(1) mtcr 0 # restore LR - ld 0, 312(1) + ld 0, 168(1) mtlr 0 # load PC - ld 0, 320(1) + ld 0, 176(1) # restore CTR mtctr 0 # adjust stack - addi 1, 1, 328 + addi 1, 1, 184 + + # zero in r3 indicates first jump to context-function + cmpdi 3, 0 + beq use_entry_arg + + # return transfer_t + std 6, 0(3) + std 5, 8(3) + + # jump to context + bctr - # use third arg as return value after jump - # use third arg as first arg in context function - mr 3, 5 +use_entry_arg: + # copy transfer_t into transfer_fn arg registers + mr 3, 6 + mr 4, 5 # jump to context bctr diff --git a/thirdparty/boost/asm/jump_riscv64_sysv_elf_gas.S b/thirdparty/boost/asm/jump_riscv64_sysv_elf_gas.S index 23f66d60b2..a2f9a2f3bb 100644 --- a/thirdparty/boost/asm/jump_riscv64_sysv_elf_gas.S +++ b/thirdparty/boost/asm/jump_riscv64_sysv_elf_gas.S @@ -66,8 +66,6 @@ swoole_jump_fcontext: # prepare stack for GP + FPU addi sp, sp, -0xd0 - beqz a3, .L1 - # save fs0 - fs11 fsd fs0, 0x00(sp) fsd fs1, 0x08(sp) @@ -81,7 +79,6 @@ swoole_jump_fcontext: fsd fs9, 0x48(sp) fsd fs10, 0x50(sp) fsd fs11, 0x58(sp) -.L1: # save s0-s11, ra sd s0, 0x60(sp) @@ -101,13 +98,12 @@ swoole_jump_fcontext: # save RA as PC sd ra, 0xc8(sp) - # store SP (pointing to context-data) in A0 - sd sp, (a0) + # store SP (pointing to context-data) in A2 + mv a2, sp - # restore SP (pointing to context-data) from A1 - mv sp, a1 + # restore SP (pointing to context-data) from A0 + mv sp, a0 - beqz a3, .L2 # load fs0 - fs11 fld fs0, 0x00(sp) fld fs1, 0x08(sp) @@ -121,7 +117,6 @@ swoole_jump_fcontext: fld fs9, 0x48(sp) fld fs10, 0x50(sp) fld fs11, 0x58(sp) -.L2: # load s0-s11,ra ld s0, 0x60(sp) @@ -138,7 +133,9 @@ swoole_jump_fcontext: ld s11, 0xb8(sp) ld ra, 0xc0(sp) - # use A2 as return value + # return transfer_t from jump + # pass transfer_t as first arg in context function + # a0 == FCTX, a1 == DATA mv a0, a2 # load pc diff --git a/thirdparty/boost/asm/jump_sparc_sysv_elf_gas.S b/thirdparty/boost/asm/jump_sparc_sysv_elf_gas.S deleted file mode 100644 index b86c0ca571..0000000000 --- a/thirdparty/boost/asm/jump_sparc_sysv_elf_gas.S +++ /dev/null @@ -1,135 +0,0 @@ -/* - Copyright Martin Husemann 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************************* - * * - * ------------------------------------------------------------- * - * | Offset (in 4 or 8 byte units) | Content | * - * ------------------------------------------------------------- * - * | 0 | %sp | * - * ------------------------------------------------------------- * - * | 1 | %pc | * - * ------------------------------------------------------------- * - * | 2 | %i7 (return address) | * - * ------------------------------------------------------------- * - * | 3 | %g1 | * - * ------------------------------------------------------------- * - * | 4 | %g2 | * - * ------------------------------------------------------------- * - * | 5 | %g3 | * - * ------------------------------------------------------------- * - * | 6 | %g6 | * - * ------------------------------------------------------------- * - * | 7 | %g7 | * - * ------------------------------------------------------------- * - * The local and in registers are stored on the stack. * - *******************************************************************/ - -#define OFF(N) (4*(N)) -#define CCFSZ 96 -#define FC_SZ 176 -#define FC_stK 168 // offsetof(fcontext_t, fc_stack) -#define FC_FPU 0 // offsetof(fcontext_t, fc_fp) -#define FC_FSR 128 // offsetof(fcontext_t, fc_fp.fp_fsr) -#define FC_GREG 136 // offsetof(fcontext_t, fc_greg) -#define BLOCK_SIZE 8 -#ifdef __NetBSD__ -#define FLUSHW t 0x83; nop // T_FLUSHWIN -#endif - -.text -.globl swoole_jump_fcontext -.align 4 -.type swoole_jump_fcontext,@function -// intptr_t -// swoole_jump_fcontext( fcontext_t * ofc, fcontext_t const* nfc, intptr_t vp, -// bool preserve_fpu = true); -swoole_jump_fcontext: - // %o0 = pointer to old fcontext, save current state here - // %o1 = new context to jump to - // %o2 = new return value in context %o0 - // %o3 = preserve fpu registers - // Save current state in %o0 fcontext, then activate %o1. - // If %o3, include fpu registers. - - FLUSHW // make sure all shadow registers are up to date in the current stack - - // save current state to fcontext_t at %o0 - st %sp, [%o0 + FC_GREG + OFF(0)] // current stack pointer - add %o7, 8, %o4 // calculate next instruction past call - st %o4, [%o0 + FC_GREG + OFF(1)] // and store it as %pc in save context - st %o7, [%o0 + FC_GREG + OFF(2)] - st %g1, [%o0 + FC_GREG + OFF(3)] - st %g2, [%o0 + FC_GREG + OFF(4)] - st %g3, [%o0 + FC_GREG + OFF(5)] - st %g6, [%o0 + FC_GREG + OFF(6)] - st %g7, [%o0 + FC_GREG + OFF(7)] - - // do we need to handle fpu? - cmp %o3, 0 - bz Lno_fpu - nop - - add %o0, FC_FPU, %o5 - std %f0, [%o5] - std %f2, [%o5+0x08] - std %f4, [%o5+0x10] - std %f6, [%o5+0x18] - std %f8, [%o5+0x20] - std %f10, [%o5+0x28] - std %f12, [%o5+0x30] - std %f14, [%o5+0x38] - st %fsr, [%o0+FC_FSR] - - add %o1, FC_FPU, %o5 - ldd [%o5], %f0 - ldd [%o5+0x08], %f2 - ldd [%o5+0x10], %f4 - ldd [%o5+0x18], %f6 - ldd [%o5+0x20], %f8 - ldd [%o5+0x28], %f10 - ldd [%o5+0x30], %f12 - ldd [%o5+0x38], %f14 - ld [%o1+FC_FSR], %fsr - -Lno_fpu: - // load new state from %o1 - ld [%o1 + FC_GREG + OFF(1)], %o4 - ld [%o1 + FC_GREG + OFF(2)], %o7 - ld [%o1 + FC_GREG + OFF(3)], %g1 - ld [%o1 + FC_GREG + OFF(4)], %g2 - ld [%o1 + FC_GREG + OFF(5)], %g3 - ld [%o1 + FC_GREG + OFF(6)], %g6 - ld [%o1 + FC_GREG + OFF(7)], %g7 - // switch to new stack - ld [%o1 + FC_GREG + OFF(0)], %sp - // and now reload from this stack the shadow regist bank contents - ld [%sp + OFF(0)], %l0 - ld [%sp + OFF(1)], %l1 - ld [%sp + OFF(2)], %l2 - ld [%sp + OFF(3)], %l3 - ld [%sp + OFF(4)], %l4 - ld [%sp + OFF(5)], %l5 - ld [%sp + OFF(6)], %l6 - ld [%sp + OFF(7)], %l7 - ld [%sp + OFF(8)], %i0 - ld [%sp + OFF(9)], %i1 - ld [%sp + OFF(10)], %i2 - ld [%sp + OFF(11)], %i3 - ld [%sp + OFF(12)], %i4 - ld [%sp + OFF(13)], %i5 - ld [%sp + OFF(14)], %i6 - ld [%sp + OFF(15)], %i7 - - // finally continue execution in new context - jmp %o4 - mov %o2, %o0 // return arg as result - -.size swoole_jump_fcontext,.-swoole_jump_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/jump_x86_64_sysv_elf_gas.S b/thirdparty/boost/asm/jump_x86_64_sysv_elf_gas.S index 64193d5a5f..7b80132b67 100644 --- a/thirdparty/boost/asm/jump_x86_64_sysv_elf_gas.S +++ b/thirdparty/boost/asm/jump_x86_64_sysv_elf_gas.S @@ -12,95 +12,131 @@ * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | * + * | fc_mxcsr|fc_x87_cw| guard | R12 | R13 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * - * | R15 | RBX | RBP | RIP | * + * | R14 | R15 | RBX | RBP | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * - * | 16 | 17 | | * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ---------------------------------------------------------------------------------- * * | 0x40 | 0x44 | | * * ---------------------------------------------------------------------------------- * - * | EXIT | | * + * | RIP | | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ -#ifdef __CET__ -#include -#else -#define _CET_ENDBR -#endif +# if defined __CET__ +# include +# define SWOOLE_SHSTK_ENABLED (__CET__ & 0x2) +# define SWOOLE_CONTEXT_SHADOW_STACK (SWOOLE_SHSTK_ENABLED && SHADOW_STACK_SYSCALL) +# else +# define _CET_ENDBR +# endif +.file "jump_x86_64_sysv_elf_gas.S" .text .globl swoole_jump_fcontext .type swoole_jump_fcontext,@function .align 16 swoole_jump_fcontext: _CET_ENDBR - pushq %rbp /* save RBP */ - pushq %rbx /* save RBX */ - pushq %r15 /* save R15 */ - pushq %r14 /* save R14 */ - pushq %r13 /* save R13 */ - pushq %r12 /* save R12 */ - - /* prepare stack for FPU */ - leaq -0x8(%rsp), %rsp + leaq -0x40(%rsp), %rsp /* prepare stack */ - /* test for flag preserve_fpu */ - cmp $0, %rcx - je 1f +#if !defined(SWOOLE_USE_TSX) + stmxcsr (%rsp) /* save MMX control- and status-word */ + fnstcw 0x4(%rsp) /* save x87 control-word */ +#endif - /* save MMX control- and status-word */ - stmxcsr (%rsp) - /* save x87 control-word */ - fnstcw 0x4(%rsp) +#if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) + movq %fs:0x28, %rcx /* read stack guard from TLS record */ + movq %rcx, 0x8(%rsp) /* save stack guard */ +#endif -1: - /* store RSP (pointing to context-data) in RDI */ - movq %rsp, (%rdi) + movq %r12, 0x10(%rsp) /* save R12 */ + movq %r13, 0x18(%rsp) /* save R13 */ + movq %r14, 0x20(%rsp) /* save R14 */ + movq %r15, 0x28(%rsp) /* save R15 */ + movq %rbx, 0x30(%rsp) /* save RBX */ + movq %rbp, 0x38(%rsp) /* save RBP */ - /* restore RSP (pointing to context-data) from RSI */ - movq %rsi, %rsp +#if SWOOLE_CONTEXT_SHADOW_STACK + /* grow the stack to reserve space for shadow stack pointer(SSP) */ + leaq -0x8(%rsp), %rsp + /* read the current SSP and store it */ + rdsspq %rcx + movq %rcx, (%rsp) +#endif - /* test for flag preserve_fpu */ - cmp $0, %rcx - je 2f + /* store RSP (pointing to context-data) in RAX */ + movq %rsp, %rax - /* restore MMX control- and status-word */ - ldmxcsr (%rsp) - /* restore x87 control-word */ - fldcw 0x4(%rsp) + /* restore RSP (pointing to context-data) from RDI */ + movq %rdi, %rsp -2: - /* prepare stack for FPU */ +#if SWOOLE_CONTEXT_SHADOW_STACK + /* first 8 bytes are SSP */ + movq (%rsp), %rcx leaq 0x8(%rsp), %rsp - popq %r12 /* restrore R12 */ - popq %r13 /* restrore R13 */ - popq %r14 /* restrore R14 */ - popq %r15 /* restrore R15 */ - popq %rbx /* restrore RBX */ - popq %rbp /* restrore RBP */ + /* Restore target(new) shadow stack */ + rstorssp -8(%rcx) + /* restore token for previous shadow stack is pushed */ + /* on previous shadow stack after saveprevssp */ + saveprevssp + + /* when return, swoole_jump_fcontext jump to restored return address */ + /* (r8) instead of RET. This miss of RET implies us to unwind */ + /* shadow stack accordingly. Otherwise mismatch occur */ + movq $1, %rcx + incsspq %rcx +#endif + + movq 0x40(%rsp), %r8 /* restore return-address */ - /* restore return-address */ - popq %r8 +#if !defined(SWOOLE_USE_TSX) + ldmxcsr (%rsp) /* restore MMX control- and status-word */ + fldcw 0x4(%rsp) /* restore x87 control-word */ +#endif + +#if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) + movq 0x8(%rsp), %rdx /* load stack guard */ + movq %rdx, %fs:0x28 /* restore stack guard to TLS record */ +#endif + + movq 0x10(%rsp), %r12 /* restore R12 */ + movq 0x18(%rsp), %r13 /* restore R13 */ + movq 0x20(%rsp), %r14 /* restore R14 */ + movq 0x28(%rsp), %r15 /* restore R15 */ + movq 0x30(%rsp), %rbx /* restore RBX */ + movq 0x38(%rsp), %rbp /* restore RBP */ + + leaq 0x48(%rsp), %rsp /* prepare stack */ - /* use third arg as return-value after jump */ - movq %rdx, %rax - /* use third arg as first arg in context function */ - movq %rdx, %rdi + /* return transfer_t from jump */ +#if !defined(_ILP32) + /* RAX == fctx, RDX == data */ + movq %rsi, %rdx +#else + /* RAX == data:fctx */ + salq $32, %rsi + orq %rsi, %rax +#endif + /* pass transfer_t as first arg in context function */ +#if !defined(_ILP32) + /* RDI == fctx, RSI == data */ +#else + /* RDI == data:fctx */ +#endif + movq %rax, %rdi /* indirect jump to context */ jmp *%r8 .size swoole_jump_fcontext,.-swoole_jump_fcontext -#ifndef __NetBSD__ /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/jump_x86_64_sysv_macho_gas.S b/thirdparty/boost/asm/jump_x86_64_sysv_macho_gas.S index 1515a6e7f9..0bf18bd763 100644 --- a/thirdparty/boost/asm/jump_x86_64_sysv_macho_gas.S +++ b/thirdparty/boost/asm/jump_x86_64_sysv_macho_gas.S @@ -21,13 +21,6 @@ * ---------------------------------------------------------------------------------- * * | R15 | RBX | RBP | RIP | * * ---------------------------------------------------------------------------------- * - * ---------------------------------------------------------------------------------- * - * | 16 | 17 | | * - * ---------------------------------------------------------------------------------- * - * | 0x40 | 0x44 | | * - * ---------------------------------------------------------------------------------- * - * | EXIT | | * - * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ @@ -35,59 +28,48 @@ .globl _swoole_jump_fcontext .align 8 _swoole_jump_fcontext: - pushq %rbp /* save RBP */ - pushq %rbx /* save RBX */ - pushq %r15 /* save R15 */ - pushq %r14 /* save R14 */ - pushq %r13 /* save R13 */ - pushq %r12 /* save R12 */ - - /* prepare stack for FPU */ - leaq -0x8(%rsp), %rsp - - /* test for flag preserve_fpu */ - cmp $0, %rcx - je 1f + leaq -0x38(%rsp), %rsp /* prepare stack */ - /* save MMX control- and status-word */ - stmxcsr (%rsp) - /* save x87 control-word */ - fnstcw 0x4(%rsp) +#if !defined(SWOOLE_USE_TSX) + stmxcsr (%rsp) /* save MMX control- and status-word */ + fnstcw 0x4(%rsp) /* save x87 control-word */ +#endif -1: - /* store RSP (pointing to context-data) in RDI */ - movq %rsp, (%rdi) + movq %r12, 0x8(%rsp) /* save R12 */ + movq %r13, 0x10(%rsp) /* save R13 */ + movq %r14, 0x18(%rsp) /* save R14 */ + movq %r15, 0x20(%rsp) /* save R15 */ + movq %rbx, 0x28(%rsp) /* save RBX */ + movq %rbp, 0x30(%rsp) /* save RBP */ - /* restore RSP (pointing to context-data) from RSI */ - movq %rsi, %rsp + /* store RSP (pointing to context-data) in RAX */ + movq %rsp, %rax - /* test for flag preserve_fpu */ - cmp $0, %rcx - je 2f + /* restore RSP (pointing to context-data) from RDI */ + movq %rdi, %rsp - /* restore MMX control- and status-word */ - ldmxcsr (%rsp) - /* restore x87 control-word */ - fldcw 0x4(%rsp) + movq 0x38(%rsp), %r8 /* restore return-address */ -2: - /* prepare stack for FPU */ - leaq 0x8(%rsp), %rsp +#if !defined(SWOOLE_USE_TSX) + ldmxcsr (%rsp) /* restore MMX control- and status-word */ + fldcw 0x4(%rsp) /* restore x87 control-word */ +#endif - popq %r12 /* restrore R12 */ - popq %r13 /* restrore R13 */ - popq %r14 /* restrore R14 */ - popq %r15 /* restrore R15 */ - popq %rbx /* restrore RBX */ - popq %rbp /* restrore RBP */ + movq 0x8(%rsp), %r12 /* restore R12 */ + movq 0x10(%rsp), %r13 /* restore R13 */ + movq 0x18(%rsp), %r14 /* restore R14 */ + movq 0x20(%rsp), %r15 /* restore R15 */ + movq 0x28(%rsp), %rbx /* restore RBX */ + movq 0x30(%rsp), %rbp /* restore RBP */ - /* restore return-address */ - popq %r8 + leaq 0x40(%rsp), %rsp /* prepare stack */ - /* use third arg as return-value after jump */ - movq %rdx, %rax - /* use third arg as first arg in context function */ - movq %rdx, %rdi + /* return transfer_t from jump */ + /* RAX == fctx, RDX == data */ + movq %rsi, %rdx + /* pass transfer_t as first arg in context function */ + /* RDI == fctx, RSI == data */ + movq %rax, %rdi /* indirect jump to context */ jmp *%r8 diff --git a/thirdparty/boost/asm/make_arm64_aapcs_elf_gas.S b/thirdparty/boost/asm/make_arm64_aapcs_elf_gas.S index 1fc23f4e36..fd98d15984 100644 --- a/thirdparty/boost/asm/make_arm64_aapcs_elf_gas.S +++ b/thirdparty/boost/asm/make_arm64_aapcs_elf_gas.S @@ -1,5 +1,5 @@ /* - Copyright Edward Nevill 2015 + Copyright Edward Nevill + Oliver Kowalke 2015 Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -51,7 +51,7 @@ * * *******************************************************/ -.cpu generic+fp+simd +.file "make_arm64_aapcs_elf_gas.S" .text .align 2 .global swoole_make_fcontext @@ -81,7 +81,5 @@ finish: bl _exit .size swoole_make_fcontext,.-swoole_make_fcontext -#ifndef __NetBSD__ # Mark that we don't need executable stack. .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/make_arm64_aapcs_macho_gas.S b/thirdparty/boost/asm/make_arm64_aapcs_macho_gas.S index 556cc15e51..7977c0ee9b 100644 --- a/thirdparty/boost/asm/make_arm64_aapcs_macho_gas.S +++ b/thirdparty/boost/asm/make_arm64_aapcs_macho_gas.S @@ -1,3 +1,9 @@ +/* + Copyright Edward Nevill + Oliver Kowalke 2015 + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ /******************************************************* * * * ------------------------------------------------- * @@ -45,7 +51,6 @@ * * *******************************************************/ - .text .globl _swoole_make_fcontext .balign 16 @@ -61,9 +66,6 @@ _swoole_make_fcontext: ; store address as a PC to jump in str x2, [x0, #0xa0] - ; compute abs address of label finish - ; 0x0c = 3 instructions * size (4) before label 'finish' - adr x1, finish ; save address of finish as return-address for context-function diff --git a/thirdparty/boost/asm/make_arm_aapcs_elf_gas.S b/thirdparty/boost/asm/make_arm_aapcs_elf_gas.S deleted file mode 100644 index 2d930313a4..0000000000 --- a/thirdparty/boost/asm/make_arm_aapcs_elf_gas.S +++ /dev/null @@ -1,73 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * - * ------------------------------------------------- * - * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * - * ------------------------------------------------- * - * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * - * ------------------------------------------------- * - * | v1 | v2 | v3 | v4 | v5 | v6 | v7 | v8 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | | * - * ------------------------------------------------- * - * | 0x60| 0x64| | * - * ------------------------------------------------- * - * | lr | pc | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl swoole_make_fcontext -.align 2 -.type swoole_make_fcontext,%function -swoole_make_fcontext: - @ shift address in A1 to lower 16 byte boundary - bic a1, a1, #15 - - @ reserve space for context-data on context-stack - sub a1, a1, #104 - - @ third arg of swoole_make_fcontext() == address of context-function - str a3, [a1,#100] - - @ compute abs address of label finish - adr a2, finish - @ save address of finish as return-address for context-function - @ will be entered after context-function returns - str a2, [a1,#96] - - bx lr @ return pointer to context-data - -finish: - @ exit code is zero - mov a1, #0 - @ exit application - bl _exit@PLT -.size swoole_make_fcontext,.-swoole_make_fcontext - -#ifndef __NetBSD__ -@ Mark that we don't need executable stack. -.section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/make_arm_aapcs_macho_gas.S b/thirdparty/boost/asm/make_arm_aapcs_macho_gas.S deleted file mode 100644 index 8daf3b1ece..0000000000 --- a/thirdparty/boost/asm/make_arm_aapcs_macho_gas.S +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * - * ------------------------------------------------- * - * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * - * ------------------------------------------------- * - * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * - * ------------------------------------------------- * - * | sjlj| v1 | v2 | v3 | v4 | v5 | v6 | v7 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | | * - * ------------------------------------------------- * - * | 0x60| 0x64| 0x68| | * - * ------------------------------------------------- * - * | v8 | lr | pc | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl _swoole_make_fcontext -.align 2 -_swoole_make_fcontext: - @ shift address in A1 to lower 16 byte boundary - bic a1, a1, #15 - - @ reserve space for context-data on context-stack - sub a1, a1, #108 - - @ third arg of swoole_make_fcontext() == address of context-function - str a3, [a1,#104] - - @ compute abs address of label finish - adr a2, finish - @ save address of finish as return-address for context-function - @ will be entered after context-function returns - str a2, [a1,#100] - - bx lr @ return pointer to context-data - -finish: - @ exit code is zero - mov a1, #0 - @ exit application - bl __exit diff --git a/thirdparty/boost/asm/make_arm_aapcs_pe_armasm.asm b/thirdparty/boost/asm/make_arm_aapcs_pe_armasm.asm deleted file mode 100644 index 3856b5838c..0000000000 --- a/thirdparty/boost/asm/make_arm_aapcs_pe_armasm.asm +++ /dev/null @@ -1,86 +0,0 @@ -;/* -; Copyright Oliver Kowalke 2009. -; Distributed under the Boost Software License, Version 1.0. -; (See accompanying file LICENSE_1_0.txt or copy at -; http://www.boost.org/LICENSE_1_0.txt) -;*/ - -; ******************************************************* -; * * -; * ------------------------------------------------- * -; * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * -; * ------------------------------------------------- * -; * | 0x0 | 0x4 | 0x8 | 0xc | 0x10| 0x14| 0x18| 0x1c| * -; * ------------------------------------------------- * -; * | s16 | s17 | s18 | s19 | s20 | s21 | s22 | s23 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * -; * ------------------------------------------------- * -; * | 0x20| 0x24| 0x28| 0x2c| 0x30| 0x34| 0x38| 0x3c| * -; * ------------------------------------------------- * -; * | s24 | s25 | s26 | s27 | s28 | s29 | s30 | s31 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * -; * ------------------------------------------------- * -; * | 0x40| 0x44| 0x48| 0x4c| 0x50| 0x54| 0x58| 0x5c| * -; * ------------------------------------------------- * -; * |deall|limit| base| v1 | v2 | v3 | v4 | v5 | * -; * ------------------------------------------------- * -; * ------------------------------------------------- * -; * | 24 | 25 | 26 | 27 | 28 | | * -; * ------------------------------------------------- * -; * | 0x60| 0x64| 0x68| 0x6c| 0x70| | * -; * ------------------------------------------------- * -; * | v6 | v7 | v8 | lr | pc | | * -; * ------------------------------------------------- * -; * * -; ******************************************************* - - - AREA |.text|, CODE - ALIGN 4 - EXPORT swoole_make_fcontext - IMPORT _exit - -swoole_make_fcontext PROC - ; first arg of swoole_make_fcontext() == top of context-stack - ; save top of context-stack (base) A4 - mov a4, a1 - - ; shift address in A1 to lower 16 byte boundary - bic a1, a1, #0x0f - - ; reserve space for context-data on context-stack - sub a1, a1, #0x74 - - ; save top address of context_stack as 'base' - str a4, [a1,#0x48] - ; second arg of swoole_make_fcontext() == size of context-stack - ; compute bottom address of context-stack (limit) - sub a4, a4, a2 - ; save bottom address of context-stack as 'limit' - str a4, [a1,#0x44] - ; save bottom address of context-stack as 'dealloction stack' - str a4, [a1,#0x40] - - ; third arg of swoole_make_fcontext() == address of context-function - str a3, [a1,#0x70] - - ; compute abs address of label finish - adr a2, finish - ; save address of finish as return-address for context-function - ; will be entered after context-function returns - str a2, [a1,#0x6c] - - bx lr ; return pointer to context-data - -finish - ; exit code is zero - mov a1, #0 - ; exit application - bl _exit - - ENDP - END diff --git a/thirdparty/boost/asm/make_combined_sysv_macho_gas.S b/thirdparty/boost/asm/make_combined_sysv_macho_gas.S index b22fa7ebe9..226dd9a16d 100644 --- a/thirdparty/boost/asm/make_combined_sysv_macho_gas.S +++ b/thirdparty/boost/asm/make_combined_sysv_macho_gas.S @@ -7,16 +7,10 @@ // Stub file for universal binary -#if defined(__i386__) - #include "make_i386_sysv_macho_gas.S" -#elif defined(__x86_64__) +#if defined(__x86_64__) #include "make_x86_64_sysv_macho_gas.S" -#elif defined(__ppc__) - #include "make_ppc32_sysv_macho_gas.S" #elif defined(__ppc64__) #include "make_ppc64_sysv_macho_gas.S" -#elif defined(__arm__) - #include "make_arm_aapcs_macho_gas.S" #elif defined(__arm64__) #include "make_arm64_aapcs_macho_gas.S" #else diff --git a/thirdparty/boost/asm/make_i386_sysv_elf_gas.S b/thirdparty/boost/asm/make_i386_sysv_elf_gas.S deleted file mode 100644 index 2ea1f19283..0000000000 --- a/thirdparty/boost/asm/make_i386_sysv_elf_gas.S +++ /dev/null @@ -1,77 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/**************************************************************************************** - * * - * ---------------------------------------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ---------------------------------------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * - * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| EDI | ESI | EBX | EBP | EIP | EXIT | * - * ---------------------------------------------------------------------------------- * - * * - ****************************************************************************************/ - -.text -.globl swoole_make_fcontext -.align 2 -.type swoole_make_fcontext,@function -swoole_make_fcontext: - /* first arg of swoole_make_fcontext() == top of context-stack */ - movl 0x4(%esp), %eax - - /* reserve space for first argument of context-function - rax might already point to a 16byte border */ - leal -0x8(%eax), %eax - - /* shift address in EAX to lower 16 byte boundary */ - andl $-16, %eax - - /* reserve space for context-data on context-stack */ - /* size for fc_mxcsr .. EIP + return-address for context-function */ - /* on context-function entry: (ESP -0x4) % 8 == 0 */ - leal -0x20(%eax), %eax - - /* third arg of swoole_make_fcontext() == address of context-function */ - movl 0xc(%esp), %edx - movl %edx, 0x18(%eax) - - /* save MMX control- and status-word */ - stmxcsr (%eax) - /* save x87 control-word */ - fnstcw 0x4(%eax) - - /* compute abs address of label finish */ - call 1f - /* address of label 1 */ -1: popl %ecx - /* compute abs address of label finish */ - addl $finish-1b, %ecx - /* save address of finish as return-address for context-function */ - /* will be entered after context-function returns */ - movl %ecx, 0x1c(%eax) - - ret /* return pointer to context-data */ - -finish: - call 2f - /* address of label 2 */ -2: popl %ebx - /* compute address of GOT and store it in EBX */ - addl $_GLOBAL_OFFSET_TABLE_+[.-2b], %ebx - - /* exit code is zero */ - xorl %eax, %eax - movl %eax, (%esp) - /* exit application */ - call _exit@PLT - hlt -.size swoole_make_fcontext,.-swoole_make_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/make_i386_sysv_macho_gas.S b/thirdparty/boost/asm/make_i386_sysv_macho_gas.S deleted file mode 100644 index 5709f34658..0000000000 --- a/thirdparty/boost/asm/make_i386_sysv_macho_gas.S +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/**************************************************************************************** - * * - * ---------------------------------------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ---------------------------------------------------------------------------------- * - * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * - * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| EDI | ESI | EBX | EBP | EIP | EXIT | * - * ---------------------------------------------------------------------------------- * - * * - ****************************************************************************************/ - -.text -.globl _swoole_make_fcontext -.align 2 -_swoole_make_fcontext: - /* first arg of swoole_make_fcontext() == top of context-stack */ - movl 0x4(%esp), %eax - - /* reserve space for first argument of context-function - rax might already point to a 16byte border */ - leal -0x8(%eax), %eax - - /* shift address in EAX to lower 16 byte boundary */ - andl $-16, %eax - - /* reserve space for context-data on context-stack */ - /* size for fc_mxcsr .. EIP + return-address for context-function */ - /* on context-function entry: (ESP -0x4) % 8 == 0 */ - leal -0x20(%eax), %eax - - /* thrid arg of swoole_make_fcontext() == address of context-function */ - movl 0xc(%esp), %edx - movl %edx, 0x18(%eax) - - /* save MMX control- and status-word */ - stmxcsr (%eax) - /* save x87 control-word */ - fnstcw 0x4(%eax) - - /* compute abs address of label finish */ - call 1f - /* address of label 1 */ -1: popl %ecx - /* compute abs address of label finish */ - addl $finish-1b, %ecx - /* save address of finish as return-address for context-function */ - /* will be entered after context-function returns */ - movl %ecx, 0x1c(%eax) - - ret /* return pointer to context-data */ - -finish: - /* exit code is zero */ - xorl %eax, %eax - movl %eax, (%esp) - /* exit application */ - call __exit - hlt diff --git a/thirdparty/boost/asm/make_i386_x86_64_sysv_macho_gas.S b/thirdparty/boost/asm/make_i386_x86_64_sysv_macho_gas.S deleted file mode 100644 index e364b2db62..0000000000 --- a/thirdparty/boost/asm/make_i386_x86_64_sysv_macho_gas.S +++ /dev/null @@ -1,16 +0,0 @@ -/* - Copyright Sergue E. Leontiev 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -// Stub file for universal binary - -#if defined(__i386__) - #include "make_i386_sysv_macho_gas.S" -#elif defined(__x86_64__) - #include "make_x86_64_sysv_macho_gas.S" -#else - #error "No arch's" -#endif diff --git a/thirdparty/boost/asm/make_loongarch64_sysv_elf_gas.S b/thirdparty/boost/asm/make_loongarch64_sysv_elf_gas.S new file mode 100644 index 0000000000..5359e0235e --- /dev/null +++ b/thirdparty/boost/asm/make_loongarch64_sysv_elf_gas.S @@ -0,0 +1,72 @@ +/******************************************************* + * * + * ------------------------------------------------- * + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * + * ------------------------------------------------- * + * | 0 | 8 | 16 | 24 | * + * ------------------------------------------------- * + * | FS0 | FS1 | FS2 | FS3 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * + * ------------------------------------------------- * + * | 32 | 40 | 48 | 56 | * + * ------------------------------------------------- * + * | FS4 | FS5 | FS6 | FS7 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * + * ------------------------------------------------- * + * | 64 | 72 | 80 | 88 | * + * ------------------------------------------------- * + * | S0 | S1 | S2 | S3 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * + * ------------------------------------------------- * + * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * + * ------------------------------------------------- * + * | S4 | S5 | S6 | S7 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * + * ------------------------------------------------- * + * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * + * ------------------------------------------------- * + * | S8 | FP | RA | PC | * + * ------------------------------------------------- * + * * + * *****************************************************/ + +.file "make_loongarch64_sysv_elf_gas.S" +.text +.globl swoole_make_fcontext +.align 2 +.type swoole_make_fcontext,@function +swoole_make_fcontext: + # shift address in A0 to lower 16 byte boundary + bstrins.d $a0, $zero, 3, 0 + + # reserve space for context-data on context-stack + addi.d $a0, $a0, -160 + + # third arg of swoole_make_fcontext() == address of context-function + st.d $a2, $a0, 152 + + # save address of finish as return-address for context-function + # will be entered after context-function returns + la.local $a4, finish + st.d $a4, $a0, 144 + + # return pointer to context-data + jr $ra + +finish: + # exit code is zero + li.d $a0, 0 + # call _exit(0) + b %plt(_exit) + +.size swoole_make_fcontext, .-swoole_make_fcontext +/* Mark that we don't need executable stack. */ +.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/make_mips32_o32_elf_gas.S b/thirdparty/boost/asm/make_mips32_o32_elf_gas.S deleted file mode 100644 index 6d0a305b68..0000000000 --- a/thirdparty/boost/asm/make_mips32_o32_elf_gas.S +++ /dev/null @@ -1,89 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F20 | F22 | F24 | F26 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F28 | F30 | S0 | S1 | S2 | S3 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | | * - * ------------------------------------------------- * - * | S4 | S5 | S6 | S7 | FP | RA | PC | | * - * ------------------------------------------------- * - * * - * *****************************************************/ - -.text -.globl swoole_make_fcontext -.align 2 -.type swoole_make_fcontext,@function -.ent swoole_make_fcontext -swoole_make_fcontext: -#ifdef __PIC__ -.set noreorder -.cpload $t9 -.set reorder -#endif - # first arg of swoole_make_fcontext() == top address of context-stack - move $v0, $a0 - - # shift address in A0 to lower 16 byte boundary - move $v1, $v0 - li $v0, -16 # 0xfffffffffffffff0 - and $v0, $v1, $v0 - - # reserve space for context-data on context-stack - # including 48 byte of shadow space (sp % 16 == 0) - addiu $v0, $v0, -140 - - # third arg of swoole_make_fcontext() == address of context-function - sw $a2, 88($v0) - # save global pointer in context-data - # S0 will contain address of global pointer - sw $gp, 48($v0) - - # compute abs address of label finish - la $t9, finish - # save address of finish as return-address for context-function - # will be entered after context-function returns - sw $t9, 84($v0) - - jr $ra # return pointer to context-data - -finish: - # allocate stack space (contains shadow space for subroutines) - addiu $sp, $sp, -32 - # save return address - sw $ra, 28($sp) - - # restore GP (global pointer) - move $gp, $s0 - # exit code is zero - move $a0, $zero - # address of exit - lw $t9, %call16(_exit)($gp) - # exit application - jalr $t9 -.end swoole_make_fcontext -.size swoole_make_fcontext, .-swoole_make_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/make_mips64_n64_elf_gas.S b/thirdparty/boost/asm/make_mips64_n64_elf_gas.S index 888ddc26ca..d3d46313b6 100644 --- a/thirdparty/boost/asm/make_mips64_n64_elf_gas.S +++ b/thirdparty/boost/asm/make_mips64_n64_elf_gas.S @@ -45,6 +45,7 @@ * * * *****************************************************/ +.file "make_mips64_n64_elf_gas.S" .text .globl swoole_make_fcontext .align 3 diff --git a/thirdparty/boost/asm/make_ppc32_ppc64_sysv_macho_gas.S b/thirdparty/boost/asm/make_ppc32_ppc64_sysv_macho_gas.S deleted file mode 100644 index 52e7220933..0000000000 --- a/thirdparty/boost/asm/make_ppc32_ppc64_sysv_macho_gas.S +++ /dev/null @@ -1,16 +0,0 @@ -/* - Copyright Sergue E. Leontiev 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -// Stub file for universal binary - -#if defined(__ppc__) - #include "make_ppc32_sysv_macho_gas.S" -#elif defined(__ppc64__) - #include "make_ppc64_sysv_macho_gas.S" -#else - #error "No arch's" -#endif diff --git a/thirdparty/boost/asm/make_ppc32_sysv_elf_gas.S b/thirdparty/boost/asm/make_ppc32_sysv_elf_gas.S deleted file mode 100644 index 9e2a6168e3..0000000000 --- a/thirdparty/boost/asm/make_ppc32_sysv_elf_gas.S +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * - * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * - * ------------------------------------------------- * - * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * - * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * - * ------------------------------------------------- * - * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * - * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | R14 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * - * ------------------------------------------------- * - * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * - * ------------------------------------------------- * - * | R15 | R16 | R17 | R18 | R19 | R20 | R21 | R22 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * - * ------------------------------------------------- * - * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * - * ------------------------------------------------- * - * | R23 | R24 | R25 | R26 | R27 | R28 | R29 | R30 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 56 | 57 | 58 | 59 | | * - * ------------------------------------------------- * - * | 224 | 228 | 232 | 236 | | * - * ------------------------------------------------- * - * | R31 | CR | LR | PC | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl swoole_make_fcontext -.align 2 -.type swoole_make_fcontext,@function -swoole_make_fcontext: - # save return address into R6 - mflr %r6 - - # first arg of swoole_make_fcontext() == top address of context-function - # shift address in R3 to lower 16 byte boundary - clrrwi %r3, %r3, 4 - - # reserve space for context-data on context-stack - # including 64 byte of linkage + parameter area (R1 % 16 == 0) - subi %r3, %r3, 304 - - # third arg of swoole_make_fcontext() == address of context-function - stw %r5, 236(%r3) - - # load LR - mflr %r0 - # jump to label 1 - bl 1f -1: - # load LR into R4 - mflr %r4 - # compute abs address of label finish - addi %r4, %r4, finish - 1b - # restore LR - mtlr %r0 - # save address of finish as return-address for context-function - # will be entered after context-function returns - stw %r4, 232(%r3) - - # restore return address from R6 - mtlr %r6 - - blr # return pointer to context-data - -finish: - # save return address into R0 - mflr %r0 - # save return address on stack, set up stack frame - stw %r0, 4(%r1) - # allocate stack space, R1 % 16 == 0 - stwu %r1, -16(%r1) - - # exit code is zero - li %r3, 0 - # exit application - bl _exit@plt -.size swoole_make_fcontext, .-swoole_make_fcontext - -#ifndef __NetBSD__ -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/make_ppc32_sysv_macho_gas.S b/thirdparty/boost/asm/make_ppc32_sysv_macho_gas.S deleted file mode 100644 index 9749dac74b..0000000000 --- a/thirdparty/boost/asm/make_ppc32_sysv_macho_gas.S +++ /dev/null @@ -1,118 +0,0 @@ -/* - Copyright Oliver Kowalke 2009. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************* - * * - * ------------------------------------------------- * - * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * - * ------------------------------------------------- * - * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * - * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * - * ------------------------------------------------- * - * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * - * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * - * ------------------------------------------------- * - * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * - * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * - * ------------------------------------------------- * - * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * - * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * - * ------------------------------------------------- * - * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * - * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | R14 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * - * ------------------------------------------------- * - * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * - * ------------------------------------------------- * - * | R15 | R16 | R17 | R18 | R19 | R20 | R21 | R22 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * - * ------------------------------------------------- * - * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * - * ------------------------------------------------- * - * | R23 | R24 | R25 | R26 | R27 | R28 | R29 | R30 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 56 | 57 | 58 | 59 | | * - * ------------------------------------------------- * - * | 224 | 228 | 232 | 236 | | * - * ------------------------------------------------- * - * | R31 | CR | LR | PC | | * - * ------------------------------------------------- * - * * - *******************************************************/ - -.text -.globl _swoole_make_fcontext -.align 2 -_swoole_make_fcontext: - ; save return address into R6 - mflr r6 - - ; first arg of swoole_make_fcontext() == top address of context-function - ; shift address in R3 to lower 16 byte boundary - clrrwi r3, r3, 4 - - ; reserve space for context-data on context-stack - ; including 64 byte of linkage + parameter area (R1 % 16 == 0) - subi r3, r3, 304 - - ; third arg of swoole_make_fcontext() == address of context-function - stw r5, 236(r3) - - ; load LR - mflr r0 - ; jump to label 1 - bl l1 -l1: - ; load LR into R4 - mflr r4 - ; compute abs address of label finish - addi r4, r4, lo16((finish - .)+4) - # restore LR - mtlr r0 - ; save address of finish as return-address for context-function - ; will be entered after context-function returns - stw r4, 232(r3) - - ; restore return address from R6 - mtlr r6 - - blr ; return pointer to context-data - -finish: - ; save return address into R0 - mflr r0 - ; save return address on stack, set up stack frame - stw r0, 4(r1) - ; allocate stack space, R1 % 16 == 0 - stwu r1, -16(r1) - - ; exit code is zero - li r3, 0 - ; exit application - bl __exit diff --git a/thirdparty/boost/asm/make_ppc32_sysv_xcoff_gas.S b/thirdparty/boost/asm/make_ppc32_sysv_xcoff_gas.S deleted file mode 100644 index f21857b4de..0000000000 --- a/thirdparty/boost/asm/make_ppc32_sysv_xcoff_gas.S +++ /dev/null @@ -1,55 +0,0 @@ - .globl swoole_make_fcontext[DS] - .globl .swoole_make_fcontext[PR] - .align 2 - .csect swoole_make_fcontext[DS] -swoole_make_fcontext: - .long .swoole_make_fcontext[PR] - .csect .swoole_make_fcontext[PR], 3 -#.swoole_make_fcontext: - # save return address into R6 - mflr 6 - - # first arg of swoole_make_fcontext() == top address of context-function - # shift address in R3 to lower 16 byte boundary - clrrwi 3, 3, 4 - - # reserve space for context-data on context-stack - # including 64 byte of linkage + parameter area (R1 % 16 == 0) - subi 3, 3, 304 - - # third arg of swoole_make_fcontext() == address of context-function - stw 5, 236(3) - - # load LR - mflr 0 - # jump to label 1 - bl .Label -.Label: - # load LR into R4 - mflr 4 - # compute abs address of label .L_finish - addi 4, 4, .L_finish - .Label - # restore LR - mtlr 0 - # save address of finish as return-address for context-function - # will be entered after context-function returns - stw 4, 232(3) - - # restore return address from R6 - mtlr 6 - - blr # return pointer to context-data - -.L_finish: - # save return address into R0 - mflr 0 - # save return address on stack, set up stack frame - stw 0, 4(1) - # allocate stack space, R1 % 16 == 0 - stwu 1, -16(1) - - # exit code is zero - li 3, 0 - # exit application - bl ._exit - nop diff --git a/thirdparty/boost/asm/make_ppc64_sysv_elf_gas.S b/thirdparty/boost/asm/make_ppc64_sysv_elf_gas.S index 71af0db1a5..59354f8dde 100644 --- a/thirdparty/boost/asm/make_ppc64_sysv_elf_gas.S +++ b/thirdparty/boost/asm/make_ppc64_sysv_elf_gas.S @@ -12,82 +12,61 @@ * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * + * | TOC | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * + * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * + * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * + * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * - * | F30 | F31 | fpscr | TOC | * + * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * - * | R14 | R15 | R16 | R17 | * + * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * - * | R18 | R19 | R20 | R21 | * + * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * - * | R22 | R23 | R24 | R25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | * - * ------------------------------------------------- * - * | 256 | 260 | 264 | 268 | 272 | 276 | 280 | 284 | * - * ------------------------------------------------- * - * | R26 | R27 | R28 | R29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | * - * ------------------------------------------------- * - * | 288 | 292 | 296 | 300 | 304 | 308 | 312 | 316 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | R30 | R31 | CR | LR | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 80 | 81 | | * - * ------------------------------------------------- * - * | 320 | 324 | | * - * ------------------------------------------------- * - * | PC | | * + * | TOC saved | FCTX | DATA | | * * ------------------------------------------------- * * * *******************************************************/ +.file "make_ppc64_sysv_elf_gas.S" .globl swoole_make_fcontext #if _CALL_ELF == 2 .text @@ -126,20 +105,29 @@ swoole_make_fcontext: # reserve space for context-data on context-stack # including 64 byte of linkage + parameter area (R1 % 16 == 0) - subi %r3, %r3, 392 + subi %r3, %r3, 248 # third arg of swoole_make_fcontext() == address of context-function # entry point (ELFv2) or descriptor (ELFv1) #if _CALL_ELF == 2 # save address of context-function entry point - std %r5, 320(%r3) + std %r5, 176(%r3) #else # save address of context-function entry point ld %r4, 0(%r5) - std %r4, 320(%r3) + std %r4, 176(%r3) # save TOC of context-function ld %r4, 8(%r5) - std %r4, 152(%r3) + std %r4, 0(%r3) +#endif + + # set back-chain to zero + li %r0, 0 + std %r0, 184(%r3) + +#if _CALL_ELF != 2 + # zero in r3 indicates first jump to context-function + std %r0, 152(%r3) #endif # load LR @@ -155,7 +143,7 @@ swoole_make_fcontext: mtlr %r0 # save address of finish as return-address for context-function # will be entered after context-function returns - std %r4, 312(%r3) + std %r4, 168(%r3) # restore return address from R6 mtlr %r6 @@ -185,7 +173,5 @@ finish: # endif #endif -#ifndef __NetBSD__ /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/make_ppc64_sysv_macho_gas.S b/thirdparty/boost/asm/make_ppc64_sysv_macho_gas.S index d656cab655..717f3bb2cf 100644 --- a/thirdparty/boost/asm/make_ppc64_sysv_macho_gas.S +++ b/thirdparty/boost/asm/make_ppc64_sysv_macho_gas.S @@ -12,78 +12,56 @@ * ------------------------------------------------- * * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * * ------------------------------------------------- * - * | F14 | F15 | F16 | F17 | * + * | R13 | R14 | R15 | R16 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ------------------------------------------------- * * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * * ------------------------------------------------- * - * | F18 | F19 | F20 | F21 | * + * | R17 | R18 | R19 | R20 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ------------------------------------------------- * * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * * ------------------------------------------------- * - * | F22 | F23 | F24 | F25 | * + * | R21 | R22 | R23 | R24 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * * ------------------------------------------------- * * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * * ------------------------------------------------- * - * | F26 | F27 | F28 | F29 | * + * | R25 | R26 | R27 | R28 | * * ------------------------------------------------- * * ------------------------------------------------- * * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * * ------------------------------------------------- * * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * * ------------------------------------------------- * - * | F30 | F31 | fpscr | R13 | * + * | R29 | R30 | R31 | hidden | * * ------------------------------------------------- * * ------------------------------------------------- * * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * * ------------------------------------------------- * * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * * ------------------------------------------------- * - * | R14 | R15 | R16 | R17 | * + * | CR | LR | PC | back-chain| * * ------------------------------------------------- * * ------------------------------------------------- * * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * * ------------------------------------------------- * * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * * ------------------------------------------------- * - * | R18 | R19 | R20 | R21 | * + * | cr saved | lr saved | compiler | linker | * * ------------------------------------------------- * * ------------------------------------------------- * * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * * ------------------------------------------------- * * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * * ------------------------------------------------- * - * | R22 | R23 | R24 | R25 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | * - * ------------------------------------------------- * - * | 256 | 260 | 264 | 268 | 272 | 276 | 280 | 284 | * - * ------------------------------------------------- * - * | R26 | R27 | R28 | R29 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | * - * ------------------------------------------------- * - * | 288 | 292 | 296 | 300 | 304 | 308 | 312 | 316 | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | R30 | R31 | CR | LR | * - * ------------------------------------------------- * - * ------------------------------------------------- * - * | 80 | 81 | | * - * ------------------------------------------------- * - * | 320 | 324 | | * - * ------------------------------------------------- * - * | PC | | * + * | FCTX | DATA | | | * * ------------------------------------------------- * * * *******************************************************/ @@ -100,10 +78,19 @@ _swoole_make_fcontext: ; reserve space for context-data on context-stack ; including 64 byte of linkage + parameter area (R1 16 == 0) - subi r3, r3, 392 + subi r3, r3, 240 ; third arg of swoole_make_fcontext() == address of context-function - stw r5, 320(r3) + stw r5, 176(r3) + + ; set back-chain to zero + li r0, 0 + std r0, 184(r3) + + ; compute address of returned transfer_t + addi r0, r3, 224 + mr r4, r0 + std r4, 152(r3) ; load LR mflr r0 @@ -118,7 +105,7 @@ l1: mtlr r0 ; save address of finish as return-address for context-function ; will be entered after context-function returns - std r4, 312(r3) + std r4, 168(r3) ; restore return address from R6 mtlr r6 diff --git a/thirdparty/boost/asm/make_ppc64_sysv_xcoff_gas.S b/thirdparty/boost/asm/make_ppc64_sysv_xcoff_gas.S index b9dfb18976..58fd12eb91 100644 --- a/thirdparty/boost/asm/make_ppc64_sysv_xcoff_gas.S +++ b/thirdparty/boost/asm/make_ppc64_sysv_xcoff_gas.S @@ -1,22 +1,106 @@ - .globl swoole_make_fcontext[DS] - .globl .swoole_make_fcontext[PR] - .align 2 - .csect .swoole_make_fcontext[PR], 3 - .globl _swoole_make_fcontext -#._swoole_make_fcontext: +/* + Copyright Oliver Kowalke 2009. + Distributed under the Boost Software License, Version 1.0. + (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +/******************************************************* + * * + * ------------------------------------------------- * + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | * + * ------------------------------------------------- * + * | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | * + * ------------------------------------------------- * + * | TOC | R14 | R15 | R16 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * + * ------------------------------------------------- * + * | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 | * + * ------------------------------------------------- * + * | R17 | R18 | R19 | R20 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * + * ------------------------------------------------- * + * | 64 | 68 | 72 | 76 | 80 | 84 | 88 | 92 | * + * ------------------------------------------------- * + * | R21 | R22 | R23 | R24 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | * + * ------------------------------------------------- * + * | 96 | 100 | 104 | 108 | 112 | 116 | 120 | 124 | * + * ------------------------------------------------- * + * | R25 | R26 | R27 | R28 | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | * + * ------------------------------------------------- * + * | 128 | 132 | 136 | 140 | 144 | 148 | 152 | 156 | * + * ------------------------------------------------- * + * | R29 | R30 | R31 | hidden | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | * + * ------------------------------------------------- * + * | 160 | 164 | 168 | 172 | 176 | 180 | 184 | 188 | * + * ------------------------------------------------- * + * | CR | LR | PC | back-chain| * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | * + * ------------------------------------------------- * + * | 192 | 196 | 200 | 204 | 208 | 212 | 216 | 220 | * + * ------------------------------------------------- * + * | cr saved | lr saved | compiler | linker | * + * ------------------------------------------------- * + * ------------------------------------------------- * + * | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | * + * ------------------------------------------------- * + * | 224 | 228 | 232 | 236 | 240 | 244 | 248 | 252 | * + * ------------------------------------------------- * + * | TOC saved | FCTX | DATA | | * + * ------------------------------------------------- * + * * + *******************************************************/ + + .file "make_ppc64_sysv_xcoff_gas.S" + .toc + .csect .text[PR], 5 + .align 2 + .globl swoole_make_fcontext[DS] + .globl .swoole_make_fcontext + .csect swoole_make_fcontext[DS], 3 +swoole_make_fcontext: + .llong .swoole_make_fcontext[PR], TOC[tc0], 0 + .csect .text[PR], 5 +.swoole_make_fcontext: # save return address into R6 mflr 6 # first arg of swoole_make_fcontext() == top address of context-function # shift address in R3 to lower 16 byte boundary - clrrwi 3, 3, 4 + clrrdi 3, 3, 4 # reserve space for context-data on context-stack # including 64 byte of linkage + parameter area (R1 % 16 == 0) - subi 3, 3, 392 + subi 3, 3, 248 + + # third arg of swoole_make_fcontext() == address of context-function descriptor + ld 4, 0(5) + std 4, 176(3) + # save TOC of context-function + ld 4, 8(5) + std 4, 0(3) + + # set back-chain to zero + li 0, 0 + std 0, 184(3) - # third arg of swoole_make_fcontext() == address of context-function - stw 5, 320(3) + # zero in r3 indicates first jump to context-function + std 0, 152(3) # load LR mflr 0 @@ -31,7 +115,7 @@ mtlr 0 # save address of finish as return-address for context-function # will be entered after context-function returns - stw 4, 312(3) + std 4, 168(3) # restore return address from R6 mtlr 6 @@ -42,9 +126,9 @@ # save return address into R0 mflr 0 # save return address on stack, set up stack frame - stw 0, 8(1) + std 0, 8(1) # allocate stack space, R1 % 16 == 0 - stwu 1, -32(1) + stdu 1, -32(1) # exit code is zero li 3, 0 diff --git a/thirdparty/boost/asm/make_sparc_sysv_elf_gas.S b/thirdparty/boost/asm/make_sparc_sysv_elf_gas.S deleted file mode 100644 index 0b09ee7dc1..0000000000 --- a/thirdparty/boost/asm/make_sparc_sysv_elf_gas.S +++ /dev/null @@ -1,85 +0,0 @@ -/* - Copyright Martin Husemann 2013. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -/******************************************************************* - * * - * ------------------------------------------------------------- * - * | Offset (in 4 or 8 byte units) | Content | * - * ------------------------------------------------------------- * - * | 0 | %sp | * - * ------------------------------------------------------------- * - * | 1 | %pc | * - * ------------------------------------------------------------- * - * | 2 | %i7 (return address) | * - * ------------------------------------------------------------- * - * | 3 | %g1 | * - * ------------------------------------------------------------- * - * | 4 | %g2 | * - * ------------------------------------------------------------- * - * | 5 | %g3 | * - * ------------------------------------------------------------- * - * | 6 | %g6 | * - * ------------------------------------------------------------- * - * | 7 | %g7 | * - * ------------------------------------------------------------- * - * The local and in registers are stored on the stack. * - *******************************************************************/ - -#define OFF(N) (4*(N)) -#define CCFSZ 96 -#define FC_SZ 176 -#define FC_stK 168 // offsetof(fcontext_t, fc_stack) -#define FC_FPU 0 // offsetof(fcontext_t, fc_fp) -#define FC_FSR 128 // offsetof(fcontext_t, fc_fp.fp_fsr) -#define FC_GREG 136 // offsetof(fcontext_t, fc_greg) -#define BLOCK_SIZE 8 - -.text -.globl swoole_make_fcontext -.align 4 -.type swoole_make_fcontext,@function -// fcontext_t * -// swoole_make_fcontext( void * sp, std::size_t size, void (* fn)( intptr_t) ) -swoole_make_fcontext: - save %sp, -CCFSZ, %sp - // %i0 initial stack pointer - // %i1 stack size limit - // %i2 function pointer for context start function - - sub %i0, FC_SZ, %i4 // allocate fcontext_t at on the new stack and keep pointer as return value - andn %i4, BLOCK_SIZE-1, %i5 // force block ops usable alignement and keep pointer to fcontext in %i5 - - st %i0, [%i5+FC_stK+OFF(0)] // save fs_stack.sp - st %i1, [%i5+FC_stK+OFF(1)] // save fs_stack.size - sub %i5, CCFSZ, %o1 // leave space for one register window - st %o1, [%i5+FC_GREG+OFF(0)] // save new stack pointer - st %i2, [%i5+FC_GREG+OFF(1)] // save new %pc (function pointer) - st %g1, [%i5+FC_GREG+OFF(3)] - st %g2, [%i5+FC_GREG+OFF(4)] - st %g3, [%i5+FC_GREG+OFF(5)] - st %g6, [%i5+FC_GREG+OFF(6)] - st %g7, [%i5+FC_GREG+OFF(7)] - - // synthesize "return address": jump to finish - mov %i7, %l0 -2: call 3f - nop -3: add finish-2b-8, %o7, %i4 - st %i4, [%i5+FC_GREG+OFF(2)] - - ret - restore %g0, %i5, %o0 // return fcontext_t - -finish: - mov %g0, %o0 - call _exit - nop - -.size swoole_make_fcontext,.-swoole_make_fcontext - -/* Mark that we don't need executable stack. */ -.section .note.GNU-stack,"",%progbits diff --git a/thirdparty/boost/asm/make_x86_64_sysv_elf_gas.S b/thirdparty/boost/asm/make_x86_64_sysv_elf_gas.S index 34ce3b26f4..00674735f3 100644 --- a/thirdparty/boost/asm/make_x86_64_sysv_elf_gas.S +++ b/thirdparty/boost/asm/make_x86_64_sysv_elf_gas.S @@ -12,36 +12,44 @@ * ---------------------------------------------------------------------------------- * * | 0x0 | 0x4 | 0x8 | 0xc | 0x10 | 0x14 | 0x18 | 0x1c | * * ---------------------------------------------------------------------------------- * - * | fc_mxcsr|fc_x87_cw| R12 | R13 | R14 | * + * | fc_mxcsr|fc_x87_cw| guard | R12 | R13 | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * * | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | * * ---------------------------------------------------------------------------------- * * | 0x20 | 0x24 | 0x28 | 0x2c | 0x30 | 0x34 | 0x38 | 0x3c | * * ---------------------------------------------------------------------------------- * - * | R15 | RBX | RBP | RIP | * + * | R14 | R15 | RBX | RBP | * * ---------------------------------------------------------------------------------- * * ---------------------------------------------------------------------------------- * - * | 16 | 17 | | * + * | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | * * ---------------------------------------------------------------------------------- * * | 0x40 | 0x44 | | * * ---------------------------------------------------------------------------------- * - * | EXIT | | * + * | RIP | | * * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ -#ifdef __CET__ -#include -#else -#define _CET_ENDBR -#endif +# if defined __CET__ +# include +# define SWOOLE_SHSTK_ENABLED (__CET__ & 0x2) +# define SWOOLE_CONTEXT_SHADOW_STACK (SWOOLE_SHSTK_ENABLED && SHADOW_STACK_SYSCALL) +# else +# define _CET_ENDBR +# endif +.file "make_x86_64_sysv_elf_gas.S" .text .globl swoole_make_fcontext .type swoole_make_fcontext,@function .align 16 swoole_make_fcontext: _CET_ENDBR +#if SWOOLE_CONTEXT_SHADOW_STACK + /* the new shadow stack pointer (SSP) */ + movq -0x8(%rdi), %r9 +#endif + /* first arg of swoole_make_fcontext() == top of context-stack */ movq %rdi, %rax @@ -49,26 +57,83 @@ swoole_make_fcontext: andq $-16, %rax /* reserve space for context-data on context-stack */ - /* size for fc_mxcsr .. RIP + return-address for context-function */ /* on context-function entry: (RSP -0x8) % 16 == 0 */ leaq -0x48(%rax), %rax /* third arg of swoole_make_fcontext() == address of context-function */ - movq %rdx, 0x38(%rax) + /* stored in RBX */ + movq %rdx, 0x30(%rax) /* save MMX control- and status-word */ stmxcsr (%rax) /* save x87 control-word */ fnstcw 0x4(%rax) +#if defined(SWOOLE_CONTEXT_TLS_STACK_PROTECTOR) + /* save stack guard */ + movq %fs:0x28, %rcx /* read stack guard from TLS record */ + movq %rcx, 0x8(%rsp) /* save stack guard */ +#endif + + /* compute abs address of label trampoline */ + leaq trampoline(%rip), %rcx + /* save address of trampoline as return-address for context-function */ + /* will be entered after calling jump_fcontext() first time */ + movq %rcx, 0x40(%rax) + /* compute abs address of label finish */ leaq finish(%rip), %rcx /* save address of finish as return-address for context-function */ /* will be entered after context-function returns */ - movq %rcx, 0x40(%rax) + movq %rcx, 0x38(%rax) + +#if SWOOLE_CONTEXT_SHADOW_STACK + /* Populate the shadow stack and normal stack */ + /* get original SSP */ + rdsspq %r8 + /* restore new shadow stack */ + rstorssp -0x8(%r9) + /* save the restore token on the original shadow stack */ + saveprevssp + /* push the address of "jmp trampoline" to the new shadow stack */ + /* as well as the stack */ + call 1f + jmp trampoline +1: + /* save address of "jmp trampoline" as return-address */ + /* for context-function */ + pop 0x38(%rax) + /* Get the new SSP. */ + rdsspq %r9 + /* restore original shadow stack */ + rstorssp -0x8(%r8) + /* save the restore token on the new shadow stack. */ + saveprevssp + + /* reserve space for the new SSP */ + leaq -0x8(%rax), %rax + /* save the new SSP to this fcontext */ + movq %r9, (%rax) +#endif ret /* return pointer to context-data */ +trampoline: + _CET_ENDBR + /* store return address on stack */ + /* fix stack alignment */ +#if SWOOLE_CONTEXT_SHADOW_STACK + /* save address of "jmp *%rbp" as return-address */ + /* on stack and shadow stack */ + call 2f + jmp *%rbp +2: +#else + push %rbp +#endif + /* jump to context-function */ + jmp *%rbx + finish: _CET_ENDBR /* exit code is zero */ @@ -78,7 +143,5 @@ finish: hlt .size swoole_make_fcontext,.-swoole_make_fcontext -#ifndef __NetBSD__ /* Mark that we don't need executable stack. */ .section .note.GNU-stack,"",%progbits -#endif diff --git a/thirdparty/boost/asm/make_x86_64_sysv_macho_gas.S b/thirdparty/boost/asm/make_x86_64_sysv_macho_gas.S index 79995c8833..46f8bd15cc 100644 --- a/thirdparty/boost/asm/make_x86_64_sysv_macho_gas.S +++ b/thirdparty/boost/asm/make_x86_64_sysv_macho_gas.S @@ -21,13 +21,6 @@ * ---------------------------------------------------------------------------------- * * | R15 | RBX | RBP | RIP | * * ---------------------------------------------------------------------------------- * - * ---------------------------------------------------------------------------------- * - * | 16 | 17 | | * - * ---------------------------------------------------------------------------------- * - * | 0x40 | 0x44 | | * - * ---------------------------------------------------------------------------------- * - * | EXIT | | * - * ---------------------------------------------------------------------------------- * * * ****************************************************************************************/ @@ -39,30 +32,42 @@ _swoole_make_fcontext: movq %rdi, %rax /* shift address in RAX to lower 16 byte boundary */ - movabs $-16, %r8 - andq %r8, %rax + andq $-16, %rax /* reserve space for context-data on context-stack */ - /* size for fc_mxcsr .. RIP + return-address for context-function */ /* on context-function entry: (RSP -0x8) % 16 == 0 */ - leaq -0x48(%rax), %rax + leaq -0x40(%rax), %rax /* third arg of swoole_make_fcontext() == address of context-function */ - movq %rdx, 0x38(%rax) + /* stored in RBX */ + movq %rdx, 0x28(%rax) /* save MMX control- and status-word */ stmxcsr (%rax) /* save x87 control-word */ fnstcw 0x4(%rax) + /* compute abs address of label trampoline */ + leaq trampoline(%rip), %rcx + /* save address of trampoline as return-address for context-function */ + /* will be entered after calling jump_fcontext() first time */ + movq %rcx, 0x38(%rax) + /* compute abs address of label finish */ leaq finish(%rip), %rcx /* save address of finish as return-address for context-function */ /* will be entered after context-function returns */ - movq %rcx, 0x40(%rax) + movq %rcx, 0x30(%rax) ret /* return pointer to context-data */ +trampoline: + /* store return address on stack */ + /* fix stack alignment */ + push %rbp + /* jump to context-function */ + jmp *%rbx + finish: /* exit code is zero */ xorq %rdi, %rdi diff --git a/thirdparty/hiredis/hiredis.c b/thirdparty/hiredis/hiredis.c index 0a01e68754..690fd86543 100644 --- a/thirdparty/hiredis/hiredis.c +++ b/thirdparty/hiredis/hiredis.c @@ -42,6 +42,7 @@ #include "net.h" #include "sds.h" +#define SW_HOOK_POLL_FAKE #include "swoole_socket_hook.h" extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout); @@ -176,6 +177,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) { return NULL; if (elements > 0) { + if (SIZE_MAX / sizeof(redisReply*) < elements) return NULL; /* Don't overflow */ r->element = hi_calloc(elements,sizeof(redisReply*)); if (r->element == NULL) { freeReplyObject(r); diff --git a/thirdparty/hiredis/sds.c b/thirdparty/hiredis/sds.c index 35baa057eb..a989fd59e6 100644 --- a/thirdparty/hiredis/sds.c +++ b/thirdparty/hiredis/sds.c @@ -68,6 +68,20 @@ static inline char sdsReqType(size_t string_size) { return SDS_TYPE_64; } +static inline size_t sdsTypeMaxSize(char type) { + if (type == SDS_TYPE_5) + return (1<<5) - 1; + if (type == SDS_TYPE_8) + return (1<<8) - 1; + if (type == SDS_TYPE_16) + return (1<<16) - 1; +#if (LONG_MAX == LLONG_MAX) + if (type == SDS_TYPE_32) + return (1ll<<32) - 1; +#endif + return -1; /* this is equivalent to the max SDS_TYPE_64 or SDS_TYPE_32 */ +} + /* Create a new sds string with the content specified by the 'init' pointer * and 'initlen'. * If NULL is used for 'init' the string is initialized with zero bytes. @@ -90,6 +104,7 @@ sds sdsnewlen(const void *init, size_t initlen) { int hdrlen = sdsHdrSize(type); unsigned char *fp; /* flags pointer. */ + if (initlen > SIZE_MAX - hdrlen - 1) return NULL; /* Catch size_t overflow */ sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (!init) @@ -193,7 +208,7 @@ void sdsclear(sds s) { * * Note: this does not change the *length* of the sds string as returned * by sdslen(), but only the free buffer space we have. */ -sds sdsMakeRoomFor(sds s, size_t addlen) { + sds sdsMakeRoomFor(sds s, size_t addlen) { void *sh, *newsh; size_t avail = sdsavail(s); size_t len, newlen; @@ -204,8 +219,12 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { if (avail >= addlen) return s; len = sdslen(s); - sh = (char*)s-sdsHdrSize(oldtype); - newlen = (len+addlen); + sh = (char*)s - sdsHdrSize(oldtype); + + /* Fix: Prevent Integer Overflow */ + if (addlen > SIZE_MAX - len) return NULL; /* Prevent overflow */ + newlen = len + addlen; + if (newlen < SDS_MAX_PREALLOC) newlen *= 2; else @@ -219,25 +238,34 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { if (type == SDS_TYPE_5) type = SDS_TYPE_8; hdrlen = sdsHdrSize(type); - if (oldtype==type) { - newsh = s_realloc(sh, hdrlen+newlen+1); + + /* Fix: Ensure safe memory allocation */ + if (hdrlen + newlen + 1 < newlen) return NULL; /* Prevent overflow */ + + if (oldtype == type) { + newsh = s_realloc(sh, hdrlen + newlen + 1); if (newsh == NULL) return NULL; - s = (char*)newsh+hdrlen; + s = (char*)newsh + hdrlen; } else { /* Since the header size changes, need to move the string forward, * and can't use realloc */ - newsh = s_malloc(hdrlen+newlen+1); + newsh = s_malloc(hdrlen + newlen + 1); if (newsh == NULL) return NULL; - memcpy((char*)newsh+hdrlen, s, len+1); + memcpy((char*)newsh + hdrlen, s, len + 1); s_free(sh); - s = (char*)newsh+hdrlen; + s = (char*)newsh + hdrlen; s[-1] = type; sdssetlen(s, len); } + + /* Fix: Prevent setting a too-large allocation */ + if (newlen > sdsTypeMaxSize(type)) newlen = sdsTypeMaxSize(type); sdssetalloc(s, newlen); + return s; } + /* Reallocate the sds string so that it has no free space at the end. The * contained string remains not altered, but next concatenation operations * will require a reallocation. diff --git a/thirdparty/llhttp/LICENSE b/thirdparty/llhttp/LICENSE new file mode 100644 index 0000000000..6c1512dd6b --- /dev/null +++ b/thirdparty/llhttp/LICENSE @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/llhttp/LICENSE-MIT b/thirdparty/llhttp/LICENSE-MIT new file mode 100644 index 0000000000..6c1512dd6b --- /dev/null +++ b/thirdparty/llhttp/LICENSE-MIT @@ -0,0 +1,22 @@ +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2018. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/llhttp/api.c b/thirdparty/llhttp/api.c new file mode 100644 index 0000000000..0245254177 --- /dev/null +++ b/thirdparty/llhttp/api.c @@ -0,0 +1,509 @@ +#include +#include +#include + +#include "llhttp.h" + +#define CALLBACK_MAYBE(PARSER, NAME) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER)); \ + } while (0) + +#define SPAN_CALLBACK_MAYBE(PARSER, NAME, START, LEN) \ + do { \ + const llhttp_settings_t* settings; \ + settings = (const llhttp_settings_t*) (PARSER)->settings; \ + if (settings == NULL || settings->NAME == NULL) { \ + err = 0; \ + break; \ + } \ + err = settings->NAME((PARSER), (START), (LEN)); \ + if (err == -1) { \ + err = HPE_USER; \ + llhttp_set_error_reason((PARSER), "Span callback error in " #NAME); \ + } \ + } while (0) + +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings) { + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; +} + + +#if defined(__wasm__) + +extern int wasm_on_message_begin(llhttp_t * p); +extern int wasm_on_url(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_status(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_field(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_header_value(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_headers_complete(llhttp_t * p, int status_code, + uint8_t upgrade, int should_keep_alive); +extern int wasm_on_body(llhttp_t* p, const char* at, size_t length); +extern int wasm_on_message_complete(llhttp_t * p); + +static int wasm_on_headers_complete_wrap(llhttp_t* p) { + return wasm_on_headers_complete(p, p->status_code, p->upgrade, + llhttp_should_keep_alive(p)); +} + +const llhttp_settings_t wasm_settings = { + .on_message_begin = wasm_on_message_begin, + .on_url = wasm_on_url, + .on_status = wasm_on_status, + .on_header_field = wasm_on_header_field, + .on_header_value = wasm_on_header_value, + .on_headers_complete = wasm_on_headers_complete_wrap, + .on_body = wasm_on_body, + .on_message_complete = wasm_on_message_complete, +}; + + +llhttp_t* llhttp_alloc(llhttp_type_t type) { + llhttp_t* parser = malloc(sizeof(llhttp_t)); + llhttp_init(parser, type, &wasm_settings); + return parser; +} + +void llhttp_free(llhttp_t* parser) { + free(parser); +} + +#endif // defined(__wasm__) + +/* Some getters required to get stuff from the parser */ + +uint8_t llhttp_get_type(llhttp_t* parser) { + return parser->type; +} + +uint8_t llhttp_get_http_major(llhttp_t* parser) { + return parser->http_major; +} + +uint8_t llhttp_get_http_minor(llhttp_t* parser) { + return parser->http_minor; +} + +uint8_t llhttp_get_method(llhttp_t* parser) { + return parser->method; +} + +int llhttp_get_status_code(llhttp_t* parser) { + return parser->status_code; +} + +uint8_t llhttp_get_upgrade(llhttp_t* parser) { + return parser->upgrade; +} + + +void llhttp_reset(llhttp_t* parser) { + llhttp_type_t type = parser->type; + const llhttp_settings_t* settings = parser->settings; + void* data = parser->data; + uint16_t lenient_flags = parser->lenient_flags; + + llhttp__internal_init(parser); + + parser->type = type; + parser->settings = (void*) settings; + parser->data = data; + parser->lenient_flags = lenient_flags; +} + + +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) { + return llhttp__internal_execute(parser, data, data + len); +} + + +void llhttp_settings_init(llhttp_settings_t* settings) { + memset(settings, 0, sizeof(*settings)); +} + + +llhttp_errno_t llhttp_finish(llhttp_t* parser) { + int err; + + /* We're in an error state. Don't bother doing anything. */ + if (parser->error != 0) { + return 0; + } + + switch (parser->finish) { + case HTTP_FINISH_SAFE_WITH_CB: + CALLBACK_MAYBE(parser, on_message_complete); + if (err != HPE_OK) return err; + + /* FALLTHROUGH */ + case HTTP_FINISH_SAFE: + return HPE_OK; + case HTTP_FINISH_UNSAFE: + parser->reason = "Invalid EOF state"; + return HPE_INVALID_EOF_STATE; + default: + abort(); + } +} + + +void llhttp_pause(llhttp_t* parser) { + if (parser->error != HPE_OK) { + return; + } + + parser->error = HPE_PAUSED; + parser->reason = "Paused"; +} + + +void llhttp_resume(llhttp_t* parser) { + if (parser->error != HPE_PAUSED) { + return; + } + + parser->error = 0; +} + + +void llhttp_resume_after_upgrade(llhttp_t* parser) { + if (parser->error != HPE_PAUSED_UPGRADE) { + return; + } + + parser->error = 0; +} + + +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) { + return parser->error; +} + + +const char* llhttp_get_error_reason(const llhttp_t* parser) { + return parser->reason; +} + + +void llhttp_set_error_reason(llhttp_t* parser, const char* reason) { + parser->reason = reason; +} + + +const char* llhttp_get_error_pos(const llhttp_t* parser) { + return parser->error_pos; +} + + +const char* llhttp_errno_name(llhttp_errno_t err) { +#define HTTP_ERRNO_GEN(CODE, NAME, _) case HPE_##NAME: return "HPE_" #NAME; + switch (err) { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) + default: abort(); + } +#undef HTTP_ERRNO_GEN +} + + +const char* llhttp_method_name(llhttp_method_t method) { +#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING; + switch (method) { + HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN) + default: abort(); + } +#undef HTTP_METHOD_GEN +} + +const char* llhttp_status_name(llhttp_status_t status) { +#define HTTP_STATUS_GEN(NUM, NAME, STRING) case HTTP_STATUS_##NAME: return #STRING; + switch (status) { + HTTP_STATUS_MAP(HTTP_STATUS_GEN) + default: abort(); + } +#undef HTTP_STATUS_GEN +} + + +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_HEADERS; + } else { + parser->lenient_flags &= ~LENIENT_HEADERS; + } +} + + +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_CHUNKED_LENGTH; + } else { + parser->lenient_flags &= ~LENIENT_CHUNKED_LENGTH; + } +} + + +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_KEEP_ALIVE; + } else { + parser->lenient_flags &= ~LENIENT_KEEP_ALIVE; + } +} + +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_TRANSFER_ENCODING; + } else { + parser->lenient_flags &= ~LENIENT_TRANSFER_ENCODING; + } +} + +void llhttp_set_lenient_version(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_VERSION; + } else { + parser->lenient_flags &= ~LENIENT_VERSION; + } +} + +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_DATA_AFTER_CLOSE; + } else { + parser->lenient_flags &= ~LENIENT_DATA_AFTER_CLOSE; + } +} + +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_LF_AFTER_CR; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_LF_AFTER_CR; + } +} + +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_CRLF_AFTER_CHUNK; + } +} + +void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_OPTIONAL_CR_BEFORE_LF; + } else { + parser->lenient_flags &= ~LENIENT_OPTIONAL_CR_BEFORE_LF; + } +} + +void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) { + if (enabled) { + parser->lenient_flags |= LENIENT_SPACES_AFTER_CHUNK_SIZE; + } else { + parser->lenient_flags &= ~LENIENT_SPACES_AFTER_CHUNK_SIZE; + } +} + +/* Callbacks */ + + +int llhttp__on_message_begin(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_begin); + return err; +} + + +int llhttp__on_protocol(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_protocol, p, endp - p); + return err; +} + + +int llhttp__on_protocol_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_protocol_complete); + return err; +} + + +int llhttp__on_url(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_url, p, endp - p); + return err; +} + + +int llhttp__on_url_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_url_complete); + return err; +} + + +int llhttp__on_status(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_status, p, endp - p); + return err; +} + + +int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_status_complete); + return err; +} + + +int llhttp__on_method(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_method, p, endp - p); + return err; +} + + +int llhttp__on_method_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_method_complete); + return err; +} + + +int llhttp__on_version(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_version, p, endp - p); + return err; +} + + +int llhttp__on_version_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_version_complete); + return err; +} + + +int llhttp__on_header_field(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_field, p, endp - p); + return err; +} + + +int llhttp__on_header_field_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_field_complete); + return err; +} + + +int llhttp__on_header_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_header_value, p, endp - p); + return err; +} + + +int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_header_value_complete); + return err; +} + + +int llhttp__on_headers_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_headers_complete); + return err; +} + + +int llhttp__on_message_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_message_complete); + return err; +} + + +int llhttp__on_body(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_body, p, endp - p); + return err; +} + + +int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_header); + return err; +} + + +int llhttp__on_chunk_extension_name(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_name, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_name_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_name_complete); + return err; +} + + +int llhttp__on_chunk_extension_value(llhttp_t* s, const char* p, const char* endp) { + int err; + SPAN_CALLBACK_MAYBE(s, on_chunk_extension_value, p, endp - p); + return err; +} + + +int llhttp__on_chunk_extension_value_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_extension_value_complete); + return err; +} + + +int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_chunk_complete); + return err; +} + + +int llhttp__on_reset(llhttp_t* s, const char* p, const char* endp) { + int err; + CALLBACK_MAYBE(s, on_reset); + return err; +} + + +/* Private */ + + +void llhttp__debug(llhttp_t* s, const char* p, const char* endp, + const char* msg) { + if (p == endp) { + fprintf(stderr, "p=%p type=%d flags=%02x next=null debug=%s\n", s, s->type, + s->flags, msg); + } else { + fprintf(stderr, "p=%p type=%d flags=%02x next=%02x debug=%s\n", s, + s->type, s->flags, *p, msg); + } +} diff --git a/core-tests/deps/llhttp/src/http.c b/thirdparty/llhttp/http.c similarity index 81% rename from core-tests/deps/llhttp/src/http.c rename to thirdparty/llhttp/http.c index 6e4906d840..1ab91a5579 100644 --- a/core-tests/deps/llhttp/src/http.c +++ b/thirdparty/llhttp/http.c @@ -39,19 +39,41 @@ int llhttp__after_headers_complete(llhttp_t* parser, const char* p, int hasBody; hasBody = parser->flags & F_CHUNKED || parser->content_length > 0; - if (parser->upgrade && (parser->method == HTTP_CONNECT || - (parser->flags & F_SKIPBODY) || !hasBody)) { + if ( + (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) || + /* See RFC 2616 section 4.4 - 1xx e.g. Continue */ + (parser->type == HTTP_RESPONSE && parser->status_code == 101) + ) { /* Exit, the rest of the message is in a different protocol. */ return 1; } - if (parser->flags & F_SKIPBODY) { + if (parser->type == HTTP_RESPONSE && parser->status_code == 100) { + /* No body, restart as the message is complete */ + return 0; + } + + /* See RFC 2616 section 4.4 */ + if ( + parser->flags & F_SKIPBODY || /* response to a HEAD request */ + ( + parser->type == HTTP_RESPONSE && ( + parser->status_code == 102 || /* Processing */ + parser->status_code == 103 || /* Early Hints */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 /* Not Modified */ + ) + ) + ) { return 0; } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header, prepare for a chunk */ return 2; } else if (parser->flags & F_TRANSFER_ENCODING) { - if (parser->type == HTTP_REQUEST && (parser->flags & F_LENIENT) == 0) { + if (parser->type == HTTP_REQUEST && + (parser->lenient_flags & LENIENT_CHUNKED_LENGTH) == 0 && + (parser->lenient_flags & LENIENT_TRANSFER_ENCODING) == 0) { /* RFC 7230 3.3.3 */ /* If a Transfer-Encoding header field @@ -97,9 +119,7 @@ int llhttp__after_message_complete(llhttp_t* parser, const char* p, should_keep_alive = llhttp_should_keep_alive(parser); parser->finish = HTTP_FINISH_SAFE; - - /* Keep `F_LENIENT` flag between messages, but reset every other flag */ - parser->flags &= F_LENIENT; + parser->flags = 0; /* NOTE: this is ignored in loose parsing mode */ return should_keep_alive; diff --git a/thirdparty/llhttp/llhttp.c b/thirdparty/llhttp/llhttp.c new file mode 100644 index 0000000000..aa4c468209 --- /dev/null +++ b/thirdparty/llhttp/llhttp.c @@ -0,0 +1,10099 @@ +#include +#include +#include + +#ifdef __SSE4_2__ + #ifdef _MSC_VER + #include + #else /* !_MSC_VER */ + #include + #endif /* _MSC_VER */ +#endif /* __SSE4_2__ */ + +#ifdef __ARM_NEON__ + #include +#endif /* __ARM_NEON__ */ + +#ifdef __wasm__ + #include +#endif /* __wasm__ */ + +#ifdef _MSC_VER + #define ALIGN(n) _declspec(align(n)) + #define UNREACHABLE __assume(0) +#else /* !_MSC_VER */ + #define ALIGN(n) __attribute__((aligned(n))) + #define UNREACHABLE __builtin_unreachable() +#endif /* _MSC_VER */ + +#include "llhttp.h" + +typedef int (*llhttp__internal__span_cb)( + llhttp__internal_t*, const char*, const char*); + +static const unsigned char llparse_blob0[] = { + 'o', 'n' +}; +static const unsigned char llparse_blob1[] = { + 'e', 'c', 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob2[] = { + 'l', 'o', 's', 'e' +}; +static const unsigned char llparse_blob3[] = { + 'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e' +}; +static const unsigned char llparse_blob4[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob5[] = { + 'c', 'h', 'u', 'n', 'k', 'e', 'd' +}; +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob6[] = { + 0x9, 0x9, ' ', '~', 0x80, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob7[] = { + '!', '!', '#', '\'', '*', '+', '-', '.', '0', '9', 'A', + 'Z', '^', 'z', '|', '|' +}; +#endif /* __SSE4_2__ */ +#ifdef __SSE4_2__ +static const unsigned char ALIGN(16) llparse_blob8[] = { + '~', '~', 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0 +}; +#endif /* __SSE4_2__ */ +static const unsigned char llparse_blob9[] = { + 'e', 'n', 't', '-', 'l', 'e', 'n', 'g', 't', 'h' +}; +static const unsigned char llparse_blob10[] = { + 'r', 'o', 'x', 'y', '-', 'c', 'o', 'n', 'n', 'e', 'c', + 't', 'i', 'o', 'n' +}; +static const unsigned char llparse_blob11[] = { + 'r', 'a', 'n', 's', 'f', 'e', 'r', '-', 'e', 'n', 'c', + 'o', 'd', 'i', 'n', 'g' +}; +static const unsigned char llparse_blob12[] = { + 'p', 'g', 'r', 'a', 'd', 'e' +}; +static const unsigned char llparse_blob13[] = { + 'T', 'T', 'P' +}; +static const unsigned char llparse_blob14[] = { + 0xd, 0xa, 0xd, 0xa, 'S', 'M', 0xd, 0xa, 0xd, 0xa +}; +static const unsigned char llparse_blob15[] = { + 'C', 'E' +}; +static const unsigned char llparse_blob16[] = { + 'T', 'S', 'P' +}; +static const unsigned char llparse_blob17[] = { + 'N', 'O', 'U', 'N', 'C', 'E' +}; +static const unsigned char llparse_blob18[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob19[] = { + 'E', 'C', 'K', 'O', 'U', 'T' +}; +static const unsigned char llparse_blob20[] = { + 'N', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob21[] = { + 'E', 'T', 'E' +}; +static const unsigned char llparse_blob22[] = { + 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob23[] = { + 'L', 'U', 'S', 'H' +}; +static const unsigned char llparse_blob24[] = { + 'E', 'T' +}; +static const unsigned char llparse_blob25[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob26[] = { + 'E', 'A', 'D' +}; +static const unsigned char llparse_blob27[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob28[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob29[] = { + 'S', 'E', 'A', 'R', 'C', 'H' +}; +static const unsigned char llparse_blob30[] = { + 'R', 'G', 'E' +}; +static const unsigned char llparse_blob31[] = { + 'C', 'T', 'I', 'V', 'I', 'T', 'Y' +}; +static const unsigned char llparse_blob32[] = { + 'L', 'E', 'N', 'D', 'A', 'R' +}; +static const unsigned char llparse_blob33[] = { + 'V', 'E' +}; +static const unsigned char llparse_blob34[] = { + 'O', 'T', 'I', 'F', 'Y' +}; +static const unsigned char llparse_blob35[] = { + 'P', 'T', 'I', 'O', 'N', 'S' +}; +static const unsigned char llparse_blob36[] = { + 'C', 'H' +}; +static const unsigned char llparse_blob37[] = { + 'S', 'E' +}; +static const unsigned char llparse_blob38[] = { + 'A', 'Y' +}; +static const unsigned char llparse_blob39[] = { + 'S', 'T' +}; +static const unsigned char llparse_blob40[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob41[] = { + 'A', 'T', 'C', 'H' +}; +static const unsigned char llparse_blob42[] = { + 'G', 'E' +}; +static const unsigned char llparse_blob43[] = { + 'U', 'E', 'R', 'Y' +}; +static const unsigned char llparse_blob44[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob45[] = { + 'O', 'R', 'D' +}; +static const unsigned char llparse_blob46[] = { + 'I', 'R', 'E', 'C', 'T' +}; +static const unsigned char llparse_blob47[] = { + 'O', 'R', 'T' +}; +static const unsigned char llparse_blob48[] = { + 'R', 'C', 'H' +}; +static const unsigned char llparse_blob49[] = { + 'P', 'A', 'R', 'A', 'M', 'E', 'T', 'E', 'R' +}; +static const unsigned char llparse_blob50[] = { + 'U', 'R', 'C', 'E' +}; +static const unsigned char llparse_blob51[] = { + 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob52[] = { + 'A', 'R', 'D', 'O', 'W', 'N' +}; +static const unsigned char llparse_blob53[] = { + 'A', 'C', 'E' +}; +static const unsigned char llparse_blob54[] = { + 'I', 'N', 'D' +}; +static const unsigned char llparse_blob55[] = { + 'N', 'K' +}; +static const unsigned char llparse_blob56[] = { + 'C', 'K' +}; +static const unsigned char llparse_blob57[] = { + 'U', 'B', 'S', 'C', 'R', 'I', 'B', 'E' +}; +static const unsigned char llparse_blob58[] = { + 'T', 'T', 'P' +}; +static const unsigned char llparse_blob59[] = { + 'C', 'E' +}; +static const unsigned char llparse_blob60[] = { + 'T', 'S', 'P' +}; +static const unsigned char llparse_blob61[] = { + 'A', 'D' +}; +static const unsigned char llparse_blob62[] = { + 'T', 'P', '/' +}; + +enum llparse_match_status_e { + kMatchComplete, + kMatchPause, + kMatchMismatch +}; +typedef enum llparse_match_status_e llparse_match_status_t; + +struct llparse_match_s { + llparse_match_status_t status; + const unsigned char* current; +}; +typedef struct llparse_match_s llparse_match_t; + +static llparse_match_t llparse__match_sequence_to_lower( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p)); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_to_lower_unsafe( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = ((*p) | 0x20); + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +static llparse_match_t llparse__match_sequence_id( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp, + const unsigned char* seq, uint32_t seq_len) { + uint32_t index; + llparse_match_t res; + + index = s->_index; + for (; p != endp; p++) { + unsigned char current; + + current = *p; + if (current == seq[index]) { + if (++index == seq_len) { + res.status = kMatchComplete; + goto reset; + } + } else { + res.status = kMatchMismatch; + goto reset; + } + } + s->_index = index; + res.status = kMatchPause; + res.current = p; + return res; +reset: + s->_index = 0; + res.current = p; + return res; +} + +enum llparse_state_e { + s_error, + s_n_llhttp__internal__n_closed, + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete, + s_n_llhttp__internal__n_pause_1, + s_n_llhttp__internal__n_invoke_is_equal_upgrade, + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2, + s_n_llhttp__internal__n_chunk_data_almost_done_1, + s_n_llhttp__internal__n_chunk_data_almost_done, + s_n_llhttp__internal__n_consume_content_length, + s_n_llhttp__internal__n_span_start_llhttp__on_body, + s_n_llhttp__internal__n_invoke_is_equal_content_length, + s_n_llhttp__internal__n_chunk_size_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_9, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2, + s_n_llhttp__internal__n_invoke_test_lenient_flags_10, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1, + s_n_llhttp__internal__n_chunk_extension_quoted_value_done, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2, + s_n_llhttp__internal__n_error_30, + s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair, + s_n_llhttp__internal__n_error_31, + s_n_llhttp__internal__n_chunk_extension_quoted_value, + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3, + s_n_llhttp__internal__n_error_33, + s_n_llhttp__internal__n_chunk_extension_value, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value, + s_n_llhttp__internal__n_error_34, + s_n_llhttp__internal__n_chunk_extension_name, + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name, + s_n_llhttp__internal__n_chunk_extensions, + s_n_llhttp__internal__n_chunk_size_otherwise, + s_n_llhttp__internal__n_chunk_size, + s_n_llhttp__internal__n_chunk_size_digit, + s_n_llhttp__internal__n_invoke_update_content_length_1, + s_n_llhttp__internal__n_consume_content_length_1, + s_n_llhttp__internal__n_span_start_llhttp__on_body_1, + s_n_llhttp__internal__n_eof, + s_n_llhttp__internal__n_span_start_llhttp__on_body_2, + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete, + s_n_llhttp__internal__n_error_5, + s_n_llhttp__internal__n_headers_almost_done, + s_n_llhttp__internal__n_header_field_colon_discard_ws, + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value, + s_n_llhttp__internal__n_header_value_discard_lws, + s_n_llhttp__internal__n_header_value_discard_ws_almost_done, + s_n_llhttp__internal__n_header_value_lws, + s_n_llhttp__internal__n_header_value_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_17, + s_n_llhttp__internal__n_header_value_lenient, + s_n_llhttp__internal__n_error_54, + s_n_llhttp__internal__n_header_value_otherwise, + s_n_llhttp__internal__n_header_value_connection_token, + s_n_llhttp__internal__n_header_value_connection_ws, + s_n_llhttp__internal__n_header_value_connection_1, + s_n_llhttp__internal__n_header_value_connection_2, + s_n_llhttp__internal__n_header_value_connection_3, + s_n_llhttp__internal__n_header_value_connection, + s_n_llhttp__internal__n_error_56, + s_n_llhttp__internal__n_error_57, + s_n_llhttp__internal__n_header_value_content_length_ws, + s_n_llhttp__internal__n_header_value_content_length, + s_n_llhttp__internal__n_error_59, + s_n_llhttp__internal__n_error_58, + s_n_llhttp__internal__n_header_value_te_token_ows, + s_n_llhttp__internal__n_header_value, + s_n_llhttp__internal__n_header_value_te_token, + s_n_llhttp__internal__n_header_value_te_chunked_last, + s_n_llhttp__internal__n_header_value_te_chunked, + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1, + s_n_llhttp__internal__n_header_value_discard_ws, + s_n_llhttp__internal__n_invoke_load_header_state, + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete, + s_n_llhttp__internal__n_header_field_general_otherwise, + s_n_llhttp__internal__n_header_field_general, + s_n_llhttp__internal__n_header_field_colon, + s_n_llhttp__internal__n_header_field_3, + s_n_llhttp__internal__n_header_field_4, + s_n_llhttp__internal__n_header_field_2, + s_n_llhttp__internal__n_header_field_1, + s_n_llhttp__internal__n_header_field_5, + s_n_llhttp__internal__n_header_field_6, + s_n_llhttp__internal__n_header_field_7, + s_n_llhttp__internal__n_header_field, + s_n_llhttp__internal__n_span_start_llhttp__on_header_field, + s_n_llhttp__internal__n_header_field_start, + s_n_llhttp__internal__n_headers_start, + s_n_llhttp__internal__n_url_to_http_09, + s_n_llhttp__internal__n_url_skip_to_http09, + s_n_llhttp__internal__n_url_skip_lf_to_http09_1, + s_n_llhttp__internal__n_url_skip_lf_to_http09, + s_n_llhttp__internal__n_req_pri_upgrade, + s_n_llhttp__internal__n_req_http_complete_crlf, + s_n_llhttp__internal__n_req_http_complete, + s_n_llhttp__internal__n_invoke_load_method_1, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete, + s_n_llhttp__internal__n_error_67, + s_n_llhttp__internal__n_error_74, + s_n_llhttp__internal__n_req_http_minor, + s_n_llhttp__internal__n_error_75, + s_n_llhttp__internal__n_req_http_dot, + s_n_llhttp__internal__n_error_76, + s_n_llhttp__internal__n_req_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version, + s_n_llhttp__internal__n_req_after_protocol, + s_n_llhttp__internal__n_invoke_load_method, + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete, + s_n_llhttp__internal__n_error_82, + s_n_llhttp__internal__n_req_after_http_start_1, + s_n_llhttp__internal__n_invoke_load_method_2, + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1, + s_n_llhttp__internal__n_req_after_http_start_2, + s_n_llhttp__internal__n_invoke_load_method_3, + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2, + s_n_llhttp__internal__n_req_after_http_start_3, + s_n_llhttp__internal__n_req_after_http_start, + s_n_llhttp__internal__n_span_start_llhttp__on_protocol, + s_n_llhttp__internal__n_req_http_start, + s_n_llhttp__internal__n_url_to_http, + s_n_llhttp__internal__n_url_skip_to_http, + s_n_llhttp__internal__n_url_fragment, + s_n_llhttp__internal__n_span_end_stub_query_3, + s_n_llhttp__internal__n_url_query, + s_n_llhttp__internal__n_url_query_or_fragment, + s_n_llhttp__internal__n_url_path, + s_n_llhttp__internal__n_span_start_stub_path_2, + s_n_llhttp__internal__n_span_start_stub_path, + s_n_llhttp__internal__n_span_start_stub_path_1, + s_n_llhttp__internal__n_url_server_with_at, + s_n_llhttp__internal__n_url_server, + s_n_llhttp__internal__n_url_schema_delim_1, + s_n_llhttp__internal__n_url_schema_delim, + s_n_llhttp__internal__n_span_end_stub_schema, + s_n_llhttp__internal__n_url_schema, + s_n_llhttp__internal__n_url_start, + s_n_llhttp__internal__n_span_start_llhttp__on_url_1, + s_n_llhttp__internal__n_url_entry_normal, + s_n_llhttp__internal__n_span_start_llhttp__on_url, + s_n_llhttp__internal__n_url_entry_connect, + s_n_llhttp__internal__n_req_spaces_before_url, + s_n_llhttp__internal__n_req_first_space_before_url, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1, + s_n_llhttp__internal__n_after_start_req_2, + s_n_llhttp__internal__n_after_start_req_3, + s_n_llhttp__internal__n_after_start_req_1, + s_n_llhttp__internal__n_after_start_req_4, + s_n_llhttp__internal__n_after_start_req_6, + s_n_llhttp__internal__n_after_start_req_8, + s_n_llhttp__internal__n_after_start_req_9, + s_n_llhttp__internal__n_after_start_req_7, + s_n_llhttp__internal__n_after_start_req_5, + s_n_llhttp__internal__n_after_start_req_12, + s_n_llhttp__internal__n_after_start_req_13, + s_n_llhttp__internal__n_after_start_req_11, + s_n_llhttp__internal__n_after_start_req_10, + s_n_llhttp__internal__n_after_start_req_14, + s_n_llhttp__internal__n_after_start_req_17, + s_n_llhttp__internal__n_after_start_req_16, + s_n_llhttp__internal__n_after_start_req_15, + s_n_llhttp__internal__n_after_start_req_18, + s_n_llhttp__internal__n_after_start_req_20, + s_n_llhttp__internal__n_after_start_req_21, + s_n_llhttp__internal__n_after_start_req_19, + s_n_llhttp__internal__n_after_start_req_23, + s_n_llhttp__internal__n_after_start_req_24, + s_n_llhttp__internal__n_after_start_req_26, + s_n_llhttp__internal__n_after_start_req_28, + s_n_llhttp__internal__n_after_start_req_29, + s_n_llhttp__internal__n_after_start_req_27, + s_n_llhttp__internal__n_after_start_req_25, + s_n_llhttp__internal__n_after_start_req_30, + s_n_llhttp__internal__n_after_start_req_22, + s_n_llhttp__internal__n_after_start_req_31, + s_n_llhttp__internal__n_after_start_req_32, + s_n_llhttp__internal__n_after_start_req_35, + s_n_llhttp__internal__n_after_start_req_36, + s_n_llhttp__internal__n_after_start_req_34, + s_n_llhttp__internal__n_after_start_req_37, + s_n_llhttp__internal__n_after_start_req_38, + s_n_llhttp__internal__n_after_start_req_42, + s_n_llhttp__internal__n_after_start_req_43, + s_n_llhttp__internal__n_after_start_req_41, + s_n_llhttp__internal__n_after_start_req_40, + s_n_llhttp__internal__n_after_start_req_39, + s_n_llhttp__internal__n_after_start_req_45, + s_n_llhttp__internal__n_after_start_req_44, + s_n_llhttp__internal__n_after_start_req_33, + s_n_llhttp__internal__n_after_start_req_46, + s_n_llhttp__internal__n_after_start_req_49, + s_n_llhttp__internal__n_after_start_req_50, + s_n_llhttp__internal__n_after_start_req_51, + s_n_llhttp__internal__n_after_start_req_52, + s_n_llhttp__internal__n_after_start_req_48, + s_n_llhttp__internal__n_after_start_req_47, + s_n_llhttp__internal__n_after_start_req_55, + s_n_llhttp__internal__n_after_start_req_57, + s_n_llhttp__internal__n_after_start_req_58, + s_n_llhttp__internal__n_after_start_req_56, + s_n_llhttp__internal__n_after_start_req_54, + s_n_llhttp__internal__n_after_start_req_59, + s_n_llhttp__internal__n_after_start_req_60, + s_n_llhttp__internal__n_after_start_req_53, + s_n_llhttp__internal__n_after_start_req_62, + s_n_llhttp__internal__n_after_start_req_63, + s_n_llhttp__internal__n_after_start_req_61, + s_n_llhttp__internal__n_after_start_req_66, + s_n_llhttp__internal__n_after_start_req_68, + s_n_llhttp__internal__n_after_start_req_69, + s_n_llhttp__internal__n_after_start_req_67, + s_n_llhttp__internal__n_after_start_req_70, + s_n_llhttp__internal__n_after_start_req_65, + s_n_llhttp__internal__n_after_start_req_64, + s_n_llhttp__internal__n_after_start_req, + s_n_llhttp__internal__n_span_start_llhttp__on_method_1, + s_n_llhttp__internal__n_res_line_almost_done, + s_n_llhttp__internal__n_invoke_test_lenient_flags_30, + s_n_llhttp__internal__n_res_status, + s_n_llhttp__internal__n_span_start_llhttp__on_status, + s_n_llhttp__internal__n_res_status_code_otherwise, + s_n_llhttp__internal__n_res_status_code_digit_3, + s_n_llhttp__internal__n_res_status_code_digit_2, + s_n_llhttp__internal__n_res_status_code_digit_1, + s_n_llhttp__internal__n_res_after_version, + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1, + s_n_llhttp__internal__n_error_93, + s_n_llhttp__internal__n_error_107, + s_n_llhttp__internal__n_res_http_minor, + s_n_llhttp__internal__n_error_108, + s_n_llhttp__internal__n_res_http_dot, + s_n_llhttp__internal__n_error_109, + s_n_llhttp__internal__n_res_http_major, + s_n_llhttp__internal__n_span_start_llhttp__on_version_1, + s_n_llhttp__internal__n_res_after_protocol, + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3, + s_n_llhttp__internal__n_error_115, + s_n_llhttp__internal__n_res_after_start_1, + s_n_llhttp__internal__n_res_after_start_2, + s_n_llhttp__internal__n_res_after_start_3, + s_n_llhttp__internal__n_res_after_start, + s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1, + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete, + s_n_llhttp__internal__n_req_or_res_method_2, + s_n_llhttp__internal__n_invoke_update_type_1, + s_n_llhttp__internal__n_req_or_res_method_3, + s_n_llhttp__internal__n_req_or_res_method_1, + s_n_llhttp__internal__n_req_or_res_method, + s_n_llhttp__internal__n_span_start_llhttp__on_method, + s_n_llhttp__internal__n_start_req_or_res, + s_n_llhttp__internal__n_invoke_load_type, + s_n_llhttp__internal__n_invoke_update_finish, + s_n_llhttp__internal__n_start, +}; +typedef enum llparse_state_e llparse_state_t; + +int llhttp__on_method( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_url( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_protocol( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_version( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_field( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_header_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_body( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_name( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_status( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->initial_message_completed; +} + +int llhttp__on_reset( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_finish( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 2; + return 0; +} + +int llhttp__on_message_begin( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->type; +} + +int llhttp__internal__c_store_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->method = match; + return 0; +} + +int llhttp__on_method_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method == 5; +} + +int llhttp__internal__c_update_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_major = 0; + return 0; +} + +int llhttp__internal__c_update_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->http_minor = 9; + return 0; +} + +int llhttp__on_url_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_test_lenient_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 1) == 1; +} + +int llhttp__internal__c_test_lenient_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 256) == 256; +} + +int llhttp__internal__c_test_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 128) == 128; +} + +int llhttp__on_chunk_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->upgrade == 1; +} + +int llhttp__after_message_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->content_length = 0; + return 0; +} + +int llhttp__internal__c_update_initial_message_completed( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->initial_message_completed = 1; + return 0; +} + +int llhttp__internal__c_update_finish_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 0; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 4) == 4; +} + +int llhttp__internal__c_test_lenient_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 32) == 32; +} + +int llhttp__before_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__after_headers_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_mul_add_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 16) { + return 1; + } + + state->content_length *= 16; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 512) == 512; +} + +int llhttp__on_chunk_header( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_is_equal_content_length( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->content_length == 0; +} + +int llhttp__internal__c_test_lenient_flags_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 128) == 128; +} + +int llhttp__internal__c_or_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 128; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 64) == 64; +} + +int llhttp__on_chunk_extension_name_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__on_chunk_extension_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_finish_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->finish = 1; + return 0; +} + +int llhttp__internal__c_or_flags_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 64; + return 0; +} + +int llhttp__internal__c_update_upgrade( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->upgrade = 1; + return 0; +} + +int llhttp__internal__c_store_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->header_state = match; + return 0; +} + +int llhttp__on_header_field_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->header_state; +} + +int llhttp__internal__c_test_flags_4( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 512) == 512; +} + +int llhttp__internal__c_test_lenient_flags_22( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 2) == 2; +} + +int llhttp__internal__c_or_flags_5( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 1; + return 0; +} + +int llhttp__internal__c_update_header_state( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 1; + return 0; +} + +int llhttp__on_header_value_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_or_flags_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 2; + return 0; +} + +int llhttp__internal__c_or_flags_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 4; + return 0; +} + +int llhttp__internal__c_or_flags_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 8; + return 0; +} + +int llhttp__internal__c_update_header_state_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 6; + return 0; +} + +int llhttp__internal__c_update_header_state_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 0; + return 0; +} + +int llhttp__internal__c_update_header_state_6( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 5; + return 0; +} + +int llhttp__internal__c_update_header_state_7( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 7; + return 0; +} + +int llhttp__internal__c_test_flags_2( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 32) == 32; +} + +int llhttp__internal__c_mul_add_content_length_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->content_length > 0xffffffffffffffffULL / 10) { + return 1; + } + + state->content_length *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->content_length > 0xffffffffffffffffULL - match) { + return 1; + } + } else { + if (state->content_length < 0ULL - match) { + return 1; + } + } + state->content_length += match; + return 0; +} + +int llhttp__internal__c_or_flags_17( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 32; + return 0; +} + +int llhttp__internal__c_test_flags_3( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->flags & 8) == 8; +} + +int llhttp__internal__c_test_lenient_flags_20( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 8) == 8; +} + +int llhttp__internal__c_or_flags_18( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 512; + return 0; +} + +int llhttp__internal__c_and_flags( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags &= -9; + return 0; +} + +int llhttp__internal__c_update_header_state_8( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->header_state = 8; + return 0; +} + +int llhttp__internal__c_or_flags_20( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->flags |= 16; + return 0; +} + +int llhttp__on_protocol_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_method( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->method; +} + +int llhttp__internal__c_store_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_major = match; + return 0; +} + +int llhttp__internal__c_store_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + state->http_minor = match; + return 0; +} + +int llhttp__internal__c_test_lenient_flags_24( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return (state->lenient_flags & 16) == 16; +} + +int llhttp__on_version_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_load_http_major( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_major; +} + +int llhttp__internal__c_load_http_minor( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + return state->http_minor; +} + +int llhttp__internal__c_update_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->status_code = 0; + return 0; +} + +int llhttp__internal__c_mul_add_status_code( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp, + int match) { + /* Multiplication overflow */ + if (state->status_code > 0xffff / 10) { + return 1; + } + + state->status_code *= 10; + + /* Addition overflow */ + if (match >= 0) { + if (state->status_code > 0xffff - match) { + return 1; + } + } else { + if (state->status_code < 0 - match) { + return 1; + } + } + state->status_code += match; + return 0; +} + +int llhttp__on_status_complete( + llhttp__internal_t* s, const unsigned char* p, + const unsigned char* endp); + +int llhttp__internal__c_update_type( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 1; + return 0; +} + +int llhttp__internal__c_update_type_1( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + state->type = 2; + return 0; +} + +int llhttp__internal_init(llhttp__internal_t* state) { + memset(state, 0, sizeof(*state)); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_start; + return 0; +} + +static llparse_state_t llhttp__internal__run( + llhttp__internal_t* state, + const unsigned char* p, + const unsigned char* endp) { + int match; + switch ((llparse_state_t) (intptr_t) state->_current) { + case s_n_llhttp__internal__n_closed: + s_n_llhttp__internal__n_closed: { + if (p == endp) { + return s_n_llhttp__internal__n_closed; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_closed; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_closed; + } + default: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_message_complete: { + switch (llhttp__after_message_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_content_length; + default: + goto s_n_llhttp__internal__n_invoke_update_finish_1; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_pause_1: + s_n_llhttp__internal__n_pause_1: { + state->error = 0x16; + state->reason = "Pause on CONNECT/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_is_equal_upgrade: + s_n_llhttp__internal__n_invoke_is_equal_upgrade: { + switch (llhttp__internal__c_is_equal_upgrade(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + default: + goto s_n_llhttp__internal__n_pause_1; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_13; + default: + goto s_n_llhttp__internal__n_error_38; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_data_almost_done_1: + s_n_llhttp__internal__n_chunk_data_almost_done_1: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_data_almost_done_1; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_data_almost_done: + s_n_llhttp__internal__n_chunk_data_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_data_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_6; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_data_almost_done_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_7; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_consume_content_length: + s_n_llhttp__internal__n_consume_content_length: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length; + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body: + s_n_llhttp__internal__n_span_start_llhttp__on_body: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length; + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_is_equal_content_length: + s_n_llhttp__internal__n_invoke_is_equal_content_length: { + switch (llhttp__internal__c_is_equal_content_length(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body; + default: + goto s_n_llhttp__internal__n_invoke_or_flags; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_size_almost_done: + s_n_llhttp__internal__n_chunk_size_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_8; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_9: + s_n_llhttp__internal__n_invoke_test_lenient_flags_9: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_20; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_9; + case 21: + goto s_n_llhttp__internal__n_pause_5; + default: + goto s_n_llhttp__internal__n_error_19; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_6; + default: + goto s_n_llhttp__internal__n_error_21; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extensions; + case 21: + goto s_n_llhttp__internal__n_pause_7; + default: + goto s_n_llhttp__internal__n_error_22; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_10: + s_n_llhttp__internal__n_invoke_test_lenient_flags_10: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_25; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + case 21: + goto s_n_llhttp__internal__n_pause_8; + default: + goto s_n_llhttp__internal__n_error_24; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + case 21: + goto s_n_llhttp__internal__n_pause_9; + default: + goto s_n_llhttp__internal__n_error_26; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value_done: + s_n_llhttp__internal__n_chunk_extension_quoted_value_done: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_11; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_29; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + case 21: + goto s_n_llhttp__internal__n_pause_10; + default: + goto s_n_llhttp__internal__n_error_27; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_30: + s_n_llhttp__internal__n_error_30: { + state->error = 0x2; + state->reason = "Invalid quoted-pair in chunk extensions quoted value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: + s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_31: + s_n_llhttp__internal__n_error_31: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quoted value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extension_quoted_value: + s_n_llhttp__internal__n_chunk_extension_quoted_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value_quoted_pair; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3: { + switch (llhttp__on_chunk_extension_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extensions; + case 21: + goto s_n_llhttp__internal__n_pause_11; + default: + goto s_n_llhttp__internal__n_error_32; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_33: + s_n_llhttp__internal__n_error_33: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extension_value: + s_n_llhttp__internal__n_chunk_extension_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 4, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 5, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_value; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_value; + } + case 4: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_quoted_value; + } + case 5: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_value; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3; + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_34: + s_n_llhttp__internal__n_error_34: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions name"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extension_name: + s_n_llhttp__internal__n_chunk_extension_name: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 0, 3, 3, 3, 3, 3, 0, 0, 3, 3, 0, 3, 3, 0, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 4, 0, 5, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 0, 3, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extension_name; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_chunk_extension_name; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2; + } + case 5: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: + s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_chunk_extension_name; + goto s_n_llhttp__internal__n_chunk_extension_name; + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_extensions: + s_n_llhttp__internal__n_chunk_extensions: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_extensions; + } + switch (*p) { + case 13: { + p++; + goto s_n_llhttp__internal__n_error_17; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_18; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_name; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_size_otherwise: + s_n_llhttp__internal__n_chunk_size_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_otherwise; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_5; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_chunk_size_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_4; + } + case ';': { + p++; + goto s_n_llhttp__internal__n_chunk_extensions; + } + default: { + goto s_n_llhttp__internal__n_error_35; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_size: + s_n_llhttp__internal__n_chunk_size: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_chunk_size_otherwise; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_chunk_size_digit: + s_n_llhttp__internal__n_chunk_size_digit: { + if (p == endp) { + return s_n_llhttp__internal__n_chunk_size_digit; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'A': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'B': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'C': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'D': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'E': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'F': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'a': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'b': { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'c': { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'd': { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'e': { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + case 'f': { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length; + } + default: { + goto s_n_llhttp__internal__n_error_37; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_update_content_length_1: + s_n_llhttp__internal__n_invoke_update_content_length_1: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_chunk_size_digit; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_consume_content_length_1: + s_n_llhttp__internal__n_consume_content_length_1: { + size_t avail; + uint64_t need; + + avail = endp - p; + need = state->content_length; + if (avail >= need) { + p += need; + state->content_length = 0; + goto s_n_llhttp__internal__n_span_end_llhttp__on_body_1; + } + + state->content_length -= avail; + return s_n_llhttp__internal__n_consume_content_length_1; + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_1: + s_n_llhttp__internal__n_span_start_llhttp__on_body_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_consume_content_length_1; + UNREACHABLE; + } + case s_n_llhttp__internal__n_eof: + s_n_llhttp__internal__n_eof: { + if (p == endp) { + return s_n_llhttp__internal__n_eof; + } + p++; + goto s_n_llhttp__internal__n_eof; + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_body_2: + s_n_llhttp__internal__n_span_start_llhttp__on_body_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_body; + goto s_n_llhttp__internal__n_eof; + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: + s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete: { + switch (llhttp__after_headers_complete(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 3: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_1; + case 4: + goto s_n_llhttp__internal__n_invoke_update_finish_3; + case 5: + goto s_n_llhttp__internal__n_error_39; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_5: + s_n_llhttp__internal__n_error_5: { + state->error = 0xa; + state->reason = "Invalid header field char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_headers_almost_done: + s_n_llhttp__internal__n_headers_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_flags_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_12; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_colon_discard_ws: + s_n_llhttp__internal__n_header_field_colon_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_header_field_colon; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete: { + switch (llhttp__on_header_value_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_field_start; + case 21: + goto s_n_llhttp__internal__n_pause_18; + default: + goto s_n_llhttp__internal__n_error_48; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_discard_lws: + s_n_llhttp__internal__n_header_value_discard_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_lws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_15; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state_1; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_discard_ws_almost_done: + s_n_llhttp__internal__n_header_value_discard_ws_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_lws; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_16; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_lws: + s_n_llhttp__internal__n_header_value_lws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lws; + } + switch (*p) { + case 9: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; + } + case ' ': { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_18; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_header_state_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_almost_done: + s_n_llhttp__internal__n_header_value_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_header_value_lws; + } + default: { + goto s_n_llhttp__internal__n_error_53; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_17: + s_n_llhttp__internal__n_invoke_test_lenient_flags_17: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_almost_done; + default: + goto s_n_llhttp__internal__n_error_51; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_lenient: + s_n_llhttp__internal__n_header_value_lenient: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_lenient; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5; + } + default: { + p++; + goto s_n_llhttp__internal__n_header_value_lenient; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_54: + s_n_llhttp__internal__n_error_54: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_otherwise: + s_n_llhttp__internal__n_header_value_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_otherwise; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_19; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection_token: + s_n_llhttp__internal__n_header_value_connection_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_connection_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection_ws: + s_n_llhttp__internal__n_header_value_connection_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case 13: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + case ',': { + p++; + goto s_n_llhttp__internal__n_invoke_load_header_state_6; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection_1: + s_n_llhttp__internal__n_header_value_connection_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob2, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_3; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection_2: + s_n_llhttp__internal__n_header_value_connection_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_2; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob3, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_6; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection_3: + s_n_llhttp__internal__n_header_value_connection_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob4, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_invoke_update_header_state_7; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_connection_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_connection: + s_n_llhttp__internal__n_header_value_connection: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_connection; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_connection; + } + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_1; + } + case 'k': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_2; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_value_connection_3; + } + default: { + goto s_n_llhttp__internal__n_header_value_connection_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_56: + s_n_llhttp__internal__n_error_56: { + state->error = 0xb; + state->reason = "Content-Length overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_57: + s_n_llhttp__internal__n_error_57: { + state->error = 0xb; + state->reason = "Invalid character in Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_content_length_ws: + s_n_llhttp__internal__n_header_value_content_length_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length_ws; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_or_flags_17; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_content_length: + s_n_llhttp__internal__n_header_value_content_length: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_content_length; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_content_length_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_content_length_ws; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_59: + s_n_llhttp__internal__n_error_59: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_58: + s_n_llhttp__internal__n_error_58: { + state->error = 0xf; + state->reason = "Invalid `Transfer-Encoding` header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_te_token_ows: + s_n_llhttp__internal__n_header_value_te_token_ows: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token_ows; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value: + s_n_llhttp__internal__n_header_value: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob6); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 6, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_value; + } + goto s_n_llhttp__internal__n_header_value_otherwise; + } + #endif /* __SSE4_2__ */ + #ifdef __ARM_NEON__ + while (endp - p >= 16) { + uint8x16_t input; + uint8x16_t single; + uint8x16_t mask; + uint8x8_t narrow; + uint64_t match_mask; + int match_len; + + /* Load input */ + input = vld1q_u8(p); + /* Find first character that does not match `ranges` */ + single = vceqq_u8(input, vdupq_n_u8(0x9)); + mask = single; + single = vandq_u16( + vcgeq_u8(input, vdupq_n_u8(' ')), + vcleq_u8(input, vdupq_n_u8('~')) + ); + mask = vorrq_u16(mask, single); + single = vandq_u16( + vcgeq_u8(input, vdupq_n_u8(0x80)), + vcleq_u8(input, vdupq_n_u8(0xff)) + ); + mask = vorrq_u16(mask, single); + narrow = vshrn_n_u16(mask, 4); + match_mask = ~vget_lane_u64(vreinterpret_u64_u8(narrow), 0); + match_len = __builtin_ctzll(match_mask) >> 2; + if (match_len != 16) { + p += match_len; + goto s_n_llhttp__internal__n_header_value_otherwise; + } + p += 16; + } + if (p == endp) { + return s_n_llhttp__internal__n_header_value; + } + #endif /* __ARM_NEON__ */ + #ifdef __wasm_simd128__ + while (endp - p >= 16) { + v128_t input; + v128_t mask; + v128_t single; + int match_len; + + /* Load input */ + input = wasm_v128_load(p); + /* Find first character that does not match `ranges` */ + single = wasm_i8x16_eq(input, wasm_u8x16_const_splat(0x9)); + mask = single; + single = wasm_v128_and( + wasm_i8x16_ge(input, wasm_u8x16_const_splat(' ')), + wasm_i8x16_le(input, wasm_u8x16_const_splat('~')) + ); + mask = wasm_v128_or(mask, single); + single = wasm_v128_and( + wasm_i8x16_ge(input, wasm_u8x16_const_splat(0x80)), + wasm_i8x16_le(input, wasm_u8x16_const_splat(0xff)) + ); + mask = wasm_v128_or(mask, single); + match_len = __builtin_ctz( + ~wasm_i8x16_bitmask(mask) + ); + if (match_len != 16) { + p += match_len; + goto s_n_llhttp__internal__n_header_value_otherwise; + } + p += 16; + } + if (p == endp) { + return s_n_llhttp__internal__n_header_value; + } + #endif /* __wasm_simd128__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value; + } + default: { + goto s_n_llhttp__internal__n_header_value_otherwise; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_te_token: + s_n_llhttp__internal__n_header_value_te_token: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_token; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_header_value_te_token_ows; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_te_chunked_last: + s_n_llhttp__internal__n_header_value_te_chunked_last: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked_last; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case 13: { + goto s_n_llhttp__internal__n_invoke_update_header_state_8; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case ',': { + goto s_n_llhttp__internal__n_invoke_load_type_1; + } + default: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_te_chunked: + s_n_llhttp__internal__n_header_value_te_chunked: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + match_seq = llparse__match_sequence_to_lower_unsafe(state, p, endp, llparse_blob5, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_value_te_chunked_last; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_value_te_chunked; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_header_value_te_token; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: + s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_value; + goto s_n_llhttp__internal__n_invoke_load_header_state_3; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_value_discard_ws: + s_n_llhttp__internal__n_header_value_discard_ws: { + if (p == endp) { + return s_n_llhttp__internal__n_header_value_discard_ws; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_14; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_header_state: + s_n_llhttp__internal__n_invoke_load_header_state: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 2: + goto s_n_llhttp__internal__n_invoke_test_flags_4; + case 3: + goto s_n_llhttp__internal__n_invoke_test_flags_5; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete: { + switch (llhttp__on_header_field_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_header_state; + case 21: + goto s_n_llhttp__internal__n_pause_19; + default: + goto s_n_llhttp__internal__n_error_45; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_general_otherwise: + s_n_llhttp__internal__n_header_field_general_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general_otherwise; + } + switch (*p) { + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2; + } + default: { + goto s_n_llhttp__internal__n_error_62; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_general: + s_n_llhttp__internal__n_header_field_general: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_header_field_general; + } + #ifdef __SSE4_2__ + if (endp - p >= 16) { + __m128i ranges; + __m128i input; + int match_len; + + /* Load input */ + input = _mm_loadu_si128((__m128i const*) p); + ranges = _mm_loadu_si128((__m128i const*) llparse_blob7); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 16, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + ranges = _mm_loadu_si128((__m128i const*) llparse_blob8); + + /* Find first character that does not match `ranges` */ + match_len = _mm_cmpestri(ranges, 2, + input, 16, + _SIDD_UBYTE_OPS | _SIDD_CMP_RANGES | + _SIDD_NEGATIVE_POLARITY); + + if (match_len != 0) { + p += match_len; + goto s_n_llhttp__internal__n_header_field_general; + } + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + #endif /* __SSE4_2__ */ + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_header_field_general; + } + default: { + goto s_n_llhttp__internal__n_header_field_general_otherwise; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_colon: + s_n_llhttp__internal__n_header_field_colon: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_colon; + } + switch (*p) { + case ' ': { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_13; + } + case ':': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_10; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_3: + s_n_llhttp__internal__n_header_field_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_3; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob1, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_4: + s_n_llhttp__internal__n_header_field_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_4; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob9, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_2: + s_n_llhttp__internal__n_header_field_2: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_2; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'n': { + p++; + goto s_n_llhttp__internal__n_header_field_3; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_4; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_1: + s_n_llhttp__internal__n_header_field_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_1; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob0, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_header_field_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_5: + s_n_llhttp__internal__n_header_field_5: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_5; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob10, 15); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_5; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_6: + s_n_llhttp__internal__n_header_field_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_6; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob11, 16); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_7: + s_n_llhttp__internal__n_header_field_7: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_header_field_7; + } + match_seq = llparse__match_sequence_to_lower(state, p, endp, llparse_blob12, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_header_state; + } + case kMatchPause: { + return s_n_llhttp__internal__n_header_field_7; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field: + s_n_llhttp__internal__n_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field; + } + switch (((*p) >= 'A' && (*p) <= 'Z' ? (*p | 0x20) : (*p))) { + case 'c': { + p++; + goto s_n_llhttp__internal__n_header_field_1; + } + case 'p': { + p++; + goto s_n_llhttp__internal__n_header_field_5; + } + case 't': { + p++; + goto s_n_llhttp__internal__n_header_field_6; + } + case 'u': { + p++; + goto s_n_llhttp__internal__n_header_field_7; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_header_state_11; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_header_field: + s_n_llhttp__internal__n_span_start_llhttp__on_header_field: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_header_field; + goto s_n_llhttp__internal__n_header_field; + UNREACHABLE; + } + case s_n_llhttp__internal__n_header_field_start: + s_n_llhttp__internal__n_header_field_start: { + if (p == endp) { + return s_n_llhttp__internal__n_header_field_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_1; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_headers_almost_done; + } + case ':': { + goto s_n_llhttp__internal__n_error_44; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_field; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_headers_start: + s_n_llhttp__internal__n_headers_start: { + if (p == endp) { + return s_n_llhttp__internal__n_headers_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags; + } + default: { + goto s_n_llhttp__internal__n_header_field_start; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_to_http_09: + s_n_llhttp__internal__n_url_to_http_09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http_09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_http_major; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_skip_to_http09: + s_n_llhttp__internal__n_url_skip_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09_1: + s_n_llhttp__internal__n_url_skip_lf_to_http09_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_url_to_http_09; + } + default: { + goto s_n_llhttp__internal__n_error_63; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_skip_lf_to_http09: + s_n_llhttp__internal__n_url_skip_lf_to_http09: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_lf_to_http09; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_url_skip_lf_to_http09_1; + } + default: { + goto s_n_llhttp__internal__n_error_63; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_pri_upgrade: + s_n_llhttp__internal__n_req_pri_upgrade: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob14, 10); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_error_72; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_pri_upgrade; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_73; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_complete_crlf: + s_n_llhttp__internal__n_req_http_complete_crlf: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete_crlf; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_headers_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_26; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_complete: + s_n_llhttp__internal__n_req_http_complete: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_complete; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_25; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_req_http_complete_crlf; + } + default: { + goto s_n_llhttp__internal__n_error_71; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_method_1: + s_n_llhttp__internal__n_invoke_load_method_1: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 34: + goto s_n_llhttp__internal__n_req_pri_upgrade; + default: + goto s_n_llhttp__internal__n_req_http_complete; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method_1; + case 21: + goto s_n_llhttp__internal__n_pause_21; + default: + goto s_n_llhttp__internal__n_error_68; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_67: + s_n_llhttp__internal__n_error_67: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_74: + s_n_llhttp__internal__n_error_74: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_minor: + s_n_llhttp__internal__n_req_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_2; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_75: + s_n_llhttp__internal__n_error_75: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_dot: + s_n_llhttp__internal__n_req_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_req_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_76: + s_n_llhttp__internal__n_error_76: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_major: + s_n_llhttp__internal__n_req_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_4; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version: + s_n_llhttp__internal__n_span_start_llhttp__on_version: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_req_http_major; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_after_protocol: + s_n_llhttp__internal__n_req_after_protocol: { + if (p == endp) { + return s_n_llhttp__internal__n_req_after_protocol; + } + switch (*p) { + case '/': { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_version; + } + default: { + goto s_n_llhttp__internal__n_error_77; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_method: + s_n_llhttp__internal__n_invoke_load_method: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_after_protocol; + case 1: + goto s_n_llhttp__internal__n_req_after_protocol; + case 2: + goto s_n_llhttp__internal__n_req_after_protocol; + case 3: + goto s_n_llhttp__internal__n_req_after_protocol; + case 4: + goto s_n_llhttp__internal__n_req_after_protocol; + case 5: + goto s_n_llhttp__internal__n_req_after_protocol; + case 6: + goto s_n_llhttp__internal__n_req_after_protocol; + case 7: + goto s_n_llhttp__internal__n_req_after_protocol; + case 8: + goto s_n_llhttp__internal__n_req_after_protocol; + case 9: + goto s_n_llhttp__internal__n_req_after_protocol; + case 10: + goto s_n_llhttp__internal__n_req_after_protocol; + case 11: + goto s_n_llhttp__internal__n_req_after_protocol; + case 12: + goto s_n_llhttp__internal__n_req_after_protocol; + case 13: + goto s_n_llhttp__internal__n_req_after_protocol; + case 14: + goto s_n_llhttp__internal__n_req_after_protocol; + case 15: + goto s_n_llhttp__internal__n_req_after_protocol; + case 16: + goto s_n_llhttp__internal__n_req_after_protocol; + case 17: + goto s_n_llhttp__internal__n_req_after_protocol; + case 18: + goto s_n_llhttp__internal__n_req_after_protocol; + case 19: + goto s_n_llhttp__internal__n_req_after_protocol; + case 20: + goto s_n_llhttp__internal__n_req_after_protocol; + case 21: + goto s_n_llhttp__internal__n_req_after_protocol; + case 22: + goto s_n_llhttp__internal__n_req_after_protocol; + case 23: + goto s_n_llhttp__internal__n_req_after_protocol; + case 24: + goto s_n_llhttp__internal__n_req_after_protocol; + case 25: + goto s_n_llhttp__internal__n_req_after_protocol; + case 26: + goto s_n_llhttp__internal__n_req_after_protocol; + case 27: + goto s_n_llhttp__internal__n_req_after_protocol; + case 28: + goto s_n_llhttp__internal__n_req_after_protocol; + case 29: + goto s_n_llhttp__internal__n_req_after_protocol; + case 30: + goto s_n_llhttp__internal__n_req_after_protocol; + case 31: + goto s_n_llhttp__internal__n_req_after_protocol; + case 32: + goto s_n_llhttp__internal__n_req_after_protocol; + case 33: + goto s_n_llhttp__internal__n_req_after_protocol; + case 34: + goto s_n_llhttp__internal__n_req_after_protocol; + case 46: + goto s_n_llhttp__internal__n_req_after_protocol; + default: + goto s_n_llhttp__internal__n_error_66; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete: { + switch (llhttp__on_protocol_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method; + case 21: + goto s_n_llhttp__internal__n_pause_22; + default: + goto s_n_llhttp__internal__n_error_65; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_82: + s_n_llhttp__internal__n_error_82: { + state->error = 0x8; + state->reason = "Expected HTTP/, RTSP/ or ICE/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_after_http_start_1: + s_n_llhttp__internal__n_req_after_http_start_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_after_http_start_1; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob13, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_after_http_start_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_method_2: + s_n_llhttp__internal__n_invoke_load_method_2: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 33: + goto s_n_llhttp__internal__n_req_after_protocol; + default: + goto s_n_llhttp__internal__n_error_79; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1: { + switch (llhttp__on_protocol_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method_2; + case 21: + goto s_n_llhttp__internal__n_pause_23; + default: + goto s_n_llhttp__internal__n_error_78; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_after_http_start_2: + s_n_llhttp__internal__n_req_after_http_start_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_after_http_start_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob15, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_after_http_start_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_method_3: + s_n_llhttp__internal__n_invoke_load_method_3: { + switch (llhttp__internal__c_load_method(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_req_after_protocol; + case 3: + goto s_n_llhttp__internal__n_req_after_protocol; + case 6: + goto s_n_llhttp__internal__n_req_after_protocol; + case 35: + goto s_n_llhttp__internal__n_req_after_protocol; + case 36: + goto s_n_llhttp__internal__n_req_after_protocol; + case 37: + goto s_n_llhttp__internal__n_req_after_protocol; + case 38: + goto s_n_llhttp__internal__n_req_after_protocol; + case 39: + goto s_n_llhttp__internal__n_req_after_protocol; + case 40: + goto s_n_llhttp__internal__n_req_after_protocol; + case 41: + goto s_n_llhttp__internal__n_req_after_protocol; + case 42: + goto s_n_llhttp__internal__n_req_after_protocol; + case 43: + goto s_n_llhttp__internal__n_req_after_protocol; + case 44: + goto s_n_llhttp__internal__n_req_after_protocol; + case 45: + goto s_n_llhttp__internal__n_req_after_protocol; + default: + goto s_n_llhttp__internal__n_error_81; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2: + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2: { + switch (llhttp__on_protocol_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_method_3; + case 21: + goto s_n_llhttp__internal__n_pause_24; + default: + goto s_n_llhttp__internal__n_error_80; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_after_http_start_3: + s_n_llhttp__internal__n_req_after_http_start_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_after_http_start_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob16, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_2; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_after_http_start_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_after_http_start: + s_n_llhttp__internal__n_req_after_http_start: { + if (p == endp) { + return s_n_llhttp__internal__n_req_after_http_start; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_after_http_start_1; + } + case 'I': { + p++; + goto s_n_llhttp__internal__n_req_after_http_start_2; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_req_after_http_start_3; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_protocol: + s_n_llhttp__internal__n_span_start_llhttp__on_protocol: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_protocol; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_protocol; + goto s_n_llhttp__internal__n_req_after_http_start; + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_http_start: + s_n_llhttp__internal__n_req_http_start: { + if (p == endp) { + return s_n_llhttp__internal__n_req_http_start; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_http_start; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_protocol; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_to_http: + s_n_llhttp__internal__n_url_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_skip_to_http: + s_n_llhttp__internal__n_url_skip_to_http: { + if (p == endp) { + return s_n_llhttp__internal__n_url_skip_to_http; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + p++; + goto s_n_llhttp__internal__n_url_to_http; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_fragment: + s_n_llhttp__internal__n_url_fragment: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_fragment; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_6; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_7; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_8; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + default: { + goto s_n_llhttp__internal__n_error_83; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_end_stub_query_3: + s_n_llhttp__internal__n_span_end_stub_query_3: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_query_3; + } + p++; + goto s_n_llhttp__internal__n_url_fragment; + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_query: + s_n_llhttp__internal__n_url_query: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_query; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_9; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_10; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_11; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 6: { + goto s_n_llhttp__internal__n_span_end_stub_query_3; + } + default: { + goto s_n_llhttp__internal__n_error_84; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_query_or_fragment: + s_n_llhttp__internal__n_url_query_or_fragment: { + if (p == endp) { + return s_n_llhttp__internal__n_url_query_or_fragment; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_3; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_4; + } + case ' ': { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_5; + } + case '#': { + p++; + goto s_n_llhttp__internal__n_url_fragment; + } + case '?': { + p++; + goto s_n_llhttp__internal__n_url_query; + } + default: { + goto s_n_llhttp__internal__n_error_85; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_path: + s_n_llhttp__internal__n_url_path: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_path; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + p++; + goto s_n_llhttp__internal__n_url_path; + } + default: { + goto s_n_llhttp__internal__n_url_query_or_fragment; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_stub_path_2: + s_n_llhttp__internal__n_span_start_stub_path_2: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_2; + } + p++; + goto s_n_llhttp__internal__n_url_path; + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_stub_path: + s_n_llhttp__internal__n_span_start_stub_path: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path; + } + p++; + goto s_n_llhttp__internal__n_url_path; + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_stub_path_1: + s_n_llhttp__internal__n_span_start_stub_path_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_stub_path_1; + } + p++; + goto s_n_llhttp__internal__n_url_path; + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_server_with_at: + s_n_llhttp__internal__n_url_server_with_at: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server_with_at; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_12; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_13; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_14; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path_1; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_error_86; + } + default: { + goto s_n_llhttp__internal__n_error_87; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_server: + s_n_llhttp__internal__n_url_server: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 4, 5, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 7, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 5, + 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 5, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_server; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url; + } + case 3: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_1; + } + case 4: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_url_2; + } + case 5: { + p++; + goto s_n_llhttp__internal__n_url_server; + } + case 6: { + goto s_n_llhttp__internal__n_span_start_stub_path; + } + case 7: { + p++; + goto s_n_llhttp__internal__n_url_query; + } + case 8: { + p++; + goto s_n_llhttp__internal__n_url_server_with_at; + } + default: { + goto s_n_llhttp__internal__n_error_88; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_schema_delim_1: + s_n_llhttp__internal__n_url_schema_delim_1: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim_1; + } + switch (*p) { + case '/': { + p++; + goto s_n_llhttp__internal__n_url_server; + } + default: { + goto s_n_llhttp__internal__n_error_89; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_schema_delim: + s_n_llhttp__internal__n_url_schema_delim: { + if (p == endp) { + return s_n_llhttp__internal__n_url_schema_delim; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 10: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case '/': { + p++; + goto s_n_llhttp__internal__n_url_schema_delim_1; + } + default: { + goto s_n_llhttp__internal__n_error_89; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_end_stub_schema: + s_n_llhttp__internal__n_span_end_stub_schema: { + if (p == endp) { + return s_n_llhttp__internal__n_span_end_stub_schema; + } + p++; + goto s_n_llhttp__internal__n_url_schema_delim; + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_schema: + s_n_llhttp__internal__n_url_schema: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_schema; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_end_stub_schema; + } + case 3: { + p++; + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_90; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_start: + s_n_llhttp__internal__n_url_start: { + static uint8_t lookup_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + if (p == endp) { + return s_n_llhttp__internal__n_url_start; + } + switch (lookup_table[(uint8_t) *p]) { + case 1: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 2: { + goto s_n_llhttp__internal__n_span_start_stub_path_2; + } + case 3: { + goto s_n_llhttp__internal__n_url_schema; + } + default: { + goto s_n_llhttp__internal__n_error_91; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url_1: + s_n_llhttp__internal__n_span_start_llhttp__on_url_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_start; + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_entry_normal: + s_n_llhttp__internal__n_url_entry_normal: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_normal; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url_1; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_url: + s_n_llhttp__internal__n_span_start_llhttp__on_url: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_url; + goto s_n_llhttp__internal__n_url_server; + UNREACHABLE; + } + case s_n_llhttp__internal__n_url_entry_connect: + s_n_llhttp__internal__n_url_entry_connect: { + if (p == endp) { + return s_n_llhttp__internal__n_url_entry_connect; + } + switch (*p) { + case 9: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + case 12: { + p++; + goto s_n_llhttp__internal__n_error_2; + } + default: { + goto s_n_llhttp__internal__n_span_start_llhttp__on_url; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_spaces_before_url: + s_n_llhttp__internal__n_req_spaces_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_spaces_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_invoke_is_equal_method; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_first_space_before_url: + s_n_llhttp__internal__n_req_first_space_before_url: { + if (p == endp) { + return s_n_llhttp__internal__n_req_first_space_before_url; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_req_spaces_before_url; + } + default: { + goto s_n_llhttp__internal__n_error_92; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_29; + default: + goto s_n_llhttp__internal__n_error_111; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_2: + s_n_llhttp__internal__n_after_start_req_2: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_2; + } + switch (*p) { + case 'L': { + p++; + match = 19; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_3: + s_n_llhttp__internal__n_after_start_req_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob17, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 36; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_1: + s_n_llhttp__internal__n_after_start_req_1: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_1; + } + switch (*p) { + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_2; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_3; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_4: + s_n_llhttp__internal__n_after_start_req_4: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_4; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob18, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 16; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_4; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_6: + s_n_llhttp__internal__n_after_start_req_6: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_6; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob19, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 22; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_6; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_8: + s_n_llhttp__internal__n_after_start_req_8: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_8; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob20, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_8; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_9: + s_n_llhttp__internal__n_after_start_req_9: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_9; + } + switch (*p) { + case 'Y': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_7: + s_n_llhttp__internal__n_after_start_req_7: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_7; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_8; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_9; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_5: + s_n_llhttp__internal__n_after_start_req_5: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_5; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_6; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_7; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_12: + s_n_llhttp__internal__n_after_start_req_12: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_12; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob21, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_12; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_13: + s_n_llhttp__internal__n_after_start_req_13: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_13; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob22, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 35; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_13; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_11: + s_n_llhttp__internal__n_after_start_req_11: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_11; + } + switch (*p) { + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_12; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_13; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_10: + s_n_llhttp__internal__n_after_start_req_10: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_10; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_11; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_14: + s_n_llhttp__internal__n_after_start_req_14: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_14; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob23, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 45; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_14; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_17: + s_n_llhttp__internal__n_after_start_req_17: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_17; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob25, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 41; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_17; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_16: + s_n_llhttp__internal__n_after_start_req_16: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_16; + } + switch (*p) { + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_17; + } + default: { + match = 1; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_15: + s_n_llhttp__internal__n_after_start_req_15: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_15; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob24, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_after_start_req_16; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_15; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_18: + s_n_llhttp__internal__n_after_start_req_18: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_18; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob26, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_18; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_20: + s_n_llhttp__internal__n_after_start_req_20: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_20; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob27, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 31; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_20; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_21: + s_n_llhttp__internal__n_after_start_req_21: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_21; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob28, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_21; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_19: + s_n_llhttp__internal__n_after_start_req_19: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_19; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_20; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_21; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_23: + s_n_llhttp__internal__n_after_start_req_23: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_23; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob29, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 24; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_23; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_24: + s_n_llhttp__internal__n_after_start_req_24: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_24; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob30, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 23; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_24; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_26: + s_n_llhttp__internal__n_after_start_req_26: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_26; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob31, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 21; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_26; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_28: + s_n_llhttp__internal__n_after_start_req_28: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_28; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob32, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 30; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_28; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_29: + s_n_llhttp__internal__n_after_start_req_29: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_29; + } + switch (*p) { + case 'L': { + p++; + match = 10; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_27: + s_n_llhttp__internal__n_after_start_req_27: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_27; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_28; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_29; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_25: + s_n_llhttp__internal__n_after_start_req_25: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_25; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_26; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_27; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_30: + s_n_llhttp__internal__n_after_start_req_30: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_30; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob33, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 11; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_30; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_22: + s_n_llhttp__internal__n_after_start_req_22: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_22; + } + switch (*p) { + case '-': { + p++; + goto s_n_llhttp__internal__n_after_start_req_23; + } + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_24; + } + case 'K': { + p++; + goto s_n_llhttp__internal__n_after_start_req_25; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_30; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_31: + s_n_llhttp__internal__n_after_start_req_31: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_31; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob34, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 25; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_31; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_32: + s_n_llhttp__internal__n_after_start_req_32: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_32; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob35, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_32; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_35: + s_n_llhttp__internal__n_after_start_req_35: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_35; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob36, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 28; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_35; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_36: + s_n_llhttp__internal__n_after_start_req_36: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_36; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob37, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 39; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_36; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_34: + s_n_llhttp__internal__n_after_start_req_34: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_34; + } + switch (*p) { + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_35; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_36; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_37: + s_n_llhttp__internal__n_after_start_req_37: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_37; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob38, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 38; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_37; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_38: + s_n_llhttp__internal__n_after_start_req_38: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_38; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob39, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_38; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_42: + s_n_llhttp__internal__n_after_start_req_42: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_42; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob40, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 12; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_42; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_43: + s_n_llhttp__internal__n_after_start_req_43: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_43; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob41, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 13; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_43; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_41: + s_n_llhttp__internal__n_after_start_req_41: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_41; + } + switch (*p) { + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_42; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_43; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_40: + s_n_llhttp__internal__n_after_start_req_40: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_40; + } + switch (*p) { + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_41; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_39: + s_n_llhttp__internal__n_after_start_req_39: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_39; + } + switch (*p) { + case 'I': { + p++; + match = 34; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_40; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_45: + s_n_llhttp__internal__n_after_start_req_45: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_45; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob42, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 29; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_45; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_44: + s_n_llhttp__internal__n_after_start_req_44: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_44; + } + switch (*p) { + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_45; + } + case 'T': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_33: + s_n_llhttp__internal__n_after_start_req_33: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_33; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_34; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_37; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_38; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_39; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_44; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_46: + s_n_llhttp__internal__n_after_start_req_46: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_46; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob43, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 46; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_46; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_49: + s_n_llhttp__internal__n_after_start_req_49: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_49; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob44, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 17; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_49; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_50: + s_n_llhttp__internal__n_after_start_req_50: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_50; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob45, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 44; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_50; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_51: + s_n_llhttp__internal__n_after_start_req_51: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_51; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob46, 5); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 43; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_51; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_52: + s_n_llhttp__internal__n_after_start_req_52: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_52; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob47, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 20; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_52; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_48: + s_n_llhttp__internal__n_after_start_req_48: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_48; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_49; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_50; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_51; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_52; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_47: + s_n_llhttp__internal__n_after_start_req_47: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_47; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_48; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_55: + s_n_llhttp__internal__n_after_start_req_55: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_55; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob48, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 14; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_55; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_57: + s_n_llhttp__internal__n_after_start_req_57: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_57; + } + switch (*p) { + case 'P': { + p++; + match = 37; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_58: + s_n_llhttp__internal__n_after_start_req_58: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_58; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob49, 9); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 42; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_58; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_56: + s_n_llhttp__internal__n_after_start_req_56: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_56; + } + switch (*p) { + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_57; + } + case '_': { + p++; + goto s_n_llhttp__internal__n_after_start_req_58; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_54: + s_n_llhttp__internal__n_after_start_req_54: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_54; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_55; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_56; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_59: + s_n_llhttp__internal__n_after_start_req_59: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_59; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob50, 4); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 33; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_59; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_60: + s_n_llhttp__internal__n_after_start_req_60: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_60; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob51, 7); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 26; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_60; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_53: + s_n_llhttp__internal__n_after_start_req_53: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_53; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_54; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_59; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_60; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_62: + s_n_llhttp__internal__n_after_start_req_62: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_62; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob52, 6); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 40; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_62; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_63: + s_n_llhttp__internal__n_after_start_req_63: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_63; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob53, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_63; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_61: + s_n_llhttp__internal__n_after_start_req_61: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_61; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_after_start_req_62; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_63; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_66: + s_n_llhttp__internal__n_after_start_req_66: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_66; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob54, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 18; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_66; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_68: + s_n_llhttp__internal__n_after_start_req_68: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_68; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob55, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 32; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_68; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_69: + s_n_llhttp__internal__n_after_start_req_69: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_69; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob56, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 15; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_69; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_67: + s_n_llhttp__internal__n_after_start_req_67: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_67; + } + switch (*p) { + case 'I': { + p++; + goto s_n_llhttp__internal__n_after_start_req_68; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_69; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_70: + s_n_llhttp__internal__n_after_start_req_70: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_70; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob57, 8); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 27; + goto s_n_llhttp__internal__n_invoke_store_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_after_start_req_70; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_65: + s_n_llhttp__internal__n_after_start_req_65: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_65; + } + switch (*p) { + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_66; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_67; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_70; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req_64: + s_n_llhttp__internal__n_after_start_req_64: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req_64; + } + switch (*p) { + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_65; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_after_start_req: + s_n_llhttp__internal__n_after_start_req: { + if (p == endp) { + return s_n_llhttp__internal__n_after_start_req; + } + switch (*p) { + case 'A': { + p++; + goto s_n_llhttp__internal__n_after_start_req_1; + } + case 'B': { + p++; + goto s_n_llhttp__internal__n_after_start_req_4; + } + case 'C': { + p++; + goto s_n_llhttp__internal__n_after_start_req_5; + } + case 'D': { + p++; + goto s_n_llhttp__internal__n_after_start_req_10; + } + case 'F': { + p++; + goto s_n_llhttp__internal__n_after_start_req_14; + } + case 'G': { + p++; + goto s_n_llhttp__internal__n_after_start_req_15; + } + case 'H': { + p++; + goto s_n_llhttp__internal__n_after_start_req_18; + } + case 'L': { + p++; + goto s_n_llhttp__internal__n_after_start_req_19; + } + case 'M': { + p++; + goto s_n_llhttp__internal__n_after_start_req_22; + } + case 'N': { + p++; + goto s_n_llhttp__internal__n_after_start_req_31; + } + case 'O': { + p++; + goto s_n_llhttp__internal__n_after_start_req_32; + } + case 'P': { + p++; + goto s_n_llhttp__internal__n_after_start_req_33; + } + case 'Q': { + p++; + goto s_n_llhttp__internal__n_after_start_req_46; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_after_start_req_47; + } + case 'S': { + p++; + goto s_n_llhttp__internal__n_after_start_req_53; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_after_start_req_61; + } + case 'U': { + p++; + goto s_n_llhttp__internal__n_after_start_req_64; + } + default: { + goto s_n_llhttp__internal__n_error_112; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method_1: + s_n_llhttp__internal__n_span_start_llhttp__on_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_after_start_req; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_line_almost_done: + s_n_llhttp__internal__n_res_line_almost_done: { + if (p == endp) { + return s_n_llhttp__internal__n_res_line_almost_done; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + } + default: { + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_29; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_test_lenient_flags_30: + s_n_llhttp__internal__n_invoke_test_lenient_flags_30: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_98; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_status: + s_n_llhttp__internal__n_res_status: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status; + } + switch (*p) { + case 10: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status; + } + case 13: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_status_1; + } + default: { + p++; + goto s_n_llhttp__internal__n_res_status; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_status: + s_n_llhttp__internal__n_span_start_llhttp__on_status: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_status; + goto s_n_llhttp__internal__n_res_status; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_status_code_otherwise: + s_n_llhttp__internal__n_res_status_code_otherwise: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_otherwise; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_28; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + } + case ' ': { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_status; + } + default: { + goto s_n_llhttp__internal__n_error_99; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_status_code_digit_3: + s_n_llhttp__internal__n_res_status_code_digit_3: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_3; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_2; + } + default: { + goto s_n_llhttp__internal__n_error_101; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_status_code_digit_2: + s_n_llhttp__internal__n_res_status_code_digit_2: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_2; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code_1; + } + default: { + goto s_n_llhttp__internal__n_error_103; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_status_code_digit_1: + s_n_llhttp__internal__n_res_status_code_digit_1: { + if (p == endp) { + return s_n_llhttp__internal__n_res_status_code_digit_1; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_mul_add_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_105; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_version: + s_n_llhttp__internal__n_res_after_version: { + if (p == endp) { + return s_n_llhttp__internal__n_res_after_version; + } + switch (*p) { + case ' ': { + p++; + goto s_n_llhttp__internal__n_invoke_update_status_code; + } + default: { + goto s_n_llhttp__internal__n_error_106; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: + s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1: { + switch (llhttp__on_version_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_res_after_version; + case 21: + goto s_n_llhttp__internal__n_pause_28; + default: + goto s_n_llhttp__internal__n_error_94; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_93: + s_n_llhttp__internal__n_error_93: { + state->error = 0x9; + state->reason = "Invalid HTTP version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_107: + s_n_llhttp__internal__n_error_107: { + state->error = 0x9; + state->reason = "Invalid minor version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_http_minor: + s_n_llhttp__internal__n_res_http_minor: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_minor; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_minor_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_7; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_108: + s_n_llhttp__internal__n_error_108: { + state->error = 0x9; + state->reason = "Expected dot"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_http_dot: + s_n_llhttp__internal__n_res_http_dot: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_dot; + } + switch (*p) { + case '.': { + p++; + goto s_n_llhttp__internal__n_res_http_minor; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_8; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_109: + s_n_llhttp__internal__n_error_109: { + state->error = 0x9; + state->reason = "Invalid major version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_http_major: + s_n_llhttp__internal__n_res_http_major: { + if (p == endp) { + return s_n_llhttp__internal__n_res_http_major; + } + switch (*p) { + case '0': { + p++; + match = 0; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '1': { + p++; + match = 1; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '2': { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '3': { + p++; + match = 3; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '4': { + p++; + match = 4; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '5': { + p++; + match = 5; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '6': { + p++; + match = 6; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '7': { + p++; + match = 7; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '8': { + p++; + match = 8; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + case '9': { + p++; + match = 9; + goto s_n_llhttp__internal__n_invoke_store_http_major_1; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_9; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_version_1: + s_n_llhttp__internal__n_span_start_llhttp__on_version_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_version; + goto s_n_llhttp__internal__n_res_http_major; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_protocol: + s_n_llhttp__internal__n_res_after_protocol: { + if (p == endp) { + return s_n_llhttp__internal__n_res_after_protocol; + } + switch (*p) { + case '/': { + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + default: { + goto s_n_llhttp__internal__n_error_114; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3: + s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3: { + switch (llhttp__on_protocol_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_res_after_protocol; + case 21: + goto s_n_llhttp__internal__n_pause_30; + default: + goto s_n_llhttp__internal__n_error_113; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_error_115: + s_n_llhttp__internal__n_error_115: { + state->error = 0x8; + state->reason = "Expected HTTP/, RTSP/ or ICE/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_start_1: + s_n_llhttp__internal__n_res_after_start_1: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_res_after_start_1; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob58, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; + } + case kMatchPause: { + return s_n_llhttp__internal__n_res_after_start_1; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_start_2: + s_n_llhttp__internal__n_res_after_start_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_res_after_start_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob59, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; + } + case kMatchPause: { + return s_n_llhttp__internal__n_res_after_start_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_start_3: + s_n_llhttp__internal__n_res_after_start_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_res_after_start_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob60, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4; + } + case kMatchPause: { + return s_n_llhttp__internal__n_res_after_start_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_res_after_start: + s_n_llhttp__internal__n_res_after_start: { + if (p == endp) { + return s_n_llhttp__internal__n_res_after_start; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_res_after_start_1; + } + case 'I': { + p++; + goto s_n_llhttp__internal__n_res_after_start_2; + } + case 'R': { + p++; + goto s_n_llhttp__internal__n_res_after_start_3; + } + default: { + goto s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1: + s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_protocol; + goto s_n_llhttp__internal__n_res_after_start; + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: + s_n_llhttp__internal__n_invoke_llhttp__on_method_complete: { + switch (llhttp__on_method_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_first_space_before_url; + case 21: + goto s_n_llhttp__internal__n_pause_26; + default: + goto s_n_llhttp__internal__n_error_1; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_or_res_method_2: + s_n_llhttp__internal__n_req_or_res_method_2: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob61, 2); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + match = 2; + goto s_n_llhttp__internal__n_invoke_store_method; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_2; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_110; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_update_type_1: + s_n_llhttp__internal__n_invoke_update_type_1: { + switch (llhttp__internal__c_update_type_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_version_1; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_or_res_method_3: + s_n_llhttp__internal__n_req_or_res_method_3: { + llparse_match_t match_seq; + + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + match_seq = llparse__match_sequence_id(state, p, endp, llparse_blob62, 3); + p = match_seq.current; + switch (match_seq.status) { + case kMatchComplete: { + p++; + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_1; + } + case kMatchPause: { + return s_n_llhttp__internal__n_req_or_res_method_3; + } + case kMatchMismatch: { + goto s_n_llhttp__internal__n_error_110; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_or_res_method_1: + s_n_llhttp__internal__n_req_or_res_method_1: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method_1; + } + switch (*p) { + case 'E': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_2; + } + case 'T': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_3; + } + default: { + goto s_n_llhttp__internal__n_error_110; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_req_or_res_method: + s_n_llhttp__internal__n_req_or_res_method: { + if (p == endp) { + return s_n_llhttp__internal__n_req_or_res_method; + } + switch (*p) { + case 'H': { + p++; + goto s_n_llhttp__internal__n_req_or_res_method_1; + } + default: { + goto s_n_llhttp__internal__n_error_110; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_span_start_llhttp__on_method: + s_n_llhttp__internal__n_span_start_llhttp__on_method: { + if (p == endp) { + return s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + state->_span_pos0 = (void*) p; + state->_span_cb0 = llhttp__on_method; + goto s_n_llhttp__internal__n_req_or_res_method; + UNREACHABLE; + } + case s_n_llhttp__internal__n_start_req_or_res: + s_n_llhttp__internal__n_start_req_or_res: { + if (p == endp) { + return s_n_llhttp__internal__n_start_req_or_res; + } + switch (*p) { + case 'H': { + goto s_n_llhttp__internal__n_span_start_llhttp__on_method; + } + default: { + goto s_n_llhttp__internal__n_invoke_update_type_2; + } + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_load_type: + s_n_llhttp__internal__n_invoke_load_type: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + case 2: + goto s_n_llhttp__internal__n_span_start_llhttp__on_protocol_1; + default: + goto s_n_llhttp__internal__n_start_req_or_res; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_invoke_update_finish: + s_n_llhttp__internal__n_invoke_update_finish: { + switch (llhttp__internal__c_update_finish(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_begin; + } + UNREACHABLE; + } + case s_n_llhttp__internal__n_start: + s_n_llhttp__internal__n_start: { + if (p == endp) { + return s_n_llhttp__internal__n_start; + } + switch (*p) { + case 10: { + p++; + goto s_n_llhttp__internal__n_start; + } + case 13: { + p++; + goto s_n_llhttp__internal__n_start; + } + default: { + goto s_n_llhttp__internal__n_invoke_load_initial_message_completed; + } + } + UNREACHABLE; + } + default: + UNREACHABLE; + } + s_n_llhttp__internal__n_error_2: { + state->error = 0x7; + state->reason = "Invalid characters in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_finish_2: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_start; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_initial_message_completed: { + switch (llhttp__internal__c_update_initial_message_completed(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_finish_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_content_length: { + switch (llhttp__internal__c_update_content_length(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_8: { + state->error = 0x5; + state->reason = "Data after `Connection: close`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_3: { + switch (llhttp__internal__c_test_lenient_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_closed; + default: + goto s_n_llhttp__internal__n_error_8; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_2: { + switch (llhttp__internal__c_test_lenient_flags_2(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_update_initial_message_completed; + default: + goto s_n_llhttp__internal__n_closed; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_finish_1: { + switch (llhttp__internal__c_update_finish_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_13: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_upgrade; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_38: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_15: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_40: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + case 21: + goto s_n_llhttp__internal__n_pause_15; + default: + goto s_n_llhttp__internal__n_error_40; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_2: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_pause_1; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_9: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_1: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_pause_1; + case 21: + goto s_n_llhttp__internal__n_pause_2; + default: + goto s_n_llhttp__internal__n_error_9; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_36: { + state->error = 0xc; + state->reason = "Chunk size overflow"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_10: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_4: { + switch (llhttp__internal__c_test_lenient_flags_4(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_otherwise; + default: + goto s_n_llhttp__internal__n_error_10; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_3: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_content_length_1; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_14: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_content_length_1; + case 21: + goto s_n_llhttp__internal__n_pause_3; + default: + goto s_n_llhttp__internal__n_error_14; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_13: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk data"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_6: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + default: + goto s_n_llhttp__internal__n_error_13; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_15: { + state->error = 0x2; + state->reason = "Expected LF after chunk data"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_7: { + switch (llhttp__internal__c_test_lenient_flags_7(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete; + default: + goto s_n_llhttp__internal__n_error_15; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_body: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_data_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_chunk_data_almost_done; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags: { + switch (llhttp__internal__c_or_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_start; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_4: { + state->error = 0x15; + state->reason = "on_chunk_header pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_is_equal_content_length; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_12: { + state->error = 0x13; + state->reason = "`on_chunk_header` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header: { + switch (llhttp__on_chunk_header(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_is_equal_content_length; + case 21: + goto s_n_llhttp__internal__n_pause_4; + default: + goto s_n_llhttp__internal__n_error_12; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_16: { + state->error = 0x2; + state->reason = "Expected LF after chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_8: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_header; + default: + goto s_n_llhttp__internal__n_error_16; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_11: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_5: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_11; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_17: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_18: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_20: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension name"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_5: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_9; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_19: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_6: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_21: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_7: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_22: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_2; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_25: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_8: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_10; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_24: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_9: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_26: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_28: { + state->error = 0x19; + state->reason = "Missing expected CR after chunk extension value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_11: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_chunk_size_almost_done; + default: + goto s_n_llhttp__internal__n_error_28; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_29: { + state->error = 0x2; + state->reason = "Invalid character in chunk extensions quote value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_10: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_quoted_value_done; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_27: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_2; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_30; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_30; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_31; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_31; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_11: { + state->error = 0x15; + state->reason = "on_chunk_extension_value pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extensions; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_32: { + state->error = 0x23; + state->reason = "`on_chunk_extension_value` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_value_complete_3; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_value_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_33; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_33; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_12: { + state->error = 0x15; + state->reason = "on_chunk_extension_name pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_extension_value; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_23: { + state->error = 0x22; + state->reason = "`on_chunk_extension_name` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_extension_name_complete_3: { + switch (llhttp__on_chunk_extension_name_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_chunk_extension_value; + case 21: + goto s_n_llhttp__internal__n_pause_12; + default: + goto s_n_llhttp__internal__n_error_23; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_extension_value; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_chunk_extension_name_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_chunk_extension_name(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_34; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_34; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_35: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_mul_add_content_length: { + switch (llhttp__internal__c_mul_add_content_length(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_36; + default: + goto s_n_llhttp__internal__n_chunk_size; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_37: { + state->error = 0xc; + state->reason = "Invalid character in chunk size"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_body_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_body(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_finish_3: { + switch (llhttp__internal__c_update_finish_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_body_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_39: { + state->error = 0xf; + state->reason = "Request has invalid `Transfer-Encoding`"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause: { + state->error = 0x15; + state->reason = "on_message_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_7: { + state->error = 0x12; + state->reason = "`on_message_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_complete: { + switch (llhttp__on_message_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_message_complete; + case 21: + goto s_n_llhttp__internal__n_pause; + default: + goto s_n_llhttp__internal__n_error_7; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_1: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_2: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_upgrade: { + switch (llhttp__internal__c_update_upgrade(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_or_flags_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_14: { + state->error = 0x15; + state->reason = "Paused by on_headers_complete"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_6: { + state->error = 0x11; + state->reason = "User callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete: { + switch (llhttp__on_headers_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + case 1: + goto s_n_llhttp__internal__n_invoke_or_flags_1; + case 2: + goto s_n_llhttp__internal__n_invoke_update_upgrade; + case 21: + goto s_n_llhttp__internal__n_pause_14; + default: + goto s_n_llhttp__internal__n_error_6; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete: { + switch (llhttp__before_headers_complete(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags: { + switch (llhttp__internal__c_test_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_1; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_1: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_flags; + default: + goto s_n_llhttp__internal__n_error_5; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_17: { + state->error = 0x15; + state->reason = "on_chunk_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_42: { + state->error = 0x14; + state->reason = "`on_chunk_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2: { + switch (llhttp__on_chunk_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__on_message_complete_2; + case 21: + goto s_n_llhttp__internal__n_pause_17; + default: + goto s_n_llhttp__internal__n_error_42; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_3: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_4: { + switch (llhttp__internal__c_or_flags_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_upgrade_1: { + switch (llhttp__internal__c_update_upgrade(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_or_flags_4; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_16: { + state->error = 0x15; + state->reason = "Paused by on_headers_complete"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_41: { + state->error = 0x11; + state->reason = "User callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1: { + switch (llhttp__on_headers_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_llhttp__after_headers_complete; + case 1: + goto s_n_llhttp__internal__n_invoke_or_flags_3; + case 2: + goto s_n_llhttp__internal__n_invoke_update_upgrade_1; + case 21: + goto s_n_llhttp__internal__n_pause_16; + default: + goto s_n_llhttp__internal__n_error_41; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1: { + switch (llhttp__before_headers_complete(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_headers_complete_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags_1: { + switch (llhttp__internal__c_test_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_chunk_complete_2; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_43: { + state->error = 0x2; + state->reason = "Expected LF after headers"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_12: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_flags_1; + default: + goto s_n_llhttp__internal__n_error_43; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_44: { + state->error = 0xa; + state->reason = "Invalid header token"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_5; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_5; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_13: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_colon_discard_ws; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_field; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_60: { + state->error = 0xb; + state->reason = "Content-Length can't be present with Transfer-Encoding"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_47: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_15: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_ws; + default: + goto s_n_llhttp__internal__n_error_47; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_49: { + state->error = 0xb; + state->reason = "Empty Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_18: { + state->error = 0x15; + state->reason = "on_header_value_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_field_start; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_48: { + state->error = 0x1d; + state->reason = "`on_header_value_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_5: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_6: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_7: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_8: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_2: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_5; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_6; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_7; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_8; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_1: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 2: + goto s_n_llhttp__internal__n_error_49; + default: + goto s_n_llhttp__internal__n_invoke_load_header_state_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_46: { + state->error = 0xa; + state->reason = "Invalid header value char"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_14: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_46; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_50: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_16: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_discard_lws; + default: + goto s_n_llhttp__internal__n_error_50; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_1: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_4: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 8: + goto s_n_llhttp__internal__n_invoke_update_header_state_1; + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_header_value_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_52: { + state->error = 0xa; + state->reason = "Unexpected whitespace after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_18: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_load_header_state_4; + default: + goto s_n_llhttp__internal__n_error_52; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_2: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_9: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_10: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_11: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_12: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_5: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_9; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_10; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_11; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_12; + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_value_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_53: { + state->error = 0x3; + state->reason = "Missing expected LF after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_51: { + state->error = 0x19; + state->reason = "Missing expected CR after header value"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_17; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_17; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + goto s_n_llhttp__internal__n_header_value_almost_done; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_header_value_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_header_value_almost_done; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_54; + return s_error; + } + goto s_n_llhttp__internal__n_error_54; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_19: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_lenient; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_3; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_4: { + switch (llhttp__internal__c_update_header_state(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_13: { + switch (llhttp__internal__c_or_flags_5(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_14: { + switch (llhttp__internal__c_or_flags_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_15: { + switch (llhttp__internal__c_or_flags_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_4; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_16: { + switch (llhttp__internal__c_or_flags_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_6: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 5: + goto s_n_llhttp__internal__n_invoke_or_flags_13; + case 6: + goto s_n_llhttp__internal__n_invoke_or_flags_14; + case 7: + goto s_n_llhttp__internal__n_invoke_or_flags_15; + case 8: + goto s_n_llhttp__internal__n_invoke_or_flags_16; + default: + goto s_n_llhttp__internal__n_header_value_connection; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_5: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_token; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_3: { + switch (llhttp__internal__c_update_header_state_3(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_6: { + switch (llhttp__internal__c_update_header_state_6(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_7: { + switch (llhttp__internal__c_update_header_state_7(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_connection_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_56; + return s_error; + } + goto s_n_llhttp__internal__n_error_56; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_mul_add_content_length_1: { + switch (llhttp__internal__c_mul_add_content_length_1(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_6; + default: + goto s_n_llhttp__internal__n_header_value_content_length; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_17: { + switch (llhttp__internal__c_or_flags_17(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_57; + return s_error; + } + goto s_n_llhttp__internal__n_error_57; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_55: { + state->error = 0x4; + state->reason = "Duplicate Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags_2: { + switch (llhttp__internal__c_test_flags_2(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_header_value_content_length; + default: + goto s_n_llhttp__internal__n_error_55; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_59; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_59; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_8: { + switch (llhttp__internal__c_update_header_state_8(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_otherwise; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_value(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_58; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_error_58; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_20: { + switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_8; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_type_1: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_20; + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_9: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_and_flags: { + switch (llhttp__internal__c_and_flags(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_value_te_chunked; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_19: { + switch (llhttp__internal__c_or_flags_18(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_21: { + switch (llhttp__internal__c_test_lenient_flags_20(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_header_value_9; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_19; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_type_2: { + switch (llhttp__internal__c_load_type(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_21; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_19; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_18: { + switch (llhttp__internal__c_or_flags_18(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_and_flags; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags_3: { + switch (llhttp__internal__c_test_flags_3(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_load_type_2; + default: + goto s_n_llhttp__internal__n_invoke_or_flags_18; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_or_flags_20: { + switch (llhttp__internal__c_or_flags_20(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_header_state_9; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_header_state_3: { + switch (llhttp__internal__c_load_header_state(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_value_connection; + case 2: + goto s_n_llhttp__internal__n_invoke_test_flags_2; + case 3: + goto s_n_llhttp__internal__n_invoke_test_flags_3; + case 4: + goto s_n_llhttp__internal__n_invoke_or_flags_20; + default: + goto s_n_llhttp__internal__n_header_value; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_22: { + switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_error_60; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags_4: { + switch (llhttp__internal__c_test_flags_4(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_22; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_61: { + state->error = 0xf; + state->reason = "Transfer-Encoding can't be present with Content-Length"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_23: { + switch (llhttp__internal__c_test_lenient_flags_22(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_error_61; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_flags_5: { + switch (llhttp__internal__c_test_flags_2(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_23; + default: + goto s_n_llhttp__internal__n_header_value_discard_ws; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_19: { + state->error = 0x15; + state->reason = "on_header_field_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_header_state; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_45: { + state->error = 0x1c; + state->reason = "`on_header_field_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_header_field_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_header_field(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_llhttp__on_header_field_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_62: { + state->error = 0xa; + state->reason = "Invalid header token"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_10: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_header_state: { + switch (llhttp__internal__c_store_header_state(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_header_field_colon; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_header_state_11: { + switch (llhttp__internal__c_update_header_state_1(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_header_field_general; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_4: { + state->error = 0x1e; + state->reason = "Unexpected space after start line"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags: { + switch (llhttp__internal__c_test_lenient_flags(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_header_field_start; + default: + goto s_n_llhttp__internal__n_error_4; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_20: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_3: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_20; + default: + goto s_n_llhttp__internal__n_error_3; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_http_minor: { + switch (llhttp__internal__c_update_http_minor(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_llhttp__on_url_complete; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_http_major: { + switch (llhttp__internal__c_update_http_major(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_invoke_update_http_minor; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_63: { + state->error = 0x7; + state->reason = "Expected CRLF"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_72: { + state->error = 0x17; + state->reason = "Pause on PRI/Upgrade"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_73: { + state->error = 0x9; + state->reason = "Expected HTTP/2 Connection Preface"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_70: { + state->error = 0x2; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_26: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_headers_start; + default: + goto s_n_llhttp__internal__n_error_70; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_69: { + state->error = 0x9; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_25: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_req_http_complete_crlf; + default: + goto s_n_llhttp__internal__n_error_69; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_71: { + state->error = 0x9; + state->reason = "Expected CRLF after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_21: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_1; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_68: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_67; + return s_error; + } + goto s_n_llhttp__internal__n_error_67; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor_1: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor_2: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_major: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_1; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_2; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_24: { + switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_1; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_http_minor: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_24; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_74; + return s_error; + } + goto s_n_llhttp__internal__n_error_74; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_75; + return s_error; + } + goto s_n_llhttp__internal__n_error_75; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_http_major: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_req_http_dot; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_76; + return s_error; + } + goto s_n_llhttp__internal__n_error_76; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_77: { + state->error = 0x8; + state->reason = "Expected HTTP/, RTSP/ or ICE/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_66: { + state->error = 0x8; + state->reason = "Invalid method for HTTP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_22: { + state->error = 0x15; + state->reason = "on_protocol_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_65: { + state->error = 0x26; + state->reason = "`on_protocol_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol_3: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_82; + return s_error; + } + goto s_n_llhttp__internal__n_error_82; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_79: { + state->error = 0x8; + state->reason = "Expected SOURCE method for ICE/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_23: { + state->error = 0x15; + state->reason = "on_protocol_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_2; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_78: { + state->error = 0x26; + state->reason = "`on_protocol_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_81: { + state->error = 0x8; + state->reason = "Invalid method for RTSP/x.x request"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_24: { + state->error = 0x15; + state->reason = "on_protocol_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_method_3; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_80: { + state->error = 0x26; + state->reason = "`on_protocol_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_2; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_25: { + state->error = 0x15; + state->reason = "on_url_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_http_start; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_64: { + state->error = 0x1a; + state->reason = "`on_url_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_url_complete_1: { + switch (llhttp__on_url_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_req_http_start; + case 21: + goto s_n_llhttp__internal__n_pause_25; + default: + goto s_n_llhttp__internal__n_error_64; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_83: { + state->error = 0x7; + state->reason = "Invalid char in url fragment start"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_10: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_11: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_84: { + state->error = 0x7; + state->reason = "Invalid char in url query"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_85: { + state->error = 0x7; + state->reason = "Invalid char in url path"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_12: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_13: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_lf_to_http09; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_lf_to_http09; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_url_14: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_url(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_url_skip_to_http; + return s_error; + } + goto s_n_llhttp__internal__n_url_skip_to_http; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_86: { + state->error = 0x7; + state->reason = "Double @ in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_87: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_88: { + state->error = 0x7; + state->reason = "Unexpected char in url server"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_89: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_90: { + state->error = 0x7; + state->reason = "Unexpected char in url schema"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_91: { + state->error = 0x7; + state->reason = "Unexpected start char in url"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_is_equal_method: { + switch (llhttp__internal__c_is_equal_method(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_url_entry_normal; + default: + goto s_n_llhttp__internal__n_url_entry_connect; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_92: { + state->error = 0x6; + state->reason = "Expected space after method"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_29: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_111: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_2: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_method_1: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_112: { + state->error = 0x6; + state->reason = "Invalid method encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_104: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_102: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_100: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_27: { + state->error = 0x15; + state->reason = "on_status_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_headers_start; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_96: { + state->error = 0x1b; + state->reason = "`on_status_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_status_complete: { + switch (llhttp__on_status_complete(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_headers_start; + case 21: + goto s_n_llhttp__internal__n_pause_27; + default: + goto s_n_llhttp__internal__n_error_96; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_95: { + state->error = 0xd; + state->reason = "Invalid response status"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_28: { + switch (llhttp__internal__c_test_lenient_flags_1(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_95; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_97: { + state->error = 0x2; + state->reason = "Expected LF after CR"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_29: { + switch (llhttp__internal__c_test_lenient_flags_8(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_status_complete; + default: + goto s_n_llhttp__internal__n_error_97; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_98: { + state->error = 0x19; + state->reason = "Missing expected CR after response line"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_status: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_test_lenient_flags_30; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_30; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_status_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_status(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) (p + 1); + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_line_almost_done; + return s_error; + } + p++; + goto s_n_llhttp__internal__n_res_line_almost_done; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_99: { + state->error = 0xd; + state->reason = "Invalid response status"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_2: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_100; + default: + goto s_n_llhttp__internal__n_res_status_code_otherwise; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_101: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_mul_add_status_code_1: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_102; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_3; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_103: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_mul_add_status_code: { + switch (llhttp__internal__c_mul_add_status_code(state, p, endp, match)) { + case 1: + goto s_n_llhttp__internal__n_error_104; + default: + goto s_n_llhttp__internal__n_res_status_code_digit_2; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_105: { + state->error = 0xd; + state->reason = "Invalid status code"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_status_code: { + switch (llhttp__internal__c_update_status_code(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_res_status_code_digit_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_106: { + state->error = 0x9; + state->reason = "Expected space after version"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_28: { + state->error = 0x15; + state->reason = "on_version_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_version; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_94: { + state->error = 0x21; + state->reason = "`on_version_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_6: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_version_complete_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_93; + return s_error; + } + goto s_n_llhttp__internal__n_error_93; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor_3: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 9: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor_4: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_minor_5: { + switch (llhttp__internal__c_load_http_minor(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_http_major_1: { + switch (llhttp__internal__c_load_http_major(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_http_minor_3; + case 1: + goto s_n_llhttp__internal__n_invoke_load_http_minor_4; + case 2: + goto s_n_llhttp__internal__n_invoke_load_http_minor_5; + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_5; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_test_lenient_flags_27: { + switch (llhttp__internal__c_test_lenient_flags_24(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_span_end_llhttp__on_version_6; + default: + goto s_n_llhttp__internal__n_invoke_load_http_major_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_http_minor_1: { + switch (llhttp__internal__c_store_http_minor(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_test_lenient_flags_27; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_7: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_107; + return s_error; + } + goto s_n_llhttp__internal__n_error_107; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_8: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_108; + return s_error; + } + goto s_n_llhttp__internal__n_error_108; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_http_major_1: { + switch (llhttp__internal__c_store_http_major(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_res_http_dot; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_version_9: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_version(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_109; + return s_error; + } + goto s_n_llhttp__internal__n_error_109; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_114: { + state->error = 0x8; + state->reason = "Expected HTTP/, RTSP/ or ICE/"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_30: { + state->error = 0x15; + state->reason = "on_protocol_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_res_after_protocol; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_113: { + state->error = 0x26; + state->reason = "`on_protocol_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol_4: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_protocol_complete_3; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_protocol_5: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_protocol(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_error_115; + return s_error; + } + goto s_n_llhttp__internal__n_error_115; + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_26: { + state->error = 0x15; + state->reason = "on_method_complete pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_req_first_space_before_url; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_1: { + state->error = 0x20; + state->reason = "`on_method_complete` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_method: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_llhttp__on_method_complete; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_type: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_end_llhttp__on_method; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_store_method: { + switch (llhttp__internal__c_store_method(state, p, endp, match)) { + default: + goto s_n_llhttp__internal__n_invoke_update_type; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_error_110: { + state->error = 0x8; + state->reason = "Invalid word encountered"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_span_end_llhttp__on_method_1: { + const unsigned char* start; + int err; + + start = state->_span_pos0; + state->_span_pos0 = NULL; + err = llhttp__on_method(state, start, p); + if (err != 0) { + state->error = err; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_type_1; + return s_error; + } + goto s_n_llhttp__internal__n_invoke_update_type_1; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_update_type_2: { + switch (llhttp__internal__c_update_type(state, p, endp)) { + default: + goto s_n_llhttp__internal__n_span_start_llhttp__on_method_1; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_31: { + state->error = 0x15; + state->reason = "on_message_begin pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_load_type; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error: { + state->error = 0x10; + state->reason = "`on_message_begin` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_message_begin: { + switch (llhttp__on_message_begin(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_load_type; + case 21: + goto s_n_llhttp__internal__n_pause_31; + default: + goto s_n_llhttp__internal__n_error; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_pause_32: { + state->error = 0x15; + state->reason = "on_reset pause"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_invoke_update_finish; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_error_116: { + state->error = 0x1f; + state->reason = "`on_reset` callback error"; + state->error_pos = (const char*) p; + state->_current = (void*) (intptr_t) s_error; + return s_error; + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_llhttp__on_reset: { + switch (llhttp__on_reset(state, p, endp)) { + case 0: + goto s_n_llhttp__internal__n_invoke_update_finish; + case 21: + goto s_n_llhttp__internal__n_pause_32; + default: + goto s_n_llhttp__internal__n_error_116; + } + UNREACHABLE; + } + s_n_llhttp__internal__n_invoke_load_initial_message_completed: { + switch (llhttp__internal__c_load_initial_message_completed(state, p, endp)) { + case 1: + goto s_n_llhttp__internal__n_invoke_llhttp__on_reset; + default: + goto s_n_llhttp__internal__n_invoke_update_finish; + } + UNREACHABLE; + } +} + +int llhttp__internal_execute(llhttp__internal_t* state, const char* p, const char* endp) { + llparse_state_t next; + + /* check lingering errors */ + if (state->error != 0) { + return state->error; + } + + /* restart spans */ + if (state->_span_pos0 != NULL) { + state->_span_pos0 = (void*) p; + } + + next = llhttp__internal__run(state, (const unsigned char*) p, (const unsigned char*) endp); + if (next == s_error) { + return state->error; + } + state->_current = (void*) (intptr_t) next; + + /* execute spans */ + if (state->_span_pos0 != NULL) { + int error; + + error = ((llhttp__internal__span_cb) state->_span_cb0)(state, state->_span_pos0, (const char*) endp); + if (error != 0) { + state->error = error; + state->error_pos = endp; + return error; + } + } + + return 0; +} \ No newline at end of file diff --git a/thirdparty/llhttp/llhttp.h b/thirdparty/llhttp/llhttp.h new file mode 100644 index 0000000000..60544596a9 --- /dev/null +++ b/thirdparty/llhttp/llhttp.h @@ -0,0 +1,907 @@ + +#ifndef INCLUDE_LLHTTP_H_ +#define INCLUDE_LLHTTP_H_ + +#define LLHTTP_VERSION_MAJOR 9 +#define LLHTTP_VERSION_MINOR 3 +#define LLHTTP_VERSION_PATCH 0 + +#ifndef INCLUDE_LLHTTP_ITSELF_H_ +#define INCLUDE_LLHTTP_ITSELF_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct llhttp__internal_s llhttp__internal_t; +struct llhttp__internal_s { + int32_t _index; + void* _span_pos0; + void* _span_cb0; + int32_t error; + const char* reason; + const char* error_pos; + void* data; + void* _current; + uint64_t content_length; + uint8_t type; + uint8_t method; + uint8_t http_major; + uint8_t http_minor; + uint8_t header_state; + uint16_t lenient_flags; + uint8_t upgrade; + uint8_t finish; + uint16_t flags; + uint16_t status_code; + uint8_t initial_message_completed; + void* settings; +}; + +int llhttp__internal_init(llhttp__internal_t* s); +int llhttp__internal_execute(llhttp__internal_t* s, const char* p, const char* endp); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_ITSELF_H_ */ + + +#ifndef LLLLHTTP_C_HEADERS_ +#define LLLLHTTP_C_HEADERS_ +#ifdef __cplusplus +extern "C" { +#endif + +enum llhttp_errno { + HPE_OK = 0, + HPE_INTERNAL = 1, + HPE_STRICT = 2, + HPE_CR_EXPECTED = 25, + HPE_LF_EXPECTED = 3, + HPE_UNEXPECTED_CONTENT_LENGTH = 4, + HPE_UNEXPECTED_SPACE = 30, + HPE_CLOSED_CONNECTION = 5, + HPE_INVALID_METHOD = 6, + HPE_INVALID_URL = 7, + HPE_INVALID_CONSTANT = 8, + HPE_INVALID_VERSION = 9, + HPE_INVALID_HEADER_TOKEN = 10, + HPE_INVALID_CONTENT_LENGTH = 11, + HPE_INVALID_CHUNK_SIZE = 12, + HPE_INVALID_STATUS = 13, + HPE_INVALID_EOF_STATE = 14, + HPE_INVALID_TRANSFER_ENCODING = 15, + HPE_CB_MESSAGE_BEGIN = 16, + HPE_CB_HEADERS_COMPLETE = 17, + HPE_CB_MESSAGE_COMPLETE = 18, + HPE_CB_CHUNK_HEADER = 19, + HPE_CB_CHUNK_COMPLETE = 20, + HPE_PAUSED = 21, + HPE_PAUSED_UPGRADE = 22, + HPE_PAUSED_H2_UPGRADE = 23, + HPE_USER = 24, + HPE_CB_URL_COMPLETE = 26, + HPE_CB_STATUS_COMPLETE = 27, + HPE_CB_METHOD_COMPLETE = 32, + HPE_CB_VERSION_COMPLETE = 33, + HPE_CB_HEADER_FIELD_COMPLETE = 28, + HPE_CB_HEADER_VALUE_COMPLETE = 29, + HPE_CB_CHUNK_EXTENSION_NAME_COMPLETE = 34, + HPE_CB_CHUNK_EXTENSION_VALUE_COMPLETE = 35, + HPE_CB_RESET = 31, + HPE_CB_PROTOCOL_COMPLETE = 38 +}; +typedef enum llhttp_errno llhttp_errno_t; + +enum llhttp_flags { + F_CONNECTION_KEEP_ALIVE = 0x1, + F_CONNECTION_CLOSE = 0x2, + F_CONNECTION_UPGRADE = 0x4, + F_CHUNKED = 0x8, + F_UPGRADE = 0x10, + F_CONTENT_LENGTH = 0x20, + F_SKIPBODY = 0x40, + F_TRAILING = 0x80, + F_TRANSFER_ENCODING = 0x200 +}; +typedef enum llhttp_flags llhttp_flags_t; + +enum llhttp_lenient_flags { + LENIENT_HEADERS = 0x1, + LENIENT_CHUNKED_LENGTH = 0x2, + LENIENT_KEEP_ALIVE = 0x4, + LENIENT_TRANSFER_ENCODING = 0x8, + LENIENT_VERSION = 0x10, + LENIENT_DATA_AFTER_CLOSE = 0x20, + LENIENT_OPTIONAL_LF_AFTER_CR = 0x40, + LENIENT_OPTIONAL_CRLF_AFTER_CHUNK = 0x80, + LENIENT_OPTIONAL_CR_BEFORE_LF = 0x100, + LENIENT_SPACES_AFTER_CHUNK_SIZE = 0x200 +}; +typedef enum llhttp_lenient_flags llhttp_lenient_flags_t; + +enum llhttp_type { + HTTP_BOTH = 0, + HTTP_REQUEST = 1, + HTTP_RESPONSE = 2 +}; +typedef enum llhttp_type llhttp_type_t; + +enum llhttp_finish { + HTTP_FINISH_SAFE = 0, + HTTP_FINISH_SAFE_WITH_CB = 1, + HTTP_FINISH_UNSAFE = 2 +}; +typedef enum llhttp_finish llhttp_finish_t; + +enum llhttp_method { + HTTP_DELETE = 0, + HTTP_GET = 1, + HTTP_HEAD = 2, + HTTP_POST = 3, + HTTP_PUT = 4, + HTTP_CONNECT = 5, + HTTP_OPTIONS = 6, + HTTP_TRACE = 7, + HTTP_COPY = 8, + HTTP_LOCK = 9, + HTTP_MKCOL = 10, + HTTP_MOVE = 11, + HTTP_PROPFIND = 12, + HTTP_PROPPATCH = 13, + HTTP_SEARCH = 14, + HTTP_UNLOCK = 15, + HTTP_BIND = 16, + HTTP_REBIND = 17, + HTTP_UNBIND = 18, + HTTP_ACL = 19, + HTTP_REPORT = 20, + HTTP_MKACTIVITY = 21, + HTTP_CHECKOUT = 22, + HTTP_MERGE = 23, + HTTP_MSEARCH = 24, + HTTP_NOTIFY = 25, + HTTP_SUBSCRIBE = 26, + HTTP_UNSUBSCRIBE = 27, + HTTP_PATCH = 28, + HTTP_PURGE = 29, + HTTP_MKCALENDAR = 30, + HTTP_LINK = 31, + HTTP_UNLINK = 32, + HTTP_SOURCE = 33, + HTTP_PRI = 34, + HTTP_DESCRIBE = 35, + HTTP_ANNOUNCE = 36, + HTTP_SETUP = 37, + HTTP_PLAY = 38, + HTTP_PAUSE = 39, + HTTP_TEARDOWN = 40, + HTTP_GET_PARAMETER = 41, + HTTP_SET_PARAMETER = 42, + HTTP_REDIRECT = 43, + HTTP_RECORD = 44, + HTTP_FLUSH = 45, + HTTP_QUERY = 46 +}; +typedef enum llhttp_method llhttp_method_t; + +enum llhttp_status { + HTTP_STATUS_CONTINUE = 100, + HTTP_STATUS_SWITCHING_PROTOCOLS = 101, + HTTP_STATUS_PROCESSING = 102, + HTTP_STATUS_EARLY_HINTS = 103, + HTTP_STATUS_RESPONSE_IS_STALE = 110, + HTTP_STATUS_REVALIDATION_FAILED = 111, + HTTP_STATUS_DISCONNECTED_OPERATION = 112, + HTTP_STATUS_HEURISTIC_EXPIRATION = 113, + HTTP_STATUS_MISCELLANEOUS_WARNING = 199, + HTTP_STATUS_OK = 200, + HTTP_STATUS_CREATED = 201, + HTTP_STATUS_ACCEPTED = 202, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_RESET_CONTENT = 205, + HTTP_STATUS_PARTIAL_CONTENT = 206, + HTTP_STATUS_MULTI_STATUS = 207, + HTTP_STATUS_ALREADY_REPORTED = 208, + HTTP_STATUS_TRANSFORMATION_APPLIED = 214, + HTTP_STATUS_IM_USED = 226, + HTTP_STATUS_MISCELLANEOUS_PERSISTENT_WARNING = 299, + HTTP_STATUS_MULTIPLE_CHOICES = 300, + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + HTTP_STATUS_USE_PROXY = 305, + HTTP_STATUS_SWITCH_PROXY = 306, + HTTP_STATUS_TEMPORARY_REDIRECT = 307, + HTTP_STATUS_PERMANENT_REDIRECT = 308, + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED = 401, + HTTP_STATUS_PAYMENT_REQUIRED = 402, + HTTP_STATUS_FORBIDDEN = 403, + HTTP_STATUS_NOT_FOUND = 404, + HTTP_STATUS_METHOD_NOT_ALLOWED = 405, + HTTP_STATUS_NOT_ACCEPTABLE = 406, + HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_STATUS_REQUEST_TIMEOUT = 408, + HTTP_STATUS_CONFLICT = 409, + HTTP_STATUS_GONE = 410, + HTTP_STATUS_LENGTH_REQUIRED = 411, + HTTP_STATUS_PRECONDITION_FAILED = 412, + HTTP_STATUS_PAYLOAD_TOO_LARGE = 413, + HTTP_STATUS_URI_TOO_LONG = 414, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416, + HTTP_STATUS_EXPECTATION_FAILED = 417, + HTTP_STATUS_IM_A_TEAPOT = 418, + HTTP_STATUS_PAGE_EXPIRED = 419, + HTTP_STATUS_ENHANCE_YOUR_CALM = 420, + HTTP_STATUS_MISDIRECTED_REQUEST = 421, + HTTP_STATUS_UNPROCESSABLE_ENTITY = 422, + HTTP_STATUS_LOCKED = 423, + HTTP_STATUS_FAILED_DEPENDENCY = 424, + HTTP_STATUS_TOO_EARLY = 425, + HTTP_STATUS_UPGRADE_REQUIRED = 426, + HTTP_STATUS_PRECONDITION_REQUIRED = 428, + HTTP_STATUS_TOO_MANY_REQUESTS = 429, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL = 430, + HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_STATUS_LOGIN_TIMEOUT = 440, + HTTP_STATUS_NO_RESPONSE = 444, + HTTP_STATUS_RETRY_WITH = 449, + HTTP_STATUS_BLOCKED_BY_PARENTAL_CONTROL = 450, + HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + HTTP_STATUS_CLIENT_CLOSED_LOAD_BALANCED_REQUEST = 460, + HTTP_STATUS_INVALID_X_FORWARDED_FOR = 463, + HTTP_STATUS_REQUEST_HEADER_TOO_LARGE = 494, + HTTP_STATUS_SSL_CERTIFICATE_ERROR = 495, + HTTP_STATUS_SSL_CERTIFICATE_REQUIRED = 496, + HTTP_STATUS_HTTP_REQUEST_SENT_TO_HTTPS_PORT = 497, + HTTP_STATUS_INVALID_TOKEN = 498, + HTTP_STATUS_CLIENT_CLOSED_REQUEST = 499, + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED = 501, + HTTP_STATUS_BAD_GATEWAY = 502, + HTTP_STATUS_SERVICE_UNAVAILABLE = 503, + HTTP_STATUS_GATEWAY_TIMEOUT = 504, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_STATUS_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_STATUS_INSUFFICIENT_STORAGE = 507, + HTTP_STATUS_LOOP_DETECTED = 508, + HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED = 509, + HTTP_STATUS_NOT_EXTENDED = 510, + HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511, + HTTP_STATUS_WEB_SERVER_UNKNOWN_ERROR = 520, + HTTP_STATUS_WEB_SERVER_IS_DOWN = 521, + HTTP_STATUS_CONNECTION_TIMEOUT = 522, + HTTP_STATUS_ORIGIN_IS_UNREACHABLE = 523, + HTTP_STATUS_TIMEOUT_OCCURED = 524, + HTTP_STATUS_SSL_HANDSHAKE_FAILED = 525, + HTTP_STATUS_INVALID_SSL_CERTIFICATE = 526, + HTTP_STATUS_RAILGUN_ERROR = 527, + HTTP_STATUS_SITE_IS_OVERLOADED = 529, + HTTP_STATUS_SITE_IS_FROZEN = 530, + HTTP_STATUS_IDENTITY_PROVIDER_AUTHENTICATION_ERROR = 561, + HTTP_STATUS_NETWORK_READ_TIMEOUT = 598, + HTTP_STATUS_NETWORK_CONNECT_TIMEOUT = 599 +}; +typedef enum llhttp_status llhttp_status_t; + +#define HTTP_ERRNO_MAP(XX) \ + XX(0, OK, OK) \ + XX(1, INTERNAL, INTERNAL) \ + XX(2, STRICT, STRICT) \ + XX(25, CR_EXPECTED, CR_EXPECTED) \ + XX(3, LF_EXPECTED, LF_EXPECTED) \ + XX(4, UNEXPECTED_CONTENT_LENGTH, UNEXPECTED_CONTENT_LENGTH) \ + XX(30, UNEXPECTED_SPACE, UNEXPECTED_SPACE) \ + XX(5, CLOSED_CONNECTION, CLOSED_CONNECTION) \ + XX(6, INVALID_METHOD, INVALID_METHOD) \ + XX(7, INVALID_URL, INVALID_URL) \ + XX(8, INVALID_CONSTANT, INVALID_CONSTANT) \ + XX(9, INVALID_VERSION, INVALID_VERSION) \ + XX(10, INVALID_HEADER_TOKEN, INVALID_HEADER_TOKEN) \ + XX(11, INVALID_CONTENT_LENGTH, INVALID_CONTENT_LENGTH) \ + XX(12, INVALID_CHUNK_SIZE, INVALID_CHUNK_SIZE) \ + XX(13, INVALID_STATUS, INVALID_STATUS) \ + XX(14, INVALID_EOF_STATE, INVALID_EOF_STATE) \ + XX(15, INVALID_TRANSFER_ENCODING, INVALID_TRANSFER_ENCODING) \ + XX(16, CB_MESSAGE_BEGIN, CB_MESSAGE_BEGIN) \ + XX(17, CB_HEADERS_COMPLETE, CB_HEADERS_COMPLETE) \ + XX(18, CB_MESSAGE_COMPLETE, CB_MESSAGE_COMPLETE) \ + XX(19, CB_CHUNK_HEADER, CB_CHUNK_HEADER) \ + XX(20, CB_CHUNK_COMPLETE, CB_CHUNK_COMPLETE) \ + XX(21, PAUSED, PAUSED) \ + XX(22, PAUSED_UPGRADE, PAUSED_UPGRADE) \ + XX(23, PAUSED_H2_UPGRADE, PAUSED_H2_UPGRADE) \ + XX(24, USER, USER) \ + XX(26, CB_URL_COMPLETE, CB_URL_COMPLETE) \ + XX(27, CB_STATUS_COMPLETE, CB_STATUS_COMPLETE) \ + XX(32, CB_METHOD_COMPLETE, CB_METHOD_COMPLETE) \ + XX(33, CB_VERSION_COMPLETE, CB_VERSION_COMPLETE) \ + XX(28, CB_HEADER_FIELD_COMPLETE, CB_HEADER_FIELD_COMPLETE) \ + XX(29, CB_HEADER_VALUE_COMPLETE, CB_HEADER_VALUE_COMPLETE) \ + XX(34, CB_CHUNK_EXTENSION_NAME_COMPLETE, CB_CHUNK_EXTENSION_NAME_COMPLETE) \ + XX(35, CB_CHUNK_EXTENSION_VALUE_COMPLETE, CB_CHUNK_EXTENSION_VALUE_COMPLETE) \ + XX(31, CB_RESET, CB_RESET) \ + XX(38, CB_PROTOCOL_COMPLETE, CB_PROTOCOL_COMPLETE) \ + + +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + XX(46, QUERY, QUERY) \ + + +#define RTSP_METHOD_MAP(XX) \ + XX(1, GET, GET) \ + XX(3, POST, POST) \ + XX(6, OPTIONS, OPTIONS) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + + +#define HTTP_ALL_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + XX(30, MKCALENDAR, MKCALENDAR) \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + XX(33, SOURCE, SOURCE) \ + XX(34, PRI, PRI) \ + XX(35, DESCRIBE, DESCRIBE) \ + XX(36, ANNOUNCE, ANNOUNCE) \ + XX(37, SETUP, SETUP) \ + XX(38, PLAY, PLAY) \ + XX(39, PAUSE, PAUSE) \ + XX(40, TEARDOWN, TEARDOWN) \ + XX(41, GET_PARAMETER, GET_PARAMETER) \ + XX(42, SET_PARAMETER, SET_PARAMETER) \ + XX(43, REDIRECT, REDIRECT) \ + XX(44, RECORD, RECORD) \ + XX(45, FLUSH, FLUSH) \ + XX(46, QUERY, QUERY) \ + + +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, CONTINUE) \ + XX(101, SWITCHING_PROTOCOLS, SWITCHING_PROTOCOLS) \ + XX(102, PROCESSING, PROCESSING) \ + XX(103, EARLY_HINTS, EARLY_HINTS) \ + XX(110, RESPONSE_IS_STALE, RESPONSE_IS_STALE) \ + XX(111, REVALIDATION_FAILED, REVALIDATION_FAILED) \ + XX(112, DISCONNECTED_OPERATION, DISCONNECTED_OPERATION) \ + XX(113, HEURISTIC_EXPIRATION, HEURISTIC_EXPIRATION) \ + XX(199, MISCELLANEOUS_WARNING, MISCELLANEOUS_WARNING) \ + XX(200, OK, OK) \ + XX(201, CREATED, CREATED) \ + XX(202, ACCEPTED, ACCEPTED) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, NON_AUTHORITATIVE_INFORMATION) \ + XX(204, NO_CONTENT, NO_CONTENT) \ + XX(205, RESET_CONTENT, RESET_CONTENT) \ + XX(206, PARTIAL_CONTENT, PARTIAL_CONTENT) \ + XX(207, MULTI_STATUS, MULTI_STATUS) \ + XX(208, ALREADY_REPORTED, ALREADY_REPORTED) \ + XX(214, TRANSFORMATION_APPLIED, TRANSFORMATION_APPLIED) \ + XX(226, IM_USED, IM_USED) \ + XX(299, MISCELLANEOUS_PERSISTENT_WARNING, MISCELLANEOUS_PERSISTENT_WARNING) \ + XX(300, MULTIPLE_CHOICES, MULTIPLE_CHOICES) \ + XX(301, MOVED_PERMANENTLY, MOVED_PERMANENTLY) \ + XX(302, FOUND, FOUND) \ + XX(303, SEE_OTHER, SEE_OTHER) \ + XX(304, NOT_MODIFIED, NOT_MODIFIED) \ + XX(305, USE_PROXY, USE_PROXY) \ + XX(306, SWITCH_PROXY, SWITCH_PROXY) \ + XX(307, TEMPORARY_REDIRECT, TEMPORARY_REDIRECT) \ + XX(308, PERMANENT_REDIRECT, PERMANENT_REDIRECT) \ + XX(400, BAD_REQUEST, BAD_REQUEST) \ + XX(401, UNAUTHORIZED, UNAUTHORIZED) \ + XX(402, PAYMENT_REQUIRED, PAYMENT_REQUIRED) \ + XX(403, FORBIDDEN, FORBIDDEN) \ + XX(404, NOT_FOUND, NOT_FOUND) \ + XX(405, METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED) \ + XX(406, NOT_ACCEPTABLE, NOT_ACCEPTABLE) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, PROXY_AUTHENTICATION_REQUIRED) \ + XX(408, REQUEST_TIMEOUT, REQUEST_TIMEOUT) \ + XX(409, CONFLICT, CONFLICT) \ + XX(410, GONE, GONE) \ + XX(411, LENGTH_REQUIRED, LENGTH_REQUIRED) \ + XX(412, PRECONDITION_FAILED, PRECONDITION_FAILED) \ + XX(413, PAYLOAD_TOO_LARGE, PAYLOAD_TOO_LARGE) \ + XX(414, URI_TOO_LONG, URI_TOO_LONG) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, UNSUPPORTED_MEDIA_TYPE) \ + XX(416, RANGE_NOT_SATISFIABLE, RANGE_NOT_SATISFIABLE) \ + XX(417, EXPECTATION_FAILED, EXPECTATION_FAILED) \ + XX(418, IM_A_TEAPOT, IM_A_TEAPOT) \ + XX(419, PAGE_EXPIRED, PAGE_EXPIRED) \ + XX(420, ENHANCE_YOUR_CALM, ENHANCE_YOUR_CALM) \ + XX(421, MISDIRECTED_REQUEST, MISDIRECTED_REQUEST) \ + XX(422, UNPROCESSABLE_ENTITY, UNPROCESSABLE_ENTITY) \ + XX(423, LOCKED, LOCKED) \ + XX(424, FAILED_DEPENDENCY, FAILED_DEPENDENCY) \ + XX(425, TOO_EARLY, TOO_EARLY) \ + XX(426, UPGRADE_REQUIRED, UPGRADE_REQUIRED) \ + XX(428, PRECONDITION_REQUIRED, PRECONDITION_REQUIRED) \ + XX(429, TOO_MANY_REQUESTS, TOO_MANY_REQUESTS) \ + XX(430, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL, REQUEST_HEADER_FIELDS_TOO_LARGE_UNOFFICIAL) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, REQUEST_HEADER_FIELDS_TOO_LARGE) \ + XX(440, LOGIN_TIMEOUT, LOGIN_TIMEOUT) \ + XX(444, NO_RESPONSE, NO_RESPONSE) \ + XX(449, RETRY_WITH, RETRY_WITH) \ + XX(450, BLOCKED_BY_PARENTAL_CONTROL, BLOCKED_BY_PARENTAL_CONTROL) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, UNAVAILABLE_FOR_LEGAL_REASONS) \ + XX(460, CLIENT_CLOSED_LOAD_BALANCED_REQUEST, CLIENT_CLOSED_LOAD_BALANCED_REQUEST) \ + XX(463, INVALID_X_FORWARDED_FOR, INVALID_X_FORWARDED_FOR) \ + XX(494, REQUEST_HEADER_TOO_LARGE, REQUEST_HEADER_TOO_LARGE) \ + XX(495, SSL_CERTIFICATE_ERROR, SSL_CERTIFICATE_ERROR) \ + XX(496, SSL_CERTIFICATE_REQUIRED, SSL_CERTIFICATE_REQUIRED) \ + XX(497, HTTP_REQUEST_SENT_TO_HTTPS_PORT, HTTP_REQUEST_SENT_TO_HTTPS_PORT) \ + XX(498, INVALID_TOKEN, INVALID_TOKEN) \ + XX(499, CLIENT_CLOSED_REQUEST, CLIENT_CLOSED_REQUEST) \ + XX(500, INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR) \ + XX(501, NOT_IMPLEMENTED, NOT_IMPLEMENTED) \ + XX(502, BAD_GATEWAY, BAD_GATEWAY) \ + XX(503, SERVICE_UNAVAILABLE, SERVICE_UNAVAILABLE) \ + XX(504, GATEWAY_TIMEOUT, GATEWAY_TIMEOUT) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP_VERSION_NOT_SUPPORTED) \ + XX(506, VARIANT_ALSO_NEGOTIATES, VARIANT_ALSO_NEGOTIATES) \ + XX(507, INSUFFICIENT_STORAGE, INSUFFICIENT_STORAGE) \ + XX(508, LOOP_DETECTED, LOOP_DETECTED) \ + XX(509, BANDWIDTH_LIMIT_EXCEEDED, BANDWIDTH_LIMIT_EXCEEDED) \ + XX(510, NOT_EXTENDED, NOT_EXTENDED) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, NETWORK_AUTHENTICATION_REQUIRED) \ + XX(520, WEB_SERVER_UNKNOWN_ERROR, WEB_SERVER_UNKNOWN_ERROR) \ + XX(521, WEB_SERVER_IS_DOWN, WEB_SERVER_IS_DOWN) \ + XX(522, CONNECTION_TIMEOUT, CONNECTION_TIMEOUT) \ + XX(523, ORIGIN_IS_UNREACHABLE, ORIGIN_IS_UNREACHABLE) \ + XX(524, TIMEOUT_OCCURED, TIMEOUT_OCCURED) \ + XX(525, SSL_HANDSHAKE_FAILED, SSL_HANDSHAKE_FAILED) \ + XX(526, INVALID_SSL_CERTIFICATE, INVALID_SSL_CERTIFICATE) \ + XX(527, RAILGUN_ERROR, RAILGUN_ERROR) \ + XX(529, SITE_IS_OVERLOADED, SITE_IS_OVERLOADED) \ + XX(530, SITE_IS_FROZEN, SITE_IS_FROZEN) \ + XX(561, IDENTITY_PROVIDER_AUTHENTICATION_ERROR, IDENTITY_PROVIDER_AUTHENTICATION_ERROR) \ + XX(598, NETWORK_READ_TIMEOUT, NETWORK_READ_TIMEOUT) \ + XX(599, NETWORK_CONNECT_TIMEOUT, NETWORK_CONNECT_TIMEOUT) \ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* LLLLHTTP_C_HEADERS_ */ + + +#ifndef INCLUDE_LLHTTP_API_H_ +#define INCLUDE_LLHTTP_API_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include + +#if defined(__wasm__) +#define LLHTTP_EXPORT __attribute__((visibility("default"))) +#elif defined(_WIN32) +#define LLHTTP_EXPORT __declspec(dllexport) +#else +#define LLHTTP_EXPORT +#endif + +typedef llhttp__internal_t llhttp_t; +typedef struct llhttp_settings_s llhttp_settings_t; + +typedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length); +typedef int (*llhttp_cb)(llhttp_t*); + +struct llhttp_settings_s { + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_begin; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_protocol; + llhttp_data_cb on_url; + llhttp_data_cb on_status; + llhttp_data_cb on_method; + llhttp_data_cb on_version; + llhttp_data_cb on_header_field; + llhttp_data_cb on_header_value; + llhttp_data_cb on_chunk_extension_name; + llhttp_data_cb on_chunk_extension_value; + + /* Possible return values: + * 0 - Proceed normally + * 1 - Assume that request/response has no body, and proceed to parsing the + * next message + * 2 - Assume absence of body (as above) and make `llhttp_execute()` return + * `HPE_PAUSED_UPGRADE` + * -1 - Error + * `HPE_PAUSED` + */ + llhttp_cb on_headers_complete; + + /* Possible return values 0, -1, HPE_USER */ + llhttp_data_cb on_body; + + /* Possible return values 0, -1, `HPE_PAUSED` */ + llhttp_cb on_message_complete; + llhttp_cb on_protocol_complete; + llhttp_cb on_url_complete; + llhttp_cb on_status_complete; + llhttp_cb on_method_complete; + llhttp_cb on_version_complete; + llhttp_cb on_header_field_complete; + llhttp_cb on_header_value_complete; + llhttp_cb on_chunk_extension_name_complete; + llhttp_cb on_chunk_extension_value_complete; + + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + * Possible return values 0, -1, `HPE_PAUSED` + */ + llhttp_cb on_chunk_header; + llhttp_cb on_chunk_complete; + llhttp_cb on_reset; +}; + +/* Initialize the parser with specific type and user settings. + * + * NOTE: lifetime of `settings` has to be at least the same as the lifetime of + * the `parser` here. In practice, `settings` has to be either a static + * variable or be allocated with `malloc`, `new`, etc. + */ +LLHTTP_EXPORT +void llhttp_init(llhttp_t* parser, llhttp_type_t type, + const llhttp_settings_t* settings); + +LLHTTP_EXPORT +llhttp_t* llhttp_alloc(llhttp_type_t type); + +LLHTTP_EXPORT +void llhttp_free(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_type(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_major(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_http_minor(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_method(llhttp_t* parser); + +LLHTTP_EXPORT +int llhttp_get_status_code(llhttp_t* parser); + +LLHTTP_EXPORT +uint8_t llhttp_get_upgrade(llhttp_t* parser); + +/* Reset an already initialized parser back to the start state, preserving the + * existing parser type, callback settings, user data, and lenient flags. + */ +LLHTTP_EXPORT +void llhttp_reset(llhttp_t* parser); + +/* Initialize the settings object */ +LLHTTP_EXPORT +void llhttp_settings_init(llhttp_settings_t* settings); + +/* Parse full or partial request/response, invoking user callbacks along the + * way. + * + * If any of `llhttp_data_cb` returns errno not equal to `HPE_OK` - the parsing + * interrupts, and such errno is returned from `llhttp_execute()`. If + * `HPE_PAUSED` was used as a errno, the execution can be resumed with + * `llhttp_resume()` call. + * + * In a special case of CONNECT/Upgrade request/response `HPE_PAUSED_UPGRADE` + * is returned after fully parsing the request/response. If the user wishes to + * continue parsing, they need to invoke `llhttp_resume_after_upgrade()`. + * + * NOTE: if this function ever returns a non-pause type error, it will continue + * to return the same error upon each successive call up until `llhttp_init()` + * is called. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len); + +/* This method should be called when the other side has no further bytes to + * send (e.g. shutdown of readable side of the TCP connection.) + * + * Requests without `Content-Length` and other messages might require treating + * all incoming bytes as the part of the body, up to the last byte of the + * connection. This method will invoke `on_message_complete()` callback if the + * request was terminated safely. Otherwise a error code would be returned. + */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_finish(llhttp_t* parser); + +/* Returns `1` if the incoming message is parsed until the last byte, and has + * to be completed by calling `llhttp_finish()` on EOF + */ +LLHTTP_EXPORT +int llhttp_message_needs_eof(const llhttp_t* parser); + +/* Returns `1` if there might be any other messages following the last that was + * successfully parsed. + */ +LLHTTP_EXPORT +int llhttp_should_keep_alive(const llhttp_t* parser); + +/* Make further calls of `llhttp_execute()` return `HPE_PAUSED` and set + * appropriate error reason. + * + * Important: do not call this from user callbacks! User callbacks must return + * `HPE_PAUSED` if pausing is required. + */ +LLHTTP_EXPORT +void llhttp_pause(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED`. + */ +LLHTTP_EXPORT +void llhttp_resume(llhttp_t* parser); + +/* Might be called to resume the execution after the pause in user's callback. + * See `llhttp_execute()` above for details. + * + * Call this only if `llhttp_execute()` returns `HPE_PAUSED_UPGRADE` + */ +LLHTTP_EXPORT +void llhttp_resume_after_upgrade(llhttp_t* parser); + +/* Returns the latest return error */ +LLHTTP_EXPORT +llhttp_errno_t llhttp_get_errno(const llhttp_t* parser); + +/* Returns the verbal explanation of the latest returned error. + * + * Note: User callback should set error reason when returning the error. See + * `llhttp_set_error_reason()` for details. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_reason(const llhttp_t* parser); + +/* Assign verbal description to the returned error. Must be called in user + * callbacks right before returning the errno. + * + * Note: `HPE_USER` error code might be useful in user callbacks. + */ +LLHTTP_EXPORT +void llhttp_set_error_reason(llhttp_t* parser, const char* reason); + +/* Returns the pointer to the last parsed byte before the returned error. The + * pointer is relative to the `data` argument of `llhttp_execute()`. + * + * Note: this method might be useful for counting the number of parsed bytes. + */ +LLHTTP_EXPORT +const char* llhttp_get_error_pos(const llhttp_t* parser); + +/* Returns textual name of error code */ +LLHTTP_EXPORT +const char* llhttp_errno_name(llhttp_errno_t err); + +/* Returns textual name of HTTP method */ +LLHTTP_EXPORT +const char* llhttp_method_name(llhttp_method_t method); + +/* Returns textual name of HTTP status */ +LLHTTP_EXPORT +const char* llhttp_status_name(llhttp_status_t status); + +/* Enables/disables lenient header value parsing (disabled by default). + * + * Lenient parsing disables header value token checks, extending llhttp's + * protocol support to highly non-compliant clients/server. No + * `HPE_INVALID_HEADER_TOKEN` will be raised for incorrect header values when + * lenient parsing is "on". + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_headers(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of conflicting `Transfer-Encoding` and + * `Content-Length` headers (disabled by default). + * + * Normally `llhttp` would error when `Transfer-Encoding` is present in + * conjunction with `Content-Length`. This error is important to prevent HTTP + * request smuggling, but may be less desirable for small number of cases + * involving legacy servers. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_chunked_length(llhttp_t* parser, int enabled); + + +/* Enables/disables lenient handling of `Connection: close` and HTTP/1.0 + * requests responses. + * + * Normally `llhttp` would error on (in strict mode) or discard (in loose mode) + * the HTTP request/response after the request/response with `Connection: close` + * and `Content-Length`. This is important to prevent cache poisoning attacks, + * but might interact badly with outdated and insecure clients. With this flag + * the extra request/response will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of `Transfer-Encoding` header. + * + * Normally `llhttp` would error when a `Transfer-Encoding` has `chunked` value + * and another value after it (either in a single header or in multiple + * headers whose value are internally joined using `, `). + * This is mandated by the spec to reliably determine request body size and thus + * avoid request smuggling. + * With this flag the extra value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of HTTP version. + * + * Normally `llhttp` would error when the HTTP version in the request or status line + * is not `0.9`, `1.0`, `1.1` or `2.0`. + * With this flag the invalid value will be parsed normally. + * + * **Enabling this flag can pose a security issue since you will allow unsupported + * HTTP versions. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_version(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of additional data received after a message ends + * and keep-alive is disabled. + * + * Normally `llhttp` would error when additional unexpected data is received if the message + * contains the `Connection` header with `close` value. + * With this flag the extra data will discarded without throwing an error. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * poisoning attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_data_after_close(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of incomplete CRLF sequences. + * + * Normally `llhttp` would error when a CR is not followed by LF when terminating the + * request line, the status line, the headers or a chunk header. + * With this flag only a CR is required to terminate such sections. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, int enabled); + +/* + * Enables/disables lenient handling of line separators. + * + * Normally `llhttp` would error when a LF is not preceded by CR when terminating the + * request line, the status line, the headers, a chunk header or a chunk data. + * With this flag only a LF is required to terminate such sections. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of chunks not separated via CRLF. + * + * Normally `llhttp` would error when after a chunk data a CRLF is missing before + * starting a new chunk. + * With this flag the new chunk can start immediately after the previous one. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, int enabled); + +/* Enables/disables lenient handling of spaces after chunk size. + * + * Normally `llhttp` would error when after a chunk size is followed by one or more + * spaces are present instead of a CRLF or `;`. + * With this flag this check is disabled. + * + * **Enabling this flag can pose a security issue since you will be exposed to + * request smuggling attacks. USE WITH CAUTION!** + */ +LLHTTP_EXPORT +void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* INCLUDE_LLHTTP_API_H_ */ + + +#endif /* INCLUDE_LLHTTP_H_ */ diff --git a/thirdparty/multipart_parser.c b/thirdparty/multipart_parser.c index a96e403fd2..96539faf81 100644 --- a/thirdparty/multipart_parser.c +++ b/thirdparty/multipart_parser.c @@ -75,7 +75,7 @@ #define CR 13 enum state { - s_uninitialized = 1, + s_uninitialized = 0, s_start, s_start_boundary, s_header_field_start, @@ -88,7 +88,6 @@ enum state { s_part_data, s_part_data_almost_boundary, s_part_data_boundary, - s_part_data_almost_almost_end, s_part_data_almost_end, s_part_data_end, s_part_data_final_hyphen, @@ -99,9 +98,9 @@ multipart_parser *multipart_parser_init(const char *boundary, size_t boundary_length, const multipart_parser_settings *settings) { multipart_parser *p = calloc(sizeof(multipart_parser) + boundary_length + boundary_length + 9 + 4, sizeof(char)); - memcpy(p->multipart_boundary, "--", 2); - memcpy(p->multipart_boundary + 2, boundary, boundary_length); - p->multipart_boundary[2 + boundary_length] = 0; + memcpy(p->boundary, "--", 2); + memcpy(p->boundary + 2, boundary, boundary_length); + p->boundary[2 + boundary_length] = 0; p->boundary_length = boundary_length + 2; p->index = 0; @@ -128,9 +127,9 @@ int multipart_parser_error_msg(multipart_parser *p, char *buf, size_t len) { case MPPE_PAUSED: return snprintf(buf, len, "parser paused"); case MPPE_UNKNOWN: + return snprintf(buf, len, "parser unknown"); default: - abort(); - return 0; + return snprintf(buf, len, "parser abort"); case MPPE_BOUNDARY_END_NO_CRLF: ret = snprintf(buf, len, "no CRLF at first boundary end: "); break; @@ -200,10 +199,14 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le p->index = 0; p->state = s_start_boundary; /* fallthrough */ + /* no break */ case s_start_boundary: multipart_log_c("s_start_boundary"); if (p->index == p->boundary_length) { - if (c != CR) { + // https://github.com/swoole/swoole-src/issues/5168 + if (c == '-') { + p->state = s_part_data_final_hyphen; + } else if (c != CR) { ERROR_EXPECT(MPPE_BOUNDARY_END_NO_CRLF, CR); } p->index++; @@ -217,8 +220,8 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le NOTIFY_CB(part_data_begin, i + 1); break; } - if (c != p->multipart_boundary[p->index]) { - ERROR_EXPECT(MPPE_BAD_START_BOUNDARY, p->multipart_boundary[p->index]); + if (c != p->boundary[p->index]) { + ERROR_EXPECT(MPPE_BAD_START_BOUNDARY, p->boundary[p->index]); } p->index++; break; @@ -227,6 +230,7 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le mark = i; p->state = s_header_field; /* fallthrough */ + /* no break */ case s_header_field: multipart_log_c("s_header_field"); if (c == CR) { @@ -269,14 +273,14 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le mark = i; p->state = s_header_value; /* fallthrough */ + /* no break */ case s_header_value: multipart_log_c("s_header_value"); if (c == CR) { p->state = s_header_value_almost_done; EMIT_DATA_CB(header_value, i + 1, buf + mark, i - mark); - } - if (is_last) { - EMIT_DATA_CB(header_value, i + 1, buf + mark, i - mark + 1); + } else if (is_last) { + ERROR_EXPECT(MPPE_HEADER_VALUE_INCOMPLETE, CR); } break; case s_header_value_almost_done: @@ -292,20 +296,16 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le p->state = s_part_data; NOTIFY_CB(headers_complete, i); /* fallthrough */ + /* no break */ case s_part_data: data_rollback: multipart_log_c("s_part_data"); mark_end = i + 1; if (c == CR) { - mark_end--; - if (is_last) { - if (i > 1) { - EMIT_DATA_CB(part_data, i, buf + mark, mark_end - mark); - } else { - // donot trig callback - return 0; - } + if (mark_end - mark - 1 > 0) { + EMIT_DATA_CB(part_data, i + 1, buf + mark, mark_end - mark - 1); } + mark = i; p->state = s_part_data_almost_boundary; break; } @@ -315,53 +315,43 @@ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t le break; case s_part_data_almost_boundary: multipart_log_c("s_part_data_almost_boundary"); - if (c == LF) { - if (is_last) { - if (i > 2) { - EMIT_DATA_CB(part_data, mark_end, buf + mark, mark_end - mark); - } else { - // donot trig callback - return 0; - } - } + if (c != LF) { + EMIT_DATA_CB(part_data, i + 1, "\r", 1); + p->state = s_part_data; + mark = i; + goto data_rollback; + } else { p->state = s_part_data_boundary; p->index = 0; break; } - p->state = s_part_data; - goto data_rollback; case s_part_data_boundary: multipart_log_c("s_part_data_boundary"); - if (p->multipart_boundary[p->index] != c) { + if (p->boundary[p->index] != c) { + EMIT_DATA_CB(part_data, i + 1, "\r\n", 2); + if (p->index > 0) { + EMIT_DATA_CB(part_data, i + 1, p->boundary, p->index); + } + mark = i; p->state = s_part_data; goto data_rollback; - } - if (is_last) { - if (i > p->index + 2) { - EMIT_DATA_CB(part_data, i - p->index - 2, buf + mark, mark_end - mark); - } else { - // donot trig callback - return 0; + } else { + p->index++; + if (p->index == p->boundary_length) { + p->state = s_part_data_almost_end; } + break; } - if ((++p->index) == p->boundary_length) { - p->state = s_part_data_almost_almost_end; - EMIT_DATA_CB(part_data, i + 1, buf + mark, i + 1 - p->boundary_length - 2 - mark); - } - break; - case s_part_data_almost_almost_end: - multipart_log_c("s_part_data_almost_almost_end"); - p->state = s_part_data_almost_end; - NOTIFY_CB(part_data_end, i); - /* fallthrough */ case s_part_data_almost_end: multipart_log_c("s_part_data_almost_end"); if (c == '-') { p->state = s_part_data_final_hyphen; + NOTIFY_CB(part_data_end, i + 1); break; } if (c == CR) { p->state = s_part_data_end; + NOTIFY_CB(part_data_end, i + 1); break; } // should be end or another part diff --git a/thirdparty/multipart_parser.h b/thirdparty/multipart_parser.h index a468e10260..3d470b816e 100644 --- a/thirdparty/multipart_parser.h +++ b/thirdparty/multipart_parser.h @@ -32,6 +32,7 @@ enum multipart_error { MPPE_INVALID_HEADER_VALUE_CHAR, MPPE_BAD_PART_END, MPPE_END_BOUNDARY_NO_DASH, + MPPE_HEADER_VALUE_INCOMPLETE, }; #define MPPE_ERROR -1 @@ -55,7 +56,7 @@ struct multipart_parser { /* public error reason */ unsigned char error_reason; /* private boundary storage: "--" + boundary */ - char multipart_boundary[(2 + BOUNDARY_MAX_LEN) * 2 + 9]; + char boundary[(2 + BOUNDARY_MAX_LEN) * 2 + 9]; /* public error expected char */ char error_expected; /* public error unexpected char */ @@ -74,10 +75,10 @@ struct multipart_parser_settings { */ multipart_data_cb on_header_value; /* - * data callback called on body data coming + * data callback called on body data coming, + * will be called repeatedly until data end */ multipart_data_cb on_part_data; - /* * before "--" boundary */ @@ -100,6 +101,10 @@ multipart_parser *multipart_parser_init(const char *boundary, size_t boundary_length, const multipart_parser_settings *settings); void multipart_parser_free(multipart_parser *p); + +/** + * The multipart header must be complete, otherwise it will be parsed incorrectly + */ ssize_t multipart_parser_execute(multipart_parser *p, const char *buf, size_t len); int multipart_parser_error_msg(multipart_parser *p, char *buf, size_t len); diff --git a/thirdparty/nghttp2/LICENSE b/thirdparty/nghttp2/LICENSE index fe4d503d01..d9e4eb197b 100644 --- a/thirdparty/nghttp2/LICENSE +++ b/thirdparty/nghttp2/LICENSE @@ -1,23 +1 @@ -The MIT License - -Copyright (c) 2012, 2014, 2015, 2016 Tatsuhiro Tsujikawa -Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +See COPYING diff --git a/thirdparty/nghttp2/nghttp2.h b/thirdparty/nghttp2/nghttp2.h index cf099d2e0c..29f12ac79f 100644 --- a/thirdparty/nghttp2/nghttp2.h +++ b/thirdparty/nghttp2/nghttp2.h @@ -52,28 +52,27 @@ extern "C" { #include #include #include +#include +const char *nghttp2_strerror(int error_code); static inline uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) { - if (len == 0) { - return dest; - } + if (len == 0) { + return dest; + } - memcpy(dest, src, len); + memcpy(dest, src, len); - return dest + len; + return dest + len; } -#define nghttp2_min(A, B) ((A) < (B) ? (A) : (B)) -#define nghttp2_max(A, B) ((A) > (B) ? (A) : (B)) - #define DEBUGF(s, ...) - -const char *nghttp2_strerror(int error_code); +#define nghttp2_min_size(A, B) ((A) < (B) ? (A) : (B)) +#define nghttp2_max_size(A, B) ((A) > (B) ? (A) : (B)) #ifdef NGHTTP2_STATICLIB # define NGHTTP2_EXTERN -#elif defined(WIN32) || (__has_declspec_attribute(dllexport) && \ - __has_declspec_attribute(dllimport)) +#elif defined(WIN32) || \ + (__has_declspec_attribute(dllexport) && __has_declspec_attribute(dllimport)) # ifdef BUILDING_NGHTTP2 # define NGHTTP2_EXTERN __declspec(dllexport) # else /* !BUILDING_NGHTTP2 */ @@ -87,6 +86,17 @@ const char *nghttp2_strerror(int error_code); # endif /* !BUILDING_NGHTTP2 */ #endif /* !defined(WIN32) */ +#ifdef BUILDING_NGHTTP2 +# undef NGHTTP2_NO_SSIZE_T +#endif /* BUILDING_NGHTTP2 */ + +/** + * @typedef + * + * :type:`nghttp2_ssize` is a signed counterpart of size_t. + */ +typedef ptrdiff_t nghttp2_ssize; + /** * @macro * @@ -135,6 +145,15 @@ const char *nghttp2_strerror(int error_code); */ #define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN 3 +struct nghttp2_session; +/** + * @struct + * + * The primary structure to hold the resources needed for a HTTP/2 + * session. The details of this structure are intentionally hidden + * from the public API. + */ +typedef struct nghttp2_session nghttp2_session; /** * @macro @@ -175,6 +194,12 @@ typedef struct { /** * @macro * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The default weight of stream dependency. */ #define NGHTTP2_DEFAULT_WEIGHT 16 @@ -182,6 +207,12 @@ typedef struct { /** * @macro * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The maximum weight of stream dependency. */ #define NGHTTP2_MAX_WEIGHT 256 @@ -189,6 +220,12 @@ typedef struct { /** * @macro * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The minimum weight of stream dependency. */ #define NGHTTP2_MIN_WEIGHT 1 @@ -262,7 +299,7 @@ typedef enum { */ NGHTTP2_ERR_UNSUPPORTED_VERSION = -503, /** - * Used as a return value from :type:`nghttp2_send_callback`, + * Used as a return value from :type:`nghttp2_send_callback2`, * :type:`nghttp2_recv_callback` and * :type:`nghttp2_send_data_callback` to indicate that the operation * would block. @@ -282,9 +319,9 @@ typedef enum { NGHTTP2_ERR_EOF = -507, /** * Used as a return value from - * :func:`nghttp2_data_source_read_callback` to indicate that data + * :func:`nghttp2_data_source_read_callback2` to indicate that data * transfer is postponed. See - * :func:`nghttp2_data_source_read_callback` for details. + * :func:`nghttp2_data_source_read_callback2` for details. */ NGHTTP2_ERR_DEFERRED = -508, /** @@ -447,7 +484,12 @@ typedef enum { * exhaustion on server side to send these frames forever and does * not read network. */ - NGHTTP2_ERR_FLOODED = -904 + NGHTTP2_ERR_FLOODED = -904, + /** + * When a local endpoint receives too many CONTINUATION frames + * following a HEADER frame. + */ + NGHTTP2_ERR_TOO_MANY_CONTINUATIONS = -905, } nghttp2_error; /** @@ -499,6 +541,15 @@ NGHTTP2_EXTERN void nghttp2_rcbuf_decref(nghttp2_rcbuf *rcbuf); */ NGHTTP2_EXTERN nghttp2_vec nghttp2_rcbuf_get_buf(nghttp2_rcbuf *rcbuf); +/** + * @function + * + * Returns nonzero if the underlying buffer is statically allocated, + * and 0 otherwise. This can be useful for language bindings that wish + * to avoid creating duplicate strings for these buffers. + */ +NGHTTP2_EXTERN int nghttp2_rcbuf_is_static(const nghttp2_rcbuf *rcbuf); + /** * @enum * @@ -632,7 +683,11 @@ typedef enum { * The ORIGIN frame, which is defined by `RFC 8336 * `_. */ - NGHTTP2_ORIGIN = 0x0c + NGHTTP2_ORIGIN = 0x0c, + /** + * The PRIORITY_UPDATE frame, which is defined by :rfc:`9218`. + */ + NGHTTP2_PRIORITY_UPDATE = 0x10 } nghttp2_frame_type; /** @@ -701,7 +756,11 @@ typedef enum { * SETTINGS_ENABLE_CONNECT_PROTOCOL * (`RFC 8441 `_) */ - NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08 + NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, + /** + * SETTINGS_NO_RFC7540_PRIORITIES (:rfc:`9218`) + */ + NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09 } nghttp2_settings_id; /* Note: If we add SETTINGS, update the capacity of NGHTTP2_INBOUND_NUM_IV as well */ @@ -820,7 +879,7 @@ typedef struct { * @union * * This union represents the some kind of data source passed to - * :type:`nghttp2_data_source_read_callback`. + * :type:`nghttp2_data_source_read_callback2`. */ typedef union { /** @@ -837,7 +896,7 @@ typedef union { * @enum * * The flags used to set in |data_flags| output parameter in - * :type:`nghttp2_data_source_read_callback`. + * :type:`nghttp2_data_source_read_callback2`. */ typedef enum { /** @@ -851,8 +910,8 @@ typedef enum { /** * Indicates that END_STREAM flag must not be set even if * NGHTTP2_DATA_FLAG_EOF is set. Usually this flag is used to send - * trailer fields with `nghttp2_submit_request()` or - * `nghttp2_submit_response()`. + * trailer fields with `nghttp2_submit_request2()` or + * `nghttp2_submit_response2()`. */ NGHTTP2_DATA_FLAG_NO_END_STREAM = 0x02, /** @@ -862,61 +921,188 @@ typedef enum { NGHTTP2_DATA_FLAG_NO_COPY = 0x04 } nghttp2_data_flag; +#ifndef NGHTTP2_NO_SSIZE_T /** * @functypedef * - * Custom memory allocator to replace malloc(). The |mem_user_data| - * is the mem_user_data member of :type:`nghttp2_mem` structure. - */ -typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data); - -/** - * @functypedef + * .. warning:: * - * Custom memory allocator to replace free(). The |mem_user_data| is - * the mem_user_data member of :type:`nghttp2_mem` structure. + * Deprecated. Use :type:`nghttp2_data_source_read_callback2` + * instead. + * + * Callback function invoked when the library wants to read data from + * the |source|. The read data is sent in the stream |stream_id|. + * The implementation of this function must read at most |length| + * bytes of data from |source| (or possibly other places) and store + * them in |buf| and return number of data stored in |buf|. If EOF is + * reached, set :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag + * in |*data_flags|. + * + * Sometime it is desirable to avoid copying data into |buf| and let + * application to send data directly. To achieve this, set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to + * |*data_flags| (and possibly other flags, just like when we do + * copy), and return the number of bytes to send without copying data + * into |buf|. The library, seeing + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke + * :type:`nghttp2_send_data_callback`. The application must send + * complete DATA frame in that callback. + * + * If this callback is set by `nghttp2_submit_request()`, + * `nghttp2_submit_response()` or `nghttp2_submit_headers()` and + * `nghttp2_submit_data()` with flag parameter + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to + * |*data_flags|, DATA frame will have END_STREAM flag set. Usually, + * this is expected behaviour and all are fine. One exception is send + * trailer fields. You cannot send trailer fields after sending frame + * with END_STREAM set. To avoid this problem, one can set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along + * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the + * library not to set END_STREAM in DATA frame. Then application can + * use `nghttp2_submit_trailer()` to send trailer fields. + * `nghttp2_submit_trailer()` can be called inside this callback. + * + * If the application wants to postpone DATA frames (e.g., + * asynchronous I/O, or reading data blocks for long time), it is + * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` + * without reading any data in this invocation. The library removes + * DATA frame from the outgoing queue temporarily. To move back + * deferred DATA frame to outgoing queue, call + * `nghttp2_session_resume_data()`. + * + * By default, |length| is limited to 16KiB at maximum. If peer + * allows larger frames, application can enlarge transmission buffer + * size. See :type:`nghttp2_data_source_read_length_callback` for + * more details. + * + * If the application just wants to return from + * `nghttp2_session_send()` or `nghttp2_session_mem_send()` without + * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. + * + * In case of error, there are 2 choices. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream by issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. If a different + * error code is desirable, use `nghttp2_submit_rst_stream()` with a + * desired error code and then return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Returning :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will + * signal the entire session failure. */ -typedef void (*nghttp2_free)(void *ptr, void *mem_user_data); +typedef ssize_t (*nghttp2_data_source_read_callback)( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ /** * @functypedef * - * Custom memory allocator to replace calloc(). The |mem_user_data| - * is the mem_user_data member of :type:`nghttp2_mem` structure. + * Callback function invoked when the library wants to read data from + * the |source|. The read data is sent in the stream |stream_id|. + * The implementation of this function must read at most |length| + * bytes of data from |source| (or possibly other places) and store + * them in |buf| and return number of data stored in |buf|. If EOF is + * reached, set :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag + * in |*data_flags|. + * + * Sometime it is desirable to avoid copying data into |buf| and let + * application to send data directly. To achieve this, set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` to + * |*data_flags| (and possibly other flags, just like when we do + * copy), and return the number of bytes to send without copying data + * into |buf|. The library, seeing + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY`, will invoke + * :type:`nghttp2_send_data_callback`. The application must send + * complete DATA frame in that callback. + * + * If this callback is set by `nghttp2_submit_request2()`, + * `nghttp2_submit_response2()` or `nghttp2_submit_headers()` and + * `nghttp2_submit_data2()` with flag parameter + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` set, and + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` flag is set to + * |*data_flags|, DATA frame will have END_STREAM flag set. Usually, + * this is expected behaviour and all are fine. One exception is send + * trailer fields. You cannot send trailer fields after sending frame + * with END_STREAM set. To avoid this problem, one can set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM` along + * with :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF` to signal the + * library not to set END_STREAM in DATA frame. Then application can + * use `nghttp2_submit_trailer()` to send trailer fields. + * `nghttp2_submit_trailer()` can be called inside this callback. + * + * If the application wants to postpone DATA frames (e.g., + * asynchronous I/O, or reading data blocks for long time), it is + * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` + * without reading any data in this invocation. The library removes + * DATA frame from the outgoing queue temporarily. To move back + * deferred DATA frame to outgoing queue, call + * `nghttp2_session_resume_data()`. + * + * By default, |length| is limited to 16KiB at maximum. If peer + * allows larger frames, application can enlarge transmission buffer + * size. See :type:`nghttp2_data_source_read_length_callback` for + * more details. + * + * If the application just wants to return from + * `nghttp2_session_send()` or `nghttp2_session_mem_send2()` without + * sending anything, return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. + * + * In case of error, there are 2 choices. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream by issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. If a different + * error code is desirable, use `nghttp2_submit_rst_stream()` with a + * desired error code and then return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Returning :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will + * signal the entire session failure. */ -typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data); +typedef nghttp2_ssize (*nghttp2_data_source_read_callback2)( + nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, + uint32_t *data_flags, nghttp2_data_source *source, void *user_data); +#ifndef NGHTTP2_NO_SSIZE_T /** - * @functypedef + * @struct * - * Custom memory allocator to replace realloc(). The |mem_user_data| - * is the mem_user_data member of :type:`nghttp2_mem` structure. + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_data_provider2` instead. + * + * This struct represents the data source and the way to read a chunk + * of data from it. */ -typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data); - typedef struct { /** - * An arbitrary user supplied data. This is passed to each - * allocator function. - */ - void *mem_user_data; - /** - * Custom allocator function to replace malloc(). + * The data source. */ - nghttp2_malloc malloc; + nghttp2_data_source source; /** - * Custom allocator function to replace free(). + * The callback function to read a chunk of data from the |source|. */ - nghttp2_free free; + nghttp2_data_source_read_callback read_callback; +} nghttp2_data_provider; + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @struct + * + * This struct represents the data source and the way to read a chunk + * of data from it. + */ +typedef struct { /** - * Custom allocator function to replace calloc(). + * The data source. */ - nghttp2_calloc calloc; + nghttp2_data_source source; /** - * Custom allocator function to replace realloc(). + * The callback function to read a chunk of data from the |source|. */ - nghttp2_realloc realloc; -} nghttp2_mem; + nghttp2_data_source_read_callback2 read_callback; +} nghttp2_data_provider2; /** * @struct @@ -970,6 +1156,12 @@ typedef enum { /** * @struct * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The structure to specify stream dependency. */ typedef struct { @@ -1004,6 +1196,12 @@ typedef struct { */ size_t padlen; /** + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The priority specification */ nghttp2_priority_spec pri_spec; @@ -1024,6 +1222,12 @@ typedef struct { /** * @struct * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * * The PRIORITY frame. It has the following members: */ typedef struct { @@ -1267,182 +1471,4887 @@ typedef union { nghttp2_extension ext; } nghttp2_frame; -/* HPACK API */ - -struct nghttp2_hd_deflater; - +#ifndef NGHTTP2_NO_SSIZE_T /** - * @struct + * @functypedef * - * HPACK deflater object. + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_send_callback2` instead. + * + * Callback function invoked when |session| wants to send data to the + * remote peer. The implementation of this function must send at most + * |length| bytes of data stored in |data|. The |flags| is currently + * not used and always 0. It must return the number of bytes sent if + * it succeeds. If it cannot send any single byte without blocking, + * it must return :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. For + * other errors, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_send()` to send data to the remote endpoint. If + * the application uses solely `nghttp2_session_mem_send()` instead, + * this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_send_callback()`. + * + * .. note:: + * + * The |length| may be very small. If that is the case, and + * application disables Nagle algorithm (``TCP_NODELAY``), then just + * writing |data| to the network stack leads to very small packet, + * and it is very inefficient. An application should be responsible + * to buffer up small chunks of data as necessary to avoid this + * situation. */ -typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; +typedef ssize_t (*nghttp2_send_callback)(nghttp2_session *session, + const uint8_t *data, size_t length, + int flags, void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ /** - * @function - * - * Initializes |*deflater_ptr| for deflating name/values pairs. - * - * The |max_deflate_dynamic_table_size| is the upper bound of header - * table size the deflater will use. + * @functypedef * - * If this function fails, |*deflater_ptr| is left untouched. + * Callback function invoked when |session| wants to send data to the + * remote peer. The implementation of this function must send at most + * |length| bytes of data stored in |data|. The |flags| is currently + * not used and always 0. It must return the number of bytes sent if + * it succeeds. If it cannot send any single byte without blocking, + * it must return :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. For + * other errors, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_send()` to send data to the remote endpoint. If + * the application uses solely `nghttp2_session_mem_send2()` instead, + * this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_send_callback2()`. * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: + * .. note:: * - * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` - * Out of memory. + * The |length| may be very small. If that is the case, and + * application disables Nagle algorithm (``TCP_NODELAY``), then just + * writing |data| to the network stack leads to very small packet, + * and it is very inefficient. An application should be responsible + * to buffer up small chunks of data as necessary to avoid this + * situation. */ -NGHTTP2_EXTERN int -nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, - size_t max_deflate_dynamic_table_size); +typedef nghttp2_ssize (*nghttp2_send_callback2)(nghttp2_session *session, + const uint8_t *data, + size_t length, int flags, + void *user_data); /** - * @function + * @functypedef * - * Like `nghttp2_hd_deflate_new()`, but with additional custom memory - * allocator specified in the |mem|. + * Callback function invoked when + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in + * :type:`nghttp2_data_source_read_callback` to send complete DATA + * frame. * - * The |mem| can be ``NULL`` and the call is equivalent to - * `nghttp2_hd_deflate_new()`. + * The |frame| is a DATA frame to send. The |framehd| is the + * serialized frame header (9 bytes). The |length| is the length of + * application data to send (this does not include padding). The + * |source| is the same pointer passed to + * :type:`nghttp2_data_source_read_callback`. * - * This function does not take ownership |mem|. The application is - * responsible for freeing |mem|. + * The application first must send frame header |framehd| of length 9 + * bytes. If ``frame->data.padlen > 0``, send 1 byte of value + * ``frame->data.padlen - 1``. Then send exactly |length| bytes of + * application data. Finally, if ``frame->data.padlen > 1``, send + * ``frame->data.padlen - 1`` bytes of zero as padding. + * + * The application has to send complete DATA frame in this callback. + * If all data were written successfully, return 0. + * + * If it cannot send any data at all, just return + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`; the library will call + * this callback with the same parameters later (It is recommended to + * send complete DATA frame at once in this function to deal with + * error; if partial frame data has already sent, it is impossible to + * send another data in that state, and all we can do is tear down + * connection). When data is fully processed, but application wants + * to make `nghttp2_session_mem_send2()` or `nghttp2_session_send()` + * return immediately without processing next frames, return + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE`. If application decided to + * reset this stream, return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`, then + * the library will send RST_STREAM with INTERNAL_ERROR as error code. + * The application can also return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which will + * result in connection closure. Returning any other value is treated + * as :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. + */ +typedef int (*nghttp2_send_data_callback)(nghttp2_session *session, + nghttp2_frame *frame, + const uint8_t *framehd, size_t length, + nghttp2_data_source *source, + void *user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @functypedef * - * The library code does not refer to |mem| pointer after this - * function returns, so the application can safely free it. + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_recv_callback2` instead. + * + * Callback function invoked when |session| wants to receive data from + * the remote peer. The implementation of this function must read at + * most |length| bytes of data and store it in |buf|. The |flags| is + * currently not used and always 0. It must return the number of + * bytes written in |buf| if it succeeds. If it cannot read any + * single byte without blocking, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF + * before it reads any single byte, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must + * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * Returning 0 is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. The |user_data| + * pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_recv()` to receive data from the remote endpoint. + * If the application uses solely `nghttp2_session_mem_recv()` + * instead, this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_recv_callback()`. */ -NGHTTP2_EXTERN int -nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, - size_t max_deflate_dynamic_table_size, - nghttp2_mem *mem); +typedef ssize_t (*nghttp2_recv_callback)(nghttp2_session *session, uint8_t *buf, + size_t length, int flags, + void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ /** - * @function + * @functypedef * - * Deallocates any resources allocated for |deflater|. + * Callback function invoked when |session| wants to receive data from + * the remote peer. The implementation of this function must read at + * most |length| bytes of data and store it in |buf|. The |flags| is + * currently not used and always 0. It must return the number of + * bytes written in |buf| if it succeeds. If it cannot read any + * single byte without blocking, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. If it gets EOF + * before it reads any single byte, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_EOF`. For other errors, it must + * return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * Returning 0 is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. The |user_data| + * pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * This callback is required if the application uses + * `nghttp2_session_recv()` to receive data from the remote endpoint. + * If the application uses solely `nghttp2_session_mem_recv2()` + * instead, this callback function is unnecessary. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_recv_callback2()`. */ -NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); +typedef nghttp2_ssize (*nghttp2_recv_callback2)(nghttp2_session *session, + uint8_t *buf, size_t length, + int flags, void *user_data); /** - * @function + * @functypedef * - * Changes header table size of the |deflater| to - * |settings_max_dynamic_table_size| bytes. This may trigger eviction - * in the dynamic table. + * Callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv2()` when a frame is received. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * - * The |settings_max_dynamic_table_size| should be the value received - * in SETTINGS_HEADER_TABLE_SIZE. + * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` + * member of their data structure are always ``NULL`` and 0 + * respectively. The header name/value pairs are emitted via + * :type:`nghttp2_on_header_callback`. * - * The deflater never uses more memory than - * ``max_deflate_dynamic_table_size`` bytes specified in - * `nghttp2_hd_deflate_new()`. Therefore, if - * |settings_max_dynamic_table_size| > - * ``max_deflate_dynamic_table_size``, resulting maximum table size - * becomes ``max_deflate_dynamic_table_size``. + * Only HEADERS and DATA frame can signal the end of incoming data. + * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the + * |frame| is the last frame from the remote peer in this stream. * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: + * This callback won't be called for CONTINUATION frames. + * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame. * - * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` - * Out of memory. + * The implementation of this function must return 0 if it succeeds. + * If nonzero value is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_recv_callback()`. */ -NGHTTP2_EXTERN int -nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, - size_t settings_max_dynamic_table_size); +typedef int (*nghttp2_on_frame_recv_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); /** - * @function + * @functypedef * - * Deflates the |nva|, which has the |nvlen| name/value pairs, into - * the |buf| of length |buflen|. + * Callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv2()` when an invalid non-DATA frame is + * received. The error is indicated by the |lib_error_code|, which is + * one of the values defined in :type:`nghttp2_error`. When this + * callback function is invoked, the library automatically submits + * either RST_STREAM or GOAWAY frame. The |user_data| pointer is the + * third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. + * + * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen`` + * member of their data structure are always ``NULL`` and 0 + * respectively. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_invalid_frame_recv_callback()`. + */ +typedef int (*nghttp2_on_invalid_frame_recv_callback)( + nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, + void *user_data); + +/** + * @functypedef * - * If |buf| is not large enough to store the deflated header block, - * this function fails with - * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller - * should use `nghttp2_hd_deflate_bound()` to know the upper bound of - * buffer size required to deflate given header name/value pairs. + * Callback function invoked when a chunk of data in DATA frame is + * received. The |stream_id| is the stream ID this DATA frame belongs + * to. The |flags| is the flags of DATA frame which this data chunk + * is contained. ``(flags & NGHTTP2_FLAG_END_STREAM) != 0`` does not + * necessarily mean this chunk of data is the last one in the stream. + * You should use :type:`nghttp2_on_frame_recv_callback` to know all + * data frames are received. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * If the application uses `nghttp2_session_mem_recv2()`, it can + * return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make + * `nghttp2_session_mem_recv2()` return without processing further + * input bytes. The memory by pointed by the |data| is retained until + * `nghttp2_session_mem_recv2()` or `nghttp2_session_recv()` is + * called. The application must retain the input bytes which was used + * to produce the |data| parameter, because it may refer to the memory + * region included in the input bytes. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error, and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_data_chunk_recv_callback()`. + */ +typedef int (*nghttp2_on_data_chunk_recv_callback)(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *data, + size_t len, void *user_data); + +/** + * @functypedef * - * Once this function fails, subsequent call of this function always - * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * Callback function invoked just before the non-DATA frame |frame| is + * sent. The |user_data| pointer is the third argument passed in to + * the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * The implementation of this function must return 0 if it succeeds. + * It can also return :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` to + * cancel the transmission of the given frame. + * + * If there is a fatal error while executing this callback, the + * implementation should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, which makes + * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * If the other value is returned, it is treated as if + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. + * But the implementation should not rely on this since the library + * may define new return value to extend its capability. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_before_frame_send_callback()`. + */ +typedef int (*nghttp2_before_frame_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); + +/** + * @functypedef * - * After this function returns, it is safe to delete the |nva|. + * Callback function invoked after the frame |frame| is sent. The + * |user_data| pointer is the third argument passed in to the call to + * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * - * This function returns the number of bytes written to |buf| if it - * succeeds, or one of the following negative error codes: + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * - * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` - * Out of memory. - * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` - * Deflation process has failed. - * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` - * The provided |buflen| size is too small to hold the output. + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_send_callback()`. */ -NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, - uint8_t *buf, size_t buflen, - const nghttp2_nv *nva, - size_t nvlen); +typedef int (*nghttp2_on_frame_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); /** - * @function + * @functypedef * - * Deflates the |nva|, which has the |nvlen| name/value pairs, into - * the |veclen| size of buf vector |vec|. The each size of buffer - * must be set in len field of :type:`nghttp2_vec`. If and only if - * one chunk is filled up completely, next chunk will be used. If - * |vec| is not large enough to store the deflated header block, this - * function fails with - * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller - * should use `nghttp2_hd_deflate_bound()` to know the upper bound of - * buffer size required to deflate given header name/value pairs. + * Callback function invoked after the non-DATA frame |frame| is not + * sent because of the error. The error is indicated by the + * |lib_error_code|, which is one of the values defined in + * :type:`nghttp2_error`. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. * - * Once this function fails, subsequent call of this function always - * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. * - * After this function returns, it is safe to delete the |nva|. + * `nghttp2_session_get_stream_user_data()` can be used to get + * associated data. * - * This function returns the number of bytes written to |vec| if it - * succeeds, or one of the following negative error codes: + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_frame_not_send_callback()`. + */ +typedef int (*nghttp2_on_frame_not_send_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + int lib_error_code, + void *user_data); + +/** + * @functypedef * - * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` - * Out of memory. - * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` - * Deflation process has failed. - * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` - * The provided |buflen| size is too small to hold the output. + * Callback function invoked when the stream |stream_id| is closed. + * The reason of closure is indicated by the |error_code|. The + * |error_code| is usually one of :enum:`nghttp2_error_code`, but that + * is not guaranteed. The stream_user_data, which was specified in + * `nghttp2_submit_request2()` or `nghttp2_submit_headers()`, is still + * available in this function. The |user_data| pointer is the third + * argument passed in to the call to `nghttp2_session_client_new()` or + * `nghttp2_session_server_new()`. + * + * This function is also called for a stream in reserved state. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero is returned, it is treated as fatal error and + * `nghttp2_session_recv()`, `nghttp2_session_mem_recv2()`, + * `nghttp2_session_send()`, and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_stream_close_callback()`. */ -NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, - const nghttp2_vec *vec, - size_t veclen, - const nghttp2_nv *nva, - size_t nvlen); +typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session, + int32_t stream_id, + uint32_t error_code, + void *user_data); /** - * @function + * @functypedef * - * Returns an upper bound on the compressed size after deflation of - * |nva| of length |nvlen|. + * Callback function invoked when the reception of header block in + * HEADERS or PUSH_PROMISE is started. Each header name/value pair + * will be emitted by :type:`nghttp2_on_header_callback`. + * + * The ``frame->hd.flags`` may not have + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_HEADERS` flag set, which + * indicates that one or more CONTINUATION frames are involved. But + * the application does not need to care about that because the header + * name/value pairs are emitted transparently regardless of + * CONTINUATION frames. + * + * The server applications probably create an object to store + * information about new stream if ``frame->hd.type == + * NGHTTP2_HEADERS`` and ``frame->headers.cat == + * NGHTTP2_HCAT_REQUEST``. If |session| is configured as server side, + * ``frame->headers.cat`` is either ``NGHTTP2_HCAT_REQUEST`` + * containing request headers or ``NGHTTP2_HCAT_HEADERS`` containing + * trailer fields and never get PUSH_PROMISE in this callback. + * + * For the client applications, ``frame->hd.type`` is either + * ``NGHTTP2_HEADERS`` or ``NGHTTP2_PUSH_PROMISE``. In case of + * ``NGHTTP2_HEADERS``, ``frame->headers.cat == + * NGHTTP2_HCAT_RESPONSE`` means that it is the first response + * headers, but it may be non-final response which is indicated by 1xx + * status code. In this case, there may be zero or more HEADERS frame + * with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` which has + * non-final response code and finally client gets exactly one HEADERS + * frame with ``frame->headers.cat == NGHTTP2_HCAT_HEADERS`` + * containing final response headers (non-1xx status code). The + * trailer fields also has ``frame->headers.cat == + * NGHTTP2_HCAT_HEADERS`` which does not contain any status code. + * + * Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream (promised stream if frame is PUSH_PROMISE) by + * issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, + * :type:`nghttp2_on_header_callback` and + * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a + * different error code is desirable, use + * `nghttp2_submit_rst_stream()` with a desired error code and then + * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Again, use ``frame->push_promise.promised_stream_id`` as stream_id + * parameter in `nghttp2_submit_rst_stream()` if frame is + * PUSH_PROMISE. + * + * The implementation of this function must return 0 if it succeeds. + * It can return + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` to + * reset the stream (promised stream if frame is PUSH_PROMISE). For + * critical errors, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * value is returned, it is treated as if + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned. If + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, + * `nghttp2_session_mem_recv2()` function will immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_begin_headers_callback()`. */ -NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, - const nghttp2_nv *nva, - size_t nvlen); +typedef int (*nghttp2_on_begin_headers_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data); /** - * @function + * @functypedef * - * Returns the number of entries that header table of |deflater| - * contains. This is the sum of the number of static table and - * dynamic table, so the return value is at least 61. + * Callback function invoked when a header name/value pair is received + * for the |frame|. The |name| of length |namelen| is header name. + * The |value| of length |valuelen| is header value. The |flags| is + * bitwise OR of one or more of :type:`nghttp2_nv_flag`. + * + * If :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_INDEX` is set in + * |flags|, the receiver must not index this name/value pair when + * forwarding it to the next hop. More specifically, "Literal Header + * Field never Indexed" representation must be used in HPACK encoding. + * + * When this callback is invoked, ``frame->hd.type`` is either + * :enum:`nghttp2_frame_type.NGHTTP2_HEADERS` or + * :enum:`nghttp2_frame_type.NGHTTP2_PUSH_PROMISE`. After all header + * name/value pairs are processed with this callback, and no error has + * been detected, :type:`nghttp2_on_frame_recv_callback` will be + * invoked. If there is an error in decompression, + * :type:`nghttp2_on_frame_recv_callback` for the |frame| will not be + * invoked. + * + * Both |name| and |value| are guaranteed to be NULL-terminated. The + * |namelen| and |valuelen| do not include terminal NULL. If + * `nghttp2_option_set_no_http_messaging()` is used with nonzero + * value, NULL character may be included in |name| or |value| before + * terminating NULL. + * + * Please note that unless `nghttp2_option_set_no_http_messaging()` is + * used, nghttp2 library does perform validation against the |name| + * and the |value| using `nghttp2_check_header_name()` and + * `nghttp2_check_header_value()`. In addition to this, nghttp2 + * performs validation based on HTTP Messaging rule, which is briefly + * explained in :ref:`http-messaging` section. + * + * If the application uses `nghttp2_session_mem_recv2()`, it can + * return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` to make + * `nghttp2_session_mem_recv2()` return without processing further + * input bytes. The memory pointed by |frame|, |name| and |value| + * parameters are retained until `nghttp2_session_mem_recv2()` or + * `nghttp2_session_recv()` is called. The application must retain + * the input bytes which was used to produce these parameters, because + * it may refer to the memory region included in the input bytes. + * + * Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will + * close the stream (promised stream if frame is PUSH_PROMISE) by + * issuing RST_STREAM with + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. In this case, + * :type:`nghttp2_on_header_callback` and + * :type:`nghttp2_on_frame_recv_callback` will not be invoked. If a + * different error code is desirable, use + * `nghttp2_submit_rst_stream()` with a desired error code and then + * return :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * Again, use ``frame->push_promise.promised_stream_id`` as stream_id + * parameter in `nghttp2_submit_rst_stream()` if frame is + * PUSH_PROMISE. + * + * The implementation of this function must return 0 if it succeeds. + * It may return :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` or + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. For + * other critical failures, it must return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * nonzero value is returned, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` is returned, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_header_callback()`. + * + * .. warning:: + * + * Application should properly limit the total buffer size to store + * incoming header fields. Without it, peer may send large number + * of header fields or large header fields to cause out of memory in + * local endpoint. Due to how HPACK works, peer can do this + * effectively without using much memory on their own. */ -NGHTTP2_EXTERN -size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater); +typedef int (*nghttp2_on_header_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data); /** - * @function + * @functypedef * - * Returns the table entry denoted by |idx| from header table of - * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of + * Callback function invoked when a header name/value pair is received + * for the |frame|. The |name| is header name. The |value| is header + * value. The |flags| is bitwise OR of one or more of + * :type:`nghttp2_nv_flag`. + * + * This callback behaves like :type:`nghttp2_on_header_callback`, + * except that |name| and |value| are stored in reference counted + * buffer. If application wishes to keep these references without + * copying them, use `nghttp2_rcbuf_incref()` to increment their + * reference count. It is the application's responsibility to call + * `nghttp2_rcbuf_decref()` if they called `nghttp2_rcbuf_incref()` so + * as not to leak memory. If the |session| is created by + * `nghttp2_session_server_new3()` or `nghttp2_session_client_new3()`, + * the function to free memory is the one belongs to the mem + * parameter. As long as this free function alives, |name| and + * |value| can live after |session| was destroyed. + */ +typedef int (*nghttp2_on_header_callback2)(nghttp2_session *session, + const nghttp2_frame *frame, + nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a invalid header name/value pair is + * received for the |frame|. + * + * The parameter and behaviour are similar to + * :type:`nghttp2_on_header_callback`. The difference is that this + * callback is only invoked when a invalid header name/value pair is + * received which is treated as stream error if this callback is not + * set. Only invalid regular header field are passed to this + * callback. In other words, invalid pseudo header field is not + * passed to this callback. Also header fields which includes upper + * cased latter are also treated as error without passing them to this + * callback. + * + * This callback is only considered if HTTP messaging validation is + * turned on (which is on by default, see + * `nghttp2_option_set_no_http_messaging()`). + * + * With this callback, application inspects the incoming invalid + * field, and it also can reset stream from this callback by returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By + * default, the error code is + * :enum:`nghttp2_error_code.NGHTTP2_PROTOCOL_ERROR`. To change the + * error code, call `nghttp2_submit_rst_stream()` with the error code + * of choice in addition to returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + * + * If 0 is returned, the header field is ignored, and the stream is + * not reset. + */ +typedef int (*nghttp2_on_invalid_header_callback)( + nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a invalid header name/value pair is + * received for the |frame|. + * + * The parameter and behaviour are similar to + * :type:`nghttp2_on_header_callback2`. The difference is that this + * callback is only invoked when a invalid header name/value pair is + * received which is silently ignored if this callback is not set. + * Only invalid regular header field are passed to this callback. In + * other words, invalid pseudo header field is not passed to this + * callback. Also header fields which includes upper cased latter are + * also treated as error without passing them to this callback. + * + * This callback is only considered if HTTP messaging validation is + * turned on (which is on by default, see + * `nghttp2_option_set_no_http_messaging()`). + * + * With this callback, application inspects the incoming invalid + * field, and it also can reset stream from this callback by returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. By + * default, the error code is + * :enum:`nghttp2_error_code.NGHTTP2_INTERNAL_ERROR`. To change the + * error code, call `nghttp2_submit_rst_stream()` with the error code + * of choice in addition to returning + * :enum:`nghttp2_error.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_on_invalid_header_callback2)( + nghttp2_session *session, const nghttp2_frame *frame, nghttp2_rcbuf *name, + nghttp2_rcbuf *value, uint8_t flags, void *user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @functypedef + * + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_select_padding_callback2` + * instead. + * + * Callback function invoked when the library asks application how + * many padding bytes are required for the transmission of the + * |frame|. The application must choose the total length of payload + * including padded bytes in range [frame->hd.length, max_payloadlen], + * inclusive. Choosing number not in this range will be treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning + * ``frame->hd.length`` means no padding is added. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_select_padding_callback()`. + */ +typedef ssize_t (*nghttp2_select_padding_callback)(nghttp2_session *session, + const nghttp2_frame *frame, + size_t max_payloadlen, + void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @functypedef + * + * Callback function invoked when the library asks application how + * many padding bytes are required for the transmission of the + * |frame|. The application must choose the total length of payload + * including padded bytes in range [frame->hd.length, max_payloadlen], + * inclusive. Choosing number not in this range will be treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Returning + * ``frame->hd.length`` means no padding is added. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will make + * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_select_padding_callback2()`. + */ +typedef nghttp2_ssize (*nghttp2_select_padding_callback2)( + nghttp2_session *session, const nghttp2_frame *frame, size_t max_payloadlen, + void *user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @functypedef + * + * .. warning:: + * + * Deprecated. Use + * :type:`nghttp2_data_source_read_length_callback2` instead. + * + * Callback function invoked when library wants to get max length of + * data to send data to the remote peer. The implementation of this + * function should return a value in the following range. [1, + * min(|session_remote_window_size|, |stream_remote_window_size|, + * |remote_max_frame_size|)]. If a value greater than this range is + * returned than the max allow value will be used. Returning a value + * smaller than this range is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |frame_type| is provided for future extensibility and identifies + * the type of frame (see :type:`nghttp2_frame_type`) for which to get + * the length for. Currently supported frame types are: + * :enum:`nghttp2_frame_type.NGHTTP2_DATA`. + * + * This callback can be used to control the length in bytes for which + * :type:`nghttp2_data_source_read_callback` is allowed to send to the + * remote endpoint. This callback is optional. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will signal the + * entire session failure. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_data_source_read_length_callback()`. + */ +typedef ssize_t (*nghttp2_data_source_read_length_callback)( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @functypedef + * + * Callback function invoked when library wants to get max length of + * data to send data to the remote peer. The implementation of this + * function should return a value in the following range. [1, + * min(|session_remote_window_size|, |stream_remote_window_size|, + * |remote_max_frame_size|)]. If a value greater than this range is + * returned than the max allow value will be used. Returning a value + * smaller than this range is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. The + * |frame_type| is provided for future extensibility and identifies + * the type of frame (see :type:`nghttp2_frame_type`) for which to get + * the length for. Currently supported frame types are: + * :enum:`nghttp2_frame_type.NGHTTP2_DATA`. + * + * This callback can be used to control the length in bytes for which + * :type:`nghttp2_data_source_read_callback` is allowed to send to the + * remote endpoint. This callback is optional. Returning + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` will signal the + * entire session failure. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_data_source_read_length_callback2()`. + */ +typedef nghttp2_ssize (*nghttp2_data_source_read_length_callback2)( + nghttp2_session *session, uint8_t frame_type, int32_t stream_id, + int32_t session_remote_window_size, int32_t stream_remote_window_size, + uint32_t remote_max_frame_size, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when a frame header is received. The + * |hd| points to received frame header. + * + * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will + * also be called when frame header of CONTINUATION frame is received. + * + * If both :type:`nghttp2_on_begin_frame_callback` and + * :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or + * PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback` + * will be called first. + * + * The implementation of this function must return 0 if it succeeds. + * If nonzero value is returned, it is treated as fatal error and + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * + * To set this callback to :type:`nghttp2_session_callbacks`, use + * `nghttp2_session_callbacks_set_on_begin_frame_callback()`. + */ +typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session, + const nghttp2_frame_hd *hd, + void *user_data); + +/** + * @functypedef + * + * Callback function invoked when chunk of extension frame payload is + * received. The |hd| points to frame header. The received + * chunk is |data| of length |len|. + * + * The implementation of this function must return 0 if it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_on_extension_chunk_recv_callback)( + nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library asks the application to + * unpack extension payload from its wire format. The extension + * payload has been passed to the application using + * :type:`nghttp2_on_extension_chunk_recv_callback`. The frame header + * is already unpacked by the library and provided as |hd|. + * + * To receive extension frames, the application must tell desired + * extension frame type to the library using + * `nghttp2_option_set_user_recv_extension_type()`. + * + * The implementation of this function may store the pointer to the + * created object as a result of unpacking in |*payload|, and returns + * 0. The pointer stored in |*payload| is opaque to the library, and + * the library does not own its pointer. |*payload| is initialized as + * ``NULL``. The |*payload| is available as ``frame->ext.payload`` in + * :type:`nghttp2_on_frame_recv_callback`. Therefore if application + * can free that memory inside :type:`nghttp2_on_frame_recv_callback` + * callback. Of course, application has a liberty not to use + * |*payload|, and do its own mechanism to process extension frames. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_recv()` and `nghttp2_session_mem_recv2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef int (*nghttp2_unpack_extension_callback)(nghttp2_session *session, + void **payload, + const nghttp2_frame_hd *hd, + void *user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @functypedef + * + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_pack_extension_callback2` + * instead. + * + * Callback function invoked when library asks the application to pack + * extension payload in its wire format. The frame header will be + * packed by library. Application must pack payload only. + * ``frame->ext.payload`` is the object passed to + * `nghttp2_submit_extension()` as payload parameter. Application + * must pack extension payload to the |buf| of its capacity |len| + * bytes. The |len| is at least 16KiB. + * + * The implementation of this function should return the number of + * bytes written into |buf| when it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and + * :type:`nghttp2_on_frame_not_send_callback` will be invoked. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_send()` and `nghttp2_session_mem_send()` functions + * immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return + * value is strictly larger than |len|, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef ssize_t (*nghttp2_pack_extension_callback)(nghttp2_session *session, + uint8_t *buf, size_t len, + const nghttp2_frame *frame, + void *user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @functypedef + * + * Callback function invoked when library asks the application to pack + * extension payload in its wire format. The frame header will be + * packed by library. Application must pack payload only. + * ``frame->ext.payload`` is the object passed to + * `nghttp2_submit_extension()` as payload parameter. Application + * must pack extension payload to the |buf| of its capacity |len| + * bytes. The |len| is at least 16KiB. + * + * The implementation of this function should return the number of + * bytes written into |buf| when it succeeds. + * + * To abort processing this extension frame, return + * :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL`, and + * :type:`nghttp2_on_frame_not_send_callback` will be invoked. + * + * If fatal error occurred, application should return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. In this case, + * `nghttp2_session_send()` and `nghttp2_session_mem_send2()` + * functions immediately return + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the other + * values are returned, currently they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. If the return + * value is strictly larger than |len|, it is treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + */ +typedef nghttp2_ssize (*nghttp2_pack_extension_callback2)( + nghttp2_session *session, uint8_t *buf, size_t len, + const nghttp2_frame *frame, void *user_data); + +/** + * @functypedef + * + * .. warning:: + * + * Deprecated. Use :type:`nghttp2_error_callback2` instead. + * + * Callback function invoked when library provides the error message + * intended for human consumption. This callback is solely for + * debugging purpose. The |msg| is typically NULL-terminated string + * of length |len|. |len| does not include the sentinel NULL + * character. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback)(nghttp2_session *session, const char *msg, + size_t len, void *user_data); + +/** + * @functypedef + * + * Callback function invoked when library provides the error code, and + * message. This callback is solely for debugging purpose. + * |lib_error_code| is one of error code defined in + * :enum:`nghttp2_error`. The |msg| is typically NULL-terminated + * string of length |len|, and intended for human consumption. |len| + * does not include the sentinel NULL character. + * + * The format of error message may change between nghttp2 library + * versions. The application should not depend on the particular + * format. + * + * Normally, application should return 0 from this callback. If fatal + * error occurred while doing something in this callback, application + * should return :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. + * In this case, library will return immediately with return value + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`. Currently, if + * nonzero value is returned from this callback, they are treated as + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE`, but application + * should not rely on this details. + */ +typedef int (*nghttp2_error_callback2)(nghttp2_session *session, + int lib_error_code, const char *msg, + size_t len, void *user_data); + +struct nghttp2_session_callbacks; + +/** + * @struct + * + * Callback functions for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_session_callbacks nghttp2_session_callbacks; + +/** + * @function + * + * Initializes |*callbacks_ptr| with NULL values. + * + * The initialized object can be used when initializing multiple + * :type:`nghttp2_session` objects. + * + * When the application finished using this object, it can use + * `nghttp2_session_callbacks_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr); + +/** + * @function + * + * Frees any resources allocated for |callbacks|. If |callbacks| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_session_callbacks_set_send_callback2()` + * with :type:`nghttp2_send_callback2` instead. + * + * Sets callback function invoked when a session wants to send data to + * the remote peer. This callback is not necessary if the application + * uses solely `nghttp2_session_mem_send()` to serialize data to + * transmit. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback( + nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Sets callback function invoked when a session wants to send data to + * the remote peer. This callback is not necessary if the application + * uses solely `nghttp2_session_mem_send2()` to serialize data to + * transmit. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_callback2( + nghttp2_session_callbacks *cbs, nghttp2_send_callback2 send_callback); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_session_callbacks_set_recv_callback2()` + * with :type:`nghttp2_recv_callback2` instead. + * + * Sets callback function invoked when the a session wants to receive + * data from the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_recv()` to process + * received data. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Sets callback function invoked when the a session wants to receive + * data from the remote peer. This callback is not necessary if the + * application uses solely `nghttp2_session_mem_recv2()` to process + * received data. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_recv_callback2( + nghttp2_session_callbacks *cbs, nghttp2_recv_callback2 recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv2()` when a frame is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_recv_callback on_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked by `nghttp2_session_recv()` and + * `nghttp2_session_mem_recv2()` when an invalid non-DATA frame is + * received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_invalid_frame_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback); + +/** + * @function + * + * Sets callback function invoked when a chunk of data in DATA frame + * is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback); + +/** + * @function + * + * Sets callback function invoked before a non-DATA frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_before_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_before_frame_send_callback before_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked after a frame is sent. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_send_callback on_frame_send_callback); + +/** + * @function + * + * Sets callback function invoked when a non-DATA frame is not sent + * because of an error. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_frame_not_send_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_frame_not_send_callback on_frame_not_send_callback); + +/** + * @function + * + * Sets callback function invoked when the stream is closed. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_stream_close_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_stream_close_callback on_stream_close_callback); + +/** + * @function + * + * Sets callback function invoked when the reception of header block + * in HEADERS or PUSH_PROMISE is started. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_headers_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_headers_callback on_begin_headers_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. If both + * `nghttp2_session_callbacks_set_on_header_callback()` and + * `nghttp2_session_callbacks_set_on_header_callback2()` are used to + * set callbacks, the latter has the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback on_header_callback); + +/** + * @function + * + * Sets callback function invoked when a header name/value pair is + * received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_header_callback2 on_header_callback2); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. If both + * `nghttp2_session_callbacks_set_on_invalid_header_callback()` and + * `nghttp2_session_callbacks_set_on_invalid_header_callback2()` are + * used to set callbacks, the latter takes the precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback on_invalid_header_callback); + +/** + * @function + * + * Sets callback function invoked when a invalid header name/value + * pair is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_invalid_header_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_on_invalid_header_callback2 on_invalid_header_callback2); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use + * `nghttp2_session_callbacks_set_select_padding_callback2()` with + * :type:`nghttp2_select_padding_callback2` instead. + * + * Sets callback function invoked when the library asks application + * how many padding bytes are required for the transmission of the + * given frame. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback select_padding_callback); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Sets callback function invoked when the library asks application + * how many padding bytes are required for the transmission of the + * given frame. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_select_padding_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_select_padding_callback2 select_padding_callback); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use + * `nghttp2_session_callbacks_set_data_source_read_length_callback2()` + * with :type:`nghttp2_data_source_read_length_callback2` instead. + * + * Sets callback function determine the length allowed in + * :type:`nghttp2_data_source_read_callback`. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_data_source_read_length_callback( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback data_source_read_length_callback); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Sets callback function determine the length allowed in + * :type:`nghttp2_data_source_read_callback2`. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_data_source_read_length_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_data_source_read_length_callback2 data_source_read_length_callback); + +/** + * @function + * + * Sets callback function invoked when a frame header is received. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_on_begin_frame_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_begin_frame_callback on_begin_frame_callback); + +/** + * @function + * + * Sets callback function invoked when + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_COPY` is used in + * :type:`nghttp2_data_source_read_callback2` to avoid data copy. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_send_data_callback( + nghttp2_session_callbacks *cbs, + nghttp2_send_data_callback send_data_callback); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use + * `nghttp2_session_callbacks_set_pack_extension_callback2()` with + * :type:`nghttp2_pack_extension_callback2` instead. + * + * Sets callback function invoked when the library asks the + * application to pack extension frame payload in wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback pack_extension_callback); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to pack extension frame payload in wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_pack_extension_callback2( + nghttp2_session_callbacks *cbs, + nghttp2_pack_extension_callback2 pack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when the library asks the + * application to unpack extension frame payload from wire format. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_unpack_extension_callback( + nghttp2_session_callbacks *cbs, + nghttp2_unpack_extension_callback unpack_extension_callback); + +/** + * @function + * + * Sets callback function invoked when chunk of extension frame + * payload is received. + */ +NGHTTP2_EXTERN void +nghttp2_session_callbacks_set_on_extension_chunk_recv_callback( + nghttp2_session_callbacks *cbs, + nghttp2_on_extension_chunk_recv_callback on_extension_chunk_recv_callback); + +/** + * @function + * + * .. warning:: + * + * Deprecated. Use + * `nghttp2_session_callbacks_set_error_callback2()` with + * :type:`nghttp2_error_callback2` instead. + * + * Sets callback function invoked when library tells error message to + * the application. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback( + nghttp2_session_callbacks *cbs, nghttp2_error_callback error_callback); + +/** + * @function + * + * Sets callback function invoked when library tells error code, and + * message to the application. + * + * If both :type:`nghttp2_error_callback` and + * :type:`nghttp2_error_callback2` are set, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void nghttp2_session_callbacks_set_error_callback2( + nghttp2_session_callbacks *cbs, nghttp2_error_callback2 error_callback2); + +/** + * @functypedef + * + * Custom memory allocator to replace malloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace free(). The |mem_user_data| is + * the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void (*nghttp2_free)(void *ptr, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace calloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data); + +/** + * @functypedef + * + * Custom memory allocator to replace realloc(). The |mem_user_data| + * is the mem_user_data member of :type:`nghttp2_mem` structure. + */ +typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data); + +/** + * @struct + * + * Custom memory allocator functions and user defined pointer. The + * |mem_user_data| member is passed to each allocator function. This + * can be used, for example, to achieve per-session memory pool. + * + * In the following example code, ``my_malloc``, ``my_free``, + * ``my_calloc`` and ``my_realloc`` are the replacement of the + * standard allocators ``malloc``, ``free``, ``calloc`` and + * ``realloc`` respectively:: + * + * void *my_malloc_cb(size_t size, void *mem_user_data) { + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *mem_user_data) { my_free(ptr); } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *mem_user_data) { + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *mem_user_data) { + * return my_realloc(ptr, size); + * } + * + * void session_new() { + * nghttp2_session *session; + * nghttp2_session_callbacks *callbacks; + * nghttp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * + * nghttp2_session_client_new3(&session, callbacks, NULL, NULL, &mem); + * + * ... + * } + */ +typedef struct { + /** + * An arbitrary user supplied data. This is passed to each + * allocator function. + */ + void *mem_user_data; + /** + * Custom allocator function to replace malloc(). + */ + nghttp2_malloc malloc; + /** + * Custom allocator function to replace free(). + */ + nghttp2_free free; + /** + * Custom allocator function to replace calloc(). + */ + nghttp2_calloc calloc; + /** + * Custom allocator function to replace realloc(). + */ + nghttp2_realloc realloc; +} nghttp2_mem; + +struct nghttp2_option; + +/** + * @struct + * + * Configuration options for :type:`nghttp2_session`. The details of + * this structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_option nghttp2_option; + +/** + * @function + * + * Initializes |*option_ptr| with default values. + * + * When the application finished using this object, it can use + * `nghttp2_option_del()` to free its memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_option_new(nghttp2_option **option_ptr); + +/** + * @function + * + * Frees any resources allocated for |option|. If |option| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void nghttp2_option_del(nghttp2_option *option); + +/** + * @function + * + * This option prevents the library from sending WINDOW_UPDATE for a + * connection automatically. If this option is set to nonzero, the + * library won't send WINDOW_UPDATE for DATA until application calls + * `nghttp2_session_consume()` to indicate the consumed amount of + * data. Don't use `nghttp2_submit_window_update()` for this purpose. + * By default, this option is set to zero. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val); + +/** + * @function + * + * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of + * remote endpoint as if it is received in SETTINGS frame. Without + * specifying this option, the maximum number of outgoing concurrent + * streams is initially limited to 100 to avoid issues when the local + * endpoint submits lots of requests before receiving initial SETTINGS + * frame from the remote endpoint, since sending them at once to the + * remote endpoint could lead to rejection of some of the requests. + * This value will be overwritten when the local endpoint receives + * initial SETTINGS frame from the remote endpoint, either to the + * value advertised in SETTINGS_MAX_CONCURRENT_STREAMS or to the + * default value (unlimited) if none was advertised. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option, + uint32_t val); + +/** + * @function + * + * By default, nghttp2 library, if configured as server, requires + * first 24 bytes of client magic byte string (MAGIC). In most cases, + * this will simplify the implementation of server. But sometimes + * server may want to detect the application protocol based on first + * few bytes on clear text communication. + * + * If this option is used with nonzero |val|, nghttp2 library does not + * handle MAGIC. It still checks following SETTINGS frame. This + * means that applications should deal with MAGIC by themselves. + * + * If this option is not used or used with zero value, if MAGIC does + * not match :macro:`NGHTTP2_CLIENT_MAGIC`, `nghttp2_session_recv()` + * and `nghttp2_session_mem_recv2()` will return error + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC`, which is fatal + * error. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_recv_client_magic(nghttp2_option *option, int val); + +/** + * @function + * + * By default, nghttp2 library enforces subset of HTTP Messaging rules + * described in `HTTP/2 specification, section 8 + * `_. See + * :ref:`http-messaging` section for details. For those applications + * who use nghttp2 library as non-HTTP use, give nonzero to |val| to + * disable this enforcement. Please note that disabling this feature + * does not change the fundamental client and server model of HTTP. + * That is, even if the validation is disabled, only client can send + * requests. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_http_messaging(nghttp2_option *option, + int val); + +/** + * @function + * + * RFC 7540 does not enforce any limit on the number of incoming + * reserved streams (in RFC 7540 terms, streams in reserved (remote) + * state). This only affects client side, since only server can push + * streams. Malicious server can push arbitrary number of streams, + * and make client's memory exhausted. This option can set the + * maximum number of such incoming streams to avoid possible memory + * exhaustion. If this option is set, and pushed streams are + * automatically closed on reception, without calling user provided + * callback, if they exceed the given limit. The default value is + * 200. If session is configured as server side, this option has no + * effect. Server can control the number of streams to push. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, + uint32_t val); + +/** + * @function + * + * Sets extension frame type the application is willing to handle with + * user defined callbacks (see + * :type:`nghttp2_on_extension_chunk_recv_callback` and + * :type:`nghttp2_unpack_extension_callback`). The |type| is + * extension frame type, and must be strictly greater than 0x9. + * Otherwise, this function does nothing. The application can call + * this function multiple times to set more than one frame type to + * receive. The application does not have to call this function if it + * just sends extension frames. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, + uint8_t type); + +/** + * @function + * + * Sets extension frame type the application is willing to receive + * using builtin handler. The |type| is the extension frame type to + * receive, and must be strictly greater than 0x9. Otherwise, this + * function does nothing. The application can call this function + * multiple times to set more than one frame type to receive. The + * application does not have to call this function if it just sends + * extension frames. + * + * If same frame type is passed to both + * `nghttp2_option_set_builtin_recv_extension_type()` and + * `nghttp2_option_set_user_recv_extension_type()`, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type); + +/** + * @function + * + * This option prevents the library from sending PING frame with ACK + * flag set automatically when PING frame without ACK flag set is + * received. If this option is set to nonzero, the library won't send + * PING frame with ACK flag set in the response for incoming PING + * frame. The application can send PING frame with ACK flag set using + * `nghttp2_submit_ping()` with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` + * as flags parameter. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, + int val); + +/** + * @function + * + * This option sets the maximum length of header block (a set of + * header fields per one HEADERS frame) to send. The length of a + * given set of header fields is calculated using + * `nghttp2_hd_deflate_bound()`. The default value is 64KiB. If + * application attempts to send header fields larger than this limit, + * the transmission of the frame fails with error code + * :enum:`nghttp2_error.NGHTTP2_ERR_FRAME_SIZE_ERROR`. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_send_header_block_length(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option sets the maximum dynamic table size for deflating + * header fields. The default value is 4KiB. In HTTP/2, receiver of + * deflated header block can specify maximum dynamic table size. The + * actual maximum size is the minimum of the size receiver specified + * and this option value. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option prevents the library from retaining closed streams to + * maintain the priority tree. If this option is set to nonzero, + * applications can discard closed stream completely to save memory. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, any + * closed streams are not retained regardless of this option. + */ +NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option, + int val); + +/** + * @function + * + * This function sets the maximum number of outgoing SETTINGS ACK and + * PING ACK frames retained in :type:`nghttp2_session` object. If + * more than those frames are retained, the peer is considered to be + * misbehaving and session will be closed. The default value is 1000. + */ +NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This function sets the maximum number of SETTINGS entries per + * SETTINGS frame that will be accepted. If more than those entries + * are received, the peer is considered to be misbehaving and session + * will be closed. The default value is 32. + */ +NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option, + size_t val); + +/** + * @function + * + * This option, if set to nonzero, allows server to fallback to + * :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not + * received from client, and server submitted + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * = 1 via `nghttp2_submit_settings()`. Most of the advanced + * functionality for RFC 7540 priorities are still disabled. This + * fallback only enables the minimal feature set of RFC 7540 + * priorities to deal with priority signaling from client. + * + * Client session ignores this option. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option, + int val); + +/** + * @function + * + * This option, if set to nonzero, turns off RFC 9113 leading and + * trailing white spaces validation against HTTP field value. Some + * important fields, such as HTTP/2 pseudo header fields, are + * validated more strictly and this option does not apply to them. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation( + nghttp2_option *option, int val); + +/** + * @function + * + * This function sets the rate limit for the incoming stream reset + * (RST_STREAM frame). It is server use only. It is a token-bucket + * based rate limiter. |burst| specifies the number of tokens that is + * initially available. The maximum number of tokens is capped to + * this value. |rate| specifies the number of tokens that are + * regenerated per second. An incoming RST_STREAM consumes one token. + * If there is no token available, GOAWAY is sent to tear down the + * connection. |burst| and |rate| default to 1000 and 33 + * respectively. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option, + uint64_t burst, uint64_t rate); + +/** + * @function + * + * This function sets the maximum number of CONTINUATION frames + * following an incoming HEADER frame. If more than those frames are + * received, the remote endpoint is considered to be misbehaving and + * session will be closed. The default value is 8. + */ +NGHTTP2_EXTERN void nghttp2_option_set_max_continuations(nghttp2_option *option, + size_t val); + +/** + * @function + * + * Initializes |*session_ptr| for client use. The all members of + * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| + * does not store |callbacks|. The |user_data| is an arbitrary user + * supplied data, which will be passed to the callback functions. + * + * The :type:`nghttp2_send_callback2` must be specified. If the + * application code uses `nghttp2_session_recv()`, the + * :type:`nghttp2_recv_callback` must be specified. The other members + * of |callbacks| can be ``NULL``. + * + * If this function fails, |*session_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_client_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data); + +/** + * @function + * + * Initializes |*session_ptr| for server use. The all members of + * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| + * does not store |callbacks|. The |user_data| is an arbitrary user + * supplied data, which will be passed to the callback functions. + * + * The :type:`nghttp2_send_callback2` must be specified. If the + * application code uses `nghttp2_session_recv()`, the + * :type:`nghttp2_recv_callback` must be specified. The other members + * of |callbacks| can be ``NULL``. + * + * If this function fails, |*session_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_server_new(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data); + +/** + * @function + * + * Like `nghttp2_session_client_new()`, but with additional options + * specified in the |option|. + * + * The |option| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_client_new()`. + * + * This function does not take ownership |option|. The application is + * responsible for freeing |option| if it finishes using the object. + * + * The library code does not refer to |option| after this function + * returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_client_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option); + +/** + * @function + * + * Like `nghttp2_session_server_new()`, but with additional options + * specified in the |option|. + * + * The |option| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_server_new()`. + * + * This function does not take ownership |option|. The application is + * responsible for freeing |option| if it finishes using the object. + * + * The library code does not refer to |option| after this function + * returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_server_new2(nghttp2_session **session_ptr, + const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option); + +/** + * @function + * + * Like `nghttp2_session_client_new2()`, but with additional custom + * memory allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_client_new2()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_client_new3( + nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, nghttp2_mem *mem); + +/** + * @function + * + * Like `nghttp2_session_server_new2()`, but with additional custom + * memory allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_session_server_new2()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_server_new3( + nghttp2_session **session_ptr, const nghttp2_session_callbacks *callbacks, + void *user_data, const nghttp2_option *option, nghttp2_mem *mem); + +/** + * @function + * + * Frees any resources allocated for |session|. If |session| is + * ``NULL``, this function does nothing. + */ +NGHTTP2_EXTERN void nghttp2_session_del(nghttp2_session *session); + +/** + * @function + * + * Sends pending frames to the remote peer. + * + * This function retrieves the highest prioritized frame from the + * outbound queue and sends it to the remote peer. It does this as + * many times as possible until the user callback + * :type:`nghttp2_send_callback2` returns + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`, the outbound queue + * becomes empty or flow control is triggered (remote window size + * becomes depleted or maximum number of concurrent streams is + * reached). This function calls several callback functions which are + * passed when initializing the |session|. Here is the simple time + * chart which tells when each callback is invoked: + * + * 1. Get the next frame to send from outbound queue. + * + * 2. Prepare transmission of the frame. + * + * 3. If the control frame cannot be sent because some preconditions + * are not met (e.g., request HEADERS cannot be sent after GOAWAY), + * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort + * the following steps. + * + * 4. If the frame is HEADERS, PUSH_PROMISE or DATA, + * :type:`nghttp2_select_padding_callback` is invoked. + * + * 5. If the frame is request HEADERS, the stream is opened here. + * + * 6. :type:`nghttp2_before_frame_send_callback` is invoked. + * + * 7. If :enum:`nghttp2_error.NGHTTP2_ERR_CANCEL` is returned from + * :type:`nghttp2_before_frame_send_callback`, the current frame + * transmission is canceled, and + * :type:`nghttp2_on_frame_not_send_callback` is invoked. Abort + * the following steps. + * + * 8. :type:`nghttp2_send_callback2` is invoked one or more times to + * send the frame. + * + * 9. :type:`nghttp2_on_frame_send_callback` is invoked. + * + * 10. If the transmission of the frame triggers closure of the + * stream, the stream is closed and + * :type:`nghttp2_on_stream_close_callback` is invoked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + */ +NGHTTP2_EXTERN int nghttp2_session_send(nghttp2_session *session); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_session_mem_send2()` instead. + * + * Returns the serialized data to send. + * + * This function behaves like `nghttp2_session_send()` except that it + * does not use :type:`nghttp2_send_callback` to transmit data. + * Instead, it assigns the pointer to the serialized data to the + * |*data_ptr| and returns its length. The other callbacks are called + * in the same way as they are in `nghttp2_session_send()`. + * + * If no data is available to send, this function returns 0. + * + * This function may not return all serialized data in one invocation. + * To get all data, call this function repeatedly until it returns 0 + * or one of negative error codes. + * + * The assigned |*data_ptr| is valid until the next call of + * `nghttp2_session_mem_send()` or `nghttp2_session_send()`. + * + * The caller must send all data before sending the next chunk of + * data. + * + * This function returns the length of the data pointed by the + * |*data_ptr| if it succeeds, or one of the following negative error + * codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * + * .. note:: + * + * This function may produce very small byte string. If that is the + * case, and application disables Nagle algorithm (``TCP_NODELAY``), + * then writing this small chunk leads to very small packet, and it + * is very inefficient. An application should be responsible to + * buffer up small chunks of data as necessary to avoid this + * situation. + */ +NGHTTP2_EXTERN ssize_t nghttp2_session_mem_send(nghttp2_session *session, + const uint8_t **data_ptr); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Returns the serialized data to send. + * + * This function behaves like `nghttp2_session_send()` except that it + * does not use :type:`nghttp2_send_callback2` to transmit data. + * Instead, it assigns the pointer to the serialized data to the + * |*data_ptr| and returns its length. The other callbacks are called + * in the same way as they are in `nghttp2_session_send()`. + * + * If no data is available to send, this function returns 0. + * + * This function may not return all serialized data in one invocation. + * To get all data, call this function repeatedly until it returns 0 + * or one of negative error codes. + * + * The assigned |*data_ptr| is valid until the next call of + * `nghttp2_session_mem_send2()` or `nghttp2_session_send()`. + * + * The caller must send all data before sending the next chunk of + * data. + * + * This function returns the length of the data pointed by the + * |*data_ptr| if it succeeds, or one of the following negative error + * codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * + * .. note:: + * + * This function may produce very small byte string. If that is the + * case, and application disables Nagle algorithm (``TCP_NODELAY``), + * then writing this small chunk leads to very small packet, and it + * is very inefficient. An application should be responsible to + * buffer up small chunks of data as necessary to avoid this + * situation. + */ +NGHTTP2_EXTERN nghttp2_ssize +nghttp2_session_mem_send2(nghttp2_session *session, const uint8_t **data_ptr); + +/** + * @function + * + * Receives frames from the remote peer. + * + * This function receives as many frames as possible until the user + * callback :type:`nghttp2_recv_callback` returns + * :enum:`nghttp2_error.NGHTTP2_ERR_WOULDBLOCK`. This function calls + * several callback functions which are passed when initializing the + * |session|. Here is the simple time chart which tells when each + * callback is invoked: + * + * 1. :type:`nghttp2_recv_callback` is invoked one or more times to + * receive frame header. + * + * 2. When frame header is received, + * :type:`nghttp2_on_begin_frame_callback` is invoked. + * + * 3. If the frame is DATA frame: + * + * 1. :type:`nghttp2_recv_callback` is invoked to receive DATA + * payload. For each chunk of data, + * :type:`nghttp2_on_data_chunk_recv_callback` is invoked. + * + * 2. If one DATA frame is completely received, + * :type:`nghttp2_on_frame_recv_callback` is invoked. If the + * reception of the frame triggers the closure of the stream, + * :type:`nghttp2_on_stream_close_callback` is invoked. + * + * 4. If the frame is the control frame: + * + * 1. :type:`nghttp2_recv_callback` is invoked one or more times to + * receive whole frame. + * + * 2. If the received frame is valid, then following actions are + * taken. If the frame is either HEADERS or PUSH_PROMISE, + * :type:`nghttp2_on_begin_headers_callback` is invoked. Then + * :type:`nghttp2_on_header_callback` is invoked for each header + * name/value pair. For invalid header field, + * :type:`nghttp2_on_invalid_header_callback` is called. After + * all name/value pairs are emitted successfully, + * :type:`nghttp2_on_frame_recv_callback` is invoked. For other + * frames, :type:`nghttp2_on_frame_recv_callback` is invoked. + * If the reception of the frame triggers the closure of the + * stream, :type:`nghttp2_on_stream_close_callback` is invoked. + * + * 3. If the received frame is unpacked but is interpreted as + * invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is + * invoked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_EOF` + * The remote peer did shutdown on the connection. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` + * Flooding was detected in this HTTP/2 session, and it must be + * closed. This is most likely caused by misbehaviour of peer. + */ +NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_session_mem_recv2()` instead. + * + * Processes data |in| as an input from the remote endpoint. The + * |inlen| indicates the number of bytes to receive in the |in|. + * + * This function behaves like `nghttp2_session_recv()` except that it + * does not use :type:`nghttp2_recv_callback` to receive data; the + * |in| is the only data for the invocation of this function. If all + * bytes are processed, this function returns. The other callbacks + * are called in the same way as they are in `nghttp2_session_recv()`. + * + * In the current implementation, this function always tries to + * processes |inlen| bytes of input data unless either an error occurs or + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from + * :type:`nghttp2_on_header_callback` or + * :type:`nghttp2_on_data_chunk_recv_callback`. If + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is used, the return value + * includes the number of bytes which was used to produce the data or + * frame for the callback. + * + * This function returns the number of processed bytes, or one of the + * following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` + * Flooding was detected in this HTTP/2 session, and it must be + * closed. This is most likely caused by misbehaviour of peer. + */ +NGHTTP2_EXTERN ssize_t nghttp2_session_mem_recv(nghttp2_session *session, + const uint8_t *in, + size_t inlen); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Processes data |in| as an input from the remote endpoint. The + * |inlen| indicates the number of bytes to receive in the |in|. + * + * This function behaves like `nghttp2_session_recv()` except that it + * does not use :type:`nghttp2_recv_callback` to receive data; the + * |in| is the only data for the invocation of this function. If all + * bytes are processed, this function returns. The other callbacks + * are called in the same way as they are in `nghttp2_session_recv()`. + * + * In the current implementation, this function always tries to + * processes |inlen| bytes of input data unless either an error occurs or + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is returned from + * :type:`nghttp2_on_header_callback` or + * :type:`nghttp2_on_data_chunk_recv_callback`. If + * :enum:`nghttp2_error.NGHTTP2_ERR_PAUSE` is used, the return value + * includes the number of bytes which was used to produce the data or + * frame for the callback. + * + * This function returns the number of processed bytes, or one of the + * following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BAD_CLIENT_MAGIC` + * Invalid client magic was detected. This error only returns + * when |session| was configured as server and + * `nghttp2_option_set_no_recv_client_magic()` is not used with + * nonzero value. + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOODED` + * Flooding was detected in this HTTP/2 session, and it must be + * closed. This is most likely caused by misbehaviour of peer. + */ +NGHTTP2_EXTERN nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session, + const uint8_t *in, + size_t inlen); + +/** + * @function + * + * Puts back previously deferred DATA frame in the stream |stream_id| + * to the outbound queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The stream does not exist; or no deferred data exist. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_resume_data(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns nonzero value if |session| wants to receive data from the + * remote peer. + * + * If both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0, the application should + * drop the connection. + */ +NGHTTP2_EXTERN int nghttp2_session_want_read(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero value if |session| wants to send data to the remote + * peer. + * + * If both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0, the application should + * drop the connection. + */ +NGHTTP2_EXTERN int nghttp2_session_want_write(nghttp2_session *session); + +/** + * @function + * + * Returns stream_user_data for the stream |stream_id|. The + * stream_user_data is provided by `nghttp2_submit_request2()`, + * `nghttp2_submit_headers()` or + * `nghttp2_session_set_stream_user_data()`. Unless it is set using + * `nghttp2_session_set_stream_user_data()`, if the stream is + * initiated by the remote endpoint, stream_user_data is always + * ``NULL``. If the stream does not exist, this function returns + * ``NULL``. + */ +NGHTTP2_EXTERN void * +nghttp2_session_get_stream_user_data(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Sets the |stream_user_data| to the stream denoted by the + * |stream_id|. If a stream user data is already set to the stream, + * it is replaced with the |stream_user_data|. It is valid to specify + * ``NULL`` in the |stream_user_data|, which nullifies the associated + * data pointer. + * + * It is valid to set the |stream_user_data| to the stream reserved by + * PUSH_PROMISE frame. + * + * This function returns 0 if it succeeds, or one of following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The stream does not exist + */ +NGHTTP2_EXTERN int +nghttp2_session_set_stream_user_data(nghttp2_session *session, + int32_t stream_id, void *stream_user_data); + +/** + * @function + * + * Sets |user_data| to |session|, overwriting the existing user data + * specified in `nghttp2_session_client_new()`, or + * `nghttp2_session_server_new()`. + */ +NGHTTP2_EXTERN void nghttp2_session_set_user_data(nghttp2_session *session, + void *user_data); + +/** + * @function + * + * Returns the number of frames in the outbound queue. This does not + * include the deferred DATA frames. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_outbound_queue_size(nghttp2_session *session); + +/** + * @function + * + * Returns the number of DATA payload in bytes received without + * WINDOW_UPDATE transmission for the stream |stream_id|. The local + * (receive) window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective data length. In particular, if the + * local window size is reduced by submitting negative + * window_size_increment with `nghttp2_submit_window_update()`, this + * function returns the number of bytes less than actually received. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_recv_data_length( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the local (receive) window size for the stream |stream_id|. + * The local window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective window size. + * + * This function does not take into account the amount of received + * data from the remote endpoint. Use + * `nghttp2_session_get_stream_local_window_size()` to know the amount + * of data the remote endpoint can send without receiving stream level + * WINDOW_UPDATE frame. Note that each stream is still subject to the + * connection level flow control. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_effective_local_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the amount of flow-controlled payload (e.g., DATA) that the + * remote endpoint can send without receiving stream level + * WINDOW_UPDATE frame. It is also subject to the connection level + * flow control. So the actual amount of data to send is + * min(`nghttp2_session_get_stream_local_window_size()`, + * `nghttp2_session_get_local_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_local_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the number of DATA payload in bytes received without + * WINDOW_UPDATE transmission for a connection. The local (receive) + * window size can be adjusted by `nghttp2_submit_window_update()`. + * This function takes into account that and returns effective data + * length. In particular, if the local window size is reduced by + * submitting negative window_size_increment with + * `nghttp2_submit_window_update()`, this function returns the number + * of bytes less than actually received. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_effective_recv_data_length(nghttp2_session *session); + +/** + * @function + * + * Returns the local (receive) window size for a connection. The + * local window size can be adjusted by + * `nghttp2_submit_window_update()`. This function takes into account + * that and returns effective window size. + * + * This function does not take into account the amount of received + * data from the remote endpoint. Use + * `nghttp2_session_get_local_window_size()` to know the amount of + * data the remote endpoint can send without receiving + * connection-level WINDOW_UPDATE frame. Note that each stream is + * still subject to the stream level flow control. + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_effective_local_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns the amount of flow-controlled payload (e.g., DATA) that the + * remote endpoint can send without receiving connection level + * WINDOW_UPDATE frame. Note that each stream is still subject to the + * stream level flow control (see + * `nghttp2_session_get_stream_local_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_local_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns the remote window size for a given stream |stream_id|. + * + * This is the amount of flow-controlled payload (e.g., DATA) that the + * local endpoint can send without stream level WINDOW_UPDATE. There + * is also connection level flow control, so the effective size of + * payload that the local endpoint can actually send is + * min(`nghttp2_session_get_stream_remote_window_size()`, + * `nghttp2_session_get_remote_window_size()`). + * + * This function returns -1 if it fails. + */ +NGHTTP2_EXTERN int32_t nghttp2_session_get_stream_remote_window_size( + nghttp2_session *session, int32_t stream_id); + +/** + * @function + * + * Returns the remote window size for a connection. + * + * This function always succeeds. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_remote_window_size(nghttp2_session *session); + +/** + * @function + * + * Returns 1 if local peer half closed the given stream |stream_id|. + * Returns 0 if it did not. Returns -1 if no such stream exists. + */ +NGHTTP2_EXTERN int +nghttp2_session_get_stream_local_close(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns 1 if remote peer half closed the given stream |stream_id|. + * Returns 0 if it did not. Returns -1 if no such stream exists. + */ +NGHTTP2_EXTERN int +nghttp2_session_get_stream_remote_close(nghttp2_session *session, + int32_t stream_id); + +/** + * @function + * + * Returns the current dynamic table size of HPACK inflater, including + * the overhead 32 bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_hd_inflate_dynamic_table_size(nghttp2_session *session); + +/** + * @function + * + * Returns the current dynamic table size of HPACK deflater including + * the overhead 32 bytes per entry described in RFC 7541. + */ +NGHTTP2_EXTERN size_t +nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session); + +/** + * @function + * + * Signals the session so that the connection should be terminated. + * + * The last stream ID is the minimum value between the stream ID of a + * stream for which :type:`nghttp2_on_frame_recv_callback` was called + * most recently and the last stream ID we have sent to the peer + * previously. + * + * The |error_code| is the error code of this GOAWAY frame. The + * pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * After the transmission, both `nghttp2_session_want_read()` and + * `nghttp2_session_want_write()` return 0. + * + * This function should be called when the connection should be + * terminated after sending GOAWAY. If the remaining streams should + * be processed after GOAWAY, use `nghttp2_submit_goaway()` instead. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_session_terminate_session(nghttp2_session *session, + uint32_t error_code); + +/** + * @function + * + * Signals the session so that the connection should be terminated. + * + * This function behaves like `nghttp2_session_terminate_session()`, + * but the last stream ID can be specified by the application for fine + * grained control of stream. The HTTP/2 specification does not allow + * last_stream_id to be increased. So the actual value sent as + * last_stream_id is the minimum value between the given + * |last_stream_id| and the last_stream_id we have previously sent to + * the peer. + * + * The |last_stream_id| is peer's stream ID or 0. So if |session| is + * initialized as client, |last_stream_id| must be even or 0. If + * |session| is initialized as server, |last_stream_id| must be odd or + * 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |last_stream_id| is invalid. + */ +NGHTTP2_EXTERN int nghttp2_session_terminate_session2(nghttp2_session *session, + int32_t last_stream_id, + uint32_t error_code); + +/** + * @function + * + * Signals to the client that the server started graceful shutdown + * procedure. + * + * This function is only usable for server. If this function is + * called with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * To gracefully shutdown HTTP/2 session, server should call this + * function to send GOAWAY with last_stream_id (1u << 31) - 1. And + * after some delay (e.g., 1 RTT), send another GOAWAY with the stream + * ID that the server has some processing using + * `nghttp2_submit_goaway()`. See also + * `nghttp2_session_get_last_proc_stream_id()`. + * + * Unlike `nghttp2_submit_goaway()`, this function just sends GOAWAY + * and does nothing more. This is a mere indication to the client + * that session shutdown is imminent. The application should call + * `nghttp2_submit_goaway()` with appropriate last_stream_id after + * this call. + * + * If one or more GOAWAY frame have been already sent by either + * `nghttp2_submit_goaway()` or `nghttp2_session_terminate_session()`, + * this function has no effect. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + */ +NGHTTP2_EXTERN int nghttp2_submit_shutdown_notice(nghttp2_session *session); + +/** + * @function + * + * Returns the value of SETTINGS |id| notified by a remote endpoint. + * The |id| must be one of values defined in + * :enum:`nghttp2_settings_id`. + */ +NGHTTP2_EXTERN uint32_t nghttp2_session_get_remote_settings( + nghttp2_session *session, nghttp2_settings_id id); + +/** + * @function + * + * Returns the value of SETTINGS |id| of local endpoint acknowledged + * by the remote endpoint. The |id| must be one of the values defined + * in :enum:`nghttp2_settings_id`. + */ +NGHTTP2_EXTERN uint32_t nghttp2_session_get_local_settings( + nghttp2_session *session, nghttp2_settings_id id); + +/** + * @function + * + * Tells the |session| that next stream ID is |next_stream_id|. The + * |next_stream_id| must be equal or greater than the value returned + * by `nghttp2_session_get_next_stream_id()`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |next_stream_id| is strictly less than the value + * `nghttp2_session_get_next_stream_id()` returns; or + * |next_stream_id| is invalid (e.g., even integer for client, or + * odd integer for server). + */ +NGHTTP2_EXTERN int nghttp2_session_set_next_stream_id(nghttp2_session *session, + int32_t next_stream_id); + +/** + * @function + * + * Returns the next outgoing stream ID. Notice that return type is + * uint32_t. If we run out of stream ID for this session, this + * function returns 1 << 31. + */ +NGHTTP2_EXTERN uint32_t +nghttp2_session_get_next_stream_id(nghttp2_session *session); + +/** + * @function + * + * Tells the |session| that |size| bytes for a stream denoted by + * |stream_id| were consumed by application and are ready to + * WINDOW_UPDATE. The consumed bytes are counted towards both + * connection and stream level WINDOW_UPDATE (see + * `nghttp2_session_consume_connection()` and + * `nghttp2_session_consume_stream()` to update consumption + * independently). This function is intended to be used without + * automatic window update (see + * `nghttp2_option_set_no_auto_window_update()`). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume(nghttp2_session *session, + int32_t stream_id, size_t size); + +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for connection level. Note that + * HTTP/2 maintains connection and stream level flow control windows + * independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_connection(nghttp2_session *session, + size_t size); + +/** + * @function + * + * Like `nghttp2_session_consume()`, but this only tells library that + * |size| bytes were consumed only for stream denoted by |stream_id|. + * Note that HTTP/2 maintains connection and stream level flow control + * windows independently. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * Automatic WINDOW_UPDATE is not disabled. + */ +NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session, + int32_t stream_id, + size_t size); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return 0 without doing anything. + * + * Changes priority of existing stream denoted by |stream_id|. The + * new priority specification is |pri_spec|. + * + * The priority is changed silently and instantly, and no PRIORITY + * frame will be sent to notify the peer of this change. This + * function may be useful for server to change the priority of pushed + * stream. + * + * If |session| is initialized as server, and ``pri_spec->stream_id`` + * points to the idle stream, the idle stream is created if it does + * not exist. The created idle stream will depend on root stream + * (stream 0) with weight 16. + * + * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not + * found, we use default priority instead of given |pri_spec|. That + * is make stream depend on root stream with weight 16. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, this + * function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Attempted to depend on itself; or no stream exist for the given + * |stream_id|; or |stream_id| is 0 + */ +NGHTTP2_EXTERN int +nghttp2_session_change_stream_priority(nghttp2_session *session, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return 0 without doing anything. + * + * Creates idle stream with the given |stream_id|, and priority + * |pri_spec|. + * + * The stream creation is done without sending PRIORITY frame, which + * means that peer does not know about the existence of this idle + * stream in the local endpoint. + * + * RFC 7540 does not disallow the use of creation of idle stream with + * odd or even stream ID regardless of client or server. So this + * function can create odd or even stream ID regardless of client or + * server. But probably it is a bit safer to use the stream ID the + * local endpoint can initiate (in other words, use odd stream ID for + * client, and even stream ID for server), to avoid potential + * collision from peer's instruction. Also we can use + * `nghttp2_session_set_next_stream_id()` to avoid to open created + * idle streams accidentally if we follow this recommendation. + * + * If |session| is initialized as server, and ``pri_spec->stream_id`` + * points to the idle stream, the idle stream is created if it does + * not exist. The created idle stream will depend on root stream + * (stream 0) with weight 16. + * + * Otherwise, if stream denoted by ``pri_spec->stream_id`` is not + * found, we use default priority instead of given |pri_spec|. That + * is make stream depend on root stream with weight 16. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is submitted via `nghttp2_submit_settings()`, this + * function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Attempted to depend on itself; or stream denoted by |stream_id| + * already exists; or |stream_id| cannot be used to create idle + * stream (in other words, local endpoint has already opened + * stream ID greater than or equal to the given stream ID; or + * |stream_id| is 0 + */ +NGHTTP2_EXTERN int +nghttp2_session_create_idle_stream(nghttp2_session *session, int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * .. warning:: + * + * This function is deprecated in favor of + * `nghttp2_session_upgrade2()`, because this function lacks the + * parameter to tell the library the request method used in the + * original HTTP request. This information is required for client + * to validate actual response body length against content-length + * header field (see `nghttp2_option_set_no_http_messaging()`). If + * HEAD is used in request, the length of response body must be 0 + * regardless of value included in content-length header field. + * + * Performs post-process of HTTP Upgrade request. This function can + * be called from both client and server, but the behavior is very + * different in each other. + * + * If called from client side, the |settings_payload| must be the + * value sent in ``HTTP2-Settings`` header field and must be decoded + * by base64url decoder. The |settings_payloadlen| is the length of + * |settings_payload|. The |settings_payload| is unpacked and its + * setting values will be submitted using `nghttp2_submit_settings()`. + * This means that the client application code does not need to submit + * SETTINGS by itself. The stream with stream ID=1 is opened and the + * |stream_user_data| is used for its stream_user_data. The opened + * stream becomes half-closed (local) state. + * + * If called from server side, the |settings_payload| must be the + * value received in ``HTTP2-Settings`` header field and must be + * decoded by base64url decoder. The |settings_payloadlen| is the + * length of |settings_payload|. It is treated as if the SETTINGS + * frame with that payload is received. Thus, callback functions for + * the reception of SETTINGS frame will be invoked. The stream with + * stream ID=1 is opened. The |stream_user_data| is ignored. The + * opened stream becomes half-closed (remote). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |settings_payload| is badly formed. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The stream ID 1 is already used or closed; or is not available. + */ +NGHTTP2_EXTERN int nghttp2_session_upgrade(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + void *stream_user_data); + +/** + * @function + * + * Performs post-process of HTTP Upgrade request. This function can + * be called from both client and server, but the behavior is very + * different in each other. + * + * If called from client side, the |settings_payload| must be the + * value sent in ``HTTP2-Settings`` header field and must be decoded + * by base64url decoder. The |settings_payloadlen| is the length of + * |settings_payload|. The |settings_payload| is unpacked and its + * setting values will be submitted using `nghttp2_submit_settings()`. + * This means that the client application code does not need to submit + * SETTINGS by itself. The stream with stream ID=1 is opened and the + * |stream_user_data| is used for its stream_user_data. The opened + * stream becomes half-closed (local) state. + * + * If called from server side, the |settings_payload| must be the + * value received in ``HTTP2-Settings`` header field and must be + * decoded by base64url decoder. The |settings_payloadlen| is the + * length of |settings_payload|. It is treated as if the SETTINGS + * frame with that payload is received. Thus, callback functions for + * the reception of SETTINGS frame will be invoked. The stream with + * stream ID=1 is opened. The |stream_user_data| is ignored. The + * opened stream becomes half-closed (remote). + * + * If the request method is HEAD, pass nonzero value to + * |head_request|. Otherwise, pass 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |settings_payload| is badly formed. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The stream ID 1 is already used or closed; or is not available. + */ +NGHTTP2_EXTERN int nghttp2_session_upgrade2(nghttp2_session *session, + const uint8_t *settings_payload, + size_t settings_payloadlen, + int head_request, + void *stream_user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_pack_settings_payload2()` instead. + * + * Serializes the SETTINGS values |iv| in the |buf|. The size of the + * |buf| is specified by |buflen|. The number of entries in the |iv| + * array is given by |niv|. The required space in |buf| for the |niv| + * entries is ``6*niv`` bytes and if the given buffer is too small, an + * error is returned. This function is used mainly for creating a + * SETTINGS payload to be sent with the ``HTTP2-Settings`` header + * field in an HTTP Upgrade request. The data written in |buf| is NOT + * base64url encoded and the application is responsible for encoding. + * + * This function returns the number of bytes written in |buf|, or one + * of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |iv| contains duplicate settings ID or invalid value. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_pack_settings_payload( + uint8_t *buf, size_t buflen, const nghttp2_settings_entry *iv, size_t niv); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Serializes the SETTINGS values |iv| in the |buf|. The size of the + * |buf| is specified by |buflen|. The number of entries in the |iv| + * array is given by |niv|. The required space in |buf| for the |niv| + * entries is ``6*niv`` bytes and if the given buffer is too small, an + * error is returned. This function is used mainly for creating a + * SETTINGS payload to be sent with the ``HTTP2-Settings`` header + * field in an HTTP Upgrade request. The data written in |buf| is NOT + * base64url encoded and the application is responsible for encoding. + * + * This function returns the number of bytes written in |buf|, or one + * of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |iv| contains duplicate settings ID or invalid value. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN nghttp2_ssize nghttp2_pack_settings_payload2( + uint8_t *buf, size_t buflen, const nghttp2_settings_entry *iv, size_t niv); + +/** + * @function + * + * Returns string describing the |lib_error_code|. The + * |lib_error_code| must be one of the :enum:`nghttp2_error`. + */ +NGHTTP2_EXTERN const char *nghttp2_strerror(int lib_error_code); + +/** + * @function + * + * Returns string representation of HTTP/2 error code |error_code| + * (e.g., ``PROTOCOL_ERROR`` is returned if ``error_code == + * NGHTTP2_PROTOCOL_ERROR``). If string representation is unknown for + * given |error_code|, this function returns string ``unknown``. + */ +NGHTTP2_EXTERN const char *nghttp2_http2_strerror(uint32_t error_code); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * + * Initializes |pri_spec| with the |stream_id| of the stream to depend + * on with |weight| and its exclusive flag. If |exclusive| is + * nonzero, exclusive flag is set. + * + * The |weight| must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. + */ +NGHTTP2_EXTERN void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec, + int32_t stream_id, + int32_t weight, int exclusive); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * + * Initializes |pri_spec| with the default values. The default values + * are: stream_id = 0, weight = :macro:`NGHTTP2_DEFAULT_WEIGHT` and + * exclusive = 0. + */ +NGHTTP2_EXTERN void +nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * + * Returns nonzero if the |pri_spec| is filled with default values. + */ +NGHTTP2_EXTERN int +nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_submit_request2()` instead. + * + * Submits HEADERS frame and optionally one or more DATA frames. + * + * The |pri_spec| is a deprecated priority specification of this + * request. ``NULL`` means the default priority (see + * `nghttp2_priority_spec_default_init()`). To specify the priority, + * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes + * :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, |pri_spec| is + * ignored, and treated as if ``NULL`` is specified. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * request HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. In this case, a method that allows + * request message bodies + * (https://tools.ietf.org/html/rfc7231#section-4) must be specified + * with ``:method`` key in |nva| (e.g. ``POST``). This function does + * not take ownership of the |data_prd|. The function copies the + * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have + * END_STREAM set. The |stream_user_data| is data associated to the + * stream opened by this request and can be an arbitrary pointer, + * which can be retrieved later by + * `nghttp2_session_get_stream_user_data()`. + * + * This function returns assigned stream ID if it succeeds, or one of + * the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Trying to depend on itself (new stream ID equals + * ``pri_spec->stream_id``). + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is server session. + * + * .. warning:: + * + * This function returns assigned stream ID if it succeeds. But + * that stream is not created yet. The application must not submit + * frame to that stream ID before + * :type:`nghttp2_before_frame_send_callback` is called for this + * frame. This means `nghttp2_session_get_stream_user_data()` does + * not work before the callback. But + * `nghttp2_session_set_stream_user_data()` handles this situation + * specially, and it can set data to a stream during this period. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_request( + nghttp2_session *session, const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider *data_prd, + void *stream_user_data); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Submits HEADERS frame and optionally one or more DATA frames. + * + * The |pri_spec| is a deprecated priority specification of this + * request. ``NULL`` means the default priority (see + * `nghttp2_priority_spec_default_init()`). To specify the priority, + * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. In the future release + * after the end of 2024, this function will ignore |pri_spec| and + * behave as if ``NULL`` is given. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes + * :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, |pri_spec| is + * ignored, and treated as if ``NULL`` is specified. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * request HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. In this case, a method that allows + * request message bodies + * (https://tools.ietf.org/html/rfc7231#section-4) must be specified + * with ``:method`` key in |nva| (e.g. ``POST``). This function does + * not take ownership of the |data_prd|. The function copies the + * members of the |data_prd|. If |data_prd| is ``NULL``, HEADERS have + * END_STREAM set. The |stream_user_data| is data associated to the + * stream opened by this request and can be an arbitrary pointer, + * which can be retrieved later by + * `nghttp2_session_get_stream_user_data()`. + * + * This function returns assigned stream ID if it succeeds, or one of + * the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Trying to depend on itself (new stream ID equals + * ``pri_spec->stream_id``). + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is server session. + * + * .. warning:: + * + * This function returns assigned stream ID if it succeeds. But + * that stream is not created yet. The application must not submit + * frame to that stream ID before + * :type:`nghttp2_before_frame_send_callback` is called for this + * frame. This means `nghttp2_session_get_stream_user_data()` does + * not work before the callback. But + * `nghttp2_session_set_stream_user_data()` handles this situation + * specially, and it can set data to a stream during this period. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_request2( + nghttp2_session *session, const nghttp2_priority_spec *pri_spec, + const nghttp2_nv *nva, size_t nvlen, const nghttp2_data_provider2 *data_prd, + void *stream_user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_submit_response2()` instead. + * + * Submits response HEADERS frame and optionally one or more DATA + * frames against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * response HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. This function does not take ownership + * of the |data_prd|. The function copies the members of the + * |data_prd|. If |data_prd| is ``NULL``, HEADERS will have + * END_STREAM flag set. + * + * This method can be used as normal HTTP response and push response. + * When pushing a resource using this function, the |session| must be + * configured using `nghttp2_session_server_new()` or its variants and + * the target stream denoted by the |stream_id| must be reserved using + * `nghttp2_submit_push_promise()`. + * + * To send non-final response headers (e.g., HTTP status 101), don't + * use this function because this function half-closes the outbound + * stream. Instead, use `nghttp2_submit_headers()` for this purpose. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. Normally, this does not happen, but when + * application wrongly calls `nghttp2_submit_response()` twice, + * this may happen. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is client session. + * + * .. warning:: + * + * Calling this function twice for the same stream ID may lead to + * program crash. It is generally considered to a programming error + * to commit response twice. + */ +NGHTTP2_EXTERN int +nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider *data_prd); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Submits response HEADERS frame and optionally one or more DATA + * frames against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * HTTP/2 specification has requirement about header fields in the + * response HEADERS. See the specification for more details. + * + * If |data_prd| is not ``NULL``, it provides data which will be sent + * in subsequent DATA frames. This function does not take ownership + * of the |data_prd|. The function copies the members of the + * |data_prd|. If |data_prd| is ``NULL``, HEADERS will have + * END_STREAM flag set. + * + * This method can be used as normal HTTP response and push response. + * When pushing a resource using this function, the |session| must be + * configured using `nghttp2_session_server_new()` or its variants and + * the target stream denoted by the |stream_id| must be reserved using + * `nghttp2_submit_push_promise()`. + * + * To send non-final response headers (e.g., HTTP status 101), don't + * use this function because this function half-closes the outbound + * stream. Instead, use `nghttp2_submit_headers()` for this purpose. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. Normally, this does not happen, but when + * application wrongly calls `nghttp2_submit_response2()` twice, + * this may happen. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |session| is client session. + * + * .. warning:: + * + * Calling this function twice for the same stream ID may lead to + * program crash. It is generally considered to a programming error + * to commit response twice. + */ +NGHTTP2_EXTERN int +nghttp2_submit_response2(nghttp2_session *session, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + const nghttp2_data_provider2 *data_prd); + +/** + * @function + * + * Submits trailer fields HEADERS against the stream |stream_id|. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application must not include pseudo-header + * fields (headers whose names starts with ":") in |nva|. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * For server, trailer fields must follow response HEADERS or response + * DATA without END_STREAM flat set. The library does not enforce + * this requirement, and applications should do this for themselves. + * If `nghttp2_submit_trailer()` is called before any response HEADERS + * submission (usually by `nghttp2_submit_response2()`), the content + * of |nva| will be sent as response headers, which will result in + * error. + * + * This function has the same effect with `nghttp2_submit_headers()`, + * with flags = :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` and both + * pri_spec and stream_user_data to NULL. + * + * To submit trailer fields after `nghttp2_submit_response2()` is + * called, the application has to specify + * :type:`nghttp2_data_provider2` to `nghttp2_submit_response2()`. + * Inside of :type:`nghttp2_data_source_read_callback2`, when setting + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_EOF`, also set + * :enum:`nghttp2_data_flag.NGHTTP2_DATA_FLAG_NO_END_STREAM`. After + * that, the application can send trailer fields using + * `nghttp2_submit_trailer()`. `nghttp2_submit_trailer()` can be used + * inside :type:`nghttp2_data_source_read_callback2`. + * + * This function returns 0 if it succeeds and |stream_id| is -1. + * Otherwise, this function returns 0 if it succeeds, or one of the + * following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session, + int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen); + +/** + * @function + * + * Submits HEADERS frame. The |flags| is bitwise OR of the + * following values: + * + * * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` + * + * If |flags| includes :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, + * this frame has END_STREAM flag set. + * + * The library handles the CONTINUATION frame internally and it + * correctly sets END_HEADERS to the last sequence of the PUSH_PROMISE + * or CONTINUATION frame. + * + * If the |stream_id| is -1, this frame is assumed as request (i.e., + * request HEADERS frame which opens new stream). In this case, the + * assigned stream ID will be returned. Otherwise, specify stream ID + * in |stream_id|. + * + * The |pri_spec| is a deprecated priority specification of this + * request. ``NULL`` means the default priority (see + * `nghttp2_priority_spec_default_init()`). To specify the priority, + * use `nghttp2_priority_spec_init()`. If |pri_spec| is not ``NULL``, + * this function will copy its data members. In the future release + * after the end of 2024, this function will ignore |pri_spec| and + * behave as if ``NULL`` is given. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, |pri_spec| is + * ignored, and treated as if ``NULL`` is specified. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * The |stream_user_data| is a pointer to an arbitrary data which is + * associated to the stream this frame will open. Therefore it is + * only used if this frame opens streams, in other words, it changes + * stream state from idle or reserved to open. + * + * This function is low-level in a sense that the application code can + * specify flags directly. For usual HTTP request, + * `nghttp2_submit_request2()` is useful. Likewise, for HTTP + * response, prefer `nghttp2_submit_response2()`. + * + * This function returns newly assigned stream ID if it succeeds and + * |stream_id| is -1. Otherwise, this function returns 0 if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; or trying to depend on itself (stream ID + * equals ``pri_spec->stream_id``). + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. This happens if stream denoted by |stream_id| + * is in reserved state. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * The |stream_id| is -1, and |session| is server session. + * + * .. warning:: + * + * This function returns assigned stream ID if it succeeds and + * |stream_id| is -1. But that stream is not opened yet. The + * application must not submit frame to that stream ID before + * :type:`nghttp2_before_frame_send_callback` is called for this + * frame. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_headers( + nghttp2_session *session, uint8_t flags, int32_t stream_id, + const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, + void *stream_user_data); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_submit_data2()` instead. + * + * Submits one or more DATA frames to the stream |stream_id|. The + * data to be sent are provided by |data_prd|. If |flags| contains + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, the last DATA frame + * has END_STREAM flag set. + * + * This function does not take ownership of the |data_prd|. The + * function copies the members of the |data_prd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` + * The stream was already closed; or the |stream_id| is invalid. + * + * .. note:: + * + * Currently, only one DATA or HEADERS is allowed for a stream at a + * time. Submitting these frames more than once before first DATA + * or HEADERS is finished results in + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The + * earliest callback which tells that previous frame is done is + * :type:`nghttp2_on_frame_send_callback`. In side that callback, + * new data can be submitted using `nghttp2_submit_data()`. Of + * course, all data except for last one must not have + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` flag set in |flags|. + * This sounds a bit complicated, and we recommend to use + * `nghttp2_submit_request()` and `nghttp2_submit_response()` to + * avoid this cascading issue. The experience shows that for HTTP + * use, these two functions are enough to implement both client and + * server. + */ +NGHTTP2_EXTERN int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_data_provider *data_prd); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Submits one or more DATA frames to the stream |stream_id|. The + * data to be sent are provided by |data_prd|. If |flags| contains + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM`, the last DATA frame + * has END_STREAM flag set. + * + * This function does not take ownership of the |data_prd|. The + * function copies the members of the |data_prd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` + * DATA or HEADERS has been already submitted and not fully + * processed yet. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` + * The stream was already closed; or the |stream_id| is invalid. + * + * .. note:: + * + * Currently, only one DATA or HEADERS is allowed for a stream at a + * time. Submitting these frames more than once before first DATA + * or HEADERS is finished results in + * :enum:`nghttp2_error.NGHTTP2_ERR_DATA_EXIST` error code. The + * earliest callback which tells that previous frame is done is + * :type:`nghttp2_on_frame_send_callback`. In side that callback, + * new data can be submitted using `nghttp2_submit_data2()`. Of + * course, all data except for last one must not have + * :enum:`nghttp2_flag.NGHTTP2_FLAG_END_STREAM` flag set in |flags|. + * This sounds a bit complicated, and we recommend to use + * `nghttp2_submit_request2()` and `nghttp2_submit_response2()` to + * avoid this cascading issue. The experience shows that for HTTP + * use, these two functions are enough to implement both client and + * server. + */ +NGHTTP2_EXTERN int nghttp2_submit_data2(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_data_provider2 *data_prd); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return 0 without doing anything. + * + * Submits PRIORITY frame to change the priority of stream |stream_id| + * to the priority specification |pri_spec|. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |pri_spec| is a deprecated priority specification of this + * request. ``NULL`` is not allowed for this function. To specify the + * priority, use `nghttp2_priority_spec_init()`. This function will + * copy its data members. + * + * The ``pri_spec->weight`` must be in [:macro:`NGHTTP2_MIN_WEIGHT`, + * :macro:`NGHTTP2_MAX_WEIGHT`], inclusive. If ``pri_spec->weight`` + * is strictly less than :macro:`NGHTTP2_MIN_WEIGHT`, it becomes + * :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than + * :macro:`NGHTTP2_MAX_WEIGHT`, it becomes + * :macro:`NGHTTP2_MAX_WEIGHT`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is received by a remote endpoint, this function does + * nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; or the |pri_spec| is NULL; or trying to + * depend on itself. + */ +NGHTTP2_EXTERN int +nghttp2_submit_priority(nghttp2_session *session, uint8_t flags, + int32_t stream_id, + const nghttp2_priority_spec *pri_spec); + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_DEFAULT_URGENCY` is the default urgency + * level for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_DEFAULT_URGENCY 3 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_HIGH` is the highest urgency level + * for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_HIGH 0 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW` is the lowest urgency level for + * :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LOW 7 + +/** + * @macro + * + * :macro:`NGHTTP2_EXTPRI_URGENCY_LEVELS` is the number of urgency + * levels for :rfc:`9218` extensible priorities. + */ +#define NGHTTP2_EXTPRI_URGENCY_LEVELS (NGHTTP2_EXTPRI_URGENCY_LOW + 1) + +/** + * @struct + * + * :type:`nghttp2_extpri` is :rfc:`9218` extensible priorities + * specification for a stream. + */ +typedef struct nghttp2_extpri { + /** + * :member:`urgency` is the urgency of a stream, it must be in + * [:macro:`NGHTTP2_EXTPRI_URGENCY_HIGH`, + * :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`], inclusive, and 0 is the + * highest urgency. + */ + uint32_t urgency; + /** + * :member:`inc` indicates that a content can be processed + * incrementally or not. If inc is 0, it cannot be processed + * incrementally. If inc is 1, it can be processed incrementally. + * Other value is not permitted. + */ + int inc; +} nghttp2_extpri; + +/** + * @function + * + * Submits RST_STREAM frame to cancel/reject the stream |stream_id| + * with the error code |error_code|. + * + * The pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_rst_stream(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + uint32_t error_code); + +/** + * @function + * + * Stores local settings and submits SETTINGS frame. The |iv| is the + * pointer to the array of :type:`nghttp2_settings_entry`. The |niv| + * indicates the number of :type:`nghttp2_settings_entry`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This function does not take ownership of the |iv|. This function + * copies all the elements in the |iv|. + * + * While updating individual stream's local window size, if the window + * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE, + * RST_STREAM is issued against such a stream. + * + * SETTINGS with :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` is + * automatically submitted by the library and application could not + * send it at its will. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |iv| contains invalid value (e.g., initial window size + * strictly greater than (1 << 31) - 1. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_settings(nghttp2_session *session, + uint8_t flags, + const nghttp2_settings_entry *iv, + size_t niv); + +/** + * @function + * + * Submits PUSH_PROMISE frame. + * + * The |flags| is currently ignored. The library handles the + * CONTINUATION frame internally and it correctly sets END_HEADERS to + * the last sequence of the PUSH_PROMISE or CONTINUATION frame. + * + * The |stream_id| must be client initiated stream ID. + * + * The |nva| is an array of name/value pair :type:`nghttp2_nv` with + * |nvlen| elements. The application is responsible to include + * required pseudo-header fields (header field whose name starts with + * ":") in |nva| and must place pseudo-headers before regular header + * fields. + * + * This function creates copies of all name/value pairs in |nva|. It + * also lower-cases all names in |nva|. The order of elements in + * |nva| is preserved. For header fields with + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME` and + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_VALUE` are set, + * header field name and value are not copied respectively. With + * :enum:`nghttp2_nv_flag.NGHTTP2_NV_FLAG_NO_COPY_NAME`, application + * is responsible to pass header field name in lowercase. The + * application should maintain the references to them until + * :type:`nghttp2_on_frame_send_callback` or + * :type:`nghttp2_on_frame_not_send_callback` is called. + * + * The |promised_stream_user_data| is a pointer to an arbitrary data + * which is associated to the promised stream this frame will open and + * make it in reserved state. It is available using + * `nghttp2_session_get_stream_user_data()`. The application can + * access it in :type:`nghttp2_before_frame_send_callback` and + * :type:`nghttp2_on_frame_send_callback` of this frame. + * + * The client side is not allowed to use this function. + * + * To submit response headers and data, use + * `nghttp2_submit_response2()`. + * + * This function returns assigned promised stream ID if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_PROTO` + * This function was invoked when |session| is initialized as + * client. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE` + * No stream ID is available because maximum stream ID was + * reached. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is 0; The |stream_id| does not designate stream + * that peer initiated. + * :enum:`nghttp2_error.NGHTTP2_ERR_STREAM_CLOSED` + * The stream was already closed; or the |stream_id| is invalid. + * + * .. warning:: + * + * This function returns assigned promised stream ID if it succeeds. + * As of 1.16.0, stream object for pushed resource is created when + * this function succeeds. In that case, the application can submit + * push response for the promised frame. + * + * In 1.15.0 or prior versions, pushed stream is not opened yet when + * this function succeeds. The application must not submit frame to + * that stream ID before :type:`nghttp2_before_frame_send_callback` + * is called for this frame. + * + */ +NGHTTP2_EXTERN int32_t nghttp2_submit_push_promise( + nghttp2_session *session, uint8_t flags, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, void *promised_stream_user_data); + +/** + * @function + * + * Submits PING frame. You don't have to send PING back when you + * received PING frame. The library automatically submits PING frame + * in this case. + * + * The |flags| is bitwise OR of 0 or more of the following value. + * + * * :enum:`nghttp2_flag.NGHTTP2_FLAG_ACK` + * + * Unless `nghttp2_option_set_no_auto_ping_ack()` is used, the |flags| + * should be :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * If the |opaque_data| is non ``NULL``, then it should point to the 8 + * bytes array of memory to specify opaque data to send with PING + * frame. If the |opaque_data| is ``NULL``, zero-cleared 8 bytes will + * be sent as opaque data. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags, + const uint8_t *opaque_data); + +/** + * @function + * + * Submits GOAWAY frame with the last stream ID |last_stream_id| and + * the error code |error_code|. + * + * The pre-defined error code is one of :enum:`nghttp2_error_code`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |last_stream_id| is peer's stream ID or 0. So if |session| is + * initialized as client, |last_stream_id| must be even or 0. If + * |session| is initialized as server, |last_stream_id| must be odd or + * 0. + * + * The HTTP/2 specification says last_stream_id must not be increased + * from the value previously sent. So the actual value sent as + * last_stream_id is the minimum value between the given + * |last_stream_id| and the last_stream_id previously sent to the + * peer. + * + * If the |opaque_data| is not ``NULL`` and |opaque_data_len| is not + * zero, those data will be sent as additional debug data. The + * library makes a copy of the memory region pointed by |opaque_data| + * with the length |opaque_data_len|, so the caller does not need to + * keep this memory after the return of this function. If the + * |opaque_data_len| is 0, the |opaque_data| could be ``NULL``. + * + * After successful transmission of GOAWAY, following things happen. + * All incoming streams having strictly more than |last_stream_id| are + * closed. All incoming HEADERS which starts new stream are simply + * ignored. After all active streams are handled, both + * `nghttp2_session_want_read()` and `nghttp2_session_want_write()` + * return 0 and the application can close session. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |opaque_data_len| is too large; the |last_stream_id| is + * invalid. + */ +NGHTTP2_EXTERN int nghttp2_submit_goaway(nghttp2_session *session, + uint8_t flags, int32_t last_stream_id, + uint32_t error_code, + const uint8_t *opaque_data, + size_t opaque_data_len); + +/** + * @function + * + * Returns the last stream ID of a stream for which + * :type:`nghttp2_on_frame_recv_callback` was invoked most recently. + * The returned value can be used as last_stream_id parameter for + * `nghttp2_submit_goaway()` and + * `nghttp2_session_terminate_session2()`. + * + * This function always succeeds. + */ +NGHTTP2_EXTERN int32_t +nghttp2_session_get_last_proc_stream_id(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero if new request can be sent from local endpoint. + * + * This function return 0 if request is not allowed for this session. + * There are several reasons why request is not allowed. Some of the + * reasons are: session is server; stream ID has been spent; GOAWAY + * has been sent or received. + * + * The application can call `nghttp2_submit_request2()` without + * consulting this function. In that case, + * `nghttp2_submit_request2()` may return error. Or, request is + * failed to sent, and :type:`nghttp2_on_stream_close_callback` is + * called. + */ +NGHTTP2_EXTERN int +nghttp2_session_check_request_allowed(nghttp2_session *session); + +/** + * @function + * + * Returns nonzero if |session| is initialized as server side session. + */ +NGHTTP2_EXTERN int +nghttp2_session_check_server_session(nghttp2_session *session); + +/** + * @function + * + * Submits WINDOW_UPDATE frame. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |stream_id| is the stream ID to send this WINDOW_UPDATE. To + * send connection level WINDOW_UPDATE, specify 0 to |stream_id|. + * + * If the |window_size_increment| is positive, the WINDOW_UPDATE with + * that value as window_size_increment is queued. If the + * |window_size_increment| is larger than the received bytes from the + * remote endpoint, the local window size is increased by that + * difference. If the sole purpose is to increase the local window + * size, consider to use `nghttp2_session_set_local_window_size()`. + * + * If the |window_size_increment| is negative, the local window size + * is decreased by -|window_size_increment|. If automatic + * WINDOW_UPDATE is enabled + * (`nghttp2_option_set_no_auto_window_update()`), and the library + * decided that the WINDOW_UPDATE should be submitted, then + * WINDOW_UPDATE is queued with the current received bytes count. If + * the sole purpose is to decrease the local window size, consider to + * use `nghttp2_session_set_local_window_size()`. + * + * If the |window_size_increment| is 0, the function does nothing and + * returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_FLOW_CONTROL` + * The local window size overflow or gets negative. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int nghttp2_submit_window_update(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + int32_t window_size_increment); + +/** + * @function + * + * Set local window size (local endpoints's window size) to the given + * |window_size| for the given stream denoted by |stream_id|. To + * change connection level window size, specify 0 to |stream_id|. To + * increase window size, this function may submit WINDOW_UPDATE frame + * to transmission queue. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * This sounds similar to `nghttp2_submit_window_update()`, but there + * are 2 differences. The first difference is that this function + * takes the absolute value of window size to set, rather than the + * delta. To change the window size, this may be easier to use since + * the application just declares the intended window size, rather than + * calculating delta. The second difference is that + * `nghttp2_submit_window_update()` affects the received bytes count + * which has not acked yet. By the specification of + * `nghttp2_submit_window_update()`, to strictly increase the local + * window size, we have to submit delta including all received bytes + * count, which might not be desirable in some cases. On the other + * hand, this function does not affect the received bytes count. It + * just sets the local window size to the given value. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |stream_id| is negative. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_session_set_local_window_size(nghttp2_session *session, uint8_t flags, + int32_t stream_id, int32_t window_size); + +/** + * @function + * + * Submits extension frame. + * + * Application can pass arbitrary frame flags and stream ID in |flags| + * and |stream_id| respectively. The |payload| is opaque pointer, and + * it can be accessible though ``frame->ext.payload`` in + * :type:`nghttp2_pack_extension_callback2`. The library will not own + * passed |payload| pointer. + * + * The application must set :type:`nghttp2_pack_extension_callback2` + * using `nghttp2_session_callbacks_set_pack_extension_callback2()`. + * + * The application should retain the memory pointed by |payload| until + * the transmission of extension frame is done (which is indicated by + * :type:`nghttp2_on_frame_send_callback`), or transmission fails + * (which is indicated by :type:`nghttp2_on_frame_not_send_callback`). + * If application does not touch this memory region after packing it + * into a wire format, application can free it inside + * :type:`nghttp2_pack_extension_callback2`. + * + * The standard HTTP/2 frame cannot be sent with this function, so + * |type| must be strictly grater than 0x9. Otherwise, this function + * will fail with error code + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * If :type:`nghttp2_pack_extension_callback2` is not set. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * If |type| specifies standard HTTP/2 frame type. The frame + * types in the rage [0x0, 0x9], both inclusive, are standard + * HTTP/2 frame type, and cannot be sent using this function. + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + */ +NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session, + uint8_t type, uint8_t flags, + int32_t stream_id, void *payload); + +/** + * @struct + * + * The payload of ALTSVC frame. ALTSVC frame is a non-critical + * extension to HTTP/2. If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_ALTSVC`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The pointer to origin which this alternative service is + * associated with. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; + /** + * The pointer to Alt-Svc field value contained in ALTSVC frame. + * This is not necessarily NULL-terminated. + */ + uint8_t *field_value; + /** + * The length of the |field_value|. + */ + size_t field_value_len; +} nghttp2_ext_altsvc; + +/** + * @function + * + * Submits ALTSVC frame. + * + * ALTSVC frame is a non-critical extension to HTTP/2, and defined in + * `RFC 7383 `_. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |origin| points to the origin this alternative service is + * associated with. The |origin_len| is the length of the origin. If + * |stream_id| is 0, the origin must be specified. If |stream_id| is + * not zero, the origin must be empty (in other words, |origin_len| + * must be 0). + * + * The ALTSVC frame is only usable from server side. If this function + * is invoked with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from client side session + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The sum of |origin_len| and |field_value_len| is larger than + * 16382; or |origin_len| is 0 while |stream_id| is 0; or + * |origin_len| is not 0 while |stream_id| is not 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_altsvc(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *origin, + size_t origin_len, + const uint8_t *field_value, + size_t field_value_len); + +/** + * @struct + * + * The single entry of an origin. + */ +typedef struct { + /** + * The pointer to origin. No validation is made against this field + * by the library. This is not necessarily NULL-terminated. + */ + uint8_t *origin; + /** + * The length of the |origin|. + */ + size_t origin_len; +} nghttp2_origin_entry; + +/** + * @struct + * + * The payload of ORIGIN frame. ORIGIN frame is a non-critical + * extension to HTTP/2 and defined by `RFC 8336 + * `_. + * + * If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_ORIGIN`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The number of origins contained in |ov|. + */ + size_t nov; + /** + * The pointer to the array of origins contained in ORIGIN frame. + */ + nghttp2_origin_entry *ov; +} nghttp2_ext_origin; + +/** + * @function + * + * Submits ORIGIN frame. + * + * ORIGIN frame is a non-critical extension to HTTP/2 and defined by + * `RFC 8336 `_. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |ov| points to the array of origins. The |nov| specifies the + * number of origins included in |ov|. This function creates copies + * of all elements in |ov|. + * + * The ORIGIN frame is only usable by a server. If this function is + * invoked with client side session, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from client side session. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * There are too many origins, or an origin is too large to fit + * into a default frame payload. + */ +NGHTTP2_EXTERN int nghttp2_submit_origin(nghttp2_session *session, + uint8_t flags, + const nghttp2_origin_entry *ov, + size_t nov); + +/** + * @struct + * + * The payload of PRIORITY_UPDATE frame. PRIORITY_UPDATE frame is a + * non-critical extension to HTTP/2. If this frame is received, and + * `nghttp2_option_set_user_recv_extension_type()` is not set, and + * `nghttp2_option_set_builtin_recv_extension_type()` is set for + * :enum:`nghttp2_frame_type.NGHTTP2_PRIORITY_UPDATE`, + * ``nghttp2_extension.payload`` will point to this struct. + * + * It has the following members: + */ +typedef struct { + /** + * The stream ID of the stream whose priority is updated. + */ + int32_t stream_id; + /** + * The pointer to Priority field value. It is not necessarily + * NULL-terminated. + */ + uint8_t *field_value; + /** + * The length of the :member:`field_value`. + */ + size_t field_value_len; +} nghttp2_ext_priority_update; + +/** + * @function + * + * Submits PRIORITY_UPDATE frame. + * + * PRIORITY_UPDATE frame is a non-critical extension to HTTP/2, and + * defined in :rfc:`9218#section-7.1`. + * + * The |flags| is currently ignored and should be + * :enum:`nghttp2_flag.NGHTTP2_FLAG_NONE`. + * + * The |stream_id| is the ID of stream which is prioritized. The + * |field_value| points to the Priority field value. The + * |field_value_len| is the length of the Priority field value. + * + * If this function is called by server, + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` is returned. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 0 is received by a remote endpoint (or it is omitted), + * this function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The function is called from server side session + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * The |field_value_len| is larger than 16380; or |stream_id| is + * 0. + */ +NGHTTP2_EXTERN int nghttp2_submit_priority_update(nghttp2_session *session, + uint8_t flags, + int32_t stream_id, + const uint8_t *field_value, + size_t field_value_len); + +/** + * @function + * + * Changes the priority of the existing stream denoted by |stream_id|. + * The new priority is |extpri|. This function is meant to be used by + * server for :rfc:`9218` extensible prioritization scheme. + * + * If |session| is initialized as client, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. For client, use + * `nghttp2_submit_priority_update()` instead. + * + * If :member:`extpri->urgency ` is out of + * bound, it is set to :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`. + * + * If |ignore_client_signal| is nonzero, server starts to ignore + * client priority signals for this stream. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is not submitted via `nghttp2_submit_settings()`, + * this function does nothing and returns 0. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * |stream_id| is zero; or a stream denoted by |stream_id| is not + * found. + */ +NGHTTP2_EXTERN int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, const nghttp2_extpri *extpri, + int ignore_client_signal); + +/** + * @function + * + * Stores the stream priority of the existing stream denoted by + * |stream_id| in the object pointed by |extpri|. This function is + * meant to be used by server for :rfc:`9218` extensible + * prioritization scheme. + * + * If |session| is initialized as client, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is not submitted via `nghttp2_submit_settings()`, + * this function does nothing and returns 0. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * |stream_id| is zero; or a stream denoted by |stream_id| is not + * found. + */ +NGHTTP2_EXTERN int nghttp2_session_get_extpri_stream_priority( + nghttp2_session *session, nghttp2_extpri *extpri, int32_t stream_id); + +/** + * @function + * + * Parses Priority header field value pointed by |value| of length + * |len|, and stores the result in the object pointed by |extpri|. + * Priority header field is defined in :rfc:`9218`. + * + * This function does not initialize the object pointed by |extpri| + * before storing the result. It only assigns the values that the + * parser correctly extracted to fields. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * Failed to parse the header field value. + */ +NGHTTP2_EXTERN int nghttp2_extpri_parse_priority(nghttp2_extpri *extpri, + const uint8_t *value, + size_t len); + +/** + * @function + * + * Compares ``lhs->name`` of length ``lhs->namelen`` bytes and + * ``rhs->name`` of length ``rhs->namelen`` bytes. Returns negative + * integer if ``lhs->name`` is found to be less than ``rhs->name``; or + * returns positive integer if ``lhs->name`` is found to be greater + * than ``rhs->name``; or returns 0 otherwise. + */ +NGHTTP2_EXTERN int nghttp2_nv_compare_name(const nghttp2_nv *lhs, + const nghttp2_nv *rhs); + +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_select_alpn` instead. + * + * A helper function for dealing with ALPN in server side. The |in| + * contains peer's protocol list in preferable order. The format of + * |in| is length-prefixed and not null-terminated. For example, + * ``h2`` and ``http/1.1`` stored in |in| like this:: + * + * in[0] = 2 + * in[1..2] = "h2" + * in[3] = 8 + * in[4..11] = "http/1.1" + * inlen = 12 + * + * The selection algorithm is as follows: + * + * 1. If peer's list contains HTTP/2 protocol the library supports, + * it is selected and returns 1. The following step is not taken. + * + * 2. If peer's list contains ``http/1.1``, this function selects + * ``http/1.1`` and returns 0. The following step is not taken. + * + * 3. This function selects nothing and returns -1 (So called + * non-overlap case). In this case, |out| and |outlen| are left + * untouched. + * + * Selecting ``h2`` means that ``h2`` is written into |*out| and its + * length (which is 2) is assigned to |*outlen|. + * + * For ALPN, refer to https://tools.ietf.org/html/rfc7301 + * + * To use this method you should do something like:: + * + * static int alpn_select_proto_cb(SSL* ssl, + * const unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_next_protocol((unsigned char**)out, outlen, + * in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_next_protocol(unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * A helper function for dealing with ALPN in server side. The |in| + * contains peer's protocol list in preferable order. The format of + * |in| is length-prefixed and not null-terminated. For example, + * ``h2`` and ``http/1.1`` stored in |in| like this:: + * + * in[0] = 2 + * in[1..2] = "h2" + * in[3] = 8 + * in[4..11] = "http/1.1" + * inlen = 12 + * + * The selection algorithm is as follows: + * + * 1. If peer's list contains HTTP/2 protocol the library supports, + * it is selected and returns 1. The following step is not taken. + * + * 2. If peer's list contains ``http/1.1``, this function selects + * ``http/1.1`` and returns 0. The following step is not taken. + * + * 3. This function selects nothing and returns -1 (So called + * non-overlap case). In this case, |out| and |outlen| are left + * untouched. + * + * Selecting ``h2`` means that ``h2`` is written into |*out| and its + * length (which is 2) is assigned to |*outlen|. + * + * For ALPN, refer to https://tools.ietf.org/html/rfc7301 + * + * To use this method you should do something like:: + * + * static int alpn_select_proto_cb(SSL* ssl, + * const unsigned char **out, + * unsigned char *outlen, + * const unsigned char *in, + * unsigned int inlen, + * void *arg) + * { + * int rv; + * rv = nghttp2_select_alpn(out, outlen, in, inlen); + * if (rv == -1) { + * return SSL_TLSEXT_ERR_NOACK; + * } + * if (rv == 1) { + * ((MyType*)arg)->http2_selected = 1; + * } + * return SSL_TLSEXT_ERR_OK; + * } + * ... + * SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj); + * + */ +NGHTTP2_EXTERN int nghttp2_select_alpn(const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen); + +/** + * @function + * + * Returns a pointer to a nghttp2_info struct with version information + * about the run-time library in use. The |least_version| argument + * can be set to a 24 bit numerical value for the least accepted + * version number and if the condition is not met, this function will + * return a ``NULL``. Pass in 0 to skip the version checking. + */ +NGHTTP2_EXTERN nghttp2_info *nghttp2_version(int least_version); + +/** + * @function + * + * Returns nonzero if the :type:`nghttp2_error` library error code + * |lib_error| is fatal. + */ +NGHTTP2_EXTERN int nghttp2_is_fatal(int lib_error_code); + +/** + * @function + * + * Returns nonzero if HTTP header field name |name| of length |len| is + * valid according to http://tools.ietf.org/html/rfc7230#section-3.2 + * + * Because this is a header field name in HTTP2, the upper cased alphabet + * is treated as error. + */ +NGHTTP2_EXTERN int nghttp2_check_header_name(const uint8_t *name, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2 + * + * This function is considered obsolete, and application should + * consider to use `nghttp2_check_header_value_rfc9113()` instead. + */ +NGHTTP2_EXTERN int nghttp2_check_header_value(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if HTTP header field value |value| of length |len| + * is valid according to + * http://tools.ietf.org/html/rfc7230#section-3.2, plus + * https://datatracker.ietf.org/doc/html/rfc9113#section-8.2.1 + */ +NGHTTP2_EXTERN int nghttp2_check_header_value_rfc9113(const uint8_t *value, + size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :method header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7231#section-4 and + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + */ +NGHTTP2_EXTERN int nghttp2_check_method(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of + * the :path header field is valid according to + * https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.3 + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of path. The allowed characters are all characters valid by + * `nghttp2_check_header_value` minus SPC and HT. + */ +NGHTTP2_EXTERN int nghttp2_check_path(const uint8_t *value, size_t len); + +/** + * @function + * + * Returns nonzero if the |value| which is supposed to be the value of the + * :authority or host header field is valid according to + * https://tools.ietf.org/html/rfc3986#section-3.2 + * + * Note that :authority and host field values are not authority. They + * do not include userinfo in RFC 3986, see + * https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2, that + * is, it does not include '@'. This function treats '@' as a valid + * character. + * + * |value| is valid if it merely consists of the allowed characters. + * In particular, it does not check whether |value| follows the syntax + * of authority. + */ +NGHTTP2_EXTERN int nghttp2_check_authority(const uint8_t *value, size_t len); + +/* HPACK API */ + +struct nghttp2_hd_deflater; + +/** + * @struct + * + * HPACK deflater object. + */ +typedef struct nghttp2_hd_deflater nghttp2_hd_deflater; + +/** + * @function + * + * Initializes |*deflater_ptr| for deflating name/values pairs. + * + * The |max_deflate_dynamic_table_size| is the upper bound of header + * table size the deflater will use. + * + * If this function fails, |*deflater_ptr| is left untouched. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size); + +/** + * @function + * + * Like `nghttp2_hd_deflate_new()`, but with additional custom memory + * allocator specified in the |mem|. + * + * The |mem| can be ``NULL`` and the call is equivalent to + * `nghttp2_hd_deflate_new()`. + * + * This function does not take ownership |mem|. The application is + * responsible for freeing |mem|. + * + * The library code does not refer to |mem| pointer after this + * function returns, so the application can safely free it. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr, + size_t max_deflate_dynamic_table_size, + nghttp2_mem *mem); + +/** + * @function + * + * Deallocates any resources allocated for |deflater|. + */ +NGHTTP2_EXTERN void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Changes header table size of the |deflater| to + * |settings_max_dynamic_table_size| bytes. This may trigger eviction + * in the dynamic table. + * + * The |settings_max_dynamic_table_size| should be the value received + * in SETTINGS_HEADER_TABLE_SIZE. + * + * The deflater never uses more memory than + * ``max_deflate_dynamic_table_size`` bytes specified in + * `nghttp2_hd_deflate_new()`. Therefore, if + * |settings_max_dynamic_table_size| > + * ``max_deflate_dynamic_table_size``, resulting maximum table size + * becomes ``max_deflate_dynamic_table_size``. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + */ +NGHTTP2_EXTERN int +nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, + size_t settings_max_dynamic_table_size); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_hd_deflate_hd2()` instead. + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |buf| of length |buflen|. + * + * If |buf| is not large enough to store the deflated header block, + * this function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |buf| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, + uint8_t *buf, size_t buflen, + const nghttp2_nv *nva, + size_t nvlen); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |buf| of length |buflen|. + * + * If |buf| is not large enough to store the deflated header block, + * this function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |buf| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN nghttp2_ssize +nghttp2_hd_deflate_hd2(nghttp2_hd_deflater *deflater, uint8_t *buf, + size_t buflen, const nghttp2_nv *nva, size_t nvlen); + +#ifndef NGHTTP2_NO_SSIZE_T +/** + * @function + * + * .. warning:: + * + * Deprecated. Use `nghttp2_hd_deflate_hd_vec2()` instead. + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |veclen| size of buf vector |vec|. The each size of buffer + * must be set in len field of :type:`nghttp2_vec`. If and only if + * one chunk is filled up completely, next chunk will be used. If + * |vec| is not large enough to store the deflated header block, this + * function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |vec| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, + const nghttp2_vec *vec, + size_t veclen, + const nghttp2_nv *nva, + size_t nvlen); + +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Deflates the |nva|, which has the |nvlen| name/value pairs, into + * the |veclen| size of buf vector |vec|. The each size of buffer + * must be set in len field of :type:`nghttp2_vec`. If and only if + * one chunk is filled up completely, next chunk will be used. If + * |vec| is not large enough to store the deflated header block, this + * function fails with + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE`. The caller + * should use `nghttp2_hd_deflate_bound()` to know the upper bound of + * buffer size required to deflate given header name/value pairs. + * + * Once this function fails, subsequent call of this function always + * returns :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP`. + * + * After this function returns, it is safe to delete the |nva|. + * + * This function returns the number of bytes written to |vec| if it + * succeeds, or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Deflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_INSUFF_BUFSIZE` + * The provided |buflen| size is too small to hold the output. + */ +NGHTTP2_EXTERN nghttp2_ssize nghttp2_hd_deflate_hd_vec2( + nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, + const nghttp2_nv *nva, size_t nvlen); + +/** + * @function + * + * Returns an upper bound on the compressed size after deflation of + * |nva| of length |nvlen|. + */ +NGHTTP2_EXTERN size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, + const nghttp2_nv *nva, + size_t nvlen); + +/** + * @function + * + * Returns the number of entries that header table of |deflater| + * contains. This is the sum of the number of static table and + * dynamic table, so the return value is at least 61. + */ +NGHTTP2_EXTERN +size_t nghttp2_hd_deflate_get_num_table_entries(nghttp2_hd_deflater *deflater); + +/** + * @function + * + * Returns the table entry denoted by |idx| from header table of + * |deflater|. The |idx| is 1-based, and idx=1 returns first entry of * static table. idx=62 returns first entry of dynamic table if it * exists. Specifying idx=0 is error, and this function returns NULL. * If |idx| is strictly greater than the number of entries the tables @@ -1531,7 +6440,7 @@ NGHTTP2_EXTERN void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater); * This function must not be called while header block is being * inflated. In other words, this function must be called after * initialization of |inflater|, but before calling - * `nghttp2_hd_inflate_hd2()`, or after + * `nghttp2_hd_inflate_hd3()`, or after * `nghttp2_hd_inflate_end_headers()`. Otherwise, * `NGHTTP2_ERR_INVALID_STATE` was returned. * @@ -1569,6 +6478,7 @@ typedef enum { NGHTTP2_HD_INFLATE_EMIT = 0x02 } nghttp2_hd_inflate_flag; +#ifndef NGHTTP2_NO_SSIZE_T /** * @function * @@ -1656,9 +6566,16 @@ NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, int *inflate_flags, uint8_t *in, size_t inlen, int in_final); +#endif /* NGHTTP2_NO_SSIZE_T */ + +#ifndef NGHTTP2_NO_SSIZE_T /** * @function * + * .. warning:: + * + * Deprecated. Use `nghttp2_hd_inflate_hd3()` instead. + * * Inflates name/value block stored in |in| with length |inlen|. This * function performs decompression. For each successful emission of * header name/value pair, @@ -1745,6 +6662,95 @@ NGHTTP2_EXTERN ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, const uint8_t *in, size_t inlen, int in_final); +#endif /* NGHTTP2_NO_SSIZE_T */ + +/** + * @function + * + * Inflates name/value block stored in |in| with length |inlen|. This + * function performs decompression. For each successful emission of + * header name/value pair, + * :enum:`nghttp2_hd_inflate_flag.NGHTTP2_HD_INFLATE_EMIT` is set in + * |*inflate_flags| and name/value pair is assigned to the |nv_out| + * and the function returns. The caller must not free the members of + * |nv_out|. + * + * The |nv_out| may include pointers to the memory region in the |in|. + * The caller must retain the |in| while the |nv_out| is used. + * + * The application should call this function repeatedly until the + * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and + * return value is non-negative. If that happens, all given input + * data (|inlen| bytes) are processed successfully. Then the + * application must call `nghttp2_hd_inflate_end_headers()` to prepare + * for the next header block input. + * + * In other words, if |in_final| is nonzero, and this function returns + * |inlen|, you can assert that + * :enum:`nghttp2_hd_inflate_final.NGHTTP2_HD_INFLATE_FINAL` is set in + * |*inflate_flags|. + * + * The caller can feed complete compressed header block. It also can + * feed it in several chunks. The caller must set |in_final| to + * nonzero if the given input is the last block of the compressed + * header. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_HEADER_COMP` + * Inflation process has failed. + * :enum:`nghttp2_error.NGHTTP2_ERR_BUFFER_ERROR` + * The header field name or value is too large. + * + * Example follows:: + * + * int inflate_header_block(nghttp2_hd_inflater *hd_inflater, + * uint8_t *in, size_t inlen, int final) + * { + * nghttp2_ssize rv; + * + * for(;;) { + * nghttp2_nv nv; + * int inflate_flags = 0; + * + * rv = nghttp2_hd_inflate_hd3(hd_inflater, &nv, &inflate_flags, + * in, inlen, final); + * + * if(rv < 0) { + * fprintf(stderr, "inflate failed with error code %td", rv); + * return -1; + * } + * + * in += rv; + * inlen -= rv; + * + * if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) { + * fwrite(nv.name, nv.namelen, 1, stderr); + * fprintf(stderr, ": "); + * fwrite(nv.value, nv.valuelen, 1, stderr); + * fprintf(stderr, "\n"); + * } + * if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) { + * nghttp2_hd_inflate_end_headers(hd_inflater); + * break; + * } + * if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && + * inlen == 0) { + * break; + * } + * } + * + * return 0; + * } + * + */ +NGHTTP2_EXTERN nghttp2_ssize nghttp2_hd_inflate_hd3( + nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, int in_final); + /** * @function * @@ -1798,6 +6804,32 @@ NGHTTP2_EXTERN size_t nghttp2_hd_inflate_get_max_dynamic_table_size(nghttp2_hd_inflater *inflater); +struct nghttp2_stream; + +/** + * @struct + * + * The structure to represent HTTP/2 stream. The details of this + * structure are intentionally hidden from the public API. + */ +typedef struct nghttp2_stream nghttp2_stream; + +/** + * @function + * + * Returns pointer to :type:`nghttp2_stream` object denoted by + * |stream_id|. If stream was not found, returns NULL. + * + * Returns imaginary root stream (see + * `nghttp2_session_get_root_stream()`) if 0 is given in |stream_id|. + * + * Unless |stream_id| == 0, the returned pointer is valid until next + * call of `nghttp2_session_send()`, `nghttp2_session_mem_send2()`, + * `nghttp2_session_recv()`, and `nghttp2_session_mem_recv2()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_find_stream(nghttp2_session *session, int32_t stream_id); + /** * @enum * @@ -1834,6 +6866,128 @@ typedef enum { NGHTTP2_STREAM_STATE_CLOSED } nghttp2_stream_proto_state; +/** + * @function + * + * Returns state of |stream|. The root stream retrieved by + * `nghttp2_session_get_root_stream()` will have stream state + * :enum:`nghttp2_stream_proto_state.NGHTTP2_STREAM_STATE_IDLE`. + */ +NGHTTP2_EXTERN nghttp2_stream_proto_state +nghttp2_stream_get_state(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. + * + * Returns root of dependency tree, which is imaginary stream with + * stream ID 0. The returned pointer is valid until |session| is + * freed by `nghttp2_session_del()`. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_session_get_root_stream(nghttp2_session *session); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return NULL. + * + * Returns the parent stream of |stream| in dependency tree. Returns + * NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_parent(nghttp2_stream *stream); + +NGHTTP2_EXTERN int32_t nghttp2_stream_get_stream_id(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return NULL. + * + * Returns the next sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_next_sibling(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return NULL. + * + * Returns the previous sibling stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_previous_sibling(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return NULL. + * + * Returns the first child stream of |stream| in dependency tree. + * Returns NULL if there is no such stream. + */ +NGHTTP2_EXTERN nghttp2_stream * +nghttp2_stream_get_first_child(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return + * :macro:`NGHTTP2_DEFAULT_WEIGHT`. + * + * Returns dependency weight to the parent stream of |stream|. + */ +NGHTTP2_EXTERN int32_t nghttp2_stream_get_weight(nghttp2_stream *stream); + +/** + * @function + * + * .. warning:: + * + * Deprecated. :rfc:`7540` priorities are deprecated by + * :rfc:`9113`. Consider migrating to :rfc:`9218` extensible + * prioritization scheme. In the future release after the end of + * 2024, this function will always return 0. + * + * Returns the sum of the weight for |stream|'s children. + */ +NGHTTP2_EXTERN int32_t +nghttp2_stream_get_sum_dependency_weight(nghttp2_stream *stream); + /** * @functypedef * @@ -1868,7 +7022,7 @@ typedef void (*nghttp2_debug_vprintf_callback)(const char *format, * this is important. */ NGHTTP2_EXTERN void nghttp2_set_debug_vprintf_callback( - nghttp2_debug_vprintf_callback debug_vprintf_callback); + nghttp2_debug_vprintf_callback debug_vprintf_callback); #ifdef __cplusplus } diff --git a/thirdparty/nghttp2/nghttp2_buf.c b/thirdparty/nghttp2/nghttp2_buf.c index e618400b01..3cdfe5be56 100644 --- a/thirdparty/nghttp2/nghttp2_buf.c +++ b/thirdparty/nghttp2/nghttp2_buf.c @@ -58,7 +58,7 @@ int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) { return 0; } - new_cap = nghttp2_max(new_cap, cap * 2); + new_cap = nghttp2_max_size(new_cap, cap * 2); ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap); if (ptr == NULL) { @@ -340,7 +340,7 @@ int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) { while (len) { buf = &bufs->cur->buf; - nwrite = nghttp2_min(nghttp2_buf_avail(buf), len); + nwrite = nghttp2_min_size(nghttp2_buf_avail(buf), len); if (nwrite == 0) { rv = bufs_alloc_chain(bufs); if (rv != 0) { @@ -427,7 +427,7 @@ int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) { return 0; } -ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { +nghttp2_ssize nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { size_t len; nghttp2_buf_chain *chain; nghttp2_buf *buf; @@ -459,7 +459,7 @@ ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) { *out = res; - return (ssize_t)len; + return (nghttp2_ssize)len; } size_t nghttp2_bufs_remove_copy(nghttp2_bufs *bufs, uint8_t *out) { diff --git a/thirdparty/nghttp2/nghttp2_buf.h b/thirdparty/nghttp2/nghttp2_buf.h index eaddd86852..3603a5a2e1 100644 --- a/thirdparty/nghttp2/nghttp2_buf.h +++ b/thirdparty/nghttp2/nghttp2_buf.h @@ -94,7 +94,7 @@ void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem); * |new_cap|. If extensions took place, buffer pointers in |buf| will * change. * - * This function returns 0 if it succeeds, or one of the followings + * This function returns 0 if it succeeds, or one of the following * negative error codes: * * NGHTTP2_ERR_NOMEM @@ -344,7 +344,7 @@ int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b); * NGHTTP2_ERR_NOMEM * Out of memory */ -ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out); +nghttp2_ssize nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out); /* * Copies all data stored in |bufs| to |out|. This function assumes diff --git a/thirdparty/nghttp2/nghttp2_hd.c b/thirdparty/nghttp2/nghttp2_hd.c index aa54243a66..e26ec93e54 100644 --- a/thirdparty/nghttp2/nghttp2_hd.c +++ b/thirdparty/nghttp2/nghttp2_hd.c @@ -32,9 +32,10 @@ #define MAKE_STATIC_ENT(N, V, T, H) \ { \ {NULL, NULL, (uint8_t *)(N), sizeof((N)) - 1, -1}, \ - {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \ - {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \ - T, H \ + {NULL, NULL, (uint8_t *)(V), sizeof((V)) - 1, -1}, \ + {(uint8_t *)(N), (uint8_t *)(V), sizeof((N)) - 1, sizeof((V)) - 1, 0}, \ + T, \ + H, \ } /* Generated by mkstatictbl.py */ @@ -42,67 +43,67 @@ first enum value if same header names are repeated (e.g., :status). */ static const nghttp2_hd_static_entry static_table[] = { - MAKE_STATIC_ENT(":authority", "", 0, 3153725150u), - MAKE_STATIC_ENT(":method", "GET", 1, 695666056u), - MAKE_STATIC_ENT(":method", "POST", 1, 695666056u), - MAKE_STATIC_ENT(":path", "/", 3, 3292848686u), - MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u), - MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u), - MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u), - MAKE_STATIC_ENT(":status", "200", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "204", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "206", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "304", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "400", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "404", 7, 4000288983u), - MAKE_STATIC_ENT(":status", "500", 7, 4000288983u), - MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u), - MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u), - MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u), - MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u), - MAKE_STATIC_ENT("accept", "", 18, 136609321u), - MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u), - MAKE_STATIC_ENT("age", "", 20, 742476188u), - MAKE_STATIC_ENT("allow", "", 21, 2930878514u), - MAKE_STATIC_ENT("authorization", "", 22, 2436257726u), - MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u), - MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u), - MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u), - MAKE_STATIC_ENT("content-language", "", 26, 24973587u), - MAKE_STATIC_ENT("content-length", "", 27, 1308181789u), - MAKE_STATIC_ENT("content-location", "", 28, 2302364718u), - MAKE_STATIC_ENT("content-range", "", 29, 3555523146u), - MAKE_STATIC_ENT("content-type", "", 30, 4244048277u), - MAKE_STATIC_ENT("cookie", "", 31, 2007449791u), - MAKE_STATIC_ENT("date", "", 32, 3564297305u), - MAKE_STATIC_ENT("etag", "", 33, 113792960u), - MAKE_STATIC_ENT("expect", "", 34, 2530896728u), - MAKE_STATIC_ENT("expires", "", 35, 1049544579u), - MAKE_STATIC_ENT("from", "", 36, 2513272949u), - MAKE_STATIC_ENT("host", "", 37, 2952701295u), - MAKE_STATIC_ENT("if-match", "", 38, 3597694698u), - MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u), - MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u), - MAKE_STATIC_ENT("if-range", "", 41, 2340978238u), - MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u), - MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u), - MAKE_STATIC_ENT("link", "", 44, 232457833u), - MAKE_STATIC_ENT("location", "", 45, 200649126u), - MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u), - MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u), - MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u), - MAKE_STATIC_ENT("range", "", 49, 4208725202u), - MAKE_STATIC_ENT("referer", "", 50, 3969579366u), - MAKE_STATIC_ENT("refresh", "", 51, 3572655668u), - MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u), - MAKE_STATIC_ENT("server", "", 53, 1085029842u), - MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u), - MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u), - MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u), - MAKE_STATIC_ENT("user-agent", "", 57, 606444526u), - MAKE_STATIC_ENT("vary", "", 58, 1085005381u), - MAKE_STATIC_ENT("via", "", 59, 1762798611u), - MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u), + MAKE_STATIC_ENT(":authority", "", 0, 3153725150u), + MAKE_STATIC_ENT(":method", "GET", 1, 695666056u), + MAKE_STATIC_ENT(":method", "POST", 1, 695666056u), + MAKE_STATIC_ENT(":path", "/", 3, 3292848686u), + MAKE_STATIC_ENT(":path", "/index.html", 3, 3292848686u), + MAKE_STATIC_ENT(":scheme", "http", 5, 2510477674u), + MAKE_STATIC_ENT(":scheme", "https", 5, 2510477674u), + MAKE_STATIC_ENT(":status", "200", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "204", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "206", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "304", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "400", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "404", 7, 4000288983u), + MAKE_STATIC_ENT(":status", "500", 7, 4000288983u), + MAKE_STATIC_ENT("accept-charset", "", 14, 3664010344u), + MAKE_STATIC_ENT("accept-encoding", "gzip, deflate", 15, 3379649177u), + MAKE_STATIC_ENT("accept-language", "", 16, 1979086614u), + MAKE_STATIC_ENT("accept-ranges", "", 17, 1713753958u), + MAKE_STATIC_ENT("accept", "", 18, 136609321u), + MAKE_STATIC_ENT("access-control-allow-origin", "", 19, 2710797292u), + MAKE_STATIC_ENT("age", "", 20, 742476188u), + MAKE_STATIC_ENT("allow", "", 21, 2930878514u), + MAKE_STATIC_ENT("authorization", "", 22, 2436257726u), + MAKE_STATIC_ENT("cache-control", "", 23, 1355326669u), + MAKE_STATIC_ENT("content-disposition", "", 24, 3889184348u), + MAKE_STATIC_ENT("content-encoding", "", 25, 65203592u), + MAKE_STATIC_ENT("content-language", "", 26, 24973587u), + MAKE_STATIC_ENT("content-length", "", 27, 1308181789u), + MAKE_STATIC_ENT("content-location", "", 28, 2302364718u), + MAKE_STATIC_ENT("content-range", "", 29, 3555523146u), + MAKE_STATIC_ENT("content-type", "", 30, 4244048277u), + MAKE_STATIC_ENT("cookie", "", 31, 2007449791u), + MAKE_STATIC_ENT("date", "", 32, 3564297305u), + MAKE_STATIC_ENT("etag", "", 33, 113792960u), + MAKE_STATIC_ENT("expect", "", 34, 2530896728u), + MAKE_STATIC_ENT("expires", "", 35, 1049544579u), + MAKE_STATIC_ENT("from", "", 36, 2513272949u), + MAKE_STATIC_ENT("host", "", 37, 2952701295u), + MAKE_STATIC_ENT("if-match", "", 38, 3597694698u), + MAKE_STATIC_ENT("if-modified-since", "", 39, 2213050793u), + MAKE_STATIC_ENT("if-none-match", "", 40, 2536202615u), + MAKE_STATIC_ENT("if-range", "", 41, 2340978238u), + MAKE_STATIC_ENT("if-unmodified-since", "", 42, 3794814858u), + MAKE_STATIC_ENT("last-modified", "", 43, 3226950251u), + MAKE_STATIC_ENT("link", "", 44, 232457833u), + MAKE_STATIC_ENT("location", "", 45, 200649126u), + MAKE_STATIC_ENT("max-forwards", "", 46, 1826162134u), + MAKE_STATIC_ENT("proxy-authenticate", "", 47, 2709445359u), + MAKE_STATIC_ENT("proxy-authorization", "", 48, 2686392507u), + MAKE_STATIC_ENT("range", "", 49, 4208725202u), + MAKE_STATIC_ENT("referer", "", 50, 3969579366u), + MAKE_STATIC_ENT("refresh", "", 51, 3572655668u), + MAKE_STATIC_ENT("retry-after", "", 52, 3336180598u), + MAKE_STATIC_ENT("server", "", 53, 1085029842u), + MAKE_STATIC_ENT("set-cookie", "", 54, 1848371000u), + MAKE_STATIC_ENT("strict-transport-security", "", 55, 4138147361u), + MAKE_STATIC_ENT("transfer-encoding", "", 56, 3719590988u), + MAKE_STATIC_ENT("user-agent", "", 57, 606444526u), + MAKE_STATIC_ENT("vary", "", 58, 1085005381u), + MAKE_STATIC_ENT("via", "", 59, 1762798611u), + MAKE_STATIC_ENT("www-authenticate", "", 60, 779865858u), }; static int memeq(const void *s1, const void *s2, size_t n) { @@ -265,6 +266,11 @@ static int32_t lookup_token(const uint8_t *name, size_t namelen) { return NGHTTP2_TOKEN_LOCATION; } break; + case 'y': + if (memeq("priorit", name, 7)) { + return NGHTTP2_TOKEN_PRIORITY; + } + break; } break; case 9: @@ -668,8 +674,8 @@ static int hd_context_init(nghttp2_hd_context *context, nghttp2_mem *mem) { context->bad = 0; context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; rv = hd_ringbuf_init( - &context->hd_table, - context->hd_table_bufsize_max / NGHTTP2_HD_ENTRY_OVERHEAD, mem); + &context->hd_table, + context->hd_table_bufsize_max / NGHTTP2_HD_ENTRY_OVERHEAD, mem); if (rv != 0) { return rv; } @@ -686,7 +692,7 @@ static void hd_context_free(nghttp2_hd_context *context) { int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) { return nghttp2_hd_deflate_init2( - deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem); + deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem); } int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater, @@ -841,9 +847,10 @@ static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) { * in the next call will be stored in |*shift_ptr|) and returns number * of bytes processed, or returns -1, indicating decoding error. */ -static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *fin, - uint32_t initial, size_t shift, const uint8_t *in, - const uint8_t *last, size_t prefix) { +static nghttp2_ssize decode_length(uint32_t *res, size_t *shift_ptr, int *fin, + uint32_t initial, size_t shift, + const uint8_t *in, const uint8_t *last, + size_t prefix) { uint32_t k = (uint8_t)((1 << prefix) - 1); uint32_t n = initial; const uint8_t *start = in; @@ -862,7 +869,7 @@ static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *fin, if (++in == last) { *res = n; - return (ssize_t)(in - start); + return (nghttp2_ssize)(in - start); } } @@ -897,12 +904,12 @@ static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *fin, if (in == last) { *res = n; - return (ssize_t)(in - start); + return (nghttp2_ssize)(in - start); } *res = n; *fin = 1; - return (ssize_t)(in + 1 - start); + return (nghttp2_ssize)(in + 1 - start); } static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) { @@ -1067,8 +1074,8 @@ static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv, int rv; DEBUGF( - "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n", - nv->namelen, nv->valuelen, indexing_mode); + "deflatehd: emit newname namelen=%zu, valuelen=%zu, indexing_mode=%d\n", + nv->namelen, nv->valuelen, indexing_mode); rv = nghttp2_bufs_addb(bufs, pack_first_byte(indexing_mode)); if (rv != 0) { @@ -1101,12 +1108,11 @@ static int add_hd_table_incremental(nghttp2_hd_context *context, while (context->hd_table_bufsize + room > context->hd_table_bufsize_max && context->hd_table.len > 0) { - size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); context->hd_table_bufsize -= - entry_room(ent->nv.name->len, ent->nv.value->len); + entry_room(ent->nv.name->len, ent->nv.value->len); DEBUGF("hpack: remove item from header table: %s: %s\n", (char *)ent->nv.name->base, (char *)ent->nv.value->base); @@ -1155,7 +1161,7 @@ static int add_hd_table_incremental(nghttp2_hd_context *context, } typedef struct { - ssize_t index; + nghttp2_ssize index; /* Nonzero if both name and value are matched. */ int name_value_match; } search_result; @@ -1204,8 +1210,8 @@ static search_result search_hd_table(nghttp2_hd_context *context, return res; } - res.index = - (ssize_t)(context->next_seq - 1 - ent->seq + NGHTTP2_STATIC_TABLE_LENGTH); + res.index = (nghttp2_ssize)(context->next_seq - 1 - ent->seq + + NGHTTP2_STATIC_TABLE_LENGTH); res.name_value_match = exact_match; return res; @@ -1222,7 +1228,7 @@ static void hd_context_shrink_table_size(nghttp2_hd_context *context, size_t idx = context->hd_table.len - 1; nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx); context->hd_table_bufsize -= - entry_room(ent->nv.name->len, ent->nv.value->len); + entry_room(ent->nv.name->len, ent->nv.value->len); hd_ringbuf_pop_back(&context->hd_table); if (map) { hd_map_remove(map, ent); @@ -1234,14 +1240,14 @@ static void hd_context_shrink_table_size(nghttp2_hd_context *context, } int nghttp2_hd_deflate_change_table_size( - nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) { - size_t next_bufsize = nghttp2_min(settings_max_dynamic_table_size, - deflater->deflate_hd_table_bufsize_max); + nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size) { + size_t next_bufsize = nghttp2_min_size( + settings_max_dynamic_table_size, deflater->deflate_hd_table_bufsize_max); deflater->ctx.hd_table_bufsize_max = next_bufsize; deflater->min_hd_table_bufsize_max = - nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize); + nghttp2_min_size(deflater->min_hd_table_bufsize_max, next_bufsize); deflater->notify_table_size_change = 1; @@ -1250,7 +1256,7 @@ int nghttp2_hd_deflate_change_table_size( } int nghttp2_hd_inflate_change_table_size( - nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) { + nghttp2_hd_inflater *inflater, size_t settings_max_dynamic_table_size) { switch (inflater->state) { case NGHTTP2_HD_STATE_EXPECT_TABLE_SIZE: case NGHTTP2_HD_STATE_INFLATE_START: @@ -1259,6 +1265,8 @@ int nghttp2_hd_inflate_change_table_size( return NGHTTP2_ERR_INVALID_STATE; } + inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size; + /* It seems that encoder is not required to send dynamic table size update if the table size is not changed after applying SETTINGS_HEADER_TABLE_SIZE. RFC 7541 is ambiguous here, but this @@ -1271,13 +1279,12 @@ int nghttp2_hd_inflate_change_table_size( /* Remember minimum value, and validate that encoder sends the value less than or equal to this. */ inflater->min_hd_table_bufsize_max = settings_max_dynamic_table_size; - } - inflater->settings_hd_table_bufsize_max = settings_max_dynamic_table_size; + inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; - inflater->ctx.hd_table_bufsize_max = settings_max_dynamic_table_size; + hd_context_shrink_table_size(&inflater->ctx, NULL); + } - hd_context_shrink_table_size(&inflater->ctx, NULL); return 0; } @@ -1292,7 +1299,7 @@ nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) { assert(INDEX_RANGE_VALID(context, idx)); if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) - ->nv; + ->nv; } else { const nghttp2_hd_static_entry *ent = &static_table[idx]; nghttp2_hd_nv nv = {(nghttp2_rcbuf *)&ent->name, @@ -1308,7 +1315,7 @@ static const nghttp2_nv *nghttp2_hd_table_get2(nghttp2_hd_context *context, if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) { return &hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH) - ->cnv; + ->cnv; } return &static_table[idx].cnv; @@ -1322,7 +1329,7 @@ static int hd_deflate_decide_indexing(nghttp2_hd_deflater *deflater, token == NGHTTP2_TOKEN_IF_NONE_MATCH || token == NGHTTP2_TOKEN_LOCATION || token == NGHTTP2_TOKEN_SET_COOKIE || entry_room(nv->namelen, nv->valuelen) > - deflater->ctx.hd_table_bufsize_max * 3 / 4) { + deflater->ctx.hd_table_bufsize_max * 3 / 4) { return NGHTTP2_HD_WITHOUT_INDEXING; } @@ -1333,7 +1340,7 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, const nghttp2_nv *nv) { int rv; search_result res; - ssize_t idx; + nghttp2_ssize idx; int indexing_mode; int32_t token; nghttp2_mem *mem; @@ -1355,12 +1362,11 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, entropy secret data (e.g., id/password). Also cookie header field with less than 20 bytes value is also never indexed. This is the same criteria used in Firefox codebase. */ - indexing_mode = - token == NGHTTP2_TOKEN_AUTHORIZATION || - (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) || - (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) - ? NGHTTP2_HD_NEVER_INDEXING - : hd_deflate_decide_indexing(deflater, nv, token); + indexing_mode = token == NGHTTP2_TOKEN_AUTHORIZATION || + (token == NGHTTP2_TOKEN_COOKIE && nv->valuelen < 20) || + (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) + ? NGHTTP2_HD_NEVER_INDEXING + : hd_deflate_decide_indexing(deflater, nv, token); res = search_hd_table(&deflater->ctx, nv, token, indexing_mode, &deflater->map, hash); @@ -1368,8 +1374,7 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, idx = res.index; if (res.name_value_match) { - - DEBUGF("deflatehd: name/value match index=%zd\n", idx); + DEBUGF("deflatehd: name/value match index=%td\n", idx); rv = emit_indexed_block(bufs, (size_t)idx); if (rv != 0) { @@ -1380,7 +1385,7 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, } if (res.index != -1) { - DEBUGF("deflatehd: name match index=%zd\n", res.index); + DEBUGF("deflatehd: name match index=%td\n", res.index); } if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) { @@ -1446,7 +1451,6 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, deflater->min_hd_table_bufsize_max = UINT32_MAX; if (deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) { - rv = emit_table_size(bufs, min_hd_table_bufsize_max); if (rv != 0) { @@ -1481,6 +1485,12 @@ int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater, ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nv, size_t nvlen) { + return (ssize_t)nghttp2_hd_deflate_hd2(deflater, buf, buflen, nv, nvlen); +} + +nghttp2_ssize nghttp2_hd_deflate_hd2(nghttp2_hd_deflater *deflater, + uint8_t *buf, size_t buflen, + const nghttp2_nv *nv, size_t nvlen) { nghttp2_bufs bufs; int rv; nghttp2_mem *mem; @@ -1507,12 +1517,18 @@ ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, return rv; } - return (ssize_t)buflen; + return (nghttp2_ssize)buflen; } ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, const nghttp2_vec *vec, size_t veclen, const nghttp2_nv *nv, size_t nvlen) { + return (ssize_t)nghttp2_hd_deflate_hd_vec2(deflater, vec, veclen, nv, nvlen); +} + +nghttp2_ssize nghttp2_hd_deflate_hd_vec2(nghttp2_hd_deflater *deflater, + const nghttp2_vec *vec, size_t veclen, + const nghttp2_nv *nv, size_t nvlen) { nghttp2_bufs bufs; int rv; nghttp2_mem *mem; @@ -1540,7 +1556,7 @@ ssize_t nghttp2_hd_deflate_hd_vec(nghttp2_hd_deflater *deflater, return rv; } - return (ssize_t)buflen; + return (nghttp2_ssize)buflen; } size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, @@ -1633,10 +1649,11 @@ static void hd_inflate_set_huffman_encoded(nghttp2_hd_inflater *inflater, * NGHTTP2_ERR_HEADER_COMP * Integer decoding failed */ -static ssize_t hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin, - const uint8_t *in, const uint8_t *last, - size_t prefix, size_t maxlen) { - ssize_t rv; +static nghttp2_ssize hd_inflate_read_len(nghttp2_hd_inflater *inflater, + int *rfin, const uint8_t *in, + const uint8_t *last, size_t prefix, + size_t maxlen) { + nghttp2_ssize rv; uint32_t out; *rfin = 0; @@ -1674,10 +1691,10 @@ static ssize_t hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin, * NGHTTP2_ERR_HEADER_COMP * Huffman decoding failed */ -static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater, - nghttp2_buf *buf, const uint8_t *in, - const uint8_t *last) { - ssize_t readlen; +static nghttp2_ssize hd_inflate_read_huff(nghttp2_hd_inflater *inflater, + nghttp2_buf *buf, const uint8_t *in, + const uint8_t *last) { + nghttp2_ssize readlen; int fin = 0; if ((size_t)(last - in) >= inflater->left) { last = in + inflater->left; @@ -1690,6 +1707,11 @@ static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater, DEBUGF("inflatehd: huffman decoding failed\n"); return readlen; } + if (nghttp2_hd_huff_decode_failure_state(&inflater->huff_decode_ctx)) { + DEBUGF("inflatehd: huffman decoding failed\n"); + return NGHTTP2_ERR_HEADER_COMP; + } + inflater->left -= (size_t)readlen; return readlen; } @@ -1706,14 +1728,15 @@ static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater, * NGHTTP2_ERR_HEADER_COMP * Header decompression failed */ -static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater, nghttp2_buf *buf, - const uint8_t *in, const uint8_t *last) { - size_t len = nghttp2_min((size_t)(last - in), inflater->left); +static nghttp2_ssize hd_inflate_read(nghttp2_hd_inflater *inflater, + nghttp2_buf *buf, const uint8_t *in, + const uint8_t *last) { + size_t len = nghttp2_min_size((size_t)(last - in), inflater->left); buf->last = nghttp2_cpymem(buf->last, in, len); inflater->left -= len; - return (ssize_t)len; + return (nghttp2_ssize)len; } /* @@ -1828,7 +1851,15 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out, int *inflate_flags, const uint8_t *in, size_t inlen, int in_final) { - ssize_t rv; + return (nghttp2_ssize)nghttp2_hd_inflate_hd3(inflater, nv_out, inflate_flags, + in, inlen, in_final); +} + +nghttp2_ssize nghttp2_hd_inflate_hd3(nghttp2_hd_inflater *inflater, + nghttp2_nv *nv_out, int *inflate_flags, + const uint8_t *in, size_t inlen, + int in_final) { + nghttp2_ssize rv; nghttp2_hd_nv hd_nv; rv = nghttp2_hd_inflate_hd_nv(inflater, &hd_nv, inflate_flags, in, inlen, @@ -1851,11 +1882,11 @@ ssize_t nghttp2_hd_inflate_hd2(nghttp2_hd_inflater *inflater, return rv; } -ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, - nghttp2_hd_nv *nv_out, int *inflate_flags, - const uint8_t *in, size_t inlen, - int in_final) { - ssize_t rv = 0; +nghttp2_ssize nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out, + int *inflate_flags, const uint8_t *in, + size_t inlen, int in_final) { + nghttp2_ssize rv = 0; const uint8_t *first = in; const uint8_t *last = in + inlen; int rfin = 0; @@ -1923,9 +1954,9 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, case NGHTTP2_HD_STATE_READ_TABLE_SIZE: rfin = 0; rv = hd_inflate_read_len( - inflater, &rfin, in, last, 5, - nghttp2_min(inflater->min_hd_table_bufsize_max, - inflater->settings_hd_table_bufsize_max)); + inflater, &rfin, in, last, 5, + nghttp2_min_size(inflater->min_hd_table_bufsize_max, + inflater->settings_hd_table_bufsize_max)); if (rv < 0) { goto fail; } @@ -1977,7 +2008,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; - return (ssize_t)(in - first); + return (nghttp2_ssize)(in - first); } else { inflater->index = inflater->left; --inflater->index; @@ -2012,8 +2043,8 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF; - rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1, - mem); + rv = + nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left * 2 + 1, mem); } else { inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME; rv = nghttp2_rcbuf_new(&inflater->namercbuf, inflater->left + 1, mem); @@ -2035,7 +2066,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, in += rv; - DEBUGF("inflatehd: %zd bytes read\n", rv); + DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); @@ -2057,7 +2088,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, in += rv; - DEBUGF("inflatehd: %zd bytes read\n", rv); + DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); @@ -2097,8 +2128,8 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF; - rv = nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1, - mem); + rv = + nghttp2_rcbuf_new(&inflater->valuercbuf, inflater->left * 2 + 1, mem); } else { inflater->state = NGHTTP2_HD_STATE_READ_VALUE; @@ -2123,7 +2154,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, in += rv; - DEBUGF("inflatehd: %zd bytes read\n", rv); + DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); @@ -2147,18 +2178,18 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; - return (ssize_t)(in - first); + return (nghttp2_ssize)(in - first); case NGHTTP2_HD_STATE_READ_VALUE: rv = hd_inflate_read(inflater, &inflater->valuebuf, in, last); if (rv < 0) { - DEBUGF("inflatehd: value read failure %zd: %s\n", rv, + DEBUGF("inflatehd: value read failure %td: %s\n", rv, nghttp2_strerror((int)rv)); goto fail; } in += rv; - DEBUGF("inflatehd: %zd bytes read\n", rv); + DEBUGF("inflatehd: %td bytes read\n", rv); if (inflater->left) { DEBUGF("inflatehd: still %zu bytes to go\n", inflater->left); @@ -2181,7 +2212,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, inflater->state = NGHTTP2_HD_STATE_OPCODE; *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT; - return (ssize_t)(in - first); + return (nghttp2_ssize)(in - first); } } @@ -2201,7 +2232,7 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, } *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL; } - return (ssize_t)(in - first); + return (nghttp2_ssize)(in - first); almost_ok: if (in_final) { @@ -2211,10 +2242,10 @@ ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, goto fail; } - return (ssize_t)(in - first); + return (nghttp2_ssize)(in - first); fail: - DEBUGF("inflatehd: error return %zd\n", rv); + DEBUGF("inflatehd: error return %td\n", rv); inflater->ctx.bad = 1; return rv; @@ -2269,7 +2300,6 @@ void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) { int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t idx, nghttp2_nv *nv, int indexing_mode) { - return emit_indname_block(bufs, idx, nv, indexing_mode); } @@ -2282,9 +2312,10 @@ int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size) { return emit_table_size(bufs, table_size); } -ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, - uint32_t initial, size_t shift, uint8_t *in, - uint8_t *last, size_t prefix) { +nghttp2_ssize nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, + int *fin, uint32_t initial, size_t shift, + uint8_t *in, uint8_t *last, + size_t prefix) { return decode_length(res, shift_ptr, fin, initial, shift, in, last, prefix); } diff --git a/thirdparty/nghttp2/nghttp2_hd.h b/thirdparty/nghttp2/nghttp2_hd.h index 6cd75e1310..93bd48cff0 100644 --- a/thirdparty/nghttp2/nghttp2_hd.h +++ b/thirdparty/nghttp2/nghttp2_hd.h @@ -107,6 +107,7 @@ typedef enum { NGHTTP2_TOKEN_PROXY_CONNECTION, NGHTTP2_TOKEN_UPGRADE, NGHTTP2_TOKEN__PROTOCOL, + NGHTTP2_TOKEN_PRIORITY, } nghttp2_token; struct nghttp2_hd_entry; @@ -351,9 +352,10 @@ void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater); * that return values and semantics are the same as * nghttp2_hd_inflate_hd(). */ -ssize_t nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, - nghttp2_hd_nv *nv_out, int *inflate_flags, - const uint8_t *in, size_t inlen, int in_final); +nghttp2_ssize nghttp2_hd_inflate_hd_nv(nghttp2_hd_inflater *inflater, + nghttp2_hd_nv *nv_out, + int *inflate_flags, const uint8_t *in, + size_t inlen, int in_final); /* For unittesting purpose */ int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index, @@ -370,9 +372,10 @@ int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size); nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t index); /* For unittesting purpose */ -ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *fin, - uint32_t initial, size_t shift, uint8_t *in, - uint8_t *last, size_t prefix); +nghttp2_ssize nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, + int *fin, uint32_t initial, size_t shift, + uint8_t *in, uint8_t *last, + size_t prefix); /* Huffman encoding/decoding functions */ @@ -421,9 +424,9 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx); * NGHTTP2_ERR_HEADER_COMP * Decoding process has failed. */ -ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, - nghttp2_buf *buf, const uint8_t *src, - size_t srclen, int fin); +nghttp2_ssize nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buf *buf, const uint8_t *src, + size_t srclen, int fin); /* * nghttp2_hd_huff_decode_failure_state returns nonzero if |ctx| diff --git a/thirdparty/nghttp2/nghttp2_hd_huffman.c b/thirdparty/nghttp2/nghttp2_hd_huffman.c index c0ea3bc3e6..06db7f5df3 100644 --- a/thirdparty/nghttp2/nghttp2_hd_huffman.c +++ b/thirdparty/nghttp2/nghttp2_hd_huffman.c @@ -93,7 +93,7 @@ int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src, if (nbits) { rv = nghttp2_bufs_addb( - bufs, (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1))); + bufs, (uint8_t)((uint8_t)(code >> 56) | ((1 << (8 - nbits)) - 1))); if (rv != 0) { return rv; } @@ -106,16 +106,18 @@ void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) { ctx->fstate = NGHTTP2_HUFF_ACCEPTED; } -ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, - nghttp2_buf *buf, const uint8_t *src, - size_t srclen, int final) { +nghttp2_ssize nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, + nghttp2_buf *buf, const uint8_t *src, + size_t srclen, int final) { const uint8_t *end = src + srclen; nghttp2_huff_decode node = {ctx->fstate, 0}; const nghttp2_huff_decode *t = &node; uint8_t c; /* We use the decoding algorithm described in - http://graphics.ics.uci.edu/pub/Prefix.pdf */ + - http://graphics.ics.uci.edu/pub/Prefix.pdf [!!! NO LONGER VALID !!!] + - https://ics.uci.edu/~dan/pubs/Prefix.pdf + - https://github.com/nghttp2/nghttp2/files/15141264/Prefix.pdf */ for (; src != end;) { c = *src++; t = &huff_decode_table[t->fstate & 0x1ff][c >> 4]; @@ -135,7 +137,7 @@ ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx, return NGHTTP2_ERR_HEADER_COMP; } - return (ssize_t)srclen; + return (nghttp2_ssize)srclen; } int nghttp2_hd_huff_decode_failure_state(nghttp2_hd_huff_decode_context *ctx) { diff --git a/thirdparty/nghttp2/nghttp2_hd_huffman_data.c b/thirdparty/nghttp2/nghttp2_hd_huffman_data.c index 2e2e13f7be..c8f4a6fa26 100644 --- a/thirdparty/nghttp2/nghttp2_hd_huffman_data.c +++ b/thirdparty/nghttp2/nghttp2_hd_huffman_data.c @@ -27,4954 +27,4954 @@ /* Generated by mkhufftbl.py */ const nghttp2_huff_sym huff_sym_table[] = { - {13, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u}, - {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u}, - {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u}, - {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u}, - {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u}, - {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u}, - {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u}, - {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u}, - {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u}, - {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u}, - {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u}, - {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u}, - {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u}, - {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u}, - {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u}, - {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u}, - {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u}, - {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u}, - {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u}, - {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u}, - {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u}, - {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u}, - {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u}, - {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u}, - {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u}, - {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u}, - {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u}, - {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u}, - {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u}, - {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u}, - {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u}, - {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u}, - {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u}, - {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u}, - {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u}, - {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u}, - {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u}, - {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u}, - {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u}, - {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u}, - {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u}, - {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u}, - {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u}, - {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u}, - {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u}, - {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u}, - {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u}, - {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u}, - {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u}, - {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u}, - {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u}, - {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u}, - {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u}, - {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u}, - {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u}, - {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u}, - {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u}, - {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u}, - {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u}, - {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u}, - {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u}, - {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u}, - {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u}, - {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u}, - {30, 0xfffffffcu}}; + {13, 0xffc00000u}, {23, 0xffffb000u}, {28, 0xfffffe20u}, {28, 0xfffffe30u}, + {28, 0xfffffe40u}, {28, 0xfffffe50u}, {28, 0xfffffe60u}, {28, 0xfffffe70u}, + {28, 0xfffffe80u}, {24, 0xffffea00u}, {30, 0xfffffff0u}, {28, 0xfffffe90u}, + {28, 0xfffffea0u}, {30, 0xfffffff4u}, {28, 0xfffffeb0u}, {28, 0xfffffec0u}, + {28, 0xfffffed0u}, {28, 0xfffffee0u}, {28, 0xfffffef0u}, {28, 0xffffff00u}, + {28, 0xffffff10u}, {28, 0xffffff20u}, {30, 0xfffffff8u}, {28, 0xffffff30u}, + {28, 0xffffff40u}, {28, 0xffffff50u}, {28, 0xffffff60u}, {28, 0xffffff70u}, + {28, 0xffffff80u}, {28, 0xffffff90u}, {28, 0xffffffa0u}, {28, 0xffffffb0u}, + {6, 0x50000000u}, {10, 0xfe000000u}, {10, 0xfe400000u}, {12, 0xffa00000u}, + {13, 0xffc80000u}, {6, 0x54000000u}, {8, 0xf8000000u}, {11, 0xff400000u}, + {10, 0xfe800000u}, {10, 0xfec00000u}, {8, 0xf9000000u}, {11, 0xff600000u}, + {8, 0xfa000000u}, {6, 0x58000000u}, {6, 0x5c000000u}, {6, 0x60000000u}, + {5, 0x0u}, {5, 0x8000000u}, {5, 0x10000000u}, {6, 0x64000000u}, + {6, 0x68000000u}, {6, 0x6c000000u}, {6, 0x70000000u}, {6, 0x74000000u}, + {6, 0x78000000u}, {6, 0x7c000000u}, {7, 0xb8000000u}, {8, 0xfb000000u}, + {15, 0xfff80000u}, {6, 0x80000000u}, {12, 0xffb00000u}, {10, 0xff000000u}, + {13, 0xffd00000u}, {6, 0x84000000u}, {7, 0xba000000u}, {7, 0xbc000000u}, + {7, 0xbe000000u}, {7, 0xc0000000u}, {7, 0xc2000000u}, {7, 0xc4000000u}, + {7, 0xc6000000u}, {7, 0xc8000000u}, {7, 0xca000000u}, {7, 0xcc000000u}, + {7, 0xce000000u}, {7, 0xd0000000u}, {7, 0xd2000000u}, {7, 0xd4000000u}, + {7, 0xd6000000u}, {7, 0xd8000000u}, {7, 0xda000000u}, {7, 0xdc000000u}, + {7, 0xde000000u}, {7, 0xe0000000u}, {7, 0xe2000000u}, {7, 0xe4000000u}, + {8, 0xfc000000u}, {7, 0xe6000000u}, {8, 0xfd000000u}, {13, 0xffd80000u}, + {19, 0xfffe0000u}, {13, 0xffe00000u}, {14, 0xfff00000u}, {6, 0x88000000u}, + {15, 0xfffa0000u}, {5, 0x18000000u}, {6, 0x8c000000u}, {5, 0x20000000u}, + {6, 0x90000000u}, {5, 0x28000000u}, {6, 0x94000000u}, {6, 0x98000000u}, + {6, 0x9c000000u}, {5, 0x30000000u}, {7, 0xe8000000u}, {7, 0xea000000u}, + {6, 0xa0000000u}, {6, 0xa4000000u}, {6, 0xa8000000u}, {5, 0x38000000u}, + {6, 0xac000000u}, {7, 0xec000000u}, {6, 0xb0000000u}, {5, 0x40000000u}, + {5, 0x48000000u}, {6, 0xb4000000u}, {7, 0xee000000u}, {7, 0xf0000000u}, + {7, 0xf2000000u}, {7, 0xf4000000u}, {7, 0xf6000000u}, {15, 0xfffc0000u}, + {11, 0xff800000u}, {14, 0xfff40000u}, {13, 0xffe80000u}, {28, 0xffffffc0u}, + {20, 0xfffe6000u}, {22, 0xffff4800u}, {20, 0xfffe7000u}, {20, 0xfffe8000u}, + {22, 0xffff4c00u}, {22, 0xffff5000u}, {22, 0xffff5400u}, {23, 0xffffb200u}, + {22, 0xffff5800u}, {23, 0xffffb400u}, {23, 0xffffb600u}, {23, 0xffffb800u}, + {23, 0xffffba00u}, {23, 0xffffbc00u}, {24, 0xffffeb00u}, {23, 0xffffbe00u}, + {24, 0xffffec00u}, {24, 0xffffed00u}, {22, 0xffff5c00u}, {23, 0xffffc000u}, + {24, 0xffffee00u}, {23, 0xffffc200u}, {23, 0xffffc400u}, {23, 0xffffc600u}, + {23, 0xffffc800u}, {21, 0xfffee000u}, {22, 0xffff6000u}, {23, 0xffffca00u}, + {22, 0xffff6400u}, {23, 0xffffcc00u}, {23, 0xffffce00u}, {24, 0xffffef00u}, + {22, 0xffff6800u}, {21, 0xfffee800u}, {20, 0xfffe9000u}, {22, 0xffff6c00u}, + {22, 0xffff7000u}, {23, 0xffffd000u}, {23, 0xffffd200u}, {21, 0xfffef000u}, + {23, 0xffffd400u}, {22, 0xffff7400u}, {22, 0xffff7800u}, {24, 0xfffff000u}, + {21, 0xfffef800u}, {22, 0xffff7c00u}, {23, 0xffffd600u}, {23, 0xffffd800u}, + {21, 0xffff0000u}, {21, 0xffff0800u}, {22, 0xffff8000u}, {21, 0xffff1000u}, + {23, 0xffffda00u}, {22, 0xffff8400u}, {23, 0xffffdc00u}, {23, 0xffffde00u}, + {20, 0xfffea000u}, {22, 0xffff8800u}, {22, 0xffff8c00u}, {22, 0xffff9000u}, + {23, 0xffffe000u}, {22, 0xffff9400u}, {22, 0xffff9800u}, {23, 0xffffe200u}, + {26, 0xfffff800u}, {26, 0xfffff840u}, {20, 0xfffeb000u}, {19, 0xfffe2000u}, + {22, 0xffff9c00u}, {23, 0xffffe400u}, {22, 0xffffa000u}, {25, 0xfffff600u}, + {26, 0xfffff880u}, {26, 0xfffff8c0u}, {26, 0xfffff900u}, {27, 0xfffffbc0u}, + {27, 0xfffffbe0u}, {26, 0xfffff940u}, {24, 0xfffff100u}, {25, 0xfffff680u}, + {19, 0xfffe4000u}, {21, 0xffff1800u}, {26, 0xfffff980u}, {27, 0xfffffc00u}, + {27, 0xfffffc20u}, {26, 0xfffff9c0u}, {27, 0xfffffc40u}, {24, 0xfffff200u}, + {21, 0xffff2000u}, {21, 0xffff2800u}, {26, 0xfffffa00u}, {26, 0xfffffa40u}, + {28, 0xffffffd0u}, {27, 0xfffffc60u}, {27, 0xfffffc80u}, {27, 0xfffffca0u}, + {20, 0xfffec000u}, {24, 0xfffff300u}, {20, 0xfffed000u}, {21, 0xffff3000u}, + {22, 0xffffa400u}, {21, 0xffff3800u}, {21, 0xffff4000u}, {23, 0xffffe600u}, + {22, 0xffffa800u}, {22, 0xffffac00u}, {25, 0xfffff700u}, {25, 0xfffff780u}, + {24, 0xfffff400u}, {24, 0xfffff500u}, {26, 0xfffffa80u}, {23, 0xffffe800u}, + {26, 0xfffffac0u}, {27, 0xfffffcc0u}, {26, 0xfffffb00u}, {26, 0xfffffb40u}, + {27, 0xfffffce0u}, {27, 0xfffffd00u}, {27, 0xfffffd20u}, {27, 0xfffffd40u}, + {27, 0xfffffd60u}, {28, 0xffffffe0u}, {27, 0xfffffd80u}, {27, 0xfffffda0u}, + {27, 0xfffffdc0u}, {27, 0xfffffde0u}, {27, 0xfffffe00u}, {26, 0xfffffb80u}, + {30, 0xfffffffcu}}; const nghttp2_huff_decode huff_decode_table[][16] = { - /* 0 */ - { - {0x04, 0}, - {0x05, 0}, - {0x07, 0}, - {0x08, 0}, - {0x0b, 0}, - {0x0c, 0}, - {0x10, 0}, - {0x13, 0}, - {0x19, 0}, - {0x1c, 0}, - {0x20, 0}, - {0x23, 0}, - {0x2a, 0}, - {0x31, 0}, - {0x39, 0}, - {0x4040, 0}, - }, - /* 1 */ - { - {0xc000, 48}, - {0xc000, 49}, - {0xc000, 50}, - {0xc000, 97}, - {0xc000, 99}, - {0xc000, 101}, - {0xc000, 105}, - {0xc000, 111}, - {0xc000, 115}, - {0xc000, 116}, - {0x0d, 0}, - {0x0e, 0}, - {0x11, 0}, - {0x12, 0}, - {0x14, 0}, - {0x15, 0}, - }, - /* 2 */ - { - {0x8001, 48}, - {0xc016, 48}, - {0x8001, 49}, - {0xc016, 49}, - {0x8001, 50}, - {0xc016, 50}, - {0x8001, 97}, - {0xc016, 97}, - {0x8001, 99}, - {0xc016, 99}, - {0x8001, 101}, - {0xc016, 101}, - {0x8001, 105}, - {0xc016, 105}, - {0x8001, 111}, - {0xc016, 111}, - }, - /* 3 */ - { - {0x8002, 48}, - {0x8009, 48}, - {0x8017, 48}, - {0xc028, 48}, - {0x8002, 49}, - {0x8009, 49}, - {0x8017, 49}, - {0xc028, 49}, - {0x8002, 50}, - {0x8009, 50}, - {0x8017, 50}, - {0xc028, 50}, - {0x8002, 97}, - {0x8009, 97}, - {0x8017, 97}, - {0xc028, 97}, - }, - /* 4 */ - { - {0x8003, 48}, - {0x8006, 48}, - {0x800a, 48}, - {0x800f, 48}, - {0x8018, 48}, - {0x801f, 48}, - {0x8029, 48}, - {0xc038, 48}, - {0x8003, 49}, - {0x8006, 49}, - {0x800a, 49}, - {0x800f, 49}, - {0x8018, 49}, - {0x801f, 49}, - {0x8029, 49}, - {0xc038, 49}, - }, - /* 5 */ - { - {0x8003, 50}, - {0x8006, 50}, - {0x800a, 50}, - {0x800f, 50}, - {0x8018, 50}, - {0x801f, 50}, - {0x8029, 50}, - {0xc038, 50}, - {0x8003, 97}, - {0x8006, 97}, - {0x800a, 97}, - {0x800f, 97}, - {0x8018, 97}, - {0x801f, 97}, - {0x8029, 97}, - {0xc038, 97}, - }, - /* 6 */ - { - {0x8002, 99}, - {0x8009, 99}, - {0x8017, 99}, - {0xc028, 99}, - {0x8002, 101}, - {0x8009, 101}, - {0x8017, 101}, - {0xc028, 101}, - {0x8002, 105}, - {0x8009, 105}, - {0x8017, 105}, - {0xc028, 105}, - {0x8002, 111}, - {0x8009, 111}, - {0x8017, 111}, - {0xc028, 111}, - }, - /* 7 */ - { - {0x8003, 99}, - {0x8006, 99}, - {0x800a, 99}, - {0x800f, 99}, - {0x8018, 99}, - {0x801f, 99}, - {0x8029, 99}, - {0xc038, 99}, - {0x8003, 101}, - {0x8006, 101}, - {0x800a, 101}, - {0x800f, 101}, - {0x8018, 101}, - {0x801f, 101}, - {0x8029, 101}, - {0xc038, 101}, - }, - /* 8 */ - { - {0x8003, 105}, - {0x8006, 105}, - {0x800a, 105}, - {0x800f, 105}, - {0x8018, 105}, - {0x801f, 105}, - {0x8029, 105}, - {0xc038, 105}, - {0x8003, 111}, - {0x8006, 111}, - {0x800a, 111}, - {0x800f, 111}, - {0x8018, 111}, - {0x801f, 111}, - {0x8029, 111}, - {0xc038, 111}, - }, - /* 9 */ - { - {0x8001, 115}, - {0xc016, 115}, - {0x8001, 116}, - {0xc016, 116}, - {0xc000, 32}, - {0xc000, 37}, - {0xc000, 45}, - {0xc000, 46}, - {0xc000, 47}, - {0xc000, 51}, - {0xc000, 52}, - {0xc000, 53}, - {0xc000, 54}, - {0xc000, 55}, - {0xc000, 56}, - {0xc000, 57}, - }, - /* 10 */ - { - {0x8002, 115}, - {0x8009, 115}, - {0x8017, 115}, - {0xc028, 115}, - {0x8002, 116}, - {0x8009, 116}, - {0x8017, 116}, - {0xc028, 116}, - {0x8001, 32}, - {0xc016, 32}, - {0x8001, 37}, - {0xc016, 37}, - {0x8001, 45}, - {0xc016, 45}, - {0x8001, 46}, - {0xc016, 46}, - }, - /* 11 */ - { - {0x8003, 115}, - {0x8006, 115}, - {0x800a, 115}, - {0x800f, 115}, - {0x8018, 115}, - {0x801f, 115}, - {0x8029, 115}, - {0xc038, 115}, - {0x8003, 116}, - {0x8006, 116}, - {0x800a, 116}, - {0x800f, 116}, - {0x8018, 116}, - {0x801f, 116}, - {0x8029, 116}, - {0xc038, 116}, - }, - /* 12 */ - { - {0x8002, 32}, - {0x8009, 32}, - {0x8017, 32}, - {0xc028, 32}, - {0x8002, 37}, - {0x8009, 37}, - {0x8017, 37}, - {0xc028, 37}, - {0x8002, 45}, - {0x8009, 45}, - {0x8017, 45}, - {0xc028, 45}, - {0x8002, 46}, - {0x8009, 46}, - {0x8017, 46}, - {0xc028, 46}, - }, - /* 13 */ - { - {0x8003, 32}, - {0x8006, 32}, - {0x800a, 32}, - {0x800f, 32}, - {0x8018, 32}, - {0x801f, 32}, - {0x8029, 32}, - {0xc038, 32}, - {0x8003, 37}, - {0x8006, 37}, - {0x800a, 37}, - {0x800f, 37}, - {0x8018, 37}, - {0x801f, 37}, - {0x8029, 37}, - {0xc038, 37}, - }, - /* 14 */ - { - {0x8003, 45}, - {0x8006, 45}, - {0x800a, 45}, - {0x800f, 45}, - {0x8018, 45}, - {0x801f, 45}, - {0x8029, 45}, - {0xc038, 45}, - {0x8003, 46}, - {0x8006, 46}, - {0x800a, 46}, - {0x800f, 46}, - {0x8018, 46}, - {0x801f, 46}, - {0x8029, 46}, - {0xc038, 46}, - }, - /* 15 */ - { - {0x8001, 47}, - {0xc016, 47}, - {0x8001, 51}, - {0xc016, 51}, - {0x8001, 52}, - {0xc016, 52}, - {0x8001, 53}, - {0xc016, 53}, - {0x8001, 54}, - {0xc016, 54}, - {0x8001, 55}, - {0xc016, 55}, - {0x8001, 56}, - {0xc016, 56}, - {0x8001, 57}, - {0xc016, 57}, - }, - /* 16 */ - { - {0x8002, 47}, - {0x8009, 47}, - {0x8017, 47}, - {0xc028, 47}, - {0x8002, 51}, - {0x8009, 51}, - {0x8017, 51}, - {0xc028, 51}, - {0x8002, 52}, - {0x8009, 52}, - {0x8017, 52}, - {0xc028, 52}, - {0x8002, 53}, - {0x8009, 53}, - {0x8017, 53}, - {0xc028, 53}, - }, - /* 17 */ - { - {0x8003, 47}, - {0x8006, 47}, - {0x800a, 47}, - {0x800f, 47}, - {0x8018, 47}, - {0x801f, 47}, - {0x8029, 47}, - {0xc038, 47}, - {0x8003, 51}, - {0x8006, 51}, - {0x800a, 51}, - {0x800f, 51}, - {0x8018, 51}, - {0x801f, 51}, - {0x8029, 51}, - {0xc038, 51}, - }, - /* 18 */ - { - {0x8003, 52}, - {0x8006, 52}, - {0x800a, 52}, - {0x800f, 52}, - {0x8018, 52}, - {0x801f, 52}, - {0x8029, 52}, - {0xc038, 52}, - {0x8003, 53}, - {0x8006, 53}, - {0x800a, 53}, - {0x800f, 53}, - {0x8018, 53}, - {0x801f, 53}, - {0x8029, 53}, - {0xc038, 53}, - }, - /* 19 */ - { - {0x8002, 54}, - {0x8009, 54}, - {0x8017, 54}, - {0xc028, 54}, - {0x8002, 55}, - {0x8009, 55}, - {0x8017, 55}, - {0xc028, 55}, - {0x8002, 56}, - {0x8009, 56}, - {0x8017, 56}, - {0xc028, 56}, - {0x8002, 57}, - {0x8009, 57}, - {0x8017, 57}, - {0xc028, 57}, - }, - /* 20 */ - { - {0x8003, 54}, - {0x8006, 54}, - {0x800a, 54}, - {0x800f, 54}, - {0x8018, 54}, - {0x801f, 54}, - {0x8029, 54}, - {0xc038, 54}, - {0x8003, 55}, - {0x8006, 55}, - {0x800a, 55}, - {0x800f, 55}, - {0x8018, 55}, - {0x801f, 55}, - {0x8029, 55}, - {0xc038, 55}, - }, - /* 21 */ - { - {0x8003, 56}, - {0x8006, 56}, - {0x800a, 56}, - {0x800f, 56}, - {0x8018, 56}, - {0x801f, 56}, - {0x8029, 56}, - {0xc038, 56}, - {0x8003, 57}, - {0x8006, 57}, - {0x800a, 57}, - {0x800f, 57}, - {0x8018, 57}, - {0x801f, 57}, - {0x8029, 57}, - {0xc038, 57}, - }, - /* 22 */ - { - {0x1a, 0}, - {0x1b, 0}, - {0x1d, 0}, - {0x1e, 0}, - {0x21, 0}, - {0x22, 0}, - {0x24, 0}, - {0x25, 0}, - {0x2b, 0}, - {0x2e, 0}, - {0x32, 0}, - {0x35, 0}, - {0x3a, 0}, - {0x3d, 0}, - {0x41, 0}, - {0x4044, 0}, - }, - /* 23 */ - { - {0xc000, 61}, - {0xc000, 65}, - {0xc000, 95}, - {0xc000, 98}, - {0xc000, 100}, - {0xc000, 102}, - {0xc000, 103}, - {0xc000, 104}, - {0xc000, 108}, - {0xc000, 109}, - {0xc000, 110}, - {0xc000, 112}, - {0xc000, 114}, - {0xc000, 117}, - {0x26, 0}, - {0x27, 0}, - }, - /* 24 */ - { - {0x8001, 61}, - {0xc016, 61}, - {0x8001, 65}, - {0xc016, 65}, - {0x8001, 95}, - {0xc016, 95}, - {0x8001, 98}, - {0xc016, 98}, - {0x8001, 100}, - {0xc016, 100}, - {0x8001, 102}, - {0xc016, 102}, - {0x8001, 103}, - {0xc016, 103}, - {0x8001, 104}, - {0xc016, 104}, - }, - /* 25 */ - { - {0x8002, 61}, - {0x8009, 61}, - {0x8017, 61}, - {0xc028, 61}, - {0x8002, 65}, - {0x8009, 65}, - {0x8017, 65}, - {0xc028, 65}, - {0x8002, 95}, - {0x8009, 95}, - {0x8017, 95}, - {0xc028, 95}, - {0x8002, 98}, - {0x8009, 98}, - {0x8017, 98}, - {0xc028, 98}, - }, - /* 26 */ - { - {0x8003, 61}, - {0x8006, 61}, - {0x800a, 61}, - {0x800f, 61}, - {0x8018, 61}, - {0x801f, 61}, - {0x8029, 61}, - {0xc038, 61}, - {0x8003, 65}, - {0x8006, 65}, - {0x800a, 65}, - {0x800f, 65}, - {0x8018, 65}, - {0x801f, 65}, - {0x8029, 65}, - {0xc038, 65}, - }, - /* 27 */ - { - {0x8003, 95}, - {0x8006, 95}, - {0x800a, 95}, - {0x800f, 95}, - {0x8018, 95}, - {0x801f, 95}, - {0x8029, 95}, - {0xc038, 95}, - {0x8003, 98}, - {0x8006, 98}, - {0x800a, 98}, - {0x800f, 98}, - {0x8018, 98}, - {0x801f, 98}, - {0x8029, 98}, - {0xc038, 98}, - }, - /* 28 */ - { - {0x8002, 100}, - {0x8009, 100}, - {0x8017, 100}, - {0xc028, 100}, - {0x8002, 102}, - {0x8009, 102}, - {0x8017, 102}, - {0xc028, 102}, - {0x8002, 103}, - {0x8009, 103}, - {0x8017, 103}, - {0xc028, 103}, - {0x8002, 104}, - {0x8009, 104}, - {0x8017, 104}, - {0xc028, 104}, - }, - /* 29 */ - { - {0x8003, 100}, - {0x8006, 100}, - {0x800a, 100}, - {0x800f, 100}, - {0x8018, 100}, - {0x801f, 100}, - {0x8029, 100}, - {0xc038, 100}, - {0x8003, 102}, - {0x8006, 102}, - {0x800a, 102}, - {0x800f, 102}, - {0x8018, 102}, - {0x801f, 102}, - {0x8029, 102}, - {0xc038, 102}, - }, - /* 30 */ - { - {0x8003, 103}, - {0x8006, 103}, - {0x800a, 103}, - {0x800f, 103}, - {0x8018, 103}, - {0x801f, 103}, - {0x8029, 103}, - {0xc038, 103}, - {0x8003, 104}, - {0x8006, 104}, - {0x800a, 104}, - {0x800f, 104}, - {0x8018, 104}, - {0x801f, 104}, - {0x8029, 104}, - {0xc038, 104}, - }, - /* 31 */ - { - {0x8001, 108}, - {0xc016, 108}, - {0x8001, 109}, - {0xc016, 109}, - {0x8001, 110}, - {0xc016, 110}, - {0x8001, 112}, - {0xc016, 112}, - {0x8001, 114}, - {0xc016, 114}, - {0x8001, 117}, - {0xc016, 117}, - {0xc000, 58}, - {0xc000, 66}, - {0xc000, 67}, - {0xc000, 68}, - }, - /* 32 */ - { - {0x8002, 108}, - {0x8009, 108}, - {0x8017, 108}, - {0xc028, 108}, - {0x8002, 109}, - {0x8009, 109}, - {0x8017, 109}, - {0xc028, 109}, - {0x8002, 110}, - {0x8009, 110}, - {0x8017, 110}, - {0xc028, 110}, - {0x8002, 112}, - {0x8009, 112}, - {0x8017, 112}, - {0xc028, 112}, - }, - /* 33 */ - { - {0x8003, 108}, - {0x8006, 108}, - {0x800a, 108}, - {0x800f, 108}, - {0x8018, 108}, - {0x801f, 108}, - {0x8029, 108}, - {0xc038, 108}, - {0x8003, 109}, - {0x8006, 109}, - {0x800a, 109}, - {0x800f, 109}, - {0x8018, 109}, - {0x801f, 109}, - {0x8029, 109}, - {0xc038, 109}, - }, - /* 34 */ - { - {0x8003, 110}, - {0x8006, 110}, - {0x800a, 110}, - {0x800f, 110}, - {0x8018, 110}, - {0x801f, 110}, - {0x8029, 110}, - {0xc038, 110}, - {0x8003, 112}, - {0x8006, 112}, - {0x800a, 112}, - {0x800f, 112}, - {0x8018, 112}, - {0x801f, 112}, - {0x8029, 112}, - {0xc038, 112}, - }, - /* 35 */ - { - {0x8002, 114}, - {0x8009, 114}, - {0x8017, 114}, - {0xc028, 114}, - {0x8002, 117}, - {0x8009, 117}, - {0x8017, 117}, - {0xc028, 117}, - {0x8001, 58}, - {0xc016, 58}, - {0x8001, 66}, - {0xc016, 66}, - {0x8001, 67}, - {0xc016, 67}, - {0x8001, 68}, - {0xc016, 68}, - }, - /* 36 */ - { - {0x8003, 114}, - {0x8006, 114}, - {0x800a, 114}, - {0x800f, 114}, - {0x8018, 114}, - {0x801f, 114}, - {0x8029, 114}, - {0xc038, 114}, - {0x8003, 117}, - {0x8006, 117}, - {0x800a, 117}, - {0x800f, 117}, - {0x8018, 117}, - {0x801f, 117}, - {0x8029, 117}, - {0xc038, 117}, - }, - /* 37 */ - { - {0x8002, 58}, - {0x8009, 58}, - {0x8017, 58}, - {0xc028, 58}, - {0x8002, 66}, - {0x8009, 66}, - {0x8017, 66}, - {0xc028, 66}, - {0x8002, 67}, - {0x8009, 67}, - {0x8017, 67}, - {0xc028, 67}, - {0x8002, 68}, - {0x8009, 68}, - {0x8017, 68}, - {0xc028, 68}, - }, - /* 38 */ - { - {0x8003, 58}, - {0x8006, 58}, - {0x800a, 58}, - {0x800f, 58}, - {0x8018, 58}, - {0x801f, 58}, - {0x8029, 58}, - {0xc038, 58}, - {0x8003, 66}, - {0x8006, 66}, - {0x800a, 66}, - {0x800f, 66}, - {0x8018, 66}, - {0x801f, 66}, - {0x8029, 66}, - {0xc038, 66}, - }, - /* 39 */ - { - {0x8003, 67}, - {0x8006, 67}, - {0x800a, 67}, - {0x800f, 67}, - {0x8018, 67}, - {0x801f, 67}, - {0x8029, 67}, - {0xc038, 67}, - {0x8003, 68}, - {0x8006, 68}, - {0x800a, 68}, - {0x800f, 68}, - {0x8018, 68}, - {0x801f, 68}, - {0x8029, 68}, - {0xc038, 68}, - }, - /* 40 */ - { - {0x2c, 0}, - {0x2d, 0}, - {0x2f, 0}, - {0x30, 0}, - {0x33, 0}, - {0x34, 0}, - {0x36, 0}, - {0x37, 0}, - {0x3b, 0}, - {0x3c, 0}, - {0x3e, 0}, - {0x3f, 0}, - {0x42, 0}, - {0x43, 0}, - {0x45, 0}, - {0x4048, 0}, - }, - /* 41 */ - { - {0xc000, 69}, - {0xc000, 70}, - {0xc000, 71}, - {0xc000, 72}, - {0xc000, 73}, - {0xc000, 74}, - {0xc000, 75}, - {0xc000, 76}, - {0xc000, 77}, - {0xc000, 78}, - {0xc000, 79}, - {0xc000, 80}, - {0xc000, 81}, - {0xc000, 82}, - {0xc000, 83}, - {0xc000, 84}, - }, - /* 42 */ - { - {0x8001, 69}, - {0xc016, 69}, - {0x8001, 70}, - {0xc016, 70}, - {0x8001, 71}, - {0xc016, 71}, - {0x8001, 72}, - {0xc016, 72}, - {0x8001, 73}, - {0xc016, 73}, - {0x8001, 74}, - {0xc016, 74}, - {0x8001, 75}, - {0xc016, 75}, - {0x8001, 76}, - {0xc016, 76}, - }, - /* 43 */ - { - {0x8002, 69}, - {0x8009, 69}, - {0x8017, 69}, - {0xc028, 69}, - {0x8002, 70}, - {0x8009, 70}, - {0x8017, 70}, - {0xc028, 70}, - {0x8002, 71}, - {0x8009, 71}, - {0x8017, 71}, - {0xc028, 71}, - {0x8002, 72}, - {0x8009, 72}, - {0x8017, 72}, - {0xc028, 72}, - }, - /* 44 */ - { - {0x8003, 69}, - {0x8006, 69}, - {0x800a, 69}, - {0x800f, 69}, - {0x8018, 69}, - {0x801f, 69}, - {0x8029, 69}, - {0xc038, 69}, - {0x8003, 70}, - {0x8006, 70}, - {0x800a, 70}, - {0x800f, 70}, - {0x8018, 70}, - {0x801f, 70}, - {0x8029, 70}, - {0xc038, 70}, - }, - /* 45 */ - { - {0x8003, 71}, - {0x8006, 71}, - {0x800a, 71}, - {0x800f, 71}, - {0x8018, 71}, - {0x801f, 71}, - {0x8029, 71}, - {0xc038, 71}, - {0x8003, 72}, - {0x8006, 72}, - {0x800a, 72}, - {0x800f, 72}, - {0x8018, 72}, - {0x801f, 72}, - {0x8029, 72}, - {0xc038, 72}, - }, - /* 46 */ - { - {0x8002, 73}, - {0x8009, 73}, - {0x8017, 73}, - {0xc028, 73}, - {0x8002, 74}, - {0x8009, 74}, - {0x8017, 74}, - {0xc028, 74}, - {0x8002, 75}, - {0x8009, 75}, - {0x8017, 75}, - {0xc028, 75}, - {0x8002, 76}, - {0x8009, 76}, - {0x8017, 76}, - {0xc028, 76}, - }, - /* 47 */ - { - {0x8003, 73}, - {0x8006, 73}, - {0x800a, 73}, - {0x800f, 73}, - {0x8018, 73}, - {0x801f, 73}, - {0x8029, 73}, - {0xc038, 73}, - {0x8003, 74}, - {0x8006, 74}, - {0x800a, 74}, - {0x800f, 74}, - {0x8018, 74}, - {0x801f, 74}, - {0x8029, 74}, - {0xc038, 74}, - }, - /* 48 */ - { - {0x8003, 75}, - {0x8006, 75}, - {0x800a, 75}, - {0x800f, 75}, - {0x8018, 75}, - {0x801f, 75}, - {0x8029, 75}, - {0xc038, 75}, - {0x8003, 76}, - {0x8006, 76}, - {0x800a, 76}, - {0x800f, 76}, - {0x8018, 76}, - {0x801f, 76}, - {0x8029, 76}, - {0xc038, 76}, - }, - /* 49 */ - { - {0x8001, 77}, - {0xc016, 77}, - {0x8001, 78}, - {0xc016, 78}, - {0x8001, 79}, - {0xc016, 79}, - {0x8001, 80}, - {0xc016, 80}, - {0x8001, 81}, - {0xc016, 81}, - {0x8001, 82}, - {0xc016, 82}, - {0x8001, 83}, - {0xc016, 83}, - {0x8001, 84}, - {0xc016, 84}, - }, - /* 50 */ - { - {0x8002, 77}, - {0x8009, 77}, - {0x8017, 77}, - {0xc028, 77}, - {0x8002, 78}, - {0x8009, 78}, - {0x8017, 78}, - {0xc028, 78}, - {0x8002, 79}, - {0x8009, 79}, - {0x8017, 79}, - {0xc028, 79}, - {0x8002, 80}, - {0x8009, 80}, - {0x8017, 80}, - {0xc028, 80}, - }, - /* 51 */ - { - {0x8003, 77}, - {0x8006, 77}, - {0x800a, 77}, - {0x800f, 77}, - {0x8018, 77}, - {0x801f, 77}, - {0x8029, 77}, - {0xc038, 77}, - {0x8003, 78}, - {0x8006, 78}, - {0x800a, 78}, - {0x800f, 78}, - {0x8018, 78}, - {0x801f, 78}, - {0x8029, 78}, - {0xc038, 78}, - }, - /* 52 */ - { - {0x8003, 79}, - {0x8006, 79}, - {0x800a, 79}, - {0x800f, 79}, - {0x8018, 79}, - {0x801f, 79}, - {0x8029, 79}, - {0xc038, 79}, - {0x8003, 80}, - {0x8006, 80}, - {0x800a, 80}, - {0x800f, 80}, - {0x8018, 80}, - {0x801f, 80}, - {0x8029, 80}, - {0xc038, 80}, - }, - /* 53 */ - { - {0x8002, 81}, - {0x8009, 81}, - {0x8017, 81}, - {0xc028, 81}, - {0x8002, 82}, - {0x8009, 82}, - {0x8017, 82}, - {0xc028, 82}, - {0x8002, 83}, - {0x8009, 83}, - {0x8017, 83}, - {0xc028, 83}, - {0x8002, 84}, - {0x8009, 84}, - {0x8017, 84}, - {0xc028, 84}, - }, - /* 54 */ - { - {0x8003, 81}, - {0x8006, 81}, - {0x800a, 81}, - {0x800f, 81}, - {0x8018, 81}, - {0x801f, 81}, - {0x8029, 81}, - {0xc038, 81}, - {0x8003, 82}, - {0x8006, 82}, - {0x800a, 82}, - {0x800f, 82}, - {0x8018, 82}, - {0x801f, 82}, - {0x8029, 82}, - {0xc038, 82}, - }, - /* 55 */ - { - {0x8003, 83}, - {0x8006, 83}, - {0x800a, 83}, - {0x800f, 83}, - {0x8018, 83}, - {0x801f, 83}, - {0x8029, 83}, - {0xc038, 83}, - {0x8003, 84}, - {0x8006, 84}, - {0x800a, 84}, - {0x800f, 84}, - {0x8018, 84}, - {0x801f, 84}, - {0x8029, 84}, - {0xc038, 84}, - }, - /* 56 */ - { - {0xc000, 85}, - {0xc000, 86}, - {0xc000, 87}, - {0xc000, 89}, - {0xc000, 106}, - {0xc000, 107}, - {0xc000, 113}, - {0xc000, 118}, - {0xc000, 119}, - {0xc000, 120}, - {0xc000, 121}, - {0xc000, 122}, - {0x46, 0}, - {0x47, 0}, - {0x49, 0}, - {0x404a, 0}, - }, - /* 57 */ - { - {0x8001, 85}, - {0xc016, 85}, - {0x8001, 86}, - {0xc016, 86}, - {0x8001, 87}, - {0xc016, 87}, - {0x8001, 89}, - {0xc016, 89}, - {0x8001, 106}, - {0xc016, 106}, - {0x8001, 107}, - {0xc016, 107}, - {0x8001, 113}, - {0xc016, 113}, - {0x8001, 118}, - {0xc016, 118}, - }, - /* 58 */ - { - {0x8002, 85}, - {0x8009, 85}, - {0x8017, 85}, - {0xc028, 85}, - {0x8002, 86}, - {0x8009, 86}, - {0x8017, 86}, - {0xc028, 86}, - {0x8002, 87}, - {0x8009, 87}, - {0x8017, 87}, - {0xc028, 87}, - {0x8002, 89}, - {0x8009, 89}, - {0x8017, 89}, - {0xc028, 89}, - }, - /* 59 */ - { - {0x8003, 85}, - {0x8006, 85}, - {0x800a, 85}, - {0x800f, 85}, - {0x8018, 85}, - {0x801f, 85}, - {0x8029, 85}, - {0xc038, 85}, - {0x8003, 86}, - {0x8006, 86}, - {0x800a, 86}, - {0x800f, 86}, - {0x8018, 86}, - {0x801f, 86}, - {0x8029, 86}, - {0xc038, 86}, - }, - /* 60 */ - { - {0x8003, 87}, - {0x8006, 87}, - {0x800a, 87}, - {0x800f, 87}, - {0x8018, 87}, - {0x801f, 87}, - {0x8029, 87}, - {0xc038, 87}, - {0x8003, 89}, - {0x8006, 89}, - {0x800a, 89}, - {0x800f, 89}, - {0x8018, 89}, - {0x801f, 89}, - {0x8029, 89}, - {0xc038, 89}, - }, - /* 61 */ - { - {0x8002, 106}, - {0x8009, 106}, - {0x8017, 106}, - {0xc028, 106}, - {0x8002, 107}, - {0x8009, 107}, - {0x8017, 107}, - {0xc028, 107}, - {0x8002, 113}, - {0x8009, 113}, - {0x8017, 113}, - {0xc028, 113}, - {0x8002, 118}, - {0x8009, 118}, - {0x8017, 118}, - {0xc028, 118}, - }, - /* 62 */ - { - {0x8003, 106}, - {0x8006, 106}, - {0x800a, 106}, - {0x800f, 106}, - {0x8018, 106}, - {0x801f, 106}, - {0x8029, 106}, - {0xc038, 106}, - {0x8003, 107}, - {0x8006, 107}, - {0x800a, 107}, - {0x800f, 107}, - {0x8018, 107}, - {0x801f, 107}, - {0x8029, 107}, - {0xc038, 107}, - }, - /* 63 */ - { - {0x8003, 113}, - {0x8006, 113}, - {0x800a, 113}, - {0x800f, 113}, - {0x8018, 113}, - {0x801f, 113}, - {0x8029, 113}, - {0xc038, 113}, - {0x8003, 118}, - {0x8006, 118}, - {0x800a, 118}, - {0x800f, 118}, - {0x8018, 118}, - {0x801f, 118}, - {0x8029, 118}, - {0xc038, 118}, - }, - /* 64 */ - { - {0x8001, 119}, - {0xc016, 119}, - {0x8001, 120}, - {0xc016, 120}, - {0x8001, 121}, - {0xc016, 121}, - {0x8001, 122}, - {0xc016, 122}, - {0xc000, 38}, - {0xc000, 42}, - {0xc000, 44}, - {0xc000, 59}, - {0xc000, 88}, - {0xc000, 90}, - {0x4b, 0}, - {0x4e, 0}, - }, - /* 65 */ - { - {0x8002, 119}, - {0x8009, 119}, - {0x8017, 119}, - {0xc028, 119}, - {0x8002, 120}, - {0x8009, 120}, - {0x8017, 120}, - {0xc028, 120}, - {0x8002, 121}, - {0x8009, 121}, - {0x8017, 121}, - {0xc028, 121}, - {0x8002, 122}, - {0x8009, 122}, - {0x8017, 122}, - {0xc028, 122}, - }, - /* 66 */ - { - {0x8003, 119}, - {0x8006, 119}, - {0x800a, 119}, - {0x800f, 119}, - {0x8018, 119}, - {0x801f, 119}, - {0x8029, 119}, - {0xc038, 119}, - {0x8003, 120}, - {0x8006, 120}, - {0x800a, 120}, - {0x800f, 120}, - {0x8018, 120}, - {0x801f, 120}, - {0x8029, 120}, - {0xc038, 120}, - }, - /* 67 */ - { - {0x8003, 121}, - {0x8006, 121}, - {0x800a, 121}, - {0x800f, 121}, - {0x8018, 121}, - {0x801f, 121}, - {0x8029, 121}, - {0xc038, 121}, - {0x8003, 122}, - {0x8006, 122}, - {0x800a, 122}, - {0x800f, 122}, - {0x8018, 122}, - {0x801f, 122}, - {0x8029, 122}, - {0xc038, 122}, - }, - /* 68 */ - { - {0x8001, 38}, - {0xc016, 38}, - {0x8001, 42}, - {0xc016, 42}, - {0x8001, 44}, - {0xc016, 44}, - {0x8001, 59}, - {0xc016, 59}, - {0x8001, 88}, - {0xc016, 88}, - {0x8001, 90}, - {0xc016, 90}, - {0x4c, 0}, - {0x4d, 0}, - {0x4f, 0}, - {0x51, 0}, - }, - /* 69 */ - { - {0x8002, 38}, - {0x8009, 38}, - {0x8017, 38}, - {0xc028, 38}, - {0x8002, 42}, - {0x8009, 42}, - {0x8017, 42}, - {0xc028, 42}, - {0x8002, 44}, - {0x8009, 44}, - {0x8017, 44}, - {0xc028, 44}, - {0x8002, 59}, - {0x8009, 59}, - {0x8017, 59}, - {0xc028, 59}, - }, - /* 70 */ - { - {0x8003, 38}, - {0x8006, 38}, - {0x800a, 38}, - {0x800f, 38}, - {0x8018, 38}, - {0x801f, 38}, - {0x8029, 38}, - {0xc038, 38}, - {0x8003, 42}, - {0x8006, 42}, - {0x800a, 42}, - {0x800f, 42}, - {0x8018, 42}, - {0x801f, 42}, - {0x8029, 42}, - {0xc038, 42}, - }, - /* 71 */ - { - {0x8003, 44}, - {0x8006, 44}, - {0x800a, 44}, - {0x800f, 44}, - {0x8018, 44}, - {0x801f, 44}, - {0x8029, 44}, - {0xc038, 44}, - {0x8003, 59}, - {0x8006, 59}, - {0x800a, 59}, - {0x800f, 59}, - {0x8018, 59}, - {0x801f, 59}, - {0x8029, 59}, - {0xc038, 59}, - }, - /* 72 */ - { - {0x8002, 88}, - {0x8009, 88}, - {0x8017, 88}, - {0xc028, 88}, - {0x8002, 90}, - {0x8009, 90}, - {0x8017, 90}, - {0xc028, 90}, - {0xc000, 33}, - {0xc000, 34}, - {0xc000, 40}, - {0xc000, 41}, - {0xc000, 63}, - {0x50, 0}, - {0x52, 0}, - {0x54, 0}, - }, - /* 73 */ - { - {0x8003, 88}, - {0x8006, 88}, - {0x800a, 88}, - {0x800f, 88}, - {0x8018, 88}, - {0x801f, 88}, - {0x8029, 88}, - {0xc038, 88}, - {0x8003, 90}, - {0x8006, 90}, - {0x800a, 90}, - {0x800f, 90}, - {0x8018, 90}, - {0x801f, 90}, - {0x8029, 90}, - {0xc038, 90}, - }, - /* 74 */ - { - {0x8001, 33}, - {0xc016, 33}, - {0x8001, 34}, - {0xc016, 34}, - {0x8001, 40}, - {0xc016, 40}, - {0x8001, 41}, - {0xc016, 41}, - {0x8001, 63}, - {0xc016, 63}, - {0xc000, 39}, - {0xc000, 43}, - {0xc000, 124}, - {0x53, 0}, - {0x55, 0}, - {0x58, 0}, - }, - /* 75 */ - { - {0x8002, 33}, - {0x8009, 33}, - {0x8017, 33}, - {0xc028, 33}, - {0x8002, 34}, - {0x8009, 34}, - {0x8017, 34}, - {0xc028, 34}, - {0x8002, 40}, - {0x8009, 40}, - {0x8017, 40}, - {0xc028, 40}, - {0x8002, 41}, - {0x8009, 41}, - {0x8017, 41}, - {0xc028, 41}, - }, - /* 76 */ - { - {0x8003, 33}, - {0x8006, 33}, - {0x800a, 33}, - {0x800f, 33}, - {0x8018, 33}, - {0x801f, 33}, - {0x8029, 33}, - {0xc038, 33}, - {0x8003, 34}, - {0x8006, 34}, - {0x800a, 34}, - {0x800f, 34}, - {0x8018, 34}, - {0x801f, 34}, - {0x8029, 34}, - {0xc038, 34}, - }, - /* 77 */ - { - {0x8003, 40}, - {0x8006, 40}, - {0x800a, 40}, - {0x800f, 40}, - {0x8018, 40}, - {0x801f, 40}, - {0x8029, 40}, - {0xc038, 40}, - {0x8003, 41}, - {0x8006, 41}, - {0x800a, 41}, - {0x800f, 41}, - {0x8018, 41}, - {0x801f, 41}, - {0x8029, 41}, - {0xc038, 41}, - }, - /* 78 */ - { - {0x8002, 63}, - {0x8009, 63}, - {0x8017, 63}, - {0xc028, 63}, - {0x8001, 39}, - {0xc016, 39}, - {0x8001, 43}, - {0xc016, 43}, - {0x8001, 124}, - {0xc016, 124}, - {0xc000, 35}, - {0xc000, 62}, - {0x56, 0}, - {0x57, 0}, - {0x59, 0}, - {0x5a, 0}, - }, - /* 79 */ - { - {0x8003, 63}, - {0x8006, 63}, - {0x800a, 63}, - {0x800f, 63}, - {0x8018, 63}, - {0x801f, 63}, - {0x8029, 63}, - {0xc038, 63}, - {0x8002, 39}, - {0x8009, 39}, - {0x8017, 39}, - {0xc028, 39}, - {0x8002, 43}, - {0x8009, 43}, - {0x8017, 43}, - {0xc028, 43}, - }, - /* 80 */ - { - {0x8003, 39}, - {0x8006, 39}, - {0x800a, 39}, - {0x800f, 39}, - {0x8018, 39}, - {0x801f, 39}, - {0x8029, 39}, - {0xc038, 39}, - {0x8003, 43}, - {0x8006, 43}, - {0x800a, 43}, - {0x800f, 43}, - {0x8018, 43}, - {0x801f, 43}, - {0x8029, 43}, - {0xc038, 43}, - }, - /* 81 */ - { - {0x8002, 124}, - {0x8009, 124}, - {0x8017, 124}, - {0xc028, 124}, - {0x8001, 35}, - {0xc016, 35}, - {0x8001, 62}, - {0xc016, 62}, - {0xc000, 0}, - {0xc000, 36}, - {0xc000, 64}, - {0xc000, 91}, - {0xc000, 93}, - {0xc000, 126}, - {0x5b, 0}, - {0x5c, 0}, - }, - /* 82 */ - { - {0x8003, 124}, - {0x8006, 124}, - {0x800a, 124}, - {0x800f, 124}, - {0x8018, 124}, - {0x801f, 124}, - {0x8029, 124}, - {0xc038, 124}, - {0x8002, 35}, - {0x8009, 35}, - {0x8017, 35}, - {0xc028, 35}, - {0x8002, 62}, - {0x8009, 62}, - {0x8017, 62}, - {0xc028, 62}, - }, - /* 83 */ - { - {0x8003, 35}, - {0x8006, 35}, - {0x800a, 35}, - {0x800f, 35}, - {0x8018, 35}, - {0x801f, 35}, - {0x8029, 35}, - {0xc038, 35}, - {0x8003, 62}, - {0x8006, 62}, - {0x800a, 62}, - {0x800f, 62}, - {0x8018, 62}, - {0x801f, 62}, - {0x8029, 62}, - {0xc038, 62}, - }, - /* 84 */ - { - {0x8001, 0}, - {0xc016, 0}, - {0x8001, 36}, - {0xc016, 36}, - {0x8001, 64}, - {0xc016, 64}, - {0x8001, 91}, - {0xc016, 91}, - {0x8001, 93}, - {0xc016, 93}, - {0x8001, 126}, - {0xc016, 126}, - {0xc000, 94}, - {0xc000, 125}, - {0x5d, 0}, - {0x5e, 0}, - }, - /* 85 */ - { - {0x8002, 0}, - {0x8009, 0}, - {0x8017, 0}, - {0xc028, 0}, - {0x8002, 36}, - {0x8009, 36}, - {0x8017, 36}, - {0xc028, 36}, - {0x8002, 64}, - {0x8009, 64}, - {0x8017, 64}, - {0xc028, 64}, - {0x8002, 91}, - {0x8009, 91}, - {0x8017, 91}, - {0xc028, 91}, - }, - /* 86 */ - { - {0x8003, 0}, - {0x8006, 0}, - {0x800a, 0}, - {0x800f, 0}, - {0x8018, 0}, - {0x801f, 0}, - {0x8029, 0}, - {0xc038, 0}, - {0x8003, 36}, - {0x8006, 36}, - {0x800a, 36}, - {0x800f, 36}, - {0x8018, 36}, - {0x801f, 36}, - {0x8029, 36}, - {0xc038, 36}, - }, - /* 87 */ - { - {0x8003, 64}, - {0x8006, 64}, - {0x800a, 64}, - {0x800f, 64}, - {0x8018, 64}, - {0x801f, 64}, - {0x8029, 64}, - {0xc038, 64}, - {0x8003, 91}, - {0x8006, 91}, - {0x800a, 91}, - {0x800f, 91}, - {0x8018, 91}, - {0x801f, 91}, - {0x8029, 91}, - {0xc038, 91}, - }, - /* 88 */ - { - {0x8002, 93}, - {0x8009, 93}, - {0x8017, 93}, - {0xc028, 93}, - {0x8002, 126}, - {0x8009, 126}, - {0x8017, 126}, - {0xc028, 126}, - {0x8001, 94}, - {0xc016, 94}, - {0x8001, 125}, - {0xc016, 125}, - {0xc000, 60}, - {0xc000, 96}, - {0xc000, 123}, - {0x5f, 0}, - }, - /* 89 */ - { - {0x8003, 93}, - {0x8006, 93}, - {0x800a, 93}, - {0x800f, 93}, - {0x8018, 93}, - {0x801f, 93}, - {0x8029, 93}, - {0xc038, 93}, - {0x8003, 126}, - {0x8006, 126}, - {0x800a, 126}, - {0x800f, 126}, - {0x8018, 126}, - {0x801f, 126}, - {0x8029, 126}, - {0xc038, 126}, - }, - /* 90 */ - { - {0x8002, 94}, - {0x8009, 94}, - {0x8017, 94}, - {0xc028, 94}, - {0x8002, 125}, - {0x8009, 125}, - {0x8017, 125}, - {0xc028, 125}, - {0x8001, 60}, - {0xc016, 60}, - {0x8001, 96}, - {0xc016, 96}, - {0x8001, 123}, - {0xc016, 123}, - {0x60, 0}, - {0x6e, 0}, - }, - /* 91 */ - { - {0x8003, 94}, - {0x8006, 94}, - {0x800a, 94}, - {0x800f, 94}, - {0x8018, 94}, - {0x801f, 94}, - {0x8029, 94}, - {0xc038, 94}, - {0x8003, 125}, - {0x8006, 125}, - {0x800a, 125}, - {0x800f, 125}, - {0x8018, 125}, - {0x801f, 125}, - {0x8029, 125}, - {0xc038, 125}, - }, - /* 92 */ - { - {0x8002, 60}, - {0x8009, 60}, - {0x8017, 60}, - {0xc028, 60}, - {0x8002, 96}, - {0x8009, 96}, - {0x8017, 96}, - {0xc028, 96}, - {0x8002, 123}, - {0x8009, 123}, - {0x8017, 123}, - {0xc028, 123}, - {0x61, 0}, - {0x65, 0}, - {0x6f, 0}, - {0x85, 0}, - }, - /* 93 */ - { - {0x8003, 60}, - {0x8006, 60}, - {0x800a, 60}, - {0x800f, 60}, - {0x8018, 60}, - {0x801f, 60}, - {0x8029, 60}, - {0xc038, 60}, - {0x8003, 96}, - {0x8006, 96}, - {0x800a, 96}, - {0x800f, 96}, - {0x8018, 96}, - {0x801f, 96}, - {0x8029, 96}, - {0xc038, 96}, - }, - /* 94 */ - { - {0x8003, 123}, - {0x8006, 123}, - {0x800a, 123}, - {0x800f, 123}, - {0x8018, 123}, - {0x801f, 123}, - {0x8029, 123}, - {0xc038, 123}, - {0x62, 0}, - {0x63, 0}, - {0x66, 0}, - {0x69, 0}, - {0x70, 0}, - {0x77, 0}, - {0x86, 0}, - {0x99, 0}, - }, - /* 95 */ - { - {0xc000, 92}, - {0xc000, 195}, - {0xc000, 208}, - {0x64, 0}, - {0x67, 0}, - {0x68, 0}, - {0x6a, 0}, - {0x6b, 0}, - {0x71, 0}, - {0x74, 0}, - {0x78, 0}, - {0x7e, 0}, - {0x87, 0}, - {0x8e, 0}, - {0x9a, 0}, - {0xa9, 0}, - }, - /* 96 */ - { - {0x8001, 92}, - {0xc016, 92}, - {0x8001, 195}, - {0xc016, 195}, - {0x8001, 208}, - {0xc016, 208}, - {0xc000, 128}, - {0xc000, 130}, - {0xc000, 131}, - {0xc000, 162}, - {0xc000, 184}, - {0xc000, 194}, - {0xc000, 224}, - {0xc000, 226}, - {0x6c, 0}, - {0x6d, 0}, - }, - /* 97 */ - { - {0x8002, 92}, - {0x8009, 92}, - {0x8017, 92}, - {0xc028, 92}, - {0x8002, 195}, - {0x8009, 195}, - {0x8017, 195}, - {0xc028, 195}, - {0x8002, 208}, - {0x8009, 208}, - {0x8017, 208}, - {0xc028, 208}, - {0x8001, 128}, - {0xc016, 128}, - {0x8001, 130}, - {0xc016, 130}, - }, - /* 98 */ - { - {0x8003, 92}, - {0x8006, 92}, - {0x800a, 92}, - {0x800f, 92}, - {0x8018, 92}, - {0x801f, 92}, - {0x8029, 92}, - {0xc038, 92}, - {0x8003, 195}, - {0x8006, 195}, - {0x800a, 195}, - {0x800f, 195}, - {0x8018, 195}, - {0x801f, 195}, - {0x8029, 195}, - {0xc038, 195}, - }, - /* 99 */ - { - {0x8003, 208}, - {0x8006, 208}, - {0x800a, 208}, - {0x800f, 208}, - {0x8018, 208}, - {0x801f, 208}, - {0x8029, 208}, - {0xc038, 208}, - {0x8002, 128}, - {0x8009, 128}, - {0x8017, 128}, - {0xc028, 128}, - {0x8002, 130}, - {0x8009, 130}, - {0x8017, 130}, - {0xc028, 130}, - }, - /* 100 */ - { - {0x8003, 128}, - {0x8006, 128}, - {0x800a, 128}, - {0x800f, 128}, - {0x8018, 128}, - {0x801f, 128}, - {0x8029, 128}, - {0xc038, 128}, - {0x8003, 130}, - {0x8006, 130}, - {0x800a, 130}, - {0x800f, 130}, - {0x8018, 130}, - {0x801f, 130}, - {0x8029, 130}, - {0xc038, 130}, - }, - /* 101 */ - { - {0x8001, 131}, - {0xc016, 131}, - {0x8001, 162}, - {0xc016, 162}, - {0x8001, 184}, - {0xc016, 184}, - {0x8001, 194}, - {0xc016, 194}, - {0x8001, 224}, - {0xc016, 224}, - {0x8001, 226}, - {0xc016, 226}, - {0xc000, 153}, - {0xc000, 161}, - {0xc000, 167}, - {0xc000, 172}, - }, - /* 102 */ - { - {0x8002, 131}, - {0x8009, 131}, - {0x8017, 131}, - {0xc028, 131}, - {0x8002, 162}, - {0x8009, 162}, - {0x8017, 162}, - {0xc028, 162}, - {0x8002, 184}, - {0x8009, 184}, - {0x8017, 184}, - {0xc028, 184}, - {0x8002, 194}, - {0x8009, 194}, - {0x8017, 194}, - {0xc028, 194}, - }, - /* 103 */ - { - {0x8003, 131}, - {0x8006, 131}, - {0x800a, 131}, - {0x800f, 131}, - {0x8018, 131}, - {0x801f, 131}, - {0x8029, 131}, - {0xc038, 131}, - {0x8003, 162}, - {0x8006, 162}, - {0x800a, 162}, - {0x800f, 162}, - {0x8018, 162}, - {0x801f, 162}, - {0x8029, 162}, - {0xc038, 162}, - }, - /* 104 */ - { - {0x8003, 184}, - {0x8006, 184}, - {0x800a, 184}, - {0x800f, 184}, - {0x8018, 184}, - {0x801f, 184}, - {0x8029, 184}, - {0xc038, 184}, - {0x8003, 194}, - {0x8006, 194}, - {0x800a, 194}, - {0x800f, 194}, - {0x8018, 194}, - {0x801f, 194}, - {0x8029, 194}, - {0xc038, 194}, - }, - /* 105 */ - { - {0x8002, 224}, - {0x8009, 224}, - {0x8017, 224}, - {0xc028, 224}, - {0x8002, 226}, - {0x8009, 226}, - {0x8017, 226}, - {0xc028, 226}, - {0x8001, 153}, - {0xc016, 153}, - {0x8001, 161}, - {0xc016, 161}, - {0x8001, 167}, - {0xc016, 167}, - {0x8001, 172}, - {0xc016, 172}, - }, - /* 106 */ - { - {0x8003, 224}, - {0x8006, 224}, - {0x800a, 224}, - {0x800f, 224}, - {0x8018, 224}, - {0x801f, 224}, - {0x8029, 224}, - {0xc038, 224}, - {0x8003, 226}, - {0x8006, 226}, - {0x800a, 226}, - {0x800f, 226}, - {0x8018, 226}, - {0x801f, 226}, - {0x8029, 226}, - {0xc038, 226}, - }, - /* 107 */ - { - {0x8002, 153}, - {0x8009, 153}, - {0x8017, 153}, - {0xc028, 153}, - {0x8002, 161}, - {0x8009, 161}, - {0x8017, 161}, - {0xc028, 161}, - {0x8002, 167}, - {0x8009, 167}, - {0x8017, 167}, - {0xc028, 167}, - {0x8002, 172}, - {0x8009, 172}, - {0x8017, 172}, - {0xc028, 172}, - }, - /* 108 */ - { - {0x8003, 153}, - {0x8006, 153}, - {0x800a, 153}, - {0x800f, 153}, - {0x8018, 153}, - {0x801f, 153}, - {0x8029, 153}, - {0xc038, 153}, - {0x8003, 161}, - {0x8006, 161}, - {0x800a, 161}, - {0x800f, 161}, - {0x8018, 161}, - {0x801f, 161}, - {0x8029, 161}, - {0xc038, 161}, - }, - /* 109 */ - { - {0x8003, 167}, - {0x8006, 167}, - {0x800a, 167}, - {0x800f, 167}, - {0x8018, 167}, - {0x801f, 167}, - {0x8029, 167}, - {0xc038, 167}, - {0x8003, 172}, - {0x8006, 172}, - {0x800a, 172}, - {0x800f, 172}, - {0x8018, 172}, - {0x801f, 172}, - {0x8029, 172}, - {0xc038, 172}, - }, - /* 110 */ - { - {0x72, 0}, - {0x73, 0}, - {0x75, 0}, - {0x76, 0}, - {0x79, 0}, - {0x7b, 0}, - {0x7f, 0}, - {0x82, 0}, - {0x88, 0}, - {0x8b, 0}, - {0x8f, 0}, - {0x92, 0}, - {0x9b, 0}, - {0xa2, 0}, - {0xaa, 0}, - {0xb4, 0}, - }, - /* 111 */ - { - {0xc000, 176}, - {0xc000, 177}, - {0xc000, 179}, - {0xc000, 209}, - {0xc000, 216}, - {0xc000, 217}, - {0xc000, 227}, - {0xc000, 229}, - {0xc000, 230}, - {0x7a, 0}, - {0x7c, 0}, - {0x7d, 0}, - {0x80, 0}, - {0x81, 0}, - {0x83, 0}, - {0x84, 0}, - }, - /* 112 */ - { - {0x8001, 176}, - {0xc016, 176}, - {0x8001, 177}, - {0xc016, 177}, - {0x8001, 179}, - {0xc016, 179}, - {0x8001, 209}, - {0xc016, 209}, - {0x8001, 216}, - {0xc016, 216}, - {0x8001, 217}, - {0xc016, 217}, - {0x8001, 227}, - {0xc016, 227}, - {0x8001, 229}, - {0xc016, 229}, - }, - /* 113 */ - { - {0x8002, 176}, - {0x8009, 176}, - {0x8017, 176}, - {0xc028, 176}, - {0x8002, 177}, - {0x8009, 177}, - {0x8017, 177}, - {0xc028, 177}, - {0x8002, 179}, - {0x8009, 179}, - {0x8017, 179}, - {0xc028, 179}, - {0x8002, 209}, - {0x8009, 209}, - {0x8017, 209}, - {0xc028, 209}, - }, - /* 114 */ - { - {0x8003, 176}, - {0x8006, 176}, - {0x800a, 176}, - {0x800f, 176}, - {0x8018, 176}, - {0x801f, 176}, - {0x8029, 176}, - {0xc038, 176}, - {0x8003, 177}, - {0x8006, 177}, - {0x800a, 177}, - {0x800f, 177}, - {0x8018, 177}, - {0x801f, 177}, - {0x8029, 177}, - {0xc038, 177}, - }, - /* 115 */ - { - {0x8003, 179}, - {0x8006, 179}, - {0x800a, 179}, - {0x800f, 179}, - {0x8018, 179}, - {0x801f, 179}, - {0x8029, 179}, - {0xc038, 179}, - {0x8003, 209}, - {0x8006, 209}, - {0x800a, 209}, - {0x800f, 209}, - {0x8018, 209}, - {0x801f, 209}, - {0x8029, 209}, - {0xc038, 209}, - }, - /* 116 */ - { - {0x8002, 216}, - {0x8009, 216}, - {0x8017, 216}, - {0xc028, 216}, - {0x8002, 217}, - {0x8009, 217}, - {0x8017, 217}, - {0xc028, 217}, - {0x8002, 227}, - {0x8009, 227}, - {0x8017, 227}, - {0xc028, 227}, - {0x8002, 229}, - {0x8009, 229}, - {0x8017, 229}, - {0xc028, 229}, - }, - /* 117 */ - { - {0x8003, 216}, - {0x8006, 216}, - {0x800a, 216}, - {0x800f, 216}, - {0x8018, 216}, - {0x801f, 216}, - {0x8029, 216}, - {0xc038, 216}, - {0x8003, 217}, - {0x8006, 217}, - {0x800a, 217}, - {0x800f, 217}, - {0x8018, 217}, - {0x801f, 217}, - {0x8029, 217}, - {0xc038, 217}, - }, - /* 118 */ - { - {0x8003, 227}, - {0x8006, 227}, - {0x800a, 227}, - {0x800f, 227}, - {0x8018, 227}, - {0x801f, 227}, - {0x8029, 227}, - {0xc038, 227}, - {0x8003, 229}, - {0x8006, 229}, - {0x800a, 229}, - {0x800f, 229}, - {0x8018, 229}, - {0x801f, 229}, - {0x8029, 229}, - {0xc038, 229}, - }, - /* 119 */ - { - {0x8001, 230}, - {0xc016, 230}, - {0xc000, 129}, - {0xc000, 132}, - {0xc000, 133}, - {0xc000, 134}, - {0xc000, 136}, - {0xc000, 146}, - {0xc000, 154}, - {0xc000, 156}, - {0xc000, 160}, - {0xc000, 163}, - {0xc000, 164}, - {0xc000, 169}, - {0xc000, 170}, - {0xc000, 173}, - }, - /* 120 */ - { - {0x8002, 230}, - {0x8009, 230}, - {0x8017, 230}, - {0xc028, 230}, - {0x8001, 129}, - {0xc016, 129}, - {0x8001, 132}, - {0xc016, 132}, - {0x8001, 133}, - {0xc016, 133}, - {0x8001, 134}, - {0xc016, 134}, - {0x8001, 136}, - {0xc016, 136}, - {0x8001, 146}, - {0xc016, 146}, - }, - /* 121 */ - { - {0x8003, 230}, - {0x8006, 230}, - {0x800a, 230}, - {0x800f, 230}, - {0x8018, 230}, - {0x801f, 230}, - {0x8029, 230}, - {0xc038, 230}, - {0x8002, 129}, - {0x8009, 129}, - {0x8017, 129}, - {0xc028, 129}, - {0x8002, 132}, - {0x8009, 132}, - {0x8017, 132}, - {0xc028, 132}, - }, - /* 122 */ - { - {0x8003, 129}, - {0x8006, 129}, - {0x800a, 129}, - {0x800f, 129}, - {0x8018, 129}, - {0x801f, 129}, - {0x8029, 129}, - {0xc038, 129}, - {0x8003, 132}, - {0x8006, 132}, - {0x800a, 132}, - {0x800f, 132}, - {0x8018, 132}, - {0x801f, 132}, - {0x8029, 132}, - {0xc038, 132}, - }, - /* 123 */ - { - {0x8002, 133}, - {0x8009, 133}, - {0x8017, 133}, - {0xc028, 133}, - {0x8002, 134}, - {0x8009, 134}, - {0x8017, 134}, - {0xc028, 134}, - {0x8002, 136}, - {0x8009, 136}, - {0x8017, 136}, - {0xc028, 136}, - {0x8002, 146}, - {0x8009, 146}, - {0x8017, 146}, - {0xc028, 146}, - }, - /* 124 */ - { - {0x8003, 133}, - {0x8006, 133}, - {0x800a, 133}, - {0x800f, 133}, - {0x8018, 133}, - {0x801f, 133}, - {0x8029, 133}, - {0xc038, 133}, - {0x8003, 134}, - {0x8006, 134}, - {0x800a, 134}, - {0x800f, 134}, - {0x8018, 134}, - {0x801f, 134}, - {0x8029, 134}, - {0xc038, 134}, - }, - /* 125 */ - { - {0x8003, 136}, - {0x8006, 136}, - {0x800a, 136}, - {0x800f, 136}, - {0x8018, 136}, - {0x801f, 136}, - {0x8029, 136}, - {0xc038, 136}, - {0x8003, 146}, - {0x8006, 146}, - {0x800a, 146}, - {0x800f, 146}, - {0x8018, 146}, - {0x801f, 146}, - {0x8029, 146}, - {0xc038, 146}, - }, - /* 126 */ - { - {0x8001, 154}, - {0xc016, 154}, - {0x8001, 156}, - {0xc016, 156}, - {0x8001, 160}, - {0xc016, 160}, - {0x8001, 163}, - {0xc016, 163}, - {0x8001, 164}, - {0xc016, 164}, - {0x8001, 169}, - {0xc016, 169}, - {0x8001, 170}, - {0xc016, 170}, - {0x8001, 173}, - {0xc016, 173}, - }, - /* 127 */ - { - {0x8002, 154}, - {0x8009, 154}, - {0x8017, 154}, - {0xc028, 154}, - {0x8002, 156}, - {0x8009, 156}, - {0x8017, 156}, - {0xc028, 156}, - {0x8002, 160}, - {0x8009, 160}, - {0x8017, 160}, - {0xc028, 160}, - {0x8002, 163}, - {0x8009, 163}, - {0x8017, 163}, - {0xc028, 163}, - }, - /* 128 */ - { - {0x8003, 154}, - {0x8006, 154}, - {0x800a, 154}, - {0x800f, 154}, - {0x8018, 154}, - {0x801f, 154}, - {0x8029, 154}, - {0xc038, 154}, - {0x8003, 156}, - {0x8006, 156}, - {0x800a, 156}, - {0x800f, 156}, - {0x8018, 156}, - {0x801f, 156}, - {0x8029, 156}, - {0xc038, 156}, - }, - /* 129 */ - { - {0x8003, 160}, - {0x8006, 160}, - {0x800a, 160}, - {0x800f, 160}, - {0x8018, 160}, - {0x801f, 160}, - {0x8029, 160}, - {0xc038, 160}, - {0x8003, 163}, - {0x8006, 163}, - {0x800a, 163}, - {0x800f, 163}, - {0x8018, 163}, - {0x801f, 163}, - {0x8029, 163}, - {0xc038, 163}, - }, - /* 130 */ - { - {0x8002, 164}, - {0x8009, 164}, - {0x8017, 164}, - {0xc028, 164}, - {0x8002, 169}, - {0x8009, 169}, - {0x8017, 169}, - {0xc028, 169}, - {0x8002, 170}, - {0x8009, 170}, - {0x8017, 170}, - {0xc028, 170}, - {0x8002, 173}, - {0x8009, 173}, - {0x8017, 173}, - {0xc028, 173}, - }, - /* 131 */ - { - {0x8003, 164}, - {0x8006, 164}, - {0x800a, 164}, - {0x800f, 164}, - {0x8018, 164}, - {0x801f, 164}, - {0x8029, 164}, - {0xc038, 164}, - {0x8003, 169}, - {0x8006, 169}, - {0x800a, 169}, - {0x800f, 169}, - {0x8018, 169}, - {0x801f, 169}, - {0x8029, 169}, - {0xc038, 169}, - }, - /* 132 */ - { - {0x8003, 170}, - {0x8006, 170}, - {0x800a, 170}, - {0x800f, 170}, - {0x8018, 170}, - {0x801f, 170}, - {0x8029, 170}, - {0xc038, 170}, - {0x8003, 173}, - {0x8006, 173}, - {0x800a, 173}, - {0x800f, 173}, - {0x8018, 173}, - {0x801f, 173}, - {0x8029, 173}, - {0xc038, 173}, - }, - /* 133 */ - { - {0x89, 0}, - {0x8a, 0}, - {0x8c, 0}, - {0x8d, 0}, - {0x90, 0}, - {0x91, 0}, - {0x93, 0}, - {0x96, 0}, - {0x9c, 0}, - {0x9f, 0}, - {0xa3, 0}, - {0xa6, 0}, - {0xab, 0}, - {0xae, 0}, - {0xb5, 0}, - {0xbe, 0}, - }, - /* 134 */ - { - {0xc000, 178}, - {0xc000, 181}, - {0xc000, 185}, - {0xc000, 186}, - {0xc000, 187}, - {0xc000, 189}, - {0xc000, 190}, - {0xc000, 196}, - {0xc000, 198}, - {0xc000, 228}, - {0xc000, 232}, - {0xc000, 233}, - {0x94, 0}, - {0x95, 0}, - {0x97, 0}, - {0x98, 0}, - }, - /* 135 */ - { - {0x8001, 178}, - {0xc016, 178}, - {0x8001, 181}, - {0xc016, 181}, - {0x8001, 185}, - {0xc016, 185}, - {0x8001, 186}, - {0xc016, 186}, - {0x8001, 187}, - {0xc016, 187}, - {0x8001, 189}, - {0xc016, 189}, - {0x8001, 190}, - {0xc016, 190}, - {0x8001, 196}, - {0xc016, 196}, - }, - /* 136 */ - { - {0x8002, 178}, - {0x8009, 178}, - {0x8017, 178}, - {0xc028, 178}, - {0x8002, 181}, - {0x8009, 181}, - {0x8017, 181}, - {0xc028, 181}, - {0x8002, 185}, - {0x8009, 185}, - {0x8017, 185}, - {0xc028, 185}, - {0x8002, 186}, - {0x8009, 186}, - {0x8017, 186}, - {0xc028, 186}, - }, - /* 137 */ - { - {0x8003, 178}, - {0x8006, 178}, - {0x800a, 178}, - {0x800f, 178}, - {0x8018, 178}, - {0x801f, 178}, - {0x8029, 178}, - {0xc038, 178}, - {0x8003, 181}, - {0x8006, 181}, - {0x800a, 181}, - {0x800f, 181}, - {0x8018, 181}, - {0x801f, 181}, - {0x8029, 181}, - {0xc038, 181}, - }, - /* 138 */ - { - {0x8003, 185}, - {0x8006, 185}, - {0x800a, 185}, - {0x800f, 185}, - {0x8018, 185}, - {0x801f, 185}, - {0x8029, 185}, - {0xc038, 185}, - {0x8003, 186}, - {0x8006, 186}, - {0x800a, 186}, - {0x800f, 186}, - {0x8018, 186}, - {0x801f, 186}, - {0x8029, 186}, - {0xc038, 186}, - }, - /* 139 */ - { - {0x8002, 187}, - {0x8009, 187}, - {0x8017, 187}, - {0xc028, 187}, - {0x8002, 189}, - {0x8009, 189}, - {0x8017, 189}, - {0xc028, 189}, - {0x8002, 190}, - {0x8009, 190}, - {0x8017, 190}, - {0xc028, 190}, - {0x8002, 196}, - {0x8009, 196}, - {0x8017, 196}, - {0xc028, 196}, - }, - /* 140 */ - { - {0x8003, 187}, - {0x8006, 187}, - {0x800a, 187}, - {0x800f, 187}, - {0x8018, 187}, - {0x801f, 187}, - {0x8029, 187}, - {0xc038, 187}, - {0x8003, 189}, - {0x8006, 189}, - {0x800a, 189}, - {0x800f, 189}, - {0x8018, 189}, - {0x801f, 189}, - {0x8029, 189}, - {0xc038, 189}, - }, - /* 141 */ - { - {0x8003, 190}, - {0x8006, 190}, - {0x800a, 190}, - {0x800f, 190}, - {0x8018, 190}, - {0x801f, 190}, - {0x8029, 190}, - {0xc038, 190}, - {0x8003, 196}, - {0x8006, 196}, - {0x800a, 196}, - {0x800f, 196}, - {0x8018, 196}, - {0x801f, 196}, - {0x8029, 196}, - {0xc038, 196}, - }, - /* 142 */ - { - {0x8001, 198}, - {0xc016, 198}, - {0x8001, 228}, - {0xc016, 228}, - {0x8001, 232}, - {0xc016, 232}, - {0x8001, 233}, - {0xc016, 233}, - {0xc000, 1}, - {0xc000, 135}, - {0xc000, 137}, - {0xc000, 138}, - {0xc000, 139}, - {0xc000, 140}, - {0xc000, 141}, - {0xc000, 143}, - }, - /* 143 */ - { - {0x8002, 198}, - {0x8009, 198}, - {0x8017, 198}, - {0xc028, 198}, - {0x8002, 228}, - {0x8009, 228}, - {0x8017, 228}, - {0xc028, 228}, - {0x8002, 232}, - {0x8009, 232}, - {0x8017, 232}, - {0xc028, 232}, - {0x8002, 233}, - {0x8009, 233}, - {0x8017, 233}, - {0xc028, 233}, - }, - /* 144 */ - { - {0x8003, 198}, - {0x8006, 198}, - {0x800a, 198}, - {0x800f, 198}, - {0x8018, 198}, - {0x801f, 198}, - {0x8029, 198}, - {0xc038, 198}, - {0x8003, 228}, - {0x8006, 228}, - {0x800a, 228}, - {0x800f, 228}, - {0x8018, 228}, - {0x801f, 228}, - {0x8029, 228}, - {0xc038, 228}, - }, - /* 145 */ - { - {0x8003, 232}, - {0x8006, 232}, - {0x800a, 232}, - {0x800f, 232}, - {0x8018, 232}, - {0x801f, 232}, - {0x8029, 232}, - {0xc038, 232}, - {0x8003, 233}, - {0x8006, 233}, - {0x800a, 233}, - {0x800f, 233}, - {0x8018, 233}, - {0x801f, 233}, - {0x8029, 233}, - {0xc038, 233}, - }, - /* 146 */ - { - {0x8001, 1}, - {0xc016, 1}, - {0x8001, 135}, - {0xc016, 135}, - {0x8001, 137}, - {0xc016, 137}, - {0x8001, 138}, - {0xc016, 138}, - {0x8001, 139}, - {0xc016, 139}, - {0x8001, 140}, - {0xc016, 140}, - {0x8001, 141}, - {0xc016, 141}, - {0x8001, 143}, - {0xc016, 143}, - }, - /* 147 */ - { - {0x8002, 1}, - {0x8009, 1}, - {0x8017, 1}, - {0xc028, 1}, - {0x8002, 135}, - {0x8009, 135}, - {0x8017, 135}, - {0xc028, 135}, - {0x8002, 137}, - {0x8009, 137}, - {0x8017, 137}, - {0xc028, 137}, - {0x8002, 138}, - {0x8009, 138}, - {0x8017, 138}, - {0xc028, 138}, - }, - /* 148 */ - { - {0x8003, 1}, - {0x8006, 1}, - {0x800a, 1}, - {0x800f, 1}, - {0x8018, 1}, - {0x801f, 1}, - {0x8029, 1}, - {0xc038, 1}, - {0x8003, 135}, - {0x8006, 135}, - {0x800a, 135}, - {0x800f, 135}, - {0x8018, 135}, - {0x801f, 135}, - {0x8029, 135}, - {0xc038, 135}, - }, - /* 149 */ - { - {0x8003, 137}, - {0x8006, 137}, - {0x800a, 137}, - {0x800f, 137}, - {0x8018, 137}, - {0x801f, 137}, - {0x8029, 137}, - {0xc038, 137}, - {0x8003, 138}, - {0x8006, 138}, - {0x800a, 138}, - {0x800f, 138}, - {0x8018, 138}, - {0x801f, 138}, - {0x8029, 138}, - {0xc038, 138}, - }, - /* 150 */ - { - {0x8002, 139}, - {0x8009, 139}, - {0x8017, 139}, - {0xc028, 139}, - {0x8002, 140}, - {0x8009, 140}, - {0x8017, 140}, - {0xc028, 140}, - {0x8002, 141}, - {0x8009, 141}, - {0x8017, 141}, - {0xc028, 141}, - {0x8002, 143}, - {0x8009, 143}, - {0x8017, 143}, - {0xc028, 143}, - }, - /* 151 */ - { - {0x8003, 139}, - {0x8006, 139}, - {0x800a, 139}, - {0x800f, 139}, - {0x8018, 139}, - {0x801f, 139}, - {0x8029, 139}, - {0xc038, 139}, - {0x8003, 140}, - {0x8006, 140}, - {0x800a, 140}, - {0x800f, 140}, - {0x8018, 140}, - {0x801f, 140}, - {0x8029, 140}, - {0xc038, 140}, - }, - /* 152 */ - { - {0x8003, 141}, - {0x8006, 141}, - {0x800a, 141}, - {0x800f, 141}, - {0x8018, 141}, - {0x801f, 141}, - {0x8029, 141}, - {0xc038, 141}, - {0x8003, 143}, - {0x8006, 143}, - {0x800a, 143}, - {0x800f, 143}, - {0x8018, 143}, - {0x801f, 143}, - {0x8029, 143}, - {0xc038, 143}, - }, - /* 153 */ - { - {0x9d, 0}, - {0x9e, 0}, - {0xa0, 0}, - {0xa1, 0}, - {0xa4, 0}, - {0xa5, 0}, - {0xa7, 0}, - {0xa8, 0}, - {0xac, 0}, - {0xad, 0}, - {0xaf, 0}, - {0xb1, 0}, - {0xb6, 0}, - {0xb9, 0}, - {0xbf, 0}, - {0xcf, 0}, - }, - /* 154 */ - { - {0xc000, 147}, - {0xc000, 149}, - {0xc000, 150}, - {0xc000, 151}, - {0xc000, 152}, - {0xc000, 155}, - {0xc000, 157}, - {0xc000, 158}, - {0xc000, 165}, - {0xc000, 166}, - {0xc000, 168}, - {0xc000, 174}, - {0xc000, 175}, - {0xc000, 180}, - {0xc000, 182}, - {0xc000, 183}, - }, - /* 155 */ - { - {0x8001, 147}, - {0xc016, 147}, - {0x8001, 149}, - {0xc016, 149}, - {0x8001, 150}, - {0xc016, 150}, - {0x8001, 151}, - {0xc016, 151}, - {0x8001, 152}, - {0xc016, 152}, - {0x8001, 155}, - {0xc016, 155}, - {0x8001, 157}, - {0xc016, 157}, - {0x8001, 158}, - {0xc016, 158}, - }, - /* 156 */ - { - {0x8002, 147}, - {0x8009, 147}, - {0x8017, 147}, - {0xc028, 147}, - {0x8002, 149}, - {0x8009, 149}, - {0x8017, 149}, - {0xc028, 149}, - {0x8002, 150}, - {0x8009, 150}, - {0x8017, 150}, - {0xc028, 150}, - {0x8002, 151}, - {0x8009, 151}, - {0x8017, 151}, - {0xc028, 151}, - }, - /* 157 */ - { - {0x8003, 147}, - {0x8006, 147}, - {0x800a, 147}, - {0x800f, 147}, - {0x8018, 147}, - {0x801f, 147}, - {0x8029, 147}, - {0xc038, 147}, - {0x8003, 149}, - {0x8006, 149}, - {0x800a, 149}, - {0x800f, 149}, - {0x8018, 149}, - {0x801f, 149}, - {0x8029, 149}, - {0xc038, 149}, - }, - /* 158 */ - { - {0x8003, 150}, - {0x8006, 150}, - {0x800a, 150}, - {0x800f, 150}, - {0x8018, 150}, - {0x801f, 150}, - {0x8029, 150}, - {0xc038, 150}, - {0x8003, 151}, - {0x8006, 151}, - {0x800a, 151}, - {0x800f, 151}, - {0x8018, 151}, - {0x801f, 151}, - {0x8029, 151}, - {0xc038, 151}, - }, - /* 159 */ - { - {0x8002, 152}, - {0x8009, 152}, - {0x8017, 152}, - {0xc028, 152}, - {0x8002, 155}, - {0x8009, 155}, - {0x8017, 155}, - {0xc028, 155}, - {0x8002, 157}, - {0x8009, 157}, - {0x8017, 157}, - {0xc028, 157}, - {0x8002, 158}, - {0x8009, 158}, - {0x8017, 158}, - {0xc028, 158}, - }, - /* 160 */ - { - {0x8003, 152}, - {0x8006, 152}, - {0x800a, 152}, - {0x800f, 152}, - {0x8018, 152}, - {0x801f, 152}, - {0x8029, 152}, - {0xc038, 152}, - {0x8003, 155}, - {0x8006, 155}, - {0x800a, 155}, - {0x800f, 155}, - {0x8018, 155}, - {0x801f, 155}, - {0x8029, 155}, - {0xc038, 155}, - }, - /* 161 */ - { - {0x8003, 157}, - {0x8006, 157}, - {0x800a, 157}, - {0x800f, 157}, - {0x8018, 157}, - {0x801f, 157}, - {0x8029, 157}, - {0xc038, 157}, - {0x8003, 158}, - {0x8006, 158}, - {0x800a, 158}, - {0x800f, 158}, - {0x8018, 158}, - {0x801f, 158}, - {0x8029, 158}, - {0xc038, 158}, - }, - /* 162 */ - { - {0x8001, 165}, - {0xc016, 165}, - {0x8001, 166}, - {0xc016, 166}, - {0x8001, 168}, - {0xc016, 168}, - {0x8001, 174}, - {0xc016, 174}, - {0x8001, 175}, - {0xc016, 175}, - {0x8001, 180}, - {0xc016, 180}, - {0x8001, 182}, - {0xc016, 182}, - {0x8001, 183}, - {0xc016, 183}, - }, - /* 163 */ - { - {0x8002, 165}, - {0x8009, 165}, - {0x8017, 165}, - {0xc028, 165}, - {0x8002, 166}, - {0x8009, 166}, - {0x8017, 166}, - {0xc028, 166}, - {0x8002, 168}, - {0x8009, 168}, - {0x8017, 168}, - {0xc028, 168}, - {0x8002, 174}, - {0x8009, 174}, - {0x8017, 174}, - {0xc028, 174}, - }, - /* 164 */ - { - {0x8003, 165}, - {0x8006, 165}, - {0x800a, 165}, - {0x800f, 165}, - {0x8018, 165}, - {0x801f, 165}, - {0x8029, 165}, - {0xc038, 165}, - {0x8003, 166}, - {0x8006, 166}, - {0x800a, 166}, - {0x800f, 166}, - {0x8018, 166}, - {0x801f, 166}, - {0x8029, 166}, - {0xc038, 166}, - }, - /* 165 */ - { - {0x8003, 168}, - {0x8006, 168}, - {0x800a, 168}, - {0x800f, 168}, - {0x8018, 168}, - {0x801f, 168}, - {0x8029, 168}, - {0xc038, 168}, - {0x8003, 174}, - {0x8006, 174}, - {0x800a, 174}, - {0x800f, 174}, - {0x8018, 174}, - {0x801f, 174}, - {0x8029, 174}, - {0xc038, 174}, - }, - /* 166 */ - { - {0x8002, 175}, - {0x8009, 175}, - {0x8017, 175}, - {0xc028, 175}, - {0x8002, 180}, - {0x8009, 180}, - {0x8017, 180}, - {0xc028, 180}, - {0x8002, 182}, - {0x8009, 182}, - {0x8017, 182}, - {0xc028, 182}, - {0x8002, 183}, - {0x8009, 183}, - {0x8017, 183}, - {0xc028, 183}, - }, - /* 167 */ - { - {0x8003, 175}, - {0x8006, 175}, - {0x800a, 175}, - {0x800f, 175}, - {0x8018, 175}, - {0x801f, 175}, - {0x8029, 175}, - {0xc038, 175}, - {0x8003, 180}, - {0x8006, 180}, - {0x800a, 180}, - {0x800f, 180}, - {0x8018, 180}, - {0x801f, 180}, - {0x8029, 180}, - {0xc038, 180}, - }, - /* 168 */ - { - {0x8003, 182}, - {0x8006, 182}, - {0x800a, 182}, - {0x800f, 182}, - {0x8018, 182}, - {0x801f, 182}, - {0x8029, 182}, - {0xc038, 182}, - {0x8003, 183}, - {0x8006, 183}, - {0x800a, 183}, - {0x800f, 183}, - {0x8018, 183}, - {0x801f, 183}, - {0x8029, 183}, - {0xc038, 183}, - }, - /* 169 */ - { - {0xc000, 188}, - {0xc000, 191}, - {0xc000, 197}, - {0xc000, 231}, - {0xc000, 239}, - {0xb0, 0}, - {0xb2, 0}, - {0xb3, 0}, - {0xb7, 0}, - {0xb8, 0}, - {0xba, 0}, - {0xbb, 0}, - {0xc0, 0}, - {0xc7, 0}, - {0xd0, 0}, - {0xdf, 0}, - }, - /* 170 */ - { - {0x8001, 188}, - {0xc016, 188}, - {0x8001, 191}, - {0xc016, 191}, - {0x8001, 197}, - {0xc016, 197}, - {0x8001, 231}, - {0xc016, 231}, - {0x8001, 239}, - {0xc016, 239}, - {0xc000, 9}, - {0xc000, 142}, - {0xc000, 144}, - {0xc000, 145}, - {0xc000, 148}, - {0xc000, 159}, - }, - /* 171 */ - { - {0x8002, 188}, - {0x8009, 188}, - {0x8017, 188}, - {0xc028, 188}, - {0x8002, 191}, - {0x8009, 191}, - {0x8017, 191}, - {0xc028, 191}, - {0x8002, 197}, - {0x8009, 197}, - {0x8017, 197}, - {0xc028, 197}, - {0x8002, 231}, - {0x8009, 231}, - {0x8017, 231}, - {0xc028, 231}, - }, - /* 172 */ - { - {0x8003, 188}, - {0x8006, 188}, - {0x800a, 188}, - {0x800f, 188}, - {0x8018, 188}, - {0x801f, 188}, - {0x8029, 188}, - {0xc038, 188}, - {0x8003, 191}, - {0x8006, 191}, - {0x800a, 191}, - {0x800f, 191}, - {0x8018, 191}, - {0x801f, 191}, - {0x8029, 191}, - {0xc038, 191}, - }, - /* 173 */ - { - {0x8003, 197}, - {0x8006, 197}, - {0x800a, 197}, - {0x800f, 197}, - {0x8018, 197}, - {0x801f, 197}, - {0x8029, 197}, - {0xc038, 197}, - {0x8003, 231}, - {0x8006, 231}, - {0x800a, 231}, - {0x800f, 231}, - {0x8018, 231}, - {0x801f, 231}, - {0x8029, 231}, - {0xc038, 231}, - }, - /* 174 */ - { - {0x8002, 239}, - {0x8009, 239}, - {0x8017, 239}, - {0xc028, 239}, - {0x8001, 9}, - {0xc016, 9}, - {0x8001, 142}, - {0xc016, 142}, - {0x8001, 144}, - {0xc016, 144}, - {0x8001, 145}, - {0xc016, 145}, - {0x8001, 148}, - {0xc016, 148}, - {0x8001, 159}, - {0xc016, 159}, - }, - /* 175 */ - { - {0x8003, 239}, - {0x8006, 239}, - {0x800a, 239}, - {0x800f, 239}, - {0x8018, 239}, - {0x801f, 239}, - {0x8029, 239}, - {0xc038, 239}, - {0x8002, 9}, - {0x8009, 9}, - {0x8017, 9}, - {0xc028, 9}, - {0x8002, 142}, - {0x8009, 142}, - {0x8017, 142}, - {0xc028, 142}, - }, - /* 176 */ - { - {0x8003, 9}, - {0x8006, 9}, - {0x800a, 9}, - {0x800f, 9}, - {0x8018, 9}, - {0x801f, 9}, - {0x8029, 9}, - {0xc038, 9}, - {0x8003, 142}, - {0x8006, 142}, - {0x800a, 142}, - {0x800f, 142}, - {0x8018, 142}, - {0x801f, 142}, - {0x8029, 142}, - {0xc038, 142}, - }, - /* 177 */ - { - {0x8002, 144}, - {0x8009, 144}, - {0x8017, 144}, - {0xc028, 144}, - {0x8002, 145}, - {0x8009, 145}, - {0x8017, 145}, - {0xc028, 145}, - {0x8002, 148}, - {0x8009, 148}, - {0x8017, 148}, - {0xc028, 148}, - {0x8002, 159}, - {0x8009, 159}, - {0x8017, 159}, - {0xc028, 159}, - }, - /* 178 */ - { - {0x8003, 144}, - {0x8006, 144}, - {0x800a, 144}, - {0x800f, 144}, - {0x8018, 144}, - {0x801f, 144}, - {0x8029, 144}, - {0xc038, 144}, - {0x8003, 145}, - {0x8006, 145}, - {0x800a, 145}, - {0x800f, 145}, - {0x8018, 145}, - {0x801f, 145}, - {0x8029, 145}, - {0xc038, 145}, - }, - /* 179 */ - { - {0x8003, 148}, - {0x8006, 148}, - {0x800a, 148}, - {0x800f, 148}, - {0x8018, 148}, - {0x801f, 148}, - {0x8029, 148}, - {0xc038, 148}, - {0x8003, 159}, - {0x8006, 159}, - {0x800a, 159}, - {0x800f, 159}, - {0x8018, 159}, - {0x801f, 159}, - {0x8029, 159}, - {0xc038, 159}, - }, - /* 180 */ - { - {0xc000, 171}, - {0xc000, 206}, - {0xc000, 215}, - {0xc000, 225}, - {0xc000, 236}, - {0xc000, 237}, - {0xbc, 0}, - {0xbd, 0}, - {0xc1, 0}, - {0xc4, 0}, - {0xc8, 0}, - {0xcb, 0}, - {0xd1, 0}, - {0xd8, 0}, - {0xe0, 0}, - {0xee, 0}, - }, - /* 181 */ - { - {0x8001, 171}, - {0xc016, 171}, - {0x8001, 206}, - {0xc016, 206}, - {0x8001, 215}, - {0xc016, 215}, - {0x8001, 225}, - {0xc016, 225}, - {0x8001, 236}, - {0xc016, 236}, - {0x8001, 237}, - {0xc016, 237}, - {0xc000, 199}, - {0xc000, 207}, - {0xc000, 234}, - {0xc000, 235}, - }, - /* 182 */ - { - {0x8002, 171}, - {0x8009, 171}, - {0x8017, 171}, - {0xc028, 171}, - {0x8002, 206}, - {0x8009, 206}, - {0x8017, 206}, - {0xc028, 206}, - {0x8002, 215}, - {0x8009, 215}, - {0x8017, 215}, - {0xc028, 215}, - {0x8002, 225}, - {0x8009, 225}, - {0x8017, 225}, - {0xc028, 225}, - }, - /* 183 */ - { - {0x8003, 171}, - {0x8006, 171}, - {0x800a, 171}, - {0x800f, 171}, - {0x8018, 171}, - {0x801f, 171}, - {0x8029, 171}, - {0xc038, 171}, - {0x8003, 206}, - {0x8006, 206}, - {0x800a, 206}, - {0x800f, 206}, - {0x8018, 206}, - {0x801f, 206}, - {0x8029, 206}, - {0xc038, 206}, - }, - /* 184 */ - { - {0x8003, 215}, - {0x8006, 215}, - {0x800a, 215}, - {0x800f, 215}, - {0x8018, 215}, - {0x801f, 215}, - {0x8029, 215}, - {0xc038, 215}, - {0x8003, 225}, - {0x8006, 225}, - {0x800a, 225}, - {0x800f, 225}, - {0x8018, 225}, - {0x801f, 225}, - {0x8029, 225}, - {0xc038, 225}, - }, - /* 185 */ - { - {0x8002, 236}, - {0x8009, 236}, - {0x8017, 236}, - {0xc028, 236}, - {0x8002, 237}, - {0x8009, 237}, - {0x8017, 237}, - {0xc028, 237}, - {0x8001, 199}, - {0xc016, 199}, - {0x8001, 207}, - {0xc016, 207}, - {0x8001, 234}, - {0xc016, 234}, - {0x8001, 235}, - {0xc016, 235}, - }, - /* 186 */ - { - {0x8003, 236}, - {0x8006, 236}, - {0x800a, 236}, - {0x800f, 236}, - {0x8018, 236}, - {0x801f, 236}, - {0x8029, 236}, - {0xc038, 236}, - {0x8003, 237}, - {0x8006, 237}, - {0x800a, 237}, - {0x800f, 237}, - {0x8018, 237}, - {0x801f, 237}, - {0x8029, 237}, - {0xc038, 237}, - }, - /* 187 */ - { - {0x8002, 199}, - {0x8009, 199}, - {0x8017, 199}, - {0xc028, 199}, - {0x8002, 207}, - {0x8009, 207}, - {0x8017, 207}, - {0xc028, 207}, - {0x8002, 234}, - {0x8009, 234}, - {0x8017, 234}, - {0xc028, 234}, - {0x8002, 235}, - {0x8009, 235}, - {0x8017, 235}, - {0xc028, 235}, - }, - /* 188 */ - { - {0x8003, 199}, - {0x8006, 199}, - {0x800a, 199}, - {0x800f, 199}, - {0x8018, 199}, - {0x801f, 199}, - {0x8029, 199}, - {0xc038, 199}, - {0x8003, 207}, - {0x8006, 207}, - {0x800a, 207}, - {0x800f, 207}, - {0x8018, 207}, - {0x801f, 207}, - {0x8029, 207}, - {0xc038, 207}, - }, - /* 189 */ - { - {0x8003, 234}, - {0x8006, 234}, - {0x800a, 234}, - {0x800f, 234}, - {0x8018, 234}, - {0x801f, 234}, - {0x8029, 234}, - {0xc038, 234}, - {0x8003, 235}, - {0x8006, 235}, - {0x800a, 235}, - {0x800f, 235}, - {0x8018, 235}, - {0x801f, 235}, - {0x8029, 235}, - {0xc038, 235}, - }, - /* 190 */ - { - {0xc2, 0}, - {0xc3, 0}, - {0xc5, 0}, - {0xc6, 0}, - {0xc9, 0}, - {0xca, 0}, - {0xcc, 0}, - {0xcd, 0}, - {0xd2, 0}, - {0xd5, 0}, - {0xd9, 0}, - {0xdc, 0}, - {0xe1, 0}, - {0xe7, 0}, - {0xef, 0}, - {0xf6, 0}, - }, - /* 191 */ - { - {0xc000, 192}, - {0xc000, 193}, - {0xc000, 200}, - {0xc000, 201}, - {0xc000, 202}, - {0xc000, 205}, - {0xc000, 210}, - {0xc000, 213}, - {0xc000, 218}, - {0xc000, 219}, - {0xc000, 238}, - {0xc000, 240}, - {0xc000, 242}, - {0xc000, 243}, - {0xc000, 255}, - {0xce, 0}, - }, - /* 192 */ - { - {0x8001, 192}, - {0xc016, 192}, - {0x8001, 193}, - {0xc016, 193}, - {0x8001, 200}, - {0xc016, 200}, - {0x8001, 201}, - {0xc016, 201}, - {0x8001, 202}, - {0xc016, 202}, - {0x8001, 205}, - {0xc016, 205}, - {0x8001, 210}, - {0xc016, 210}, - {0x8001, 213}, - {0xc016, 213}, - }, - /* 193 */ - { - {0x8002, 192}, - {0x8009, 192}, - {0x8017, 192}, - {0xc028, 192}, - {0x8002, 193}, - {0x8009, 193}, - {0x8017, 193}, - {0xc028, 193}, - {0x8002, 200}, - {0x8009, 200}, - {0x8017, 200}, - {0xc028, 200}, - {0x8002, 201}, - {0x8009, 201}, - {0x8017, 201}, - {0xc028, 201}, - }, - /* 194 */ - { - {0x8003, 192}, - {0x8006, 192}, - {0x800a, 192}, - {0x800f, 192}, - {0x8018, 192}, - {0x801f, 192}, - {0x8029, 192}, - {0xc038, 192}, - {0x8003, 193}, - {0x8006, 193}, - {0x800a, 193}, - {0x800f, 193}, - {0x8018, 193}, - {0x801f, 193}, - {0x8029, 193}, - {0xc038, 193}, - }, - /* 195 */ - { - {0x8003, 200}, - {0x8006, 200}, - {0x800a, 200}, - {0x800f, 200}, - {0x8018, 200}, - {0x801f, 200}, - {0x8029, 200}, - {0xc038, 200}, - {0x8003, 201}, - {0x8006, 201}, - {0x800a, 201}, - {0x800f, 201}, - {0x8018, 201}, - {0x801f, 201}, - {0x8029, 201}, - {0xc038, 201}, - }, - /* 196 */ - { - {0x8002, 202}, - {0x8009, 202}, - {0x8017, 202}, - {0xc028, 202}, - {0x8002, 205}, - {0x8009, 205}, - {0x8017, 205}, - {0xc028, 205}, - {0x8002, 210}, - {0x8009, 210}, - {0x8017, 210}, - {0xc028, 210}, - {0x8002, 213}, - {0x8009, 213}, - {0x8017, 213}, - {0xc028, 213}, - }, - /* 197 */ - { - {0x8003, 202}, - {0x8006, 202}, - {0x800a, 202}, - {0x800f, 202}, - {0x8018, 202}, - {0x801f, 202}, - {0x8029, 202}, - {0xc038, 202}, - {0x8003, 205}, - {0x8006, 205}, - {0x800a, 205}, - {0x800f, 205}, - {0x8018, 205}, - {0x801f, 205}, - {0x8029, 205}, - {0xc038, 205}, - }, - /* 198 */ - { - {0x8003, 210}, - {0x8006, 210}, - {0x800a, 210}, - {0x800f, 210}, - {0x8018, 210}, - {0x801f, 210}, - {0x8029, 210}, - {0xc038, 210}, - {0x8003, 213}, - {0x8006, 213}, - {0x800a, 213}, - {0x800f, 213}, - {0x8018, 213}, - {0x801f, 213}, - {0x8029, 213}, - {0xc038, 213}, - }, - /* 199 */ - { - {0x8001, 218}, - {0xc016, 218}, - {0x8001, 219}, - {0xc016, 219}, - {0x8001, 238}, - {0xc016, 238}, - {0x8001, 240}, - {0xc016, 240}, - {0x8001, 242}, - {0xc016, 242}, - {0x8001, 243}, - {0xc016, 243}, - {0x8001, 255}, - {0xc016, 255}, - {0xc000, 203}, - {0xc000, 204}, - }, - /* 200 */ - { - {0x8002, 218}, - {0x8009, 218}, - {0x8017, 218}, - {0xc028, 218}, - {0x8002, 219}, - {0x8009, 219}, - {0x8017, 219}, - {0xc028, 219}, - {0x8002, 238}, - {0x8009, 238}, - {0x8017, 238}, - {0xc028, 238}, - {0x8002, 240}, - {0x8009, 240}, - {0x8017, 240}, - {0xc028, 240}, - }, - /* 201 */ - { - {0x8003, 218}, - {0x8006, 218}, - {0x800a, 218}, - {0x800f, 218}, - {0x8018, 218}, - {0x801f, 218}, - {0x8029, 218}, - {0xc038, 218}, - {0x8003, 219}, - {0x8006, 219}, - {0x800a, 219}, - {0x800f, 219}, - {0x8018, 219}, - {0x801f, 219}, - {0x8029, 219}, - {0xc038, 219}, - }, - /* 202 */ - { - {0x8003, 238}, - {0x8006, 238}, - {0x800a, 238}, - {0x800f, 238}, - {0x8018, 238}, - {0x801f, 238}, - {0x8029, 238}, - {0xc038, 238}, - {0x8003, 240}, - {0x8006, 240}, - {0x800a, 240}, - {0x800f, 240}, - {0x8018, 240}, - {0x801f, 240}, - {0x8029, 240}, - {0xc038, 240}, - }, - /* 203 */ - { - {0x8002, 242}, - {0x8009, 242}, - {0x8017, 242}, - {0xc028, 242}, - {0x8002, 243}, - {0x8009, 243}, - {0x8017, 243}, - {0xc028, 243}, - {0x8002, 255}, - {0x8009, 255}, - {0x8017, 255}, - {0xc028, 255}, - {0x8001, 203}, - {0xc016, 203}, - {0x8001, 204}, - {0xc016, 204}, - }, - /* 204 */ - { - {0x8003, 242}, - {0x8006, 242}, - {0x800a, 242}, - {0x800f, 242}, - {0x8018, 242}, - {0x801f, 242}, - {0x8029, 242}, - {0xc038, 242}, - {0x8003, 243}, - {0x8006, 243}, - {0x800a, 243}, - {0x800f, 243}, - {0x8018, 243}, - {0x801f, 243}, - {0x8029, 243}, - {0xc038, 243}, - }, - /* 205 */ - { - {0x8003, 255}, - {0x8006, 255}, - {0x800a, 255}, - {0x800f, 255}, - {0x8018, 255}, - {0x801f, 255}, - {0x8029, 255}, - {0xc038, 255}, - {0x8002, 203}, - {0x8009, 203}, - {0x8017, 203}, - {0xc028, 203}, - {0x8002, 204}, - {0x8009, 204}, - {0x8017, 204}, - {0xc028, 204}, - }, - /* 206 */ - { - {0x8003, 203}, - {0x8006, 203}, - {0x800a, 203}, - {0x800f, 203}, - {0x8018, 203}, - {0x801f, 203}, - {0x8029, 203}, - {0xc038, 203}, - {0x8003, 204}, - {0x8006, 204}, - {0x800a, 204}, - {0x800f, 204}, - {0x8018, 204}, - {0x801f, 204}, - {0x8029, 204}, - {0xc038, 204}, - }, - /* 207 */ - { - {0xd3, 0}, - {0xd4, 0}, - {0xd6, 0}, - {0xd7, 0}, - {0xda, 0}, - {0xdb, 0}, - {0xdd, 0}, - {0xde, 0}, - {0xe2, 0}, - {0xe4, 0}, - {0xe8, 0}, - {0xeb, 0}, - {0xf0, 0}, - {0xf3, 0}, - {0xf7, 0}, - {0xfa, 0}, - }, - /* 208 */ - { - {0xc000, 211}, - {0xc000, 212}, - {0xc000, 214}, - {0xc000, 221}, - {0xc000, 222}, - {0xc000, 223}, - {0xc000, 241}, - {0xc000, 244}, - {0xc000, 245}, - {0xc000, 246}, - {0xc000, 247}, - {0xc000, 248}, - {0xc000, 250}, - {0xc000, 251}, - {0xc000, 252}, - {0xc000, 253}, - }, - /* 209 */ - { - {0x8001, 211}, - {0xc016, 211}, - {0x8001, 212}, - {0xc016, 212}, - {0x8001, 214}, - {0xc016, 214}, - {0x8001, 221}, - {0xc016, 221}, - {0x8001, 222}, - {0xc016, 222}, - {0x8001, 223}, - {0xc016, 223}, - {0x8001, 241}, - {0xc016, 241}, - {0x8001, 244}, - {0xc016, 244}, - }, - /* 210 */ - { - {0x8002, 211}, - {0x8009, 211}, - {0x8017, 211}, - {0xc028, 211}, - {0x8002, 212}, - {0x8009, 212}, - {0x8017, 212}, - {0xc028, 212}, - {0x8002, 214}, - {0x8009, 214}, - {0x8017, 214}, - {0xc028, 214}, - {0x8002, 221}, - {0x8009, 221}, - {0x8017, 221}, - {0xc028, 221}, - }, - /* 211 */ - { - {0x8003, 211}, - {0x8006, 211}, - {0x800a, 211}, - {0x800f, 211}, - {0x8018, 211}, - {0x801f, 211}, - {0x8029, 211}, - {0xc038, 211}, - {0x8003, 212}, - {0x8006, 212}, - {0x800a, 212}, - {0x800f, 212}, - {0x8018, 212}, - {0x801f, 212}, - {0x8029, 212}, - {0xc038, 212}, - }, - /* 212 */ - { - {0x8003, 214}, - {0x8006, 214}, - {0x800a, 214}, - {0x800f, 214}, - {0x8018, 214}, - {0x801f, 214}, - {0x8029, 214}, - {0xc038, 214}, - {0x8003, 221}, - {0x8006, 221}, - {0x800a, 221}, - {0x800f, 221}, - {0x8018, 221}, - {0x801f, 221}, - {0x8029, 221}, - {0xc038, 221}, - }, - /* 213 */ - { - {0x8002, 222}, - {0x8009, 222}, - {0x8017, 222}, - {0xc028, 222}, - {0x8002, 223}, - {0x8009, 223}, - {0x8017, 223}, - {0xc028, 223}, - {0x8002, 241}, - {0x8009, 241}, - {0x8017, 241}, - {0xc028, 241}, - {0x8002, 244}, - {0x8009, 244}, - {0x8017, 244}, - {0xc028, 244}, - }, - /* 214 */ - { - {0x8003, 222}, - {0x8006, 222}, - {0x800a, 222}, - {0x800f, 222}, - {0x8018, 222}, - {0x801f, 222}, - {0x8029, 222}, - {0xc038, 222}, - {0x8003, 223}, - {0x8006, 223}, - {0x800a, 223}, - {0x800f, 223}, - {0x8018, 223}, - {0x801f, 223}, - {0x8029, 223}, - {0xc038, 223}, - }, - /* 215 */ - { - {0x8003, 241}, - {0x8006, 241}, - {0x800a, 241}, - {0x800f, 241}, - {0x8018, 241}, - {0x801f, 241}, - {0x8029, 241}, - {0xc038, 241}, - {0x8003, 244}, - {0x8006, 244}, - {0x800a, 244}, - {0x800f, 244}, - {0x8018, 244}, - {0x801f, 244}, - {0x8029, 244}, - {0xc038, 244}, - }, - /* 216 */ - { - {0x8001, 245}, - {0xc016, 245}, - {0x8001, 246}, - {0xc016, 246}, - {0x8001, 247}, - {0xc016, 247}, - {0x8001, 248}, - {0xc016, 248}, - {0x8001, 250}, - {0xc016, 250}, - {0x8001, 251}, - {0xc016, 251}, - {0x8001, 252}, - {0xc016, 252}, - {0x8001, 253}, - {0xc016, 253}, - }, - /* 217 */ - { - {0x8002, 245}, - {0x8009, 245}, - {0x8017, 245}, - {0xc028, 245}, - {0x8002, 246}, - {0x8009, 246}, - {0x8017, 246}, - {0xc028, 246}, - {0x8002, 247}, - {0x8009, 247}, - {0x8017, 247}, - {0xc028, 247}, - {0x8002, 248}, - {0x8009, 248}, - {0x8017, 248}, - {0xc028, 248}, - }, - /* 218 */ - { - {0x8003, 245}, - {0x8006, 245}, - {0x800a, 245}, - {0x800f, 245}, - {0x8018, 245}, - {0x801f, 245}, - {0x8029, 245}, - {0xc038, 245}, - {0x8003, 246}, - {0x8006, 246}, - {0x800a, 246}, - {0x800f, 246}, - {0x8018, 246}, - {0x801f, 246}, - {0x8029, 246}, - {0xc038, 246}, - }, - /* 219 */ - { - {0x8003, 247}, - {0x8006, 247}, - {0x800a, 247}, - {0x800f, 247}, - {0x8018, 247}, - {0x801f, 247}, - {0x8029, 247}, - {0xc038, 247}, - {0x8003, 248}, - {0x8006, 248}, - {0x800a, 248}, - {0x800f, 248}, - {0x8018, 248}, - {0x801f, 248}, - {0x8029, 248}, - {0xc038, 248}, - }, - /* 220 */ - { - {0x8002, 250}, - {0x8009, 250}, - {0x8017, 250}, - {0xc028, 250}, - {0x8002, 251}, - {0x8009, 251}, - {0x8017, 251}, - {0xc028, 251}, - {0x8002, 252}, - {0x8009, 252}, - {0x8017, 252}, - {0xc028, 252}, - {0x8002, 253}, - {0x8009, 253}, - {0x8017, 253}, - {0xc028, 253}, - }, - /* 221 */ - { - {0x8003, 250}, - {0x8006, 250}, - {0x800a, 250}, - {0x800f, 250}, - {0x8018, 250}, - {0x801f, 250}, - {0x8029, 250}, - {0xc038, 250}, - {0x8003, 251}, - {0x8006, 251}, - {0x800a, 251}, - {0x800f, 251}, - {0x8018, 251}, - {0x801f, 251}, - {0x8029, 251}, - {0xc038, 251}, - }, - /* 222 */ - { - {0x8003, 252}, - {0x8006, 252}, - {0x800a, 252}, - {0x800f, 252}, - {0x8018, 252}, - {0x801f, 252}, - {0x8029, 252}, - {0xc038, 252}, - {0x8003, 253}, - {0x8006, 253}, - {0x800a, 253}, - {0x800f, 253}, - {0x8018, 253}, - {0x801f, 253}, - {0x8029, 253}, - {0xc038, 253}, - }, - /* 223 */ - { - {0xc000, 254}, - {0xe3, 0}, - {0xe5, 0}, - {0xe6, 0}, - {0xe9, 0}, - {0xea, 0}, - {0xec, 0}, - {0xed, 0}, - {0xf1, 0}, - {0xf2, 0}, - {0xf4, 0}, - {0xf5, 0}, - {0xf8, 0}, - {0xf9, 0}, - {0xfb, 0}, - {0xfc, 0}, - }, - /* 224 */ - { - {0x8001, 254}, - {0xc016, 254}, - {0xc000, 2}, - {0xc000, 3}, - {0xc000, 4}, - {0xc000, 5}, - {0xc000, 6}, - {0xc000, 7}, - {0xc000, 8}, - {0xc000, 11}, - {0xc000, 12}, - {0xc000, 14}, - {0xc000, 15}, - {0xc000, 16}, - {0xc000, 17}, - {0xc000, 18}, - }, - /* 225 */ - { - {0x8002, 254}, - {0x8009, 254}, - {0x8017, 254}, - {0xc028, 254}, - {0x8001, 2}, - {0xc016, 2}, - {0x8001, 3}, - {0xc016, 3}, - {0x8001, 4}, - {0xc016, 4}, - {0x8001, 5}, - {0xc016, 5}, - {0x8001, 6}, - {0xc016, 6}, - {0x8001, 7}, - {0xc016, 7}, - }, - /* 226 */ - { - {0x8003, 254}, - {0x8006, 254}, - {0x800a, 254}, - {0x800f, 254}, - {0x8018, 254}, - {0x801f, 254}, - {0x8029, 254}, - {0xc038, 254}, - {0x8002, 2}, - {0x8009, 2}, - {0x8017, 2}, - {0xc028, 2}, - {0x8002, 3}, - {0x8009, 3}, - {0x8017, 3}, - {0xc028, 3}, - }, - /* 227 */ - { - {0x8003, 2}, - {0x8006, 2}, - {0x800a, 2}, - {0x800f, 2}, - {0x8018, 2}, - {0x801f, 2}, - {0x8029, 2}, - {0xc038, 2}, - {0x8003, 3}, - {0x8006, 3}, - {0x800a, 3}, - {0x800f, 3}, - {0x8018, 3}, - {0x801f, 3}, - {0x8029, 3}, - {0xc038, 3}, - }, - /* 228 */ - { - {0x8002, 4}, - {0x8009, 4}, - {0x8017, 4}, - {0xc028, 4}, - {0x8002, 5}, - {0x8009, 5}, - {0x8017, 5}, - {0xc028, 5}, - {0x8002, 6}, - {0x8009, 6}, - {0x8017, 6}, - {0xc028, 6}, - {0x8002, 7}, - {0x8009, 7}, - {0x8017, 7}, - {0xc028, 7}, - }, - /* 229 */ - { - {0x8003, 4}, - {0x8006, 4}, - {0x800a, 4}, - {0x800f, 4}, - {0x8018, 4}, - {0x801f, 4}, - {0x8029, 4}, - {0xc038, 4}, - {0x8003, 5}, - {0x8006, 5}, - {0x800a, 5}, - {0x800f, 5}, - {0x8018, 5}, - {0x801f, 5}, - {0x8029, 5}, - {0xc038, 5}, - }, - /* 230 */ - { - {0x8003, 6}, - {0x8006, 6}, - {0x800a, 6}, - {0x800f, 6}, - {0x8018, 6}, - {0x801f, 6}, - {0x8029, 6}, - {0xc038, 6}, - {0x8003, 7}, - {0x8006, 7}, - {0x800a, 7}, - {0x800f, 7}, - {0x8018, 7}, - {0x801f, 7}, - {0x8029, 7}, - {0xc038, 7}, - }, - /* 231 */ - { - {0x8001, 8}, - {0xc016, 8}, - {0x8001, 11}, - {0xc016, 11}, - {0x8001, 12}, - {0xc016, 12}, - {0x8001, 14}, - {0xc016, 14}, - {0x8001, 15}, - {0xc016, 15}, - {0x8001, 16}, - {0xc016, 16}, - {0x8001, 17}, - {0xc016, 17}, - {0x8001, 18}, - {0xc016, 18}, - }, - /* 232 */ - { - {0x8002, 8}, - {0x8009, 8}, - {0x8017, 8}, - {0xc028, 8}, - {0x8002, 11}, - {0x8009, 11}, - {0x8017, 11}, - {0xc028, 11}, - {0x8002, 12}, - {0x8009, 12}, - {0x8017, 12}, - {0xc028, 12}, - {0x8002, 14}, - {0x8009, 14}, - {0x8017, 14}, - {0xc028, 14}, - }, - /* 233 */ - { - {0x8003, 8}, - {0x8006, 8}, - {0x800a, 8}, - {0x800f, 8}, - {0x8018, 8}, - {0x801f, 8}, - {0x8029, 8}, - {0xc038, 8}, - {0x8003, 11}, - {0x8006, 11}, - {0x800a, 11}, - {0x800f, 11}, - {0x8018, 11}, - {0x801f, 11}, - {0x8029, 11}, - {0xc038, 11}, - }, - /* 234 */ - { - {0x8003, 12}, - {0x8006, 12}, - {0x800a, 12}, - {0x800f, 12}, - {0x8018, 12}, - {0x801f, 12}, - {0x8029, 12}, - {0xc038, 12}, - {0x8003, 14}, - {0x8006, 14}, - {0x800a, 14}, - {0x800f, 14}, - {0x8018, 14}, - {0x801f, 14}, - {0x8029, 14}, - {0xc038, 14}, - }, - /* 235 */ - { - {0x8002, 15}, - {0x8009, 15}, - {0x8017, 15}, - {0xc028, 15}, - {0x8002, 16}, - {0x8009, 16}, - {0x8017, 16}, - {0xc028, 16}, - {0x8002, 17}, - {0x8009, 17}, - {0x8017, 17}, - {0xc028, 17}, - {0x8002, 18}, - {0x8009, 18}, - {0x8017, 18}, - {0xc028, 18}, - }, - /* 236 */ - { - {0x8003, 15}, - {0x8006, 15}, - {0x800a, 15}, - {0x800f, 15}, - {0x8018, 15}, - {0x801f, 15}, - {0x8029, 15}, - {0xc038, 15}, - {0x8003, 16}, - {0x8006, 16}, - {0x800a, 16}, - {0x800f, 16}, - {0x8018, 16}, - {0x801f, 16}, - {0x8029, 16}, - {0xc038, 16}, - }, - /* 237 */ - { - {0x8003, 17}, - {0x8006, 17}, - {0x800a, 17}, - {0x800f, 17}, - {0x8018, 17}, - {0x801f, 17}, - {0x8029, 17}, - {0xc038, 17}, - {0x8003, 18}, - {0x8006, 18}, - {0x800a, 18}, - {0x800f, 18}, - {0x8018, 18}, - {0x801f, 18}, - {0x8029, 18}, - {0xc038, 18}, - }, - /* 238 */ - { - {0xc000, 19}, - {0xc000, 20}, - {0xc000, 21}, - {0xc000, 23}, - {0xc000, 24}, - {0xc000, 25}, - {0xc000, 26}, - {0xc000, 27}, - {0xc000, 28}, - {0xc000, 29}, - {0xc000, 30}, - {0xc000, 31}, - {0xc000, 127}, - {0xc000, 220}, - {0xc000, 249}, - {0xfd, 0}, - }, - /* 239 */ - { - {0x8001, 19}, - {0xc016, 19}, - {0x8001, 20}, - {0xc016, 20}, - {0x8001, 21}, - {0xc016, 21}, - {0x8001, 23}, - {0xc016, 23}, - {0x8001, 24}, - {0xc016, 24}, - {0x8001, 25}, - {0xc016, 25}, - {0x8001, 26}, - {0xc016, 26}, - {0x8001, 27}, - {0xc016, 27}, - }, - /* 240 */ - { - {0x8002, 19}, - {0x8009, 19}, - {0x8017, 19}, - {0xc028, 19}, - {0x8002, 20}, - {0x8009, 20}, - {0x8017, 20}, - {0xc028, 20}, - {0x8002, 21}, - {0x8009, 21}, - {0x8017, 21}, - {0xc028, 21}, - {0x8002, 23}, - {0x8009, 23}, - {0x8017, 23}, - {0xc028, 23}, - }, - /* 241 */ - { - {0x8003, 19}, - {0x8006, 19}, - {0x800a, 19}, - {0x800f, 19}, - {0x8018, 19}, - {0x801f, 19}, - {0x8029, 19}, - {0xc038, 19}, - {0x8003, 20}, - {0x8006, 20}, - {0x800a, 20}, - {0x800f, 20}, - {0x8018, 20}, - {0x801f, 20}, - {0x8029, 20}, - {0xc038, 20}, - }, - /* 242 */ - { - {0x8003, 21}, - {0x8006, 21}, - {0x800a, 21}, - {0x800f, 21}, - {0x8018, 21}, - {0x801f, 21}, - {0x8029, 21}, - {0xc038, 21}, - {0x8003, 23}, - {0x8006, 23}, - {0x800a, 23}, - {0x800f, 23}, - {0x8018, 23}, - {0x801f, 23}, - {0x8029, 23}, - {0xc038, 23}, - }, - /* 243 */ - { - {0x8002, 24}, - {0x8009, 24}, - {0x8017, 24}, - {0xc028, 24}, - {0x8002, 25}, - {0x8009, 25}, - {0x8017, 25}, - {0xc028, 25}, - {0x8002, 26}, - {0x8009, 26}, - {0x8017, 26}, - {0xc028, 26}, - {0x8002, 27}, - {0x8009, 27}, - {0x8017, 27}, - {0xc028, 27}, - }, - /* 244 */ - { - {0x8003, 24}, - {0x8006, 24}, - {0x800a, 24}, - {0x800f, 24}, - {0x8018, 24}, - {0x801f, 24}, - {0x8029, 24}, - {0xc038, 24}, - {0x8003, 25}, - {0x8006, 25}, - {0x800a, 25}, - {0x800f, 25}, - {0x8018, 25}, - {0x801f, 25}, - {0x8029, 25}, - {0xc038, 25}, - }, - /* 245 */ - { - {0x8003, 26}, - {0x8006, 26}, - {0x800a, 26}, - {0x800f, 26}, - {0x8018, 26}, - {0x801f, 26}, - {0x8029, 26}, - {0xc038, 26}, - {0x8003, 27}, - {0x8006, 27}, - {0x800a, 27}, - {0x800f, 27}, - {0x8018, 27}, - {0x801f, 27}, - {0x8029, 27}, - {0xc038, 27}, - }, - /* 246 */ - { - {0x8001, 28}, - {0xc016, 28}, - {0x8001, 29}, - {0xc016, 29}, - {0x8001, 30}, - {0xc016, 30}, - {0x8001, 31}, - {0xc016, 31}, - {0x8001, 127}, - {0xc016, 127}, - {0x8001, 220}, - {0xc016, 220}, - {0x8001, 249}, - {0xc016, 249}, - {0xfe, 0}, - {0xff, 0}, - }, - /* 247 */ - { - {0x8002, 28}, - {0x8009, 28}, - {0x8017, 28}, - {0xc028, 28}, - {0x8002, 29}, - {0x8009, 29}, - {0x8017, 29}, - {0xc028, 29}, - {0x8002, 30}, - {0x8009, 30}, - {0x8017, 30}, - {0xc028, 30}, - {0x8002, 31}, - {0x8009, 31}, - {0x8017, 31}, - {0xc028, 31}, - }, - /* 248 */ - { - {0x8003, 28}, - {0x8006, 28}, - {0x800a, 28}, - {0x800f, 28}, - {0x8018, 28}, - {0x801f, 28}, - {0x8029, 28}, - {0xc038, 28}, - {0x8003, 29}, - {0x8006, 29}, - {0x800a, 29}, - {0x800f, 29}, - {0x8018, 29}, - {0x801f, 29}, - {0x8029, 29}, - {0xc038, 29}, - }, - /* 249 */ - { - {0x8003, 30}, - {0x8006, 30}, - {0x800a, 30}, - {0x800f, 30}, - {0x8018, 30}, - {0x801f, 30}, - {0x8029, 30}, - {0xc038, 30}, - {0x8003, 31}, - {0x8006, 31}, - {0x800a, 31}, - {0x800f, 31}, - {0x8018, 31}, - {0x801f, 31}, - {0x8029, 31}, - {0xc038, 31}, - }, - /* 250 */ - { - {0x8002, 127}, - {0x8009, 127}, - {0x8017, 127}, - {0xc028, 127}, - {0x8002, 220}, - {0x8009, 220}, - {0x8017, 220}, - {0xc028, 220}, - {0x8002, 249}, - {0x8009, 249}, - {0x8017, 249}, - {0xc028, 249}, - {0xc000, 10}, - {0xc000, 13}, - {0xc000, 22}, - {0x100, 0}, - }, - /* 251 */ - { - {0x8003, 127}, - {0x8006, 127}, - {0x800a, 127}, - {0x800f, 127}, - {0x8018, 127}, - {0x801f, 127}, - {0x8029, 127}, - {0xc038, 127}, - {0x8003, 220}, - {0x8006, 220}, - {0x800a, 220}, - {0x800f, 220}, - {0x8018, 220}, - {0x801f, 220}, - {0x8029, 220}, - {0xc038, 220}, - }, - /* 252 */ - { - {0x8003, 249}, - {0x8006, 249}, - {0x800a, 249}, - {0x800f, 249}, - {0x8018, 249}, - {0x801f, 249}, - {0x8029, 249}, - {0xc038, 249}, - {0x8001, 10}, - {0xc016, 10}, - {0x8001, 13}, - {0xc016, 13}, - {0x8001, 22}, - {0xc016, 22}, - {0x100, 0}, - {0x100, 0}, - }, - /* 253 */ - { - {0x8002, 10}, - {0x8009, 10}, - {0x8017, 10}, - {0xc028, 10}, - {0x8002, 13}, - {0x8009, 13}, - {0x8017, 13}, - {0xc028, 13}, - {0x8002, 22}, - {0x8009, 22}, - {0x8017, 22}, - {0xc028, 22}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - }, - /* 254 */ - { - {0x8003, 10}, - {0x8006, 10}, - {0x800a, 10}, - {0x800f, 10}, - {0x8018, 10}, - {0x801f, 10}, - {0x8029, 10}, - {0xc038, 10}, - {0x8003, 13}, - {0x8006, 13}, - {0x800a, 13}, - {0x800f, 13}, - {0x8018, 13}, - {0x801f, 13}, - {0x8029, 13}, - {0xc038, 13}, - }, - /* 255 */ - { - {0x8003, 22}, - {0x8006, 22}, - {0x800a, 22}, - {0x800f, 22}, - {0x8018, 22}, - {0x801f, 22}, - {0x8029, 22}, - {0xc038, 22}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - }, - /* 256 */ - { - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - {0x100, 0}, - }, + /* 0 */ + { + {0x04, 0}, + {0x05, 0}, + {0x07, 0}, + {0x08, 0}, + {0x0b, 0}, + {0x0c, 0}, + {0x10, 0}, + {0x13, 0}, + {0x19, 0}, + {0x1c, 0}, + {0x20, 0}, + {0x23, 0}, + {0x2a, 0}, + {0x31, 0}, + {0x39, 0}, + {0x4040, 0}, + }, + /* 1 */ + { + {0xc000, 48}, + {0xc000, 49}, + {0xc000, 50}, + {0xc000, 97}, + {0xc000, 99}, + {0xc000, 101}, + {0xc000, 105}, + {0xc000, 111}, + {0xc000, 115}, + {0xc000, 116}, + {0x0d, 0}, + {0x0e, 0}, + {0x11, 0}, + {0x12, 0}, + {0x14, 0}, + {0x15, 0}, + }, + /* 2 */ + { + {0x8001, 48}, + {0xc016, 48}, + {0x8001, 49}, + {0xc016, 49}, + {0x8001, 50}, + {0xc016, 50}, + {0x8001, 97}, + {0xc016, 97}, + {0x8001, 99}, + {0xc016, 99}, + {0x8001, 101}, + {0xc016, 101}, + {0x8001, 105}, + {0xc016, 105}, + {0x8001, 111}, + {0xc016, 111}, + }, + /* 3 */ + { + {0x8002, 48}, + {0x8009, 48}, + {0x8017, 48}, + {0xc028, 48}, + {0x8002, 49}, + {0x8009, 49}, + {0x8017, 49}, + {0xc028, 49}, + {0x8002, 50}, + {0x8009, 50}, + {0x8017, 50}, + {0xc028, 50}, + {0x8002, 97}, + {0x8009, 97}, + {0x8017, 97}, + {0xc028, 97}, + }, + /* 4 */ + { + {0x8003, 48}, + {0x8006, 48}, + {0x800a, 48}, + {0x800f, 48}, + {0x8018, 48}, + {0x801f, 48}, + {0x8029, 48}, + {0xc038, 48}, + {0x8003, 49}, + {0x8006, 49}, + {0x800a, 49}, + {0x800f, 49}, + {0x8018, 49}, + {0x801f, 49}, + {0x8029, 49}, + {0xc038, 49}, + }, + /* 5 */ + { + {0x8003, 50}, + {0x8006, 50}, + {0x800a, 50}, + {0x800f, 50}, + {0x8018, 50}, + {0x801f, 50}, + {0x8029, 50}, + {0xc038, 50}, + {0x8003, 97}, + {0x8006, 97}, + {0x800a, 97}, + {0x800f, 97}, + {0x8018, 97}, + {0x801f, 97}, + {0x8029, 97}, + {0xc038, 97}, + }, + /* 6 */ + { + {0x8002, 99}, + {0x8009, 99}, + {0x8017, 99}, + {0xc028, 99}, + {0x8002, 101}, + {0x8009, 101}, + {0x8017, 101}, + {0xc028, 101}, + {0x8002, 105}, + {0x8009, 105}, + {0x8017, 105}, + {0xc028, 105}, + {0x8002, 111}, + {0x8009, 111}, + {0x8017, 111}, + {0xc028, 111}, + }, + /* 7 */ + { + {0x8003, 99}, + {0x8006, 99}, + {0x800a, 99}, + {0x800f, 99}, + {0x8018, 99}, + {0x801f, 99}, + {0x8029, 99}, + {0xc038, 99}, + {0x8003, 101}, + {0x8006, 101}, + {0x800a, 101}, + {0x800f, 101}, + {0x8018, 101}, + {0x801f, 101}, + {0x8029, 101}, + {0xc038, 101}, + }, + /* 8 */ + { + {0x8003, 105}, + {0x8006, 105}, + {0x800a, 105}, + {0x800f, 105}, + {0x8018, 105}, + {0x801f, 105}, + {0x8029, 105}, + {0xc038, 105}, + {0x8003, 111}, + {0x8006, 111}, + {0x800a, 111}, + {0x800f, 111}, + {0x8018, 111}, + {0x801f, 111}, + {0x8029, 111}, + {0xc038, 111}, + }, + /* 9 */ + { + {0x8001, 115}, + {0xc016, 115}, + {0x8001, 116}, + {0xc016, 116}, + {0xc000, 32}, + {0xc000, 37}, + {0xc000, 45}, + {0xc000, 46}, + {0xc000, 47}, + {0xc000, 51}, + {0xc000, 52}, + {0xc000, 53}, + {0xc000, 54}, + {0xc000, 55}, + {0xc000, 56}, + {0xc000, 57}, + }, + /* 10 */ + { + {0x8002, 115}, + {0x8009, 115}, + {0x8017, 115}, + {0xc028, 115}, + {0x8002, 116}, + {0x8009, 116}, + {0x8017, 116}, + {0xc028, 116}, + {0x8001, 32}, + {0xc016, 32}, + {0x8001, 37}, + {0xc016, 37}, + {0x8001, 45}, + {0xc016, 45}, + {0x8001, 46}, + {0xc016, 46}, + }, + /* 11 */ + { + {0x8003, 115}, + {0x8006, 115}, + {0x800a, 115}, + {0x800f, 115}, + {0x8018, 115}, + {0x801f, 115}, + {0x8029, 115}, + {0xc038, 115}, + {0x8003, 116}, + {0x8006, 116}, + {0x800a, 116}, + {0x800f, 116}, + {0x8018, 116}, + {0x801f, 116}, + {0x8029, 116}, + {0xc038, 116}, + }, + /* 12 */ + { + {0x8002, 32}, + {0x8009, 32}, + {0x8017, 32}, + {0xc028, 32}, + {0x8002, 37}, + {0x8009, 37}, + {0x8017, 37}, + {0xc028, 37}, + {0x8002, 45}, + {0x8009, 45}, + {0x8017, 45}, + {0xc028, 45}, + {0x8002, 46}, + {0x8009, 46}, + {0x8017, 46}, + {0xc028, 46}, + }, + /* 13 */ + { + {0x8003, 32}, + {0x8006, 32}, + {0x800a, 32}, + {0x800f, 32}, + {0x8018, 32}, + {0x801f, 32}, + {0x8029, 32}, + {0xc038, 32}, + {0x8003, 37}, + {0x8006, 37}, + {0x800a, 37}, + {0x800f, 37}, + {0x8018, 37}, + {0x801f, 37}, + {0x8029, 37}, + {0xc038, 37}, + }, + /* 14 */ + { + {0x8003, 45}, + {0x8006, 45}, + {0x800a, 45}, + {0x800f, 45}, + {0x8018, 45}, + {0x801f, 45}, + {0x8029, 45}, + {0xc038, 45}, + {0x8003, 46}, + {0x8006, 46}, + {0x800a, 46}, + {0x800f, 46}, + {0x8018, 46}, + {0x801f, 46}, + {0x8029, 46}, + {0xc038, 46}, + }, + /* 15 */ + { + {0x8001, 47}, + {0xc016, 47}, + {0x8001, 51}, + {0xc016, 51}, + {0x8001, 52}, + {0xc016, 52}, + {0x8001, 53}, + {0xc016, 53}, + {0x8001, 54}, + {0xc016, 54}, + {0x8001, 55}, + {0xc016, 55}, + {0x8001, 56}, + {0xc016, 56}, + {0x8001, 57}, + {0xc016, 57}, + }, + /* 16 */ + { + {0x8002, 47}, + {0x8009, 47}, + {0x8017, 47}, + {0xc028, 47}, + {0x8002, 51}, + {0x8009, 51}, + {0x8017, 51}, + {0xc028, 51}, + {0x8002, 52}, + {0x8009, 52}, + {0x8017, 52}, + {0xc028, 52}, + {0x8002, 53}, + {0x8009, 53}, + {0x8017, 53}, + {0xc028, 53}, + }, + /* 17 */ + { + {0x8003, 47}, + {0x8006, 47}, + {0x800a, 47}, + {0x800f, 47}, + {0x8018, 47}, + {0x801f, 47}, + {0x8029, 47}, + {0xc038, 47}, + {0x8003, 51}, + {0x8006, 51}, + {0x800a, 51}, + {0x800f, 51}, + {0x8018, 51}, + {0x801f, 51}, + {0x8029, 51}, + {0xc038, 51}, + }, + /* 18 */ + { + {0x8003, 52}, + {0x8006, 52}, + {0x800a, 52}, + {0x800f, 52}, + {0x8018, 52}, + {0x801f, 52}, + {0x8029, 52}, + {0xc038, 52}, + {0x8003, 53}, + {0x8006, 53}, + {0x800a, 53}, + {0x800f, 53}, + {0x8018, 53}, + {0x801f, 53}, + {0x8029, 53}, + {0xc038, 53}, + }, + /* 19 */ + { + {0x8002, 54}, + {0x8009, 54}, + {0x8017, 54}, + {0xc028, 54}, + {0x8002, 55}, + {0x8009, 55}, + {0x8017, 55}, + {0xc028, 55}, + {0x8002, 56}, + {0x8009, 56}, + {0x8017, 56}, + {0xc028, 56}, + {0x8002, 57}, + {0x8009, 57}, + {0x8017, 57}, + {0xc028, 57}, + }, + /* 20 */ + { + {0x8003, 54}, + {0x8006, 54}, + {0x800a, 54}, + {0x800f, 54}, + {0x8018, 54}, + {0x801f, 54}, + {0x8029, 54}, + {0xc038, 54}, + {0x8003, 55}, + {0x8006, 55}, + {0x800a, 55}, + {0x800f, 55}, + {0x8018, 55}, + {0x801f, 55}, + {0x8029, 55}, + {0xc038, 55}, + }, + /* 21 */ + { + {0x8003, 56}, + {0x8006, 56}, + {0x800a, 56}, + {0x800f, 56}, + {0x8018, 56}, + {0x801f, 56}, + {0x8029, 56}, + {0xc038, 56}, + {0x8003, 57}, + {0x8006, 57}, + {0x800a, 57}, + {0x800f, 57}, + {0x8018, 57}, + {0x801f, 57}, + {0x8029, 57}, + {0xc038, 57}, + }, + /* 22 */ + { + {0x1a, 0}, + {0x1b, 0}, + {0x1d, 0}, + {0x1e, 0}, + {0x21, 0}, + {0x22, 0}, + {0x24, 0}, + {0x25, 0}, + {0x2b, 0}, + {0x2e, 0}, + {0x32, 0}, + {0x35, 0}, + {0x3a, 0}, + {0x3d, 0}, + {0x41, 0}, + {0x4044, 0}, + }, + /* 23 */ + { + {0xc000, 61}, + {0xc000, 65}, + {0xc000, 95}, + {0xc000, 98}, + {0xc000, 100}, + {0xc000, 102}, + {0xc000, 103}, + {0xc000, 104}, + {0xc000, 108}, + {0xc000, 109}, + {0xc000, 110}, + {0xc000, 112}, + {0xc000, 114}, + {0xc000, 117}, + {0x26, 0}, + {0x27, 0}, + }, + /* 24 */ + { + {0x8001, 61}, + {0xc016, 61}, + {0x8001, 65}, + {0xc016, 65}, + {0x8001, 95}, + {0xc016, 95}, + {0x8001, 98}, + {0xc016, 98}, + {0x8001, 100}, + {0xc016, 100}, + {0x8001, 102}, + {0xc016, 102}, + {0x8001, 103}, + {0xc016, 103}, + {0x8001, 104}, + {0xc016, 104}, + }, + /* 25 */ + { + {0x8002, 61}, + {0x8009, 61}, + {0x8017, 61}, + {0xc028, 61}, + {0x8002, 65}, + {0x8009, 65}, + {0x8017, 65}, + {0xc028, 65}, + {0x8002, 95}, + {0x8009, 95}, + {0x8017, 95}, + {0xc028, 95}, + {0x8002, 98}, + {0x8009, 98}, + {0x8017, 98}, + {0xc028, 98}, + }, + /* 26 */ + { + {0x8003, 61}, + {0x8006, 61}, + {0x800a, 61}, + {0x800f, 61}, + {0x8018, 61}, + {0x801f, 61}, + {0x8029, 61}, + {0xc038, 61}, + {0x8003, 65}, + {0x8006, 65}, + {0x800a, 65}, + {0x800f, 65}, + {0x8018, 65}, + {0x801f, 65}, + {0x8029, 65}, + {0xc038, 65}, + }, + /* 27 */ + { + {0x8003, 95}, + {0x8006, 95}, + {0x800a, 95}, + {0x800f, 95}, + {0x8018, 95}, + {0x801f, 95}, + {0x8029, 95}, + {0xc038, 95}, + {0x8003, 98}, + {0x8006, 98}, + {0x800a, 98}, + {0x800f, 98}, + {0x8018, 98}, + {0x801f, 98}, + {0x8029, 98}, + {0xc038, 98}, + }, + /* 28 */ + { + {0x8002, 100}, + {0x8009, 100}, + {0x8017, 100}, + {0xc028, 100}, + {0x8002, 102}, + {0x8009, 102}, + {0x8017, 102}, + {0xc028, 102}, + {0x8002, 103}, + {0x8009, 103}, + {0x8017, 103}, + {0xc028, 103}, + {0x8002, 104}, + {0x8009, 104}, + {0x8017, 104}, + {0xc028, 104}, + }, + /* 29 */ + { + {0x8003, 100}, + {0x8006, 100}, + {0x800a, 100}, + {0x800f, 100}, + {0x8018, 100}, + {0x801f, 100}, + {0x8029, 100}, + {0xc038, 100}, + {0x8003, 102}, + {0x8006, 102}, + {0x800a, 102}, + {0x800f, 102}, + {0x8018, 102}, + {0x801f, 102}, + {0x8029, 102}, + {0xc038, 102}, + }, + /* 30 */ + { + {0x8003, 103}, + {0x8006, 103}, + {0x800a, 103}, + {0x800f, 103}, + {0x8018, 103}, + {0x801f, 103}, + {0x8029, 103}, + {0xc038, 103}, + {0x8003, 104}, + {0x8006, 104}, + {0x800a, 104}, + {0x800f, 104}, + {0x8018, 104}, + {0x801f, 104}, + {0x8029, 104}, + {0xc038, 104}, + }, + /* 31 */ + { + {0x8001, 108}, + {0xc016, 108}, + {0x8001, 109}, + {0xc016, 109}, + {0x8001, 110}, + {0xc016, 110}, + {0x8001, 112}, + {0xc016, 112}, + {0x8001, 114}, + {0xc016, 114}, + {0x8001, 117}, + {0xc016, 117}, + {0xc000, 58}, + {0xc000, 66}, + {0xc000, 67}, + {0xc000, 68}, + }, + /* 32 */ + { + {0x8002, 108}, + {0x8009, 108}, + {0x8017, 108}, + {0xc028, 108}, + {0x8002, 109}, + {0x8009, 109}, + {0x8017, 109}, + {0xc028, 109}, + {0x8002, 110}, + {0x8009, 110}, + {0x8017, 110}, + {0xc028, 110}, + {0x8002, 112}, + {0x8009, 112}, + {0x8017, 112}, + {0xc028, 112}, + }, + /* 33 */ + { + {0x8003, 108}, + {0x8006, 108}, + {0x800a, 108}, + {0x800f, 108}, + {0x8018, 108}, + {0x801f, 108}, + {0x8029, 108}, + {0xc038, 108}, + {0x8003, 109}, + {0x8006, 109}, + {0x800a, 109}, + {0x800f, 109}, + {0x8018, 109}, + {0x801f, 109}, + {0x8029, 109}, + {0xc038, 109}, + }, + /* 34 */ + { + {0x8003, 110}, + {0x8006, 110}, + {0x800a, 110}, + {0x800f, 110}, + {0x8018, 110}, + {0x801f, 110}, + {0x8029, 110}, + {0xc038, 110}, + {0x8003, 112}, + {0x8006, 112}, + {0x800a, 112}, + {0x800f, 112}, + {0x8018, 112}, + {0x801f, 112}, + {0x8029, 112}, + {0xc038, 112}, + }, + /* 35 */ + { + {0x8002, 114}, + {0x8009, 114}, + {0x8017, 114}, + {0xc028, 114}, + {0x8002, 117}, + {0x8009, 117}, + {0x8017, 117}, + {0xc028, 117}, + {0x8001, 58}, + {0xc016, 58}, + {0x8001, 66}, + {0xc016, 66}, + {0x8001, 67}, + {0xc016, 67}, + {0x8001, 68}, + {0xc016, 68}, + }, + /* 36 */ + { + {0x8003, 114}, + {0x8006, 114}, + {0x800a, 114}, + {0x800f, 114}, + {0x8018, 114}, + {0x801f, 114}, + {0x8029, 114}, + {0xc038, 114}, + {0x8003, 117}, + {0x8006, 117}, + {0x800a, 117}, + {0x800f, 117}, + {0x8018, 117}, + {0x801f, 117}, + {0x8029, 117}, + {0xc038, 117}, + }, + /* 37 */ + { + {0x8002, 58}, + {0x8009, 58}, + {0x8017, 58}, + {0xc028, 58}, + {0x8002, 66}, + {0x8009, 66}, + {0x8017, 66}, + {0xc028, 66}, + {0x8002, 67}, + {0x8009, 67}, + {0x8017, 67}, + {0xc028, 67}, + {0x8002, 68}, + {0x8009, 68}, + {0x8017, 68}, + {0xc028, 68}, + }, + /* 38 */ + { + {0x8003, 58}, + {0x8006, 58}, + {0x800a, 58}, + {0x800f, 58}, + {0x8018, 58}, + {0x801f, 58}, + {0x8029, 58}, + {0xc038, 58}, + {0x8003, 66}, + {0x8006, 66}, + {0x800a, 66}, + {0x800f, 66}, + {0x8018, 66}, + {0x801f, 66}, + {0x8029, 66}, + {0xc038, 66}, + }, + /* 39 */ + { + {0x8003, 67}, + {0x8006, 67}, + {0x800a, 67}, + {0x800f, 67}, + {0x8018, 67}, + {0x801f, 67}, + {0x8029, 67}, + {0xc038, 67}, + {0x8003, 68}, + {0x8006, 68}, + {0x800a, 68}, + {0x800f, 68}, + {0x8018, 68}, + {0x801f, 68}, + {0x8029, 68}, + {0xc038, 68}, + }, + /* 40 */ + { + {0x2c, 0}, + {0x2d, 0}, + {0x2f, 0}, + {0x30, 0}, + {0x33, 0}, + {0x34, 0}, + {0x36, 0}, + {0x37, 0}, + {0x3b, 0}, + {0x3c, 0}, + {0x3e, 0}, + {0x3f, 0}, + {0x42, 0}, + {0x43, 0}, + {0x45, 0}, + {0x4048, 0}, + }, + /* 41 */ + { + {0xc000, 69}, + {0xc000, 70}, + {0xc000, 71}, + {0xc000, 72}, + {0xc000, 73}, + {0xc000, 74}, + {0xc000, 75}, + {0xc000, 76}, + {0xc000, 77}, + {0xc000, 78}, + {0xc000, 79}, + {0xc000, 80}, + {0xc000, 81}, + {0xc000, 82}, + {0xc000, 83}, + {0xc000, 84}, + }, + /* 42 */ + { + {0x8001, 69}, + {0xc016, 69}, + {0x8001, 70}, + {0xc016, 70}, + {0x8001, 71}, + {0xc016, 71}, + {0x8001, 72}, + {0xc016, 72}, + {0x8001, 73}, + {0xc016, 73}, + {0x8001, 74}, + {0xc016, 74}, + {0x8001, 75}, + {0xc016, 75}, + {0x8001, 76}, + {0xc016, 76}, + }, + /* 43 */ + { + {0x8002, 69}, + {0x8009, 69}, + {0x8017, 69}, + {0xc028, 69}, + {0x8002, 70}, + {0x8009, 70}, + {0x8017, 70}, + {0xc028, 70}, + {0x8002, 71}, + {0x8009, 71}, + {0x8017, 71}, + {0xc028, 71}, + {0x8002, 72}, + {0x8009, 72}, + {0x8017, 72}, + {0xc028, 72}, + }, + /* 44 */ + { + {0x8003, 69}, + {0x8006, 69}, + {0x800a, 69}, + {0x800f, 69}, + {0x8018, 69}, + {0x801f, 69}, + {0x8029, 69}, + {0xc038, 69}, + {0x8003, 70}, + {0x8006, 70}, + {0x800a, 70}, + {0x800f, 70}, + {0x8018, 70}, + {0x801f, 70}, + {0x8029, 70}, + {0xc038, 70}, + }, + /* 45 */ + { + {0x8003, 71}, + {0x8006, 71}, + {0x800a, 71}, + {0x800f, 71}, + {0x8018, 71}, + {0x801f, 71}, + {0x8029, 71}, + {0xc038, 71}, + {0x8003, 72}, + {0x8006, 72}, + {0x800a, 72}, + {0x800f, 72}, + {0x8018, 72}, + {0x801f, 72}, + {0x8029, 72}, + {0xc038, 72}, + }, + /* 46 */ + { + {0x8002, 73}, + {0x8009, 73}, + {0x8017, 73}, + {0xc028, 73}, + {0x8002, 74}, + {0x8009, 74}, + {0x8017, 74}, + {0xc028, 74}, + {0x8002, 75}, + {0x8009, 75}, + {0x8017, 75}, + {0xc028, 75}, + {0x8002, 76}, + {0x8009, 76}, + {0x8017, 76}, + {0xc028, 76}, + }, + /* 47 */ + { + {0x8003, 73}, + {0x8006, 73}, + {0x800a, 73}, + {0x800f, 73}, + {0x8018, 73}, + {0x801f, 73}, + {0x8029, 73}, + {0xc038, 73}, + {0x8003, 74}, + {0x8006, 74}, + {0x800a, 74}, + {0x800f, 74}, + {0x8018, 74}, + {0x801f, 74}, + {0x8029, 74}, + {0xc038, 74}, + }, + /* 48 */ + { + {0x8003, 75}, + {0x8006, 75}, + {0x800a, 75}, + {0x800f, 75}, + {0x8018, 75}, + {0x801f, 75}, + {0x8029, 75}, + {0xc038, 75}, + {0x8003, 76}, + {0x8006, 76}, + {0x800a, 76}, + {0x800f, 76}, + {0x8018, 76}, + {0x801f, 76}, + {0x8029, 76}, + {0xc038, 76}, + }, + /* 49 */ + { + {0x8001, 77}, + {0xc016, 77}, + {0x8001, 78}, + {0xc016, 78}, + {0x8001, 79}, + {0xc016, 79}, + {0x8001, 80}, + {0xc016, 80}, + {0x8001, 81}, + {0xc016, 81}, + {0x8001, 82}, + {0xc016, 82}, + {0x8001, 83}, + {0xc016, 83}, + {0x8001, 84}, + {0xc016, 84}, + }, + /* 50 */ + { + {0x8002, 77}, + {0x8009, 77}, + {0x8017, 77}, + {0xc028, 77}, + {0x8002, 78}, + {0x8009, 78}, + {0x8017, 78}, + {0xc028, 78}, + {0x8002, 79}, + {0x8009, 79}, + {0x8017, 79}, + {0xc028, 79}, + {0x8002, 80}, + {0x8009, 80}, + {0x8017, 80}, + {0xc028, 80}, + }, + /* 51 */ + { + {0x8003, 77}, + {0x8006, 77}, + {0x800a, 77}, + {0x800f, 77}, + {0x8018, 77}, + {0x801f, 77}, + {0x8029, 77}, + {0xc038, 77}, + {0x8003, 78}, + {0x8006, 78}, + {0x800a, 78}, + {0x800f, 78}, + {0x8018, 78}, + {0x801f, 78}, + {0x8029, 78}, + {0xc038, 78}, + }, + /* 52 */ + { + {0x8003, 79}, + {0x8006, 79}, + {0x800a, 79}, + {0x800f, 79}, + {0x8018, 79}, + {0x801f, 79}, + {0x8029, 79}, + {0xc038, 79}, + {0x8003, 80}, + {0x8006, 80}, + {0x800a, 80}, + {0x800f, 80}, + {0x8018, 80}, + {0x801f, 80}, + {0x8029, 80}, + {0xc038, 80}, + }, + /* 53 */ + { + {0x8002, 81}, + {0x8009, 81}, + {0x8017, 81}, + {0xc028, 81}, + {0x8002, 82}, + {0x8009, 82}, + {0x8017, 82}, + {0xc028, 82}, + {0x8002, 83}, + {0x8009, 83}, + {0x8017, 83}, + {0xc028, 83}, + {0x8002, 84}, + {0x8009, 84}, + {0x8017, 84}, + {0xc028, 84}, + }, + /* 54 */ + { + {0x8003, 81}, + {0x8006, 81}, + {0x800a, 81}, + {0x800f, 81}, + {0x8018, 81}, + {0x801f, 81}, + {0x8029, 81}, + {0xc038, 81}, + {0x8003, 82}, + {0x8006, 82}, + {0x800a, 82}, + {0x800f, 82}, + {0x8018, 82}, + {0x801f, 82}, + {0x8029, 82}, + {0xc038, 82}, + }, + /* 55 */ + { + {0x8003, 83}, + {0x8006, 83}, + {0x800a, 83}, + {0x800f, 83}, + {0x8018, 83}, + {0x801f, 83}, + {0x8029, 83}, + {0xc038, 83}, + {0x8003, 84}, + {0x8006, 84}, + {0x800a, 84}, + {0x800f, 84}, + {0x8018, 84}, + {0x801f, 84}, + {0x8029, 84}, + {0xc038, 84}, + }, + /* 56 */ + { + {0xc000, 85}, + {0xc000, 86}, + {0xc000, 87}, + {0xc000, 89}, + {0xc000, 106}, + {0xc000, 107}, + {0xc000, 113}, + {0xc000, 118}, + {0xc000, 119}, + {0xc000, 120}, + {0xc000, 121}, + {0xc000, 122}, + {0x46, 0}, + {0x47, 0}, + {0x49, 0}, + {0x404a, 0}, + }, + /* 57 */ + { + {0x8001, 85}, + {0xc016, 85}, + {0x8001, 86}, + {0xc016, 86}, + {0x8001, 87}, + {0xc016, 87}, + {0x8001, 89}, + {0xc016, 89}, + {0x8001, 106}, + {0xc016, 106}, + {0x8001, 107}, + {0xc016, 107}, + {0x8001, 113}, + {0xc016, 113}, + {0x8001, 118}, + {0xc016, 118}, + }, + /* 58 */ + { + {0x8002, 85}, + {0x8009, 85}, + {0x8017, 85}, + {0xc028, 85}, + {0x8002, 86}, + {0x8009, 86}, + {0x8017, 86}, + {0xc028, 86}, + {0x8002, 87}, + {0x8009, 87}, + {0x8017, 87}, + {0xc028, 87}, + {0x8002, 89}, + {0x8009, 89}, + {0x8017, 89}, + {0xc028, 89}, + }, + /* 59 */ + { + {0x8003, 85}, + {0x8006, 85}, + {0x800a, 85}, + {0x800f, 85}, + {0x8018, 85}, + {0x801f, 85}, + {0x8029, 85}, + {0xc038, 85}, + {0x8003, 86}, + {0x8006, 86}, + {0x800a, 86}, + {0x800f, 86}, + {0x8018, 86}, + {0x801f, 86}, + {0x8029, 86}, + {0xc038, 86}, + }, + /* 60 */ + { + {0x8003, 87}, + {0x8006, 87}, + {0x800a, 87}, + {0x800f, 87}, + {0x8018, 87}, + {0x801f, 87}, + {0x8029, 87}, + {0xc038, 87}, + {0x8003, 89}, + {0x8006, 89}, + {0x800a, 89}, + {0x800f, 89}, + {0x8018, 89}, + {0x801f, 89}, + {0x8029, 89}, + {0xc038, 89}, + }, + /* 61 */ + { + {0x8002, 106}, + {0x8009, 106}, + {0x8017, 106}, + {0xc028, 106}, + {0x8002, 107}, + {0x8009, 107}, + {0x8017, 107}, + {0xc028, 107}, + {0x8002, 113}, + {0x8009, 113}, + {0x8017, 113}, + {0xc028, 113}, + {0x8002, 118}, + {0x8009, 118}, + {0x8017, 118}, + {0xc028, 118}, + }, + /* 62 */ + { + {0x8003, 106}, + {0x8006, 106}, + {0x800a, 106}, + {0x800f, 106}, + {0x8018, 106}, + {0x801f, 106}, + {0x8029, 106}, + {0xc038, 106}, + {0x8003, 107}, + {0x8006, 107}, + {0x800a, 107}, + {0x800f, 107}, + {0x8018, 107}, + {0x801f, 107}, + {0x8029, 107}, + {0xc038, 107}, + }, + /* 63 */ + { + {0x8003, 113}, + {0x8006, 113}, + {0x800a, 113}, + {0x800f, 113}, + {0x8018, 113}, + {0x801f, 113}, + {0x8029, 113}, + {0xc038, 113}, + {0x8003, 118}, + {0x8006, 118}, + {0x800a, 118}, + {0x800f, 118}, + {0x8018, 118}, + {0x801f, 118}, + {0x8029, 118}, + {0xc038, 118}, + }, + /* 64 */ + { + {0x8001, 119}, + {0xc016, 119}, + {0x8001, 120}, + {0xc016, 120}, + {0x8001, 121}, + {0xc016, 121}, + {0x8001, 122}, + {0xc016, 122}, + {0xc000, 38}, + {0xc000, 42}, + {0xc000, 44}, + {0xc000, 59}, + {0xc000, 88}, + {0xc000, 90}, + {0x4b, 0}, + {0x4e, 0}, + }, + /* 65 */ + { + {0x8002, 119}, + {0x8009, 119}, + {0x8017, 119}, + {0xc028, 119}, + {0x8002, 120}, + {0x8009, 120}, + {0x8017, 120}, + {0xc028, 120}, + {0x8002, 121}, + {0x8009, 121}, + {0x8017, 121}, + {0xc028, 121}, + {0x8002, 122}, + {0x8009, 122}, + {0x8017, 122}, + {0xc028, 122}, + }, + /* 66 */ + { + {0x8003, 119}, + {0x8006, 119}, + {0x800a, 119}, + {0x800f, 119}, + {0x8018, 119}, + {0x801f, 119}, + {0x8029, 119}, + {0xc038, 119}, + {0x8003, 120}, + {0x8006, 120}, + {0x800a, 120}, + {0x800f, 120}, + {0x8018, 120}, + {0x801f, 120}, + {0x8029, 120}, + {0xc038, 120}, + }, + /* 67 */ + { + {0x8003, 121}, + {0x8006, 121}, + {0x800a, 121}, + {0x800f, 121}, + {0x8018, 121}, + {0x801f, 121}, + {0x8029, 121}, + {0xc038, 121}, + {0x8003, 122}, + {0x8006, 122}, + {0x800a, 122}, + {0x800f, 122}, + {0x8018, 122}, + {0x801f, 122}, + {0x8029, 122}, + {0xc038, 122}, + }, + /* 68 */ + { + {0x8001, 38}, + {0xc016, 38}, + {0x8001, 42}, + {0xc016, 42}, + {0x8001, 44}, + {0xc016, 44}, + {0x8001, 59}, + {0xc016, 59}, + {0x8001, 88}, + {0xc016, 88}, + {0x8001, 90}, + {0xc016, 90}, + {0x4c, 0}, + {0x4d, 0}, + {0x4f, 0}, + {0x51, 0}, + }, + /* 69 */ + { + {0x8002, 38}, + {0x8009, 38}, + {0x8017, 38}, + {0xc028, 38}, + {0x8002, 42}, + {0x8009, 42}, + {0x8017, 42}, + {0xc028, 42}, + {0x8002, 44}, + {0x8009, 44}, + {0x8017, 44}, + {0xc028, 44}, + {0x8002, 59}, + {0x8009, 59}, + {0x8017, 59}, + {0xc028, 59}, + }, + /* 70 */ + { + {0x8003, 38}, + {0x8006, 38}, + {0x800a, 38}, + {0x800f, 38}, + {0x8018, 38}, + {0x801f, 38}, + {0x8029, 38}, + {0xc038, 38}, + {0x8003, 42}, + {0x8006, 42}, + {0x800a, 42}, + {0x800f, 42}, + {0x8018, 42}, + {0x801f, 42}, + {0x8029, 42}, + {0xc038, 42}, + }, + /* 71 */ + { + {0x8003, 44}, + {0x8006, 44}, + {0x800a, 44}, + {0x800f, 44}, + {0x8018, 44}, + {0x801f, 44}, + {0x8029, 44}, + {0xc038, 44}, + {0x8003, 59}, + {0x8006, 59}, + {0x800a, 59}, + {0x800f, 59}, + {0x8018, 59}, + {0x801f, 59}, + {0x8029, 59}, + {0xc038, 59}, + }, + /* 72 */ + { + {0x8002, 88}, + {0x8009, 88}, + {0x8017, 88}, + {0xc028, 88}, + {0x8002, 90}, + {0x8009, 90}, + {0x8017, 90}, + {0xc028, 90}, + {0xc000, 33}, + {0xc000, 34}, + {0xc000, 40}, + {0xc000, 41}, + {0xc000, 63}, + {0x50, 0}, + {0x52, 0}, + {0x54, 0}, + }, + /* 73 */ + { + {0x8003, 88}, + {0x8006, 88}, + {0x800a, 88}, + {0x800f, 88}, + {0x8018, 88}, + {0x801f, 88}, + {0x8029, 88}, + {0xc038, 88}, + {0x8003, 90}, + {0x8006, 90}, + {0x800a, 90}, + {0x800f, 90}, + {0x8018, 90}, + {0x801f, 90}, + {0x8029, 90}, + {0xc038, 90}, + }, + /* 74 */ + { + {0x8001, 33}, + {0xc016, 33}, + {0x8001, 34}, + {0xc016, 34}, + {0x8001, 40}, + {0xc016, 40}, + {0x8001, 41}, + {0xc016, 41}, + {0x8001, 63}, + {0xc016, 63}, + {0xc000, 39}, + {0xc000, 43}, + {0xc000, 124}, + {0x53, 0}, + {0x55, 0}, + {0x58, 0}, + }, + /* 75 */ + { + {0x8002, 33}, + {0x8009, 33}, + {0x8017, 33}, + {0xc028, 33}, + {0x8002, 34}, + {0x8009, 34}, + {0x8017, 34}, + {0xc028, 34}, + {0x8002, 40}, + {0x8009, 40}, + {0x8017, 40}, + {0xc028, 40}, + {0x8002, 41}, + {0x8009, 41}, + {0x8017, 41}, + {0xc028, 41}, + }, + /* 76 */ + { + {0x8003, 33}, + {0x8006, 33}, + {0x800a, 33}, + {0x800f, 33}, + {0x8018, 33}, + {0x801f, 33}, + {0x8029, 33}, + {0xc038, 33}, + {0x8003, 34}, + {0x8006, 34}, + {0x800a, 34}, + {0x800f, 34}, + {0x8018, 34}, + {0x801f, 34}, + {0x8029, 34}, + {0xc038, 34}, + }, + /* 77 */ + { + {0x8003, 40}, + {0x8006, 40}, + {0x800a, 40}, + {0x800f, 40}, + {0x8018, 40}, + {0x801f, 40}, + {0x8029, 40}, + {0xc038, 40}, + {0x8003, 41}, + {0x8006, 41}, + {0x800a, 41}, + {0x800f, 41}, + {0x8018, 41}, + {0x801f, 41}, + {0x8029, 41}, + {0xc038, 41}, + }, + /* 78 */ + { + {0x8002, 63}, + {0x8009, 63}, + {0x8017, 63}, + {0xc028, 63}, + {0x8001, 39}, + {0xc016, 39}, + {0x8001, 43}, + {0xc016, 43}, + {0x8001, 124}, + {0xc016, 124}, + {0xc000, 35}, + {0xc000, 62}, + {0x56, 0}, + {0x57, 0}, + {0x59, 0}, + {0x5a, 0}, + }, + /* 79 */ + { + {0x8003, 63}, + {0x8006, 63}, + {0x800a, 63}, + {0x800f, 63}, + {0x8018, 63}, + {0x801f, 63}, + {0x8029, 63}, + {0xc038, 63}, + {0x8002, 39}, + {0x8009, 39}, + {0x8017, 39}, + {0xc028, 39}, + {0x8002, 43}, + {0x8009, 43}, + {0x8017, 43}, + {0xc028, 43}, + }, + /* 80 */ + { + {0x8003, 39}, + {0x8006, 39}, + {0x800a, 39}, + {0x800f, 39}, + {0x8018, 39}, + {0x801f, 39}, + {0x8029, 39}, + {0xc038, 39}, + {0x8003, 43}, + {0x8006, 43}, + {0x800a, 43}, + {0x800f, 43}, + {0x8018, 43}, + {0x801f, 43}, + {0x8029, 43}, + {0xc038, 43}, + }, + /* 81 */ + { + {0x8002, 124}, + {0x8009, 124}, + {0x8017, 124}, + {0xc028, 124}, + {0x8001, 35}, + {0xc016, 35}, + {0x8001, 62}, + {0xc016, 62}, + {0xc000, 0}, + {0xc000, 36}, + {0xc000, 64}, + {0xc000, 91}, + {0xc000, 93}, + {0xc000, 126}, + {0x5b, 0}, + {0x5c, 0}, + }, + /* 82 */ + { + {0x8003, 124}, + {0x8006, 124}, + {0x800a, 124}, + {0x800f, 124}, + {0x8018, 124}, + {0x801f, 124}, + {0x8029, 124}, + {0xc038, 124}, + {0x8002, 35}, + {0x8009, 35}, + {0x8017, 35}, + {0xc028, 35}, + {0x8002, 62}, + {0x8009, 62}, + {0x8017, 62}, + {0xc028, 62}, + }, + /* 83 */ + { + {0x8003, 35}, + {0x8006, 35}, + {0x800a, 35}, + {0x800f, 35}, + {0x8018, 35}, + {0x801f, 35}, + {0x8029, 35}, + {0xc038, 35}, + {0x8003, 62}, + {0x8006, 62}, + {0x800a, 62}, + {0x800f, 62}, + {0x8018, 62}, + {0x801f, 62}, + {0x8029, 62}, + {0xc038, 62}, + }, + /* 84 */ + { + {0x8001, 0}, + {0xc016, 0}, + {0x8001, 36}, + {0xc016, 36}, + {0x8001, 64}, + {0xc016, 64}, + {0x8001, 91}, + {0xc016, 91}, + {0x8001, 93}, + {0xc016, 93}, + {0x8001, 126}, + {0xc016, 126}, + {0xc000, 94}, + {0xc000, 125}, + {0x5d, 0}, + {0x5e, 0}, + }, + /* 85 */ + { + {0x8002, 0}, + {0x8009, 0}, + {0x8017, 0}, + {0xc028, 0}, + {0x8002, 36}, + {0x8009, 36}, + {0x8017, 36}, + {0xc028, 36}, + {0x8002, 64}, + {0x8009, 64}, + {0x8017, 64}, + {0xc028, 64}, + {0x8002, 91}, + {0x8009, 91}, + {0x8017, 91}, + {0xc028, 91}, + }, + /* 86 */ + { + {0x8003, 0}, + {0x8006, 0}, + {0x800a, 0}, + {0x800f, 0}, + {0x8018, 0}, + {0x801f, 0}, + {0x8029, 0}, + {0xc038, 0}, + {0x8003, 36}, + {0x8006, 36}, + {0x800a, 36}, + {0x800f, 36}, + {0x8018, 36}, + {0x801f, 36}, + {0x8029, 36}, + {0xc038, 36}, + }, + /* 87 */ + { + {0x8003, 64}, + {0x8006, 64}, + {0x800a, 64}, + {0x800f, 64}, + {0x8018, 64}, + {0x801f, 64}, + {0x8029, 64}, + {0xc038, 64}, + {0x8003, 91}, + {0x8006, 91}, + {0x800a, 91}, + {0x800f, 91}, + {0x8018, 91}, + {0x801f, 91}, + {0x8029, 91}, + {0xc038, 91}, + }, + /* 88 */ + { + {0x8002, 93}, + {0x8009, 93}, + {0x8017, 93}, + {0xc028, 93}, + {0x8002, 126}, + {0x8009, 126}, + {0x8017, 126}, + {0xc028, 126}, + {0x8001, 94}, + {0xc016, 94}, + {0x8001, 125}, + {0xc016, 125}, + {0xc000, 60}, + {0xc000, 96}, + {0xc000, 123}, + {0x5f, 0}, + }, + /* 89 */ + { + {0x8003, 93}, + {0x8006, 93}, + {0x800a, 93}, + {0x800f, 93}, + {0x8018, 93}, + {0x801f, 93}, + {0x8029, 93}, + {0xc038, 93}, + {0x8003, 126}, + {0x8006, 126}, + {0x800a, 126}, + {0x800f, 126}, + {0x8018, 126}, + {0x801f, 126}, + {0x8029, 126}, + {0xc038, 126}, + }, + /* 90 */ + { + {0x8002, 94}, + {0x8009, 94}, + {0x8017, 94}, + {0xc028, 94}, + {0x8002, 125}, + {0x8009, 125}, + {0x8017, 125}, + {0xc028, 125}, + {0x8001, 60}, + {0xc016, 60}, + {0x8001, 96}, + {0xc016, 96}, + {0x8001, 123}, + {0xc016, 123}, + {0x60, 0}, + {0x6e, 0}, + }, + /* 91 */ + { + {0x8003, 94}, + {0x8006, 94}, + {0x800a, 94}, + {0x800f, 94}, + {0x8018, 94}, + {0x801f, 94}, + {0x8029, 94}, + {0xc038, 94}, + {0x8003, 125}, + {0x8006, 125}, + {0x800a, 125}, + {0x800f, 125}, + {0x8018, 125}, + {0x801f, 125}, + {0x8029, 125}, + {0xc038, 125}, + }, + /* 92 */ + { + {0x8002, 60}, + {0x8009, 60}, + {0x8017, 60}, + {0xc028, 60}, + {0x8002, 96}, + {0x8009, 96}, + {0x8017, 96}, + {0xc028, 96}, + {0x8002, 123}, + {0x8009, 123}, + {0x8017, 123}, + {0xc028, 123}, + {0x61, 0}, + {0x65, 0}, + {0x6f, 0}, + {0x85, 0}, + }, + /* 93 */ + { + {0x8003, 60}, + {0x8006, 60}, + {0x800a, 60}, + {0x800f, 60}, + {0x8018, 60}, + {0x801f, 60}, + {0x8029, 60}, + {0xc038, 60}, + {0x8003, 96}, + {0x8006, 96}, + {0x800a, 96}, + {0x800f, 96}, + {0x8018, 96}, + {0x801f, 96}, + {0x8029, 96}, + {0xc038, 96}, + }, + /* 94 */ + { + {0x8003, 123}, + {0x8006, 123}, + {0x800a, 123}, + {0x800f, 123}, + {0x8018, 123}, + {0x801f, 123}, + {0x8029, 123}, + {0xc038, 123}, + {0x62, 0}, + {0x63, 0}, + {0x66, 0}, + {0x69, 0}, + {0x70, 0}, + {0x77, 0}, + {0x86, 0}, + {0x99, 0}, + }, + /* 95 */ + { + {0xc000, 92}, + {0xc000, 195}, + {0xc000, 208}, + {0x64, 0}, + {0x67, 0}, + {0x68, 0}, + {0x6a, 0}, + {0x6b, 0}, + {0x71, 0}, + {0x74, 0}, + {0x78, 0}, + {0x7e, 0}, + {0x87, 0}, + {0x8e, 0}, + {0x9a, 0}, + {0xa9, 0}, + }, + /* 96 */ + { + {0x8001, 92}, + {0xc016, 92}, + {0x8001, 195}, + {0xc016, 195}, + {0x8001, 208}, + {0xc016, 208}, + {0xc000, 128}, + {0xc000, 130}, + {0xc000, 131}, + {0xc000, 162}, + {0xc000, 184}, + {0xc000, 194}, + {0xc000, 224}, + {0xc000, 226}, + {0x6c, 0}, + {0x6d, 0}, + }, + /* 97 */ + { + {0x8002, 92}, + {0x8009, 92}, + {0x8017, 92}, + {0xc028, 92}, + {0x8002, 195}, + {0x8009, 195}, + {0x8017, 195}, + {0xc028, 195}, + {0x8002, 208}, + {0x8009, 208}, + {0x8017, 208}, + {0xc028, 208}, + {0x8001, 128}, + {0xc016, 128}, + {0x8001, 130}, + {0xc016, 130}, + }, + /* 98 */ + { + {0x8003, 92}, + {0x8006, 92}, + {0x800a, 92}, + {0x800f, 92}, + {0x8018, 92}, + {0x801f, 92}, + {0x8029, 92}, + {0xc038, 92}, + {0x8003, 195}, + {0x8006, 195}, + {0x800a, 195}, + {0x800f, 195}, + {0x8018, 195}, + {0x801f, 195}, + {0x8029, 195}, + {0xc038, 195}, + }, + /* 99 */ + { + {0x8003, 208}, + {0x8006, 208}, + {0x800a, 208}, + {0x800f, 208}, + {0x8018, 208}, + {0x801f, 208}, + {0x8029, 208}, + {0xc038, 208}, + {0x8002, 128}, + {0x8009, 128}, + {0x8017, 128}, + {0xc028, 128}, + {0x8002, 130}, + {0x8009, 130}, + {0x8017, 130}, + {0xc028, 130}, + }, + /* 100 */ + { + {0x8003, 128}, + {0x8006, 128}, + {0x800a, 128}, + {0x800f, 128}, + {0x8018, 128}, + {0x801f, 128}, + {0x8029, 128}, + {0xc038, 128}, + {0x8003, 130}, + {0x8006, 130}, + {0x800a, 130}, + {0x800f, 130}, + {0x8018, 130}, + {0x801f, 130}, + {0x8029, 130}, + {0xc038, 130}, + }, + /* 101 */ + { + {0x8001, 131}, + {0xc016, 131}, + {0x8001, 162}, + {0xc016, 162}, + {0x8001, 184}, + {0xc016, 184}, + {0x8001, 194}, + {0xc016, 194}, + {0x8001, 224}, + {0xc016, 224}, + {0x8001, 226}, + {0xc016, 226}, + {0xc000, 153}, + {0xc000, 161}, + {0xc000, 167}, + {0xc000, 172}, + }, + /* 102 */ + { + {0x8002, 131}, + {0x8009, 131}, + {0x8017, 131}, + {0xc028, 131}, + {0x8002, 162}, + {0x8009, 162}, + {0x8017, 162}, + {0xc028, 162}, + {0x8002, 184}, + {0x8009, 184}, + {0x8017, 184}, + {0xc028, 184}, + {0x8002, 194}, + {0x8009, 194}, + {0x8017, 194}, + {0xc028, 194}, + }, + /* 103 */ + { + {0x8003, 131}, + {0x8006, 131}, + {0x800a, 131}, + {0x800f, 131}, + {0x8018, 131}, + {0x801f, 131}, + {0x8029, 131}, + {0xc038, 131}, + {0x8003, 162}, + {0x8006, 162}, + {0x800a, 162}, + {0x800f, 162}, + {0x8018, 162}, + {0x801f, 162}, + {0x8029, 162}, + {0xc038, 162}, + }, + /* 104 */ + { + {0x8003, 184}, + {0x8006, 184}, + {0x800a, 184}, + {0x800f, 184}, + {0x8018, 184}, + {0x801f, 184}, + {0x8029, 184}, + {0xc038, 184}, + {0x8003, 194}, + {0x8006, 194}, + {0x800a, 194}, + {0x800f, 194}, + {0x8018, 194}, + {0x801f, 194}, + {0x8029, 194}, + {0xc038, 194}, + }, + /* 105 */ + { + {0x8002, 224}, + {0x8009, 224}, + {0x8017, 224}, + {0xc028, 224}, + {0x8002, 226}, + {0x8009, 226}, + {0x8017, 226}, + {0xc028, 226}, + {0x8001, 153}, + {0xc016, 153}, + {0x8001, 161}, + {0xc016, 161}, + {0x8001, 167}, + {0xc016, 167}, + {0x8001, 172}, + {0xc016, 172}, + }, + /* 106 */ + { + {0x8003, 224}, + {0x8006, 224}, + {0x800a, 224}, + {0x800f, 224}, + {0x8018, 224}, + {0x801f, 224}, + {0x8029, 224}, + {0xc038, 224}, + {0x8003, 226}, + {0x8006, 226}, + {0x800a, 226}, + {0x800f, 226}, + {0x8018, 226}, + {0x801f, 226}, + {0x8029, 226}, + {0xc038, 226}, + }, + /* 107 */ + { + {0x8002, 153}, + {0x8009, 153}, + {0x8017, 153}, + {0xc028, 153}, + {0x8002, 161}, + {0x8009, 161}, + {0x8017, 161}, + {0xc028, 161}, + {0x8002, 167}, + {0x8009, 167}, + {0x8017, 167}, + {0xc028, 167}, + {0x8002, 172}, + {0x8009, 172}, + {0x8017, 172}, + {0xc028, 172}, + }, + /* 108 */ + { + {0x8003, 153}, + {0x8006, 153}, + {0x800a, 153}, + {0x800f, 153}, + {0x8018, 153}, + {0x801f, 153}, + {0x8029, 153}, + {0xc038, 153}, + {0x8003, 161}, + {0x8006, 161}, + {0x800a, 161}, + {0x800f, 161}, + {0x8018, 161}, + {0x801f, 161}, + {0x8029, 161}, + {0xc038, 161}, + }, + /* 109 */ + { + {0x8003, 167}, + {0x8006, 167}, + {0x800a, 167}, + {0x800f, 167}, + {0x8018, 167}, + {0x801f, 167}, + {0x8029, 167}, + {0xc038, 167}, + {0x8003, 172}, + {0x8006, 172}, + {0x800a, 172}, + {0x800f, 172}, + {0x8018, 172}, + {0x801f, 172}, + {0x8029, 172}, + {0xc038, 172}, + }, + /* 110 */ + { + {0x72, 0}, + {0x73, 0}, + {0x75, 0}, + {0x76, 0}, + {0x79, 0}, + {0x7b, 0}, + {0x7f, 0}, + {0x82, 0}, + {0x88, 0}, + {0x8b, 0}, + {0x8f, 0}, + {0x92, 0}, + {0x9b, 0}, + {0xa2, 0}, + {0xaa, 0}, + {0xb4, 0}, + }, + /* 111 */ + { + {0xc000, 176}, + {0xc000, 177}, + {0xc000, 179}, + {0xc000, 209}, + {0xc000, 216}, + {0xc000, 217}, + {0xc000, 227}, + {0xc000, 229}, + {0xc000, 230}, + {0x7a, 0}, + {0x7c, 0}, + {0x7d, 0}, + {0x80, 0}, + {0x81, 0}, + {0x83, 0}, + {0x84, 0}, + }, + /* 112 */ + { + {0x8001, 176}, + {0xc016, 176}, + {0x8001, 177}, + {0xc016, 177}, + {0x8001, 179}, + {0xc016, 179}, + {0x8001, 209}, + {0xc016, 209}, + {0x8001, 216}, + {0xc016, 216}, + {0x8001, 217}, + {0xc016, 217}, + {0x8001, 227}, + {0xc016, 227}, + {0x8001, 229}, + {0xc016, 229}, + }, + /* 113 */ + { + {0x8002, 176}, + {0x8009, 176}, + {0x8017, 176}, + {0xc028, 176}, + {0x8002, 177}, + {0x8009, 177}, + {0x8017, 177}, + {0xc028, 177}, + {0x8002, 179}, + {0x8009, 179}, + {0x8017, 179}, + {0xc028, 179}, + {0x8002, 209}, + {0x8009, 209}, + {0x8017, 209}, + {0xc028, 209}, + }, + /* 114 */ + { + {0x8003, 176}, + {0x8006, 176}, + {0x800a, 176}, + {0x800f, 176}, + {0x8018, 176}, + {0x801f, 176}, + {0x8029, 176}, + {0xc038, 176}, + {0x8003, 177}, + {0x8006, 177}, + {0x800a, 177}, + {0x800f, 177}, + {0x8018, 177}, + {0x801f, 177}, + {0x8029, 177}, + {0xc038, 177}, + }, + /* 115 */ + { + {0x8003, 179}, + {0x8006, 179}, + {0x800a, 179}, + {0x800f, 179}, + {0x8018, 179}, + {0x801f, 179}, + {0x8029, 179}, + {0xc038, 179}, + {0x8003, 209}, + {0x8006, 209}, + {0x800a, 209}, + {0x800f, 209}, + {0x8018, 209}, + {0x801f, 209}, + {0x8029, 209}, + {0xc038, 209}, + }, + /* 116 */ + { + {0x8002, 216}, + {0x8009, 216}, + {0x8017, 216}, + {0xc028, 216}, + {0x8002, 217}, + {0x8009, 217}, + {0x8017, 217}, + {0xc028, 217}, + {0x8002, 227}, + {0x8009, 227}, + {0x8017, 227}, + {0xc028, 227}, + {0x8002, 229}, + {0x8009, 229}, + {0x8017, 229}, + {0xc028, 229}, + }, + /* 117 */ + { + {0x8003, 216}, + {0x8006, 216}, + {0x800a, 216}, + {0x800f, 216}, + {0x8018, 216}, + {0x801f, 216}, + {0x8029, 216}, + {0xc038, 216}, + {0x8003, 217}, + {0x8006, 217}, + {0x800a, 217}, + {0x800f, 217}, + {0x8018, 217}, + {0x801f, 217}, + {0x8029, 217}, + {0xc038, 217}, + }, + /* 118 */ + { + {0x8003, 227}, + {0x8006, 227}, + {0x800a, 227}, + {0x800f, 227}, + {0x8018, 227}, + {0x801f, 227}, + {0x8029, 227}, + {0xc038, 227}, + {0x8003, 229}, + {0x8006, 229}, + {0x800a, 229}, + {0x800f, 229}, + {0x8018, 229}, + {0x801f, 229}, + {0x8029, 229}, + {0xc038, 229}, + }, + /* 119 */ + { + {0x8001, 230}, + {0xc016, 230}, + {0xc000, 129}, + {0xc000, 132}, + {0xc000, 133}, + {0xc000, 134}, + {0xc000, 136}, + {0xc000, 146}, + {0xc000, 154}, + {0xc000, 156}, + {0xc000, 160}, + {0xc000, 163}, + {0xc000, 164}, + {0xc000, 169}, + {0xc000, 170}, + {0xc000, 173}, + }, + /* 120 */ + { + {0x8002, 230}, + {0x8009, 230}, + {0x8017, 230}, + {0xc028, 230}, + {0x8001, 129}, + {0xc016, 129}, + {0x8001, 132}, + {0xc016, 132}, + {0x8001, 133}, + {0xc016, 133}, + {0x8001, 134}, + {0xc016, 134}, + {0x8001, 136}, + {0xc016, 136}, + {0x8001, 146}, + {0xc016, 146}, + }, + /* 121 */ + { + {0x8003, 230}, + {0x8006, 230}, + {0x800a, 230}, + {0x800f, 230}, + {0x8018, 230}, + {0x801f, 230}, + {0x8029, 230}, + {0xc038, 230}, + {0x8002, 129}, + {0x8009, 129}, + {0x8017, 129}, + {0xc028, 129}, + {0x8002, 132}, + {0x8009, 132}, + {0x8017, 132}, + {0xc028, 132}, + }, + /* 122 */ + { + {0x8003, 129}, + {0x8006, 129}, + {0x800a, 129}, + {0x800f, 129}, + {0x8018, 129}, + {0x801f, 129}, + {0x8029, 129}, + {0xc038, 129}, + {0x8003, 132}, + {0x8006, 132}, + {0x800a, 132}, + {0x800f, 132}, + {0x8018, 132}, + {0x801f, 132}, + {0x8029, 132}, + {0xc038, 132}, + }, + /* 123 */ + { + {0x8002, 133}, + {0x8009, 133}, + {0x8017, 133}, + {0xc028, 133}, + {0x8002, 134}, + {0x8009, 134}, + {0x8017, 134}, + {0xc028, 134}, + {0x8002, 136}, + {0x8009, 136}, + {0x8017, 136}, + {0xc028, 136}, + {0x8002, 146}, + {0x8009, 146}, + {0x8017, 146}, + {0xc028, 146}, + }, + /* 124 */ + { + {0x8003, 133}, + {0x8006, 133}, + {0x800a, 133}, + {0x800f, 133}, + {0x8018, 133}, + {0x801f, 133}, + {0x8029, 133}, + {0xc038, 133}, + {0x8003, 134}, + {0x8006, 134}, + {0x800a, 134}, + {0x800f, 134}, + {0x8018, 134}, + {0x801f, 134}, + {0x8029, 134}, + {0xc038, 134}, + }, + /* 125 */ + { + {0x8003, 136}, + {0x8006, 136}, + {0x800a, 136}, + {0x800f, 136}, + {0x8018, 136}, + {0x801f, 136}, + {0x8029, 136}, + {0xc038, 136}, + {0x8003, 146}, + {0x8006, 146}, + {0x800a, 146}, + {0x800f, 146}, + {0x8018, 146}, + {0x801f, 146}, + {0x8029, 146}, + {0xc038, 146}, + }, + /* 126 */ + { + {0x8001, 154}, + {0xc016, 154}, + {0x8001, 156}, + {0xc016, 156}, + {0x8001, 160}, + {0xc016, 160}, + {0x8001, 163}, + {0xc016, 163}, + {0x8001, 164}, + {0xc016, 164}, + {0x8001, 169}, + {0xc016, 169}, + {0x8001, 170}, + {0xc016, 170}, + {0x8001, 173}, + {0xc016, 173}, + }, + /* 127 */ + { + {0x8002, 154}, + {0x8009, 154}, + {0x8017, 154}, + {0xc028, 154}, + {0x8002, 156}, + {0x8009, 156}, + {0x8017, 156}, + {0xc028, 156}, + {0x8002, 160}, + {0x8009, 160}, + {0x8017, 160}, + {0xc028, 160}, + {0x8002, 163}, + {0x8009, 163}, + {0x8017, 163}, + {0xc028, 163}, + }, + /* 128 */ + { + {0x8003, 154}, + {0x8006, 154}, + {0x800a, 154}, + {0x800f, 154}, + {0x8018, 154}, + {0x801f, 154}, + {0x8029, 154}, + {0xc038, 154}, + {0x8003, 156}, + {0x8006, 156}, + {0x800a, 156}, + {0x800f, 156}, + {0x8018, 156}, + {0x801f, 156}, + {0x8029, 156}, + {0xc038, 156}, + }, + /* 129 */ + { + {0x8003, 160}, + {0x8006, 160}, + {0x800a, 160}, + {0x800f, 160}, + {0x8018, 160}, + {0x801f, 160}, + {0x8029, 160}, + {0xc038, 160}, + {0x8003, 163}, + {0x8006, 163}, + {0x800a, 163}, + {0x800f, 163}, + {0x8018, 163}, + {0x801f, 163}, + {0x8029, 163}, + {0xc038, 163}, + }, + /* 130 */ + { + {0x8002, 164}, + {0x8009, 164}, + {0x8017, 164}, + {0xc028, 164}, + {0x8002, 169}, + {0x8009, 169}, + {0x8017, 169}, + {0xc028, 169}, + {0x8002, 170}, + {0x8009, 170}, + {0x8017, 170}, + {0xc028, 170}, + {0x8002, 173}, + {0x8009, 173}, + {0x8017, 173}, + {0xc028, 173}, + }, + /* 131 */ + { + {0x8003, 164}, + {0x8006, 164}, + {0x800a, 164}, + {0x800f, 164}, + {0x8018, 164}, + {0x801f, 164}, + {0x8029, 164}, + {0xc038, 164}, + {0x8003, 169}, + {0x8006, 169}, + {0x800a, 169}, + {0x800f, 169}, + {0x8018, 169}, + {0x801f, 169}, + {0x8029, 169}, + {0xc038, 169}, + }, + /* 132 */ + { + {0x8003, 170}, + {0x8006, 170}, + {0x800a, 170}, + {0x800f, 170}, + {0x8018, 170}, + {0x801f, 170}, + {0x8029, 170}, + {0xc038, 170}, + {0x8003, 173}, + {0x8006, 173}, + {0x800a, 173}, + {0x800f, 173}, + {0x8018, 173}, + {0x801f, 173}, + {0x8029, 173}, + {0xc038, 173}, + }, + /* 133 */ + { + {0x89, 0}, + {0x8a, 0}, + {0x8c, 0}, + {0x8d, 0}, + {0x90, 0}, + {0x91, 0}, + {0x93, 0}, + {0x96, 0}, + {0x9c, 0}, + {0x9f, 0}, + {0xa3, 0}, + {0xa6, 0}, + {0xab, 0}, + {0xae, 0}, + {0xb5, 0}, + {0xbe, 0}, + }, + /* 134 */ + { + {0xc000, 178}, + {0xc000, 181}, + {0xc000, 185}, + {0xc000, 186}, + {0xc000, 187}, + {0xc000, 189}, + {0xc000, 190}, + {0xc000, 196}, + {0xc000, 198}, + {0xc000, 228}, + {0xc000, 232}, + {0xc000, 233}, + {0x94, 0}, + {0x95, 0}, + {0x97, 0}, + {0x98, 0}, + }, + /* 135 */ + { + {0x8001, 178}, + {0xc016, 178}, + {0x8001, 181}, + {0xc016, 181}, + {0x8001, 185}, + {0xc016, 185}, + {0x8001, 186}, + {0xc016, 186}, + {0x8001, 187}, + {0xc016, 187}, + {0x8001, 189}, + {0xc016, 189}, + {0x8001, 190}, + {0xc016, 190}, + {0x8001, 196}, + {0xc016, 196}, + }, + /* 136 */ + { + {0x8002, 178}, + {0x8009, 178}, + {0x8017, 178}, + {0xc028, 178}, + {0x8002, 181}, + {0x8009, 181}, + {0x8017, 181}, + {0xc028, 181}, + {0x8002, 185}, + {0x8009, 185}, + {0x8017, 185}, + {0xc028, 185}, + {0x8002, 186}, + {0x8009, 186}, + {0x8017, 186}, + {0xc028, 186}, + }, + /* 137 */ + { + {0x8003, 178}, + {0x8006, 178}, + {0x800a, 178}, + {0x800f, 178}, + {0x8018, 178}, + {0x801f, 178}, + {0x8029, 178}, + {0xc038, 178}, + {0x8003, 181}, + {0x8006, 181}, + {0x800a, 181}, + {0x800f, 181}, + {0x8018, 181}, + {0x801f, 181}, + {0x8029, 181}, + {0xc038, 181}, + }, + /* 138 */ + { + {0x8003, 185}, + {0x8006, 185}, + {0x800a, 185}, + {0x800f, 185}, + {0x8018, 185}, + {0x801f, 185}, + {0x8029, 185}, + {0xc038, 185}, + {0x8003, 186}, + {0x8006, 186}, + {0x800a, 186}, + {0x800f, 186}, + {0x8018, 186}, + {0x801f, 186}, + {0x8029, 186}, + {0xc038, 186}, + }, + /* 139 */ + { + {0x8002, 187}, + {0x8009, 187}, + {0x8017, 187}, + {0xc028, 187}, + {0x8002, 189}, + {0x8009, 189}, + {0x8017, 189}, + {0xc028, 189}, + {0x8002, 190}, + {0x8009, 190}, + {0x8017, 190}, + {0xc028, 190}, + {0x8002, 196}, + {0x8009, 196}, + {0x8017, 196}, + {0xc028, 196}, + }, + /* 140 */ + { + {0x8003, 187}, + {0x8006, 187}, + {0x800a, 187}, + {0x800f, 187}, + {0x8018, 187}, + {0x801f, 187}, + {0x8029, 187}, + {0xc038, 187}, + {0x8003, 189}, + {0x8006, 189}, + {0x800a, 189}, + {0x800f, 189}, + {0x8018, 189}, + {0x801f, 189}, + {0x8029, 189}, + {0xc038, 189}, + }, + /* 141 */ + { + {0x8003, 190}, + {0x8006, 190}, + {0x800a, 190}, + {0x800f, 190}, + {0x8018, 190}, + {0x801f, 190}, + {0x8029, 190}, + {0xc038, 190}, + {0x8003, 196}, + {0x8006, 196}, + {0x800a, 196}, + {0x800f, 196}, + {0x8018, 196}, + {0x801f, 196}, + {0x8029, 196}, + {0xc038, 196}, + }, + /* 142 */ + { + {0x8001, 198}, + {0xc016, 198}, + {0x8001, 228}, + {0xc016, 228}, + {0x8001, 232}, + {0xc016, 232}, + {0x8001, 233}, + {0xc016, 233}, + {0xc000, 1}, + {0xc000, 135}, + {0xc000, 137}, + {0xc000, 138}, + {0xc000, 139}, + {0xc000, 140}, + {0xc000, 141}, + {0xc000, 143}, + }, + /* 143 */ + { + {0x8002, 198}, + {0x8009, 198}, + {0x8017, 198}, + {0xc028, 198}, + {0x8002, 228}, + {0x8009, 228}, + {0x8017, 228}, + {0xc028, 228}, + {0x8002, 232}, + {0x8009, 232}, + {0x8017, 232}, + {0xc028, 232}, + {0x8002, 233}, + {0x8009, 233}, + {0x8017, 233}, + {0xc028, 233}, + }, + /* 144 */ + { + {0x8003, 198}, + {0x8006, 198}, + {0x800a, 198}, + {0x800f, 198}, + {0x8018, 198}, + {0x801f, 198}, + {0x8029, 198}, + {0xc038, 198}, + {0x8003, 228}, + {0x8006, 228}, + {0x800a, 228}, + {0x800f, 228}, + {0x8018, 228}, + {0x801f, 228}, + {0x8029, 228}, + {0xc038, 228}, + }, + /* 145 */ + { + {0x8003, 232}, + {0x8006, 232}, + {0x800a, 232}, + {0x800f, 232}, + {0x8018, 232}, + {0x801f, 232}, + {0x8029, 232}, + {0xc038, 232}, + {0x8003, 233}, + {0x8006, 233}, + {0x800a, 233}, + {0x800f, 233}, + {0x8018, 233}, + {0x801f, 233}, + {0x8029, 233}, + {0xc038, 233}, + }, + /* 146 */ + { + {0x8001, 1}, + {0xc016, 1}, + {0x8001, 135}, + {0xc016, 135}, + {0x8001, 137}, + {0xc016, 137}, + {0x8001, 138}, + {0xc016, 138}, + {0x8001, 139}, + {0xc016, 139}, + {0x8001, 140}, + {0xc016, 140}, + {0x8001, 141}, + {0xc016, 141}, + {0x8001, 143}, + {0xc016, 143}, + }, + /* 147 */ + { + {0x8002, 1}, + {0x8009, 1}, + {0x8017, 1}, + {0xc028, 1}, + {0x8002, 135}, + {0x8009, 135}, + {0x8017, 135}, + {0xc028, 135}, + {0x8002, 137}, + {0x8009, 137}, + {0x8017, 137}, + {0xc028, 137}, + {0x8002, 138}, + {0x8009, 138}, + {0x8017, 138}, + {0xc028, 138}, + }, + /* 148 */ + { + {0x8003, 1}, + {0x8006, 1}, + {0x800a, 1}, + {0x800f, 1}, + {0x8018, 1}, + {0x801f, 1}, + {0x8029, 1}, + {0xc038, 1}, + {0x8003, 135}, + {0x8006, 135}, + {0x800a, 135}, + {0x800f, 135}, + {0x8018, 135}, + {0x801f, 135}, + {0x8029, 135}, + {0xc038, 135}, + }, + /* 149 */ + { + {0x8003, 137}, + {0x8006, 137}, + {0x800a, 137}, + {0x800f, 137}, + {0x8018, 137}, + {0x801f, 137}, + {0x8029, 137}, + {0xc038, 137}, + {0x8003, 138}, + {0x8006, 138}, + {0x800a, 138}, + {0x800f, 138}, + {0x8018, 138}, + {0x801f, 138}, + {0x8029, 138}, + {0xc038, 138}, + }, + /* 150 */ + { + {0x8002, 139}, + {0x8009, 139}, + {0x8017, 139}, + {0xc028, 139}, + {0x8002, 140}, + {0x8009, 140}, + {0x8017, 140}, + {0xc028, 140}, + {0x8002, 141}, + {0x8009, 141}, + {0x8017, 141}, + {0xc028, 141}, + {0x8002, 143}, + {0x8009, 143}, + {0x8017, 143}, + {0xc028, 143}, + }, + /* 151 */ + { + {0x8003, 139}, + {0x8006, 139}, + {0x800a, 139}, + {0x800f, 139}, + {0x8018, 139}, + {0x801f, 139}, + {0x8029, 139}, + {0xc038, 139}, + {0x8003, 140}, + {0x8006, 140}, + {0x800a, 140}, + {0x800f, 140}, + {0x8018, 140}, + {0x801f, 140}, + {0x8029, 140}, + {0xc038, 140}, + }, + /* 152 */ + { + {0x8003, 141}, + {0x8006, 141}, + {0x800a, 141}, + {0x800f, 141}, + {0x8018, 141}, + {0x801f, 141}, + {0x8029, 141}, + {0xc038, 141}, + {0x8003, 143}, + {0x8006, 143}, + {0x800a, 143}, + {0x800f, 143}, + {0x8018, 143}, + {0x801f, 143}, + {0x8029, 143}, + {0xc038, 143}, + }, + /* 153 */ + { + {0x9d, 0}, + {0x9e, 0}, + {0xa0, 0}, + {0xa1, 0}, + {0xa4, 0}, + {0xa5, 0}, + {0xa7, 0}, + {0xa8, 0}, + {0xac, 0}, + {0xad, 0}, + {0xaf, 0}, + {0xb1, 0}, + {0xb6, 0}, + {0xb9, 0}, + {0xbf, 0}, + {0xcf, 0}, + }, + /* 154 */ + { + {0xc000, 147}, + {0xc000, 149}, + {0xc000, 150}, + {0xc000, 151}, + {0xc000, 152}, + {0xc000, 155}, + {0xc000, 157}, + {0xc000, 158}, + {0xc000, 165}, + {0xc000, 166}, + {0xc000, 168}, + {0xc000, 174}, + {0xc000, 175}, + {0xc000, 180}, + {0xc000, 182}, + {0xc000, 183}, + }, + /* 155 */ + { + {0x8001, 147}, + {0xc016, 147}, + {0x8001, 149}, + {0xc016, 149}, + {0x8001, 150}, + {0xc016, 150}, + {0x8001, 151}, + {0xc016, 151}, + {0x8001, 152}, + {0xc016, 152}, + {0x8001, 155}, + {0xc016, 155}, + {0x8001, 157}, + {0xc016, 157}, + {0x8001, 158}, + {0xc016, 158}, + }, + /* 156 */ + { + {0x8002, 147}, + {0x8009, 147}, + {0x8017, 147}, + {0xc028, 147}, + {0x8002, 149}, + {0x8009, 149}, + {0x8017, 149}, + {0xc028, 149}, + {0x8002, 150}, + {0x8009, 150}, + {0x8017, 150}, + {0xc028, 150}, + {0x8002, 151}, + {0x8009, 151}, + {0x8017, 151}, + {0xc028, 151}, + }, + /* 157 */ + { + {0x8003, 147}, + {0x8006, 147}, + {0x800a, 147}, + {0x800f, 147}, + {0x8018, 147}, + {0x801f, 147}, + {0x8029, 147}, + {0xc038, 147}, + {0x8003, 149}, + {0x8006, 149}, + {0x800a, 149}, + {0x800f, 149}, + {0x8018, 149}, + {0x801f, 149}, + {0x8029, 149}, + {0xc038, 149}, + }, + /* 158 */ + { + {0x8003, 150}, + {0x8006, 150}, + {0x800a, 150}, + {0x800f, 150}, + {0x8018, 150}, + {0x801f, 150}, + {0x8029, 150}, + {0xc038, 150}, + {0x8003, 151}, + {0x8006, 151}, + {0x800a, 151}, + {0x800f, 151}, + {0x8018, 151}, + {0x801f, 151}, + {0x8029, 151}, + {0xc038, 151}, + }, + /* 159 */ + { + {0x8002, 152}, + {0x8009, 152}, + {0x8017, 152}, + {0xc028, 152}, + {0x8002, 155}, + {0x8009, 155}, + {0x8017, 155}, + {0xc028, 155}, + {0x8002, 157}, + {0x8009, 157}, + {0x8017, 157}, + {0xc028, 157}, + {0x8002, 158}, + {0x8009, 158}, + {0x8017, 158}, + {0xc028, 158}, + }, + /* 160 */ + { + {0x8003, 152}, + {0x8006, 152}, + {0x800a, 152}, + {0x800f, 152}, + {0x8018, 152}, + {0x801f, 152}, + {0x8029, 152}, + {0xc038, 152}, + {0x8003, 155}, + {0x8006, 155}, + {0x800a, 155}, + {0x800f, 155}, + {0x8018, 155}, + {0x801f, 155}, + {0x8029, 155}, + {0xc038, 155}, + }, + /* 161 */ + { + {0x8003, 157}, + {0x8006, 157}, + {0x800a, 157}, + {0x800f, 157}, + {0x8018, 157}, + {0x801f, 157}, + {0x8029, 157}, + {0xc038, 157}, + {0x8003, 158}, + {0x8006, 158}, + {0x800a, 158}, + {0x800f, 158}, + {0x8018, 158}, + {0x801f, 158}, + {0x8029, 158}, + {0xc038, 158}, + }, + /* 162 */ + { + {0x8001, 165}, + {0xc016, 165}, + {0x8001, 166}, + {0xc016, 166}, + {0x8001, 168}, + {0xc016, 168}, + {0x8001, 174}, + {0xc016, 174}, + {0x8001, 175}, + {0xc016, 175}, + {0x8001, 180}, + {0xc016, 180}, + {0x8001, 182}, + {0xc016, 182}, + {0x8001, 183}, + {0xc016, 183}, + }, + /* 163 */ + { + {0x8002, 165}, + {0x8009, 165}, + {0x8017, 165}, + {0xc028, 165}, + {0x8002, 166}, + {0x8009, 166}, + {0x8017, 166}, + {0xc028, 166}, + {0x8002, 168}, + {0x8009, 168}, + {0x8017, 168}, + {0xc028, 168}, + {0x8002, 174}, + {0x8009, 174}, + {0x8017, 174}, + {0xc028, 174}, + }, + /* 164 */ + { + {0x8003, 165}, + {0x8006, 165}, + {0x800a, 165}, + {0x800f, 165}, + {0x8018, 165}, + {0x801f, 165}, + {0x8029, 165}, + {0xc038, 165}, + {0x8003, 166}, + {0x8006, 166}, + {0x800a, 166}, + {0x800f, 166}, + {0x8018, 166}, + {0x801f, 166}, + {0x8029, 166}, + {0xc038, 166}, + }, + /* 165 */ + { + {0x8003, 168}, + {0x8006, 168}, + {0x800a, 168}, + {0x800f, 168}, + {0x8018, 168}, + {0x801f, 168}, + {0x8029, 168}, + {0xc038, 168}, + {0x8003, 174}, + {0x8006, 174}, + {0x800a, 174}, + {0x800f, 174}, + {0x8018, 174}, + {0x801f, 174}, + {0x8029, 174}, + {0xc038, 174}, + }, + /* 166 */ + { + {0x8002, 175}, + {0x8009, 175}, + {0x8017, 175}, + {0xc028, 175}, + {0x8002, 180}, + {0x8009, 180}, + {0x8017, 180}, + {0xc028, 180}, + {0x8002, 182}, + {0x8009, 182}, + {0x8017, 182}, + {0xc028, 182}, + {0x8002, 183}, + {0x8009, 183}, + {0x8017, 183}, + {0xc028, 183}, + }, + /* 167 */ + { + {0x8003, 175}, + {0x8006, 175}, + {0x800a, 175}, + {0x800f, 175}, + {0x8018, 175}, + {0x801f, 175}, + {0x8029, 175}, + {0xc038, 175}, + {0x8003, 180}, + {0x8006, 180}, + {0x800a, 180}, + {0x800f, 180}, + {0x8018, 180}, + {0x801f, 180}, + {0x8029, 180}, + {0xc038, 180}, + }, + /* 168 */ + { + {0x8003, 182}, + {0x8006, 182}, + {0x800a, 182}, + {0x800f, 182}, + {0x8018, 182}, + {0x801f, 182}, + {0x8029, 182}, + {0xc038, 182}, + {0x8003, 183}, + {0x8006, 183}, + {0x800a, 183}, + {0x800f, 183}, + {0x8018, 183}, + {0x801f, 183}, + {0x8029, 183}, + {0xc038, 183}, + }, + /* 169 */ + { + {0xc000, 188}, + {0xc000, 191}, + {0xc000, 197}, + {0xc000, 231}, + {0xc000, 239}, + {0xb0, 0}, + {0xb2, 0}, + {0xb3, 0}, + {0xb7, 0}, + {0xb8, 0}, + {0xba, 0}, + {0xbb, 0}, + {0xc0, 0}, + {0xc7, 0}, + {0xd0, 0}, + {0xdf, 0}, + }, + /* 170 */ + { + {0x8001, 188}, + {0xc016, 188}, + {0x8001, 191}, + {0xc016, 191}, + {0x8001, 197}, + {0xc016, 197}, + {0x8001, 231}, + {0xc016, 231}, + {0x8001, 239}, + {0xc016, 239}, + {0xc000, 9}, + {0xc000, 142}, + {0xc000, 144}, + {0xc000, 145}, + {0xc000, 148}, + {0xc000, 159}, + }, + /* 171 */ + { + {0x8002, 188}, + {0x8009, 188}, + {0x8017, 188}, + {0xc028, 188}, + {0x8002, 191}, + {0x8009, 191}, + {0x8017, 191}, + {0xc028, 191}, + {0x8002, 197}, + {0x8009, 197}, + {0x8017, 197}, + {0xc028, 197}, + {0x8002, 231}, + {0x8009, 231}, + {0x8017, 231}, + {0xc028, 231}, + }, + /* 172 */ + { + {0x8003, 188}, + {0x8006, 188}, + {0x800a, 188}, + {0x800f, 188}, + {0x8018, 188}, + {0x801f, 188}, + {0x8029, 188}, + {0xc038, 188}, + {0x8003, 191}, + {0x8006, 191}, + {0x800a, 191}, + {0x800f, 191}, + {0x8018, 191}, + {0x801f, 191}, + {0x8029, 191}, + {0xc038, 191}, + }, + /* 173 */ + { + {0x8003, 197}, + {0x8006, 197}, + {0x800a, 197}, + {0x800f, 197}, + {0x8018, 197}, + {0x801f, 197}, + {0x8029, 197}, + {0xc038, 197}, + {0x8003, 231}, + {0x8006, 231}, + {0x800a, 231}, + {0x800f, 231}, + {0x8018, 231}, + {0x801f, 231}, + {0x8029, 231}, + {0xc038, 231}, + }, + /* 174 */ + { + {0x8002, 239}, + {0x8009, 239}, + {0x8017, 239}, + {0xc028, 239}, + {0x8001, 9}, + {0xc016, 9}, + {0x8001, 142}, + {0xc016, 142}, + {0x8001, 144}, + {0xc016, 144}, + {0x8001, 145}, + {0xc016, 145}, + {0x8001, 148}, + {0xc016, 148}, + {0x8001, 159}, + {0xc016, 159}, + }, + /* 175 */ + { + {0x8003, 239}, + {0x8006, 239}, + {0x800a, 239}, + {0x800f, 239}, + {0x8018, 239}, + {0x801f, 239}, + {0x8029, 239}, + {0xc038, 239}, + {0x8002, 9}, + {0x8009, 9}, + {0x8017, 9}, + {0xc028, 9}, + {0x8002, 142}, + {0x8009, 142}, + {0x8017, 142}, + {0xc028, 142}, + }, + /* 176 */ + { + {0x8003, 9}, + {0x8006, 9}, + {0x800a, 9}, + {0x800f, 9}, + {0x8018, 9}, + {0x801f, 9}, + {0x8029, 9}, + {0xc038, 9}, + {0x8003, 142}, + {0x8006, 142}, + {0x800a, 142}, + {0x800f, 142}, + {0x8018, 142}, + {0x801f, 142}, + {0x8029, 142}, + {0xc038, 142}, + }, + /* 177 */ + { + {0x8002, 144}, + {0x8009, 144}, + {0x8017, 144}, + {0xc028, 144}, + {0x8002, 145}, + {0x8009, 145}, + {0x8017, 145}, + {0xc028, 145}, + {0x8002, 148}, + {0x8009, 148}, + {0x8017, 148}, + {0xc028, 148}, + {0x8002, 159}, + {0x8009, 159}, + {0x8017, 159}, + {0xc028, 159}, + }, + /* 178 */ + { + {0x8003, 144}, + {0x8006, 144}, + {0x800a, 144}, + {0x800f, 144}, + {0x8018, 144}, + {0x801f, 144}, + {0x8029, 144}, + {0xc038, 144}, + {0x8003, 145}, + {0x8006, 145}, + {0x800a, 145}, + {0x800f, 145}, + {0x8018, 145}, + {0x801f, 145}, + {0x8029, 145}, + {0xc038, 145}, + }, + /* 179 */ + { + {0x8003, 148}, + {0x8006, 148}, + {0x800a, 148}, + {0x800f, 148}, + {0x8018, 148}, + {0x801f, 148}, + {0x8029, 148}, + {0xc038, 148}, + {0x8003, 159}, + {0x8006, 159}, + {0x800a, 159}, + {0x800f, 159}, + {0x8018, 159}, + {0x801f, 159}, + {0x8029, 159}, + {0xc038, 159}, + }, + /* 180 */ + { + {0xc000, 171}, + {0xc000, 206}, + {0xc000, 215}, + {0xc000, 225}, + {0xc000, 236}, + {0xc000, 237}, + {0xbc, 0}, + {0xbd, 0}, + {0xc1, 0}, + {0xc4, 0}, + {0xc8, 0}, + {0xcb, 0}, + {0xd1, 0}, + {0xd8, 0}, + {0xe0, 0}, + {0xee, 0}, + }, + /* 181 */ + { + {0x8001, 171}, + {0xc016, 171}, + {0x8001, 206}, + {0xc016, 206}, + {0x8001, 215}, + {0xc016, 215}, + {0x8001, 225}, + {0xc016, 225}, + {0x8001, 236}, + {0xc016, 236}, + {0x8001, 237}, + {0xc016, 237}, + {0xc000, 199}, + {0xc000, 207}, + {0xc000, 234}, + {0xc000, 235}, + }, + /* 182 */ + { + {0x8002, 171}, + {0x8009, 171}, + {0x8017, 171}, + {0xc028, 171}, + {0x8002, 206}, + {0x8009, 206}, + {0x8017, 206}, + {0xc028, 206}, + {0x8002, 215}, + {0x8009, 215}, + {0x8017, 215}, + {0xc028, 215}, + {0x8002, 225}, + {0x8009, 225}, + {0x8017, 225}, + {0xc028, 225}, + }, + /* 183 */ + { + {0x8003, 171}, + {0x8006, 171}, + {0x800a, 171}, + {0x800f, 171}, + {0x8018, 171}, + {0x801f, 171}, + {0x8029, 171}, + {0xc038, 171}, + {0x8003, 206}, + {0x8006, 206}, + {0x800a, 206}, + {0x800f, 206}, + {0x8018, 206}, + {0x801f, 206}, + {0x8029, 206}, + {0xc038, 206}, + }, + /* 184 */ + { + {0x8003, 215}, + {0x8006, 215}, + {0x800a, 215}, + {0x800f, 215}, + {0x8018, 215}, + {0x801f, 215}, + {0x8029, 215}, + {0xc038, 215}, + {0x8003, 225}, + {0x8006, 225}, + {0x800a, 225}, + {0x800f, 225}, + {0x8018, 225}, + {0x801f, 225}, + {0x8029, 225}, + {0xc038, 225}, + }, + /* 185 */ + { + {0x8002, 236}, + {0x8009, 236}, + {0x8017, 236}, + {0xc028, 236}, + {0x8002, 237}, + {0x8009, 237}, + {0x8017, 237}, + {0xc028, 237}, + {0x8001, 199}, + {0xc016, 199}, + {0x8001, 207}, + {0xc016, 207}, + {0x8001, 234}, + {0xc016, 234}, + {0x8001, 235}, + {0xc016, 235}, + }, + /* 186 */ + { + {0x8003, 236}, + {0x8006, 236}, + {0x800a, 236}, + {0x800f, 236}, + {0x8018, 236}, + {0x801f, 236}, + {0x8029, 236}, + {0xc038, 236}, + {0x8003, 237}, + {0x8006, 237}, + {0x800a, 237}, + {0x800f, 237}, + {0x8018, 237}, + {0x801f, 237}, + {0x8029, 237}, + {0xc038, 237}, + }, + /* 187 */ + { + {0x8002, 199}, + {0x8009, 199}, + {0x8017, 199}, + {0xc028, 199}, + {0x8002, 207}, + {0x8009, 207}, + {0x8017, 207}, + {0xc028, 207}, + {0x8002, 234}, + {0x8009, 234}, + {0x8017, 234}, + {0xc028, 234}, + {0x8002, 235}, + {0x8009, 235}, + {0x8017, 235}, + {0xc028, 235}, + }, + /* 188 */ + { + {0x8003, 199}, + {0x8006, 199}, + {0x800a, 199}, + {0x800f, 199}, + {0x8018, 199}, + {0x801f, 199}, + {0x8029, 199}, + {0xc038, 199}, + {0x8003, 207}, + {0x8006, 207}, + {0x800a, 207}, + {0x800f, 207}, + {0x8018, 207}, + {0x801f, 207}, + {0x8029, 207}, + {0xc038, 207}, + }, + /* 189 */ + { + {0x8003, 234}, + {0x8006, 234}, + {0x800a, 234}, + {0x800f, 234}, + {0x8018, 234}, + {0x801f, 234}, + {0x8029, 234}, + {0xc038, 234}, + {0x8003, 235}, + {0x8006, 235}, + {0x800a, 235}, + {0x800f, 235}, + {0x8018, 235}, + {0x801f, 235}, + {0x8029, 235}, + {0xc038, 235}, + }, + /* 190 */ + { + {0xc2, 0}, + {0xc3, 0}, + {0xc5, 0}, + {0xc6, 0}, + {0xc9, 0}, + {0xca, 0}, + {0xcc, 0}, + {0xcd, 0}, + {0xd2, 0}, + {0xd5, 0}, + {0xd9, 0}, + {0xdc, 0}, + {0xe1, 0}, + {0xe7, 0}, + {0xef, 0}, + {0xf6, 0}, + }, + /* 191 */ + { + {0xc000, 192}, + {0xc000, 193}, + {0xc000, 200}, + {0xc000, 201}, + {0xc000, 202}, + {0xc000, 205}, + {0xc000, 210}, + {0xc000, 213}, + {0xc000, 218}, + {0xc000, 219}, + {0xc000, 238}, + {0xc000, 240}, + {0xc000, 242}, + {0xc000, 243}, + {0xc000, 255}, + {0xce, 0}, + }, + /* 192 */ + { + {0x8001, 192}, + {0xc016, 192}, + {0x8001, 193}, + {0xc016, 193}, + {0x8001, 200}, + {0xc016, 200}, + {0x8001, 201}, + {0xc016, 201}, + {0x8001, 202}, + {0xc016, 202}, + {0x8001, 205}, + {0xc016, 205}, + {0x8001, 210}, + {0xc016, 210}, + {0x8001, 213}, + {0xc016, 213}, + }, + /* 193 */ + { + {0x8002, 192}, + {0x8009, 192}, + {0x8017, 192}, + {0xc028, 192}, + {0x8002, 193}, + {0x8009, 193}, + {0x8017, 193}, + {0xc028, 193}, + {0x8002, 200}, + {0x8009, 200}, + {0x8017, 200}, + {0xc028, 200}, + {0x8002, 201}, + {0x8009, 201}, + {0x8017, 201}, + {0xc028, 201}, + }, + /* 194 */ + { + {0x8003, 192}, + {0x8006, 192}, + {0x800a, 192}, + {0x800f, 192}, + {0x8018, 192}, + {0x801f, 192}, + {0x8029, 192}, + {0xc038, 192}, + {0x8003, 193}, + {0x8006, 193}, + {0x800a, 193}, + {0x800f, 193}, + {0x8018, 193}, + {0x801f, 193}, + {0x8029, 193}, + {0xc038, 193}, + }, + /* 195 */ + { + {0x8003, 200}, + {0x8006, 200}, + {0x800a, 200}, + {0x800f, 200}, + {0x8018, 200}, + {0x801f, 200}, + {0x8029, 200}, + {0xc038, 200}, + {0x8003, 201}, + {0x8006, 201}, + {0x800a, 201}, + {0x800f, 201}, + {0x8018, 201}, + {0x801f, 201}, + {0x8029, 201}, + {0xc038, 201}, + }, + /* 196 */ + { + {0x8002, 202}, + {0x8009, 202}, + {0x8017, 202}, + {0xc028, 202}, + {0x8002, 205}, + {0x8009, 205}, + {0x8017, 205}, + {0xc028, 205}, + {0x8002, 210}, + {0x8009, 210}, + {0x8017, 210}, + {0xc028, 210}, + {0x8002, 213}, + {0x8009, 213}, + {0x8017, 213}, + {0xc028, 213}, + }, + /* 197 */ + { + {0x8003, 202}, + {0x8006, 202}, + {0x800a, 202}, + {0x800f, 202}, + {0x8018, 202}, + {0x801f, 202}, + {0x8029, 202}, + {0xc038, 202}, + {0x8003, 205}, + {0x8006, 205}, + {0x800a, 205}, + {0x800f, 205}, + {0x8018, 205}, + {0x801f, 205}, + {0x8029, 205}, + {0xc038, 205}, + }, + /* 198 */ + { + {0x8003, 210}, + {0x8006, 210}, + {0x800a, 210}, + {0x800f, 210}, + {0x8018, 210}, + {0x801f, 210}, + {0x8029, 210}, + {0xc038, 210}, + {0x8003, 213}, + {0x8006, 213}, + {0x800a, 213}, + {0x800f, 213}, + {0x8018, 213}, + {0x801f, 213}, + {0x8029, 213}, + {0xc038, 213}, + }, + /* 199 */ + { + {0x8001, 218}, + {0xc016, 218}, + {0x8001, 219}, + {0xc016, 219}, + {0x8001, 238}, + {0xc016, 238}, + {0x8001, 240}, + {0xc016, 240}, + {0x8001, 242}, + {0xc016, 242}, + {0x8001, 243}, + {0xc016, 243}, + {0x8001, 255}, + {0xc016, 255}, + {0xc000, 203}, + {0xc000, 204}, + }, + /* 200 */ + { + {0x8002, 218}, + {0x8009, 218}, + {0x8017, 218}, + {0xc028, 218}, + {0x8002, 219}, + {0x8009, 219}, + {0x8017, 219}, + {0xc028, 219}, + {0x8002, 238}, + {0x8009, 238}, + {0x8017, 238}, + {0xc028, 238}, + {0x8002, 240}, + {0x8009, 240}, + {0x8017, 240}, + {0xc028, 240}, + }, + /* 201 */ + { + {0x8003, 218}, + {0x8006, 218}, + {0x800a, 218}, + {0x800f, 218}, + {0x8018, 218}, + {0x801f, 218}, + {0x8029, 218}, + {0xc038, 218}, + {0x8003, 219}, + {0x8006, 219}, + {0x800a, 219}, + {0x800f, 219}, + {0x8018, 219}, + {0x801f, 219}, + {0x8029, 219}, + {0xc038, 219}, + }, + /* 202 */ + { + {0x8003, 238}, + {0x8006, 238}, + {0x800a, 238}, + {0x800f, 238}, + {0x8018, 238}, + {0x801f, 238}, + {0x8029, 238}, + {0xc038, 238}, + {0x8003, 240}, + {0x8006, 240}, + {0x800a, 240}, + {0x800f, 240}, + {0x8018, 240}, + {0x801f, 240}, + {0x8029, 240}, + {0xc038, 240}, + }, + /* 203 */ + { + {0x8002, 242}, + {0x8009, 242}, + {0x8017, 242}, + {0xc028, 242}, + {0x8002, 243}, + {0x8009, 243}, + {0x8017, 243}, + {0xc028, 243}, + {0x8002, 255}, + {0x8009, 255}, + {0x8017, 255}, + {0xc028, 255}, + {0x8001, 203}, + {0xc016, 203}, + {0x8001, 204}, + {0xc016, 204}, + }, + /* 204 */ + { + {0x8003, 242}, + {0x8006, 242}, + {0x800a, 242}, + {0x800f, 242}, + {0x8018, 242}, + {0x801f, 242}, + {0x8029, 242}, + {0xc038, 242}, + {0x8003, 243}, + {0x8006, 243}, + {0x800a, 243}, + {0x800f, 243}, + {0x8018, 243}, + {0x801f, 243}, + {0x8029, 243}, + {0xc038, 243}, + }, + /* 205 */ + { + {0x8003, 255}, + {0x8006, 255}, + {0x800a, 255}, + {0x800f, 255}, + {0x8018, 255}, + {0x801f, 255}, + {0x8029, 255}, + {0xc038, 255}, + {0x8002, 203}, + {0x8009, 203}, + {0x8017, 203}, + {0xc028, 203}, + {0x8002, 204}, + {0x8009, 204}, + {0x8017, 204}, + {0xc028, 204}, + }, + /* 206 */ + { + {0x8003, 203}, + {0x8006, 203}, + {0x800a, 203}, + {0x800f, 203}, + {0x8018, 203}, + {0x801f, 203}, + {0x8029, 203}, + {0xc038, 203}, + {0x8003, 204}, + {0x8006, 204}, + {0x800a, 204}, + {0x800f, 204}, + {0x8018, 204}, + {0x801f, 204}, + {0x8029, 204}, + {0xc038, 204}, + }, + /* 207 */ + { + {0xd3, 0}, + {0xd4, 0}, + {0xd6, 0}, + {0xd7, 0}, + {0xda, 0}, + {0xdb, 0}, + {0xdd, 0}, + {0xde, 0}, + {0xe2, 0}, + {0xe4, 0}, + {0xe8, 0}, + {0xeb, 0}, + {0xf0, 0}, + {0xf3, 0}, + {0xf7, 0}, + {0xfa, 0}, + }, + /* 208 */ + { + {0xc000, 211}, + {0xc000, 212}, + {0xc000, 214}, + {0xc000, 221}, + {0xc000, 222}, + {0xc000, 223}, + {0xc000, 241}, + {0xc000, 244}, + {0xc000, 245}, + {0xc000, 246}, + {0xc000, 247}, + {0xc000, 248}, + {0xc000, 250}, + {0xc000, 251}, + {0xc000, 252}, + {0xc000, 253}, + }, + /* 209 */ + { + {0x8001, 211}, + {0xc016, 211}, + {0x8001, 212}, + {0xc016, 212}, + {0x8001, 214}, + {0xc016, 214}, + {0x8001, 221}, + {0xc016, 221}, + {0x8001, 222}, + {0xc016, 222}, + {0x8001, 223}, + {0xc016, 223}, + {0x8001, 241}, + {0xc016, 241}, + {0x8001, 244}, + {0xc016, 244}, + }, + /* 210 */ + { + {0x8002, 211}, + {0x8009, 211}, + {0x8017, 211}, + {0xc028, 211}, + {0x8002, 212}, + {0x8009, 212}, + {0x8017, 212}, + {0xc028, 212}, + {0x8002, 214}, + {0x8009, 214}, + {0x8017, 214}, + {0xc028, 214}, + {0x8002, 221}, + {0x8009, 221}, + {0x8017, 221}, + {0xc028, 221}, + }, + /* 211 */ + { + {0x8003, 211}, + {0x8006, 211}, + {0x800a, 211}, + {0x800f, 211}, + {0x8018, 211}, + {0x801f, 211}, + {0x8029, 211}, + {0xc038, 211}, + {0x8003, 212}, + {0x8006, 212}, + {0x800a, 212}, + {0x800f, 212}, + {0x8018, 212}, + {0x801f, 212}, + {0x8029, 212}, + {0xc038, 212}, + }, + /* 212 */ + { + {0x8003, 214}, + {0x8006, 214}, + {0x800a, 214}, + {0x800f, 214}, + {0x8018, 214}, + {0x801f, 214}, + {0x8029, 214}, + {0xc038, 214}, + {0x8003, 221}, + {0x8006, 221}, + {0x800a, 221}, + {0x800f, 221}, + {0x8018, 221}, + {0x801f, 221}, + {0x8029, 221}, + {0xc038, 221}, + }, + /* 213 */ + { + {0x8002, 222}, + {0x8009, 222}, + {0x8017, 222}, + {0xc028, 222}, + {0x8002, 223}, + {0x8009, 223}, + {0x8017, 223}, + {0xc028, 223}, + {0x8002, 241}, + {0x8009, 241}, + {0x8017, 241}, + {0xc028, 241}, + {0x8002, 244}, + {0x8009, 244}, + {0x8017, 244}, + {0xc028, 244}, + }, + /* 214 */ + { + {0x8003, 222}, + {0x8006, 222}, + {0x800a, 222}, + {0x800f, 222}, + {0x8018, 222}, + {0x801f, 222}, + {0x8029, 222}, + {0xc038, 222}, + {0x8003, 223}, + {0x8006, 223}, + {0x800a, 223}, + {0x800f, 223}, + {0x8018, 223}, + {0x801f, 223}, + {0x8029, 223}, + {0xc038, 223}, + }, + /* 215 */ + { + {0x8003, 241}, + {0x8006, 241}, + {0x800a, 241}, + {0x800f, 241}, + {0x8018, 241}, + {0x801f, 241}, + {0x8029, 241}, + {0xc038, 241}, + {0x8003, 244}, + {0x8006, 244}, + {0x800a, 244}, + {0x800f, 244}, + {0x8018, 244}, + {0x801f, 244}, + {0x8029, 244}, + {0xc038, 244}, + }, + /* 216 */ + { + {0x8001, 245}, + {0xc016, 245}, + {0x8001, 246}, + {0xc016, 246}, + {0x8001, 247}, + {0xc016, 247}, + {0x8001, 248}, + {0xc016, 248}, + {0x8001, 250}, + {0xc016, 250}, + {0x8001, 251}, + {0xc016, 251}, + {0x8001, 252}, + {0xc016, 252}, + {0x8001, 253}, + {0xc016, 253}, + }, + /* 217 */ + { + {0x8002, 245}, + {0x8009, 245}, + {0x8017, 245}, + {0xc028, 245}, + {0x8002, 246}, + {0x8009, 246}, + {0x8017, 246}, + {0xc028, 246}, + {0x8002, 247}, + {0x8009, 247}, + {0x8017, 247}, + {0xc028, 247}, + {0x8002, 248}, + {0x8009, 248}, + {0x8017, 248}, + {0xc028, 248}, + }, + /* 218 */ + { + {0x8003, 245}, + {0x8006, 245}, + {0x800a, 245}, + {0x800f, 245}, + {0x8018, 245}, + {0x801f, 245}, + {0x8029, 245}, + {0xc038, 245}, + {0x8003, 246}, + {0x8006, 246}, + {0x800a, 246}, + {0x800f, 246}, + {0x8018, 246}, + {0x801f, 246}, + {0x8029, 246}, + {0xc038, 246}, + }, + /* 219 */ + { + {0x8003, 247}, + {0x8006, 247}, + {0x800a, 247}, + {0x800f, 247}, + {0x8018, 247}, + {0x801f, 247}, + {0x8029, 247}, + {0xc038, 247}, + {0x8003, 248}, + {0x8006, 248}, + {0x800a, 248}, + {0x800f, 248}, + {0x8018, 248}, + {0x801f, 248}, + {0x8029, 248}, + {0xc038, 248}, + }, + /* 220 */ + { + {0x8002, 250}, + {0x8009, 250}, + {0x8017, 250}, + {0xc028, 250}, + {0x8002, 251}, + {0x8009, 251}, + {0x8017, 251}, + {0xc028, 251}, + {0x8002, 252}, + {0x8009, 252}, + {0x8017, 252}, + {0xc028, 252}, + {0x8002, 253}, + {0x8009, 253}, + {0x8017, 253}, + {0xc028, 253}, + }, + /* 221 */ + { + {0x8003, 250}, + {0x8006, 250}, + {0x800a, 250}, + {0x800f, 250}, + {0x8018, 250}, + {0x801f, 250}, + {0x8029, 250}, + {0xc038, 250}, + {0x8003, 251}, + {0x8006, 251}, + {0x800a, 251}, + {0x800f, 251}, + {0x8018, 251}, + {0x801f, 251}, + {0x8029, 251}, + {0xc038, 251}, + }, + /* 222 */ + { + {0x8003, 252}, + {0x8006, 252}, + {0x800a, 252}, + {0x800f, 252}, + {0x8018, 252}, + {0x801f, 252}, + {0x8029, 252}, + {0xc038, 252}, + {0x8003, 253}, + {0x8006, 253}, + {0x800a, 253}, + {0x800f, 253}, + {0x8018, 253}, + {0x801f, 253}, + {0x8029, 253}, + {0xc038, 253}, + }, + /* 223 */ + { + {0xc000, 254}, + {0xe3, 0}, + {0xe5, 0}, + {0xe6, 0}, + {0xe9, 0}, + {0xea, 0}, + {0xec, 0}, + {0xed, 0}, + {0xf1, 0}, + {0xf2, 0}, + {0xf4, 0}, + {0xf5, 0}, + {0xf8, 0}, + {0xf9, 0}, + {0xfb, 0}, + {0xfc, 0}, + }, + /* 224 */ + { + {0x8001, 254}, + {0xc016, 254}, + {0xc000, 2}, + {0xc000, 3}, + {0xc000, 4}, + {0xc000, 5}, + {0xc000, 6}, + {0xc000, 7}, + {0xc000, 8}, + {0xc000, 11}, + {0xc000, 12}, + {0xc000, 14}, + {0xc000, 15}, + {0xc000, 16}, + {0xc000, 17}, + {0xc000, 18}, + }, + /* 225 */ + { + {0x8002, 254}, + {0x8009, 254}, + {0x8017, 254}, + {0xc028, 254}, + {0x8001, 2}, + {0xc016, 2}, + {0x8001, 3}, + {0xc016, 3}, + {0x8001, 4}, + {0xc016, 4}, + {0x8001, 5}, + {0xc016, 5}, + {0x8001, 6}, + {0xc016, 6}, + {0x8001, 7}, + {0xc016, 7}, + }, + /* 226 */ + { + {0x8003, 254}, + {0x8006, 254}, + {0x800a, 254}, + {0x800f, 254}, + {0x8018, 254}, + {0x801f, 254}, + {0x8029, 254}, + {0xc038, 254}, + {0x8002, 2}, + {0x8009, 2}, + {0x8017, 2}, + {0xc028, 2}, + {0x8002, 3}, + {0x8009, 3}, + {0x8017, 3}, + {0xc028, 3}, + }, + /* 227 */ + { + {0x8003, 2}, + {0x8006, 2}, + {0x800a, 2}, + {0x800f, 2}, + {0x8018, 2}, + {0x801f, 2}, + {0x8029, 2}, + {0xc038, 2}, + {0x8003, 3}, + {0x8006, 3}, + {0x800a, 3}, + {0x800f, 3}, + {0x8018, 3}, + {0x801f, 3}, + {0x8029, 3}, + {0xc038, 3}, + }, + /* 228 */ + { + {0x8002, 4}, + {0x8009, 4}, + {0x8017, 4}, + {0xc028, 4}, + {0x8002, 5}, + {0x8009, 5}, + {0x8017, 5}, + {0xc028, 5}, + {0x8002, 6}, + {0x8009, 6}, + {0x8017, 6}, + {0xc028, 6}, + {0x8002, 7}, + {0x8009, 7}, + {0x8017, 7}, + {0xc028, 7}, + }, + /* 229 */ + { + {0x8003, 4}, + {0x8006, 4}, + {0x800a, 4}, + {0x800f, 4}, + {0x8018, 4}, + {0x801f, 4}, + {0x8029, 4}, + {0xc038, 4}, + {0x8003, 5}, + {0x8006, 5}, + {0x800a, 5}, + {0x800f, 5}, + {0x8018, 5}, + {0x801f, 5}, + {0x8029, 5}, + {0xc038, 5}, + }, + /* 230 */ + { + {0x8003, 6}, + {0x8006, 6}, + {0x800a, 6}, + {0x800f, 6}, + {0x8018, 6}, + {0x801f, 6}, + {0x8029, 6}, + {0xc038, 6}, + {0x8003, 7}, + {0x8006, 7}, + {0x800a, 7}, + {0x800f, 7}, + {0x8018, 7}, + {0x801f, 7}, + {0x8029, 7}, + {0xc038, 7}, + }, + /* 231 */ + { + {0x8001, 8}, + {0xc016, 8}, + {0x8001, 11}, + {0xc016, 11}, + {0x8001, 12}, + {0xc016, 12}, + {0x8001, 14}, + {0xc016, 14}, + {0x8001, 15}, + {0xc016, 15}, + {0x8001, 16}, + {0xc016, 16}, + {0x8001, 17}, + {0xc016, 17}, + {0x8001, 18}, + {0xc016, 18}, + }, + /* 232 */ + { + {0x8002, 8}, + {0x8009, 8}, + {0x8017, 8}, + {0xc028, 8}, + {0x8002, 11}, + {0x8009, 11}, + {0x8017, 11}, + {0xc028, 11}, + {0x8002, 12}, + {0x8009, 12}, + {0x8017, 12}, + {0xc028, 12}, + {0x8002, 14}, + {0x8009, 14}, + {0x8017, 14}, + {0xc028, 14}, + }, + /* 233 */ + { + {0x8003, 8}, + {0x8006, 8}, + {0x800a, 8}, + {0x800f, 8}, + {0x8018, 8}, + {0x801f, 8}, + {0x8029, 8}, + {0xc038, 8}, + {0x8003, 11}, + {0x8006, 11}, + {0x800a, 11}, + {0x800f, 11}, + {0x8018, 11}, + {0x801f, 11}, + {0x8029, 11}, + {0xc038, 11}, + }, + /* 234 */ + { + {0x8003, 12}, + {0x8006, 12}, + {0x800a, 12}, + {0x800f, 12}, + {0x8018, 12}, + {0x801f, 12}, + {0x8029, 12}, + {0xc038, 12}, + {0x8003, 14}, + {0x8006, 14}, + {0x800a, 14}, + {0x800f, 14}, + {0x8018, 14}, + {0x801f, 14}, + {0x8029, 14}, + {0xc038, 14}, + }, + /* 235 */ + { + {0x8002, 15}, + {0x8009, 15}, + {0x8017, 15}, + {0xc028, 15}, + {0x8002, 16}, + {0x8009, 16}, + {0x8017, 16}, + {0xc028, 16}, + {0x8002, 17}, + {0x8009, 17}, + {0x8017, 17}, + {0xc028, 17}, + {0x8002, 18}, + {0x8009, 18}, + {0x8017, 18}, + {0xc028, 18}, + }, + /* 236 */ + { + {0x8003, 15}, + {0x8006, 15}, + {0x800a, 15}, + {0x800f, 15}, + {0x8018, 15}, + {0x801f, 15}, + {0x8029, 15}, + {0xc038, 15}, + {0x8003, 16}, + {0x8006, 16}, + {0x800a, 16}, + {0x800f, 16}, + {0x8018, 16}, + {0x801f, 16}, + {0x8029, 16}, + {0xc038, 16}, + }, + /* 237 */ + { + {0x8003, 17}, + {0x8006, 17}, + {0x800a, 17}, + {0x800f, 17}, + {0x8018, 17}, + {0x801f, 17}, + {0x8029, 17}, + {0xc038, 17}, + {0x8003, 18}, + {0x8006, 18}, + {0x800a, 18}, + {0x800f, 18}, + {0x8018, 18}, + {0x801f, 18}, + {0x8029, 18}, + {0xc038, 18}, + }, + /* 238 */ + { + {0xc000, 19}, + {0xc000, 20}, + {0xc000, 21}, + {0xc000, 23}, + {0xc000, 24}, + {0xc000, 25}, + {0xc000, 26}, + {0xc000, 27}, + {0xc000, 28}, + {0xc000, 29}, + {0xc000, 30}, + {0xc000, 31}, + {0xc000, 127}, + {0xc000, 220}, + {0xc000, 249}, + {0xfd, 0}, + }, + /* 239 */ + { + {0x8001, 19}, + {0xc016, 19}, + {0x8001, 20}, + {0xc016, 20}, + {0x8001, 21}, + {0xc016, 21}, + {0x8001, 23}, + {0xc016, 23}, + {0x8001, 24}, + {0xc016, 24}, + {0x8001, 25}, + {0xc016, 25}, + {0x8001, 26}, + {0xc016, 26}, + {0x8001, 27}, + {0xc016, 27}, + }, + /* 240 */ + { + {0x8002, 19}, + {0x8009, 19}, + {0x8017, 19}, + {0xc028, 19}, + {0x8002, 20}, + {0x8009, 20}, + {0x8017, 20}, + {0xc028, 20}, + {0x8002, 21}, + {0x8009, 21}, + {0x8017, 21}, + {0xc028, 21}, + {0x8002, 23}, + {0x8009, 23}, + {0x8017, 23}, + {0xc028, 23}, + }, + /* 241 */ + { + {0x8003, 19}, + {0x8006, 19}, + {0x800a, 19}, + {0x800f, 19}, + {0x8018, 19}, + {0x801f, 19}, + {0x8029, 19}, + {0xc038, 19}, + {0x8003, 20}, + {0x8006, 20}, + {0x800a, 20}, + {0x800f, 20}, + {0x8018, 20}, + {0x801f, 20}, + {0x8029, 20}, + {0xc038, 20}, + }, + /* 242 */ + { + {0x8003, 21}, + {0x8006, 21}, + {0x800a, 21}, + {0x800f, 21}, + {0x8018, 21}, + {0x801f, 21}, + {0x8029, 21}, + {0xc038, 21}, + {0x8003, 23}, + {0x8006, 23}, + {0x800a, 23}, + {0x800f, 23}, + {0x8018, 23}, + {0x801f, 23}, + {0x8029, 23}, + {0xc038, 23}, + }, + /* 243 */ + { + {0x8002, 24}, + {0x8009, 24}, + {0x8017, 24}, + {0xc028, 24}, + {0x8002, 25}, + {0x8009, 25}, + {0x8017, 25}, + {0xc028, 25}, + {0x8002, 26}, + {0x8009, 26}, + {0x8017, 26}, + {0xc028, 26}, + {0x8002, 27}, + {0x8009, 27}, + {0x8017, 27}, + {0xc028, 27}, + }, + /* 244 */ + { + {0x8003, 24}, + {0x8006, 24}, + {0x800a, 24}, + {0x800f, 24}, + {0x8018, 24}, + {0x801f, 24}, + {0x8029, 24}, + {0xc038, 24}, + {0x8003, 25}, + {0x8006, 25}, + {0x800a, 25}, + {0x800f, 25}, + {0x8018, 25}, + {0x801f, 25}, + {0x8029, 25}, + {0xc038, 25}, + }, + /* 245 */ + { + {0x8003, 26}, + {0x8006, 26}, + {0x800a, 26}, + {0x800f, 26}, + {0x8018, 26}, + {0x801f, 26}, + {0x8029, 26}, + {0xc038, 26}, + {0x8003, 27}, + {0x8006, 27}, + {0x800a, 27}, + {0x800f, 27}, + {0x8018, 27}, + {0x801f, 27}, + {0x8029, 27}, + {0xc038, 27}, + }, + /* 246 */ + { + {0x8001, 28}, + {0xc016, 28}, + {0x8001, 29}, + {0xc016, 29}, + {0x8001, 30}, + {0xc016, 30}, + {0x8001, 31}, + {0xc016, 31}, + {0x8001, 127}, + {0xc016, 127}, + {0x8001, 220}, + {0xc016, 220}, + {0x8001, 249}, + {0xc016, 249}, + {0xfe, 0}, + {0xff, 0}, + }, + /* 247 */ + { + {0x8002, 28}, + {0x8009, 28}, + {0x8017, 28}, + {0xc028, 28}, + {0x8002, 29}, + {0x8009, 29}, + {0x8017, 29}, + {0xc028, 29}, + {0x8002, 30}, + {0x8009, 30}, + {0x8017, 30}, + {0xc028, 30}, + {0x8002, 31}, + {0x8009, 31}, + {0x8017, 31}, + {0xc028, 31}, + }, + /* 248 */ + { + {0x8003, 28}, + {0x8006, 28}, + {0x800a, 28}, + {0x800f, 28}, + {0x8018, 28}, + {0x801f, 28}, + {0x8029, 28}, + {0xc038, 28}, + {0x8003, 29}, + {0x8006, 29}, + {0x800a, 29}, + {0x800f, 29}, + {0x8018, 29}, + {0x801f, 29}, + {0x8029, 29}, + {0xc038, 29}, + }, + /* 249 */ + { + {0x8003, 30}, + {0x8006, 30}, + {0x800a, 30}, + {0x800f, 30}, + {0x8018, 30}, + {0x801f, 30}, + {0x8029, 30}, + {0xc038, 30}, + {0x8003, 31}, + {0x8006, 31}, + {0x800a, 31}, + {0x800f, 31}, + {0x8018, 31}, + {0x801f, 31}, + {0x8029, 31}, + {0xc038, 31}, + }, + /* 250 */ + { + {0x8002, 127}, + {0x8009, 127}, + {0x8017, 127}, + {0xc028, 127}, + {0x8002, 220}, + {0x8009, 220}, + {0x8017, 220}, + {0xc028, 220}, + {0x8002, 249}, + {0x8009, 249}, + {0x8017, 249}, + {0xc028, 249}, + {0xc000, 10}, + {0xc000, 13}, + {0xc000, 22}, + {0x100, 0}, + }, + /* 251 */ + { + {0x8003, 127}, + {0x8006, 127}, + {0x800a, 127}, + {0x800f, 127}, + {0x8018, 127}, + {0x801f, 127}, + {0x8029, 127}, + {0xc038, 127}, + {0x8003, 220}, + {0x8006, 220}, + {0x800a, 220}, + {0x800f, 220}, + {0x8018, 220}, + {0x801f, 220}, + {0x8029, 220}, + {0xc038, 220}, + }, + /* 252 */ + { + {0x8003, 249}, + {0x8006, 249}, + {0x800a, 249}, + {0x800f, 249}, + {0x8018, 249}, + {0x801f, 249}, + {0x8029, 249}, + {0xc038, 249}, + {0x8001, 10}, + {0xc016, 10}, + {0x8001, 13}, + {0xc016, 13}, + {0x8001, 22}, + {0xc016, 22}, + {0x100, 0}, + {0x100, 0}, + }, + /* 253 */ + { + {0x8002, 10}, + {0x8009, 10}, + {0x8017, 10}, + {0xc028, 10}, + {0x8002, 13}, + {0x8009, 13}, + {0x8017, 13}, + {0xc028, 13}, + {0x8002, 22}, + {0x8009, 22}, + {0x8017, 22}, + {0xc028, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 254 */ + { + {0x8003, 10}, + {0x8006, 10}, + {0x800a, 10}, + {0x800f, 10}, + {0x8018, 10}, + {0x801f, 10}, + {0x8029, 10}, + {0xc038, 10}, + {0x8003, 13}, + {0x8006, 13}, + {0x800a, 13}, + {0x800f, 13}, + {0x8018, 13}, + {0x801f, 13}, + {0x8029, 13}, + {0xc038, 13}, + }, + /* 255 */ + { + {0x8003, 22}, + {0x8006, 22}, + {0x800a, 22}, + {0x800f, 22}, + {0x8018, 22}, + {0x801f, 22}, + {0x8029, 22}, + {0xc038, 22}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, + /* 256 */ + { + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + {0x100, 0}, + }, }; diff --git a/thirdparty/nghttp2/nghttp2_helper.c b/thirdparty/nghttp2/nghttp2_helper.c index 765e5492ce..8ee736af87 100644 --- a/thirdparty/nghttp2/nghttp2_helper.c +++ b/thirdparty/nghttp2/nghttp2_helper.c @@ -117,6 +117,8 @@ const char *nghttp2_strerror(int error_code) { "closed"; case NGHTTP2_ERR_TOO_MANY_SETTINGS: return "SETTINGS frame contained more than the maximum allowed entries"; + case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS: + return "Too many CONTINUATION frames following a HEADER frame"; default: return "Unknown error code"; } diff --git a/thirdparty/nghttp2/nghttp2_mem.c b/thirdparty/nghttp2/nghttp2_mem.c index 8ca2ed332f..6a449cffd7 100644 --- a/thirdparty/nghttp2/nghttp2_mem.c +++ b/thirdparty/nghttp2/nghttp2_mem.c @@ -25,6 +25,8 @@ #include "nghttp2_mem.h" static void *default_malloc(size_t size, void *mem_user_data) { + (void)mem_user_data; + return malloc(size); } diff --git a/thirdparty/nghttp2/nghttp2ver.h b/thirdparty/nghttp2/nghttp2ver.h new file mode 100644 index 0000000000..827c998968 --- /dev/null +++ b/thirdparty/nghttp2/nghttp2ver.h @@ -0,0 +1,42 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGHTTP2VER_H +#define NGHTTP2VER_H + +/** + * @macro + * Version number of the nghttp2 library release + */ +#define NGHTTP2_VERSION "1.64.0" + +/** + * @macro + * Numerical representation of the version number of the nghttp2 library + * release. This is a 24 bit number with 8 bits for major number, 8 bits + * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. + */ +#define NGHTTP2_VERSION_NUM 0x014000 + +#endif /* NGHTTP2VER_H */ diff --git a/thirdparty/pdo_oci/CREDITS b/thirdparty/pdo_oci/CREDITS new file mode 100644 index 0000000000..63e863a22d --- /dev/null +++ b/thirdparty/pdo_oci/CREDITS @@ -0,0 +1,2 @@ +Oracle (OCI) driver for PDO +Wez Furlong diff --git a/thirdparty/pdo_oci/README.md b/thirdparty/pdo_oci/README.md new file mode 100644 index 0000000000..1a17c9df22 --- /dev/null +++ b/thirdparty/pdo_oci/README.md @@ -0,0 +1,18 @@ +This code was copied from php-8.3.28. Starting from PHP 8.4, +this extension has been removed and migrated to https://pecl.php.net/package/pdo_oci. + +However, the latest version available on PECL is 1.1.0, +which was released on August 21, 2024, whereas on May 6, 2025, +php-8.3.28 made modifications to pdo_oci +(see commit: https://github.com/php/php-src/commit/dcf9d8f812abb3854c802e4b831d82f9d7e5c26f). + +- PECL Package: https://pecl.php.net/package/pdo_oci +- GitHub Repository: https://github.com/php/pecl-database-pdo_oci + + +## Merge pdo_oci into Swoole + +```shell +git clone https://github.com/php/pecl-database-pdo_oci.git +meld soft/php/pecl-database-pdo_oci swoole-src/thirdparty/pdo_oci +``` diff --git a/thirdparty/pdo_oci/oci_driver.c b/thirdparty/pdo_oci/oci_driver.c new file mode 100644 index 0000000000..729c6a9f86 --- /dev/null +++ b/thirdparty/pdo_oci/oci_driver.c @@ -0,0 +1,881 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_exceptions.h" + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch); + +static void pdo_oci_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + + einfo = &H->einfo; + + if (stmt) { + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + if (S->einfo.errmsg) { + einfo = &S->einfo; + } + } + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +ub4 _oci_error(OCIError *err, + pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + char *what, + sword status, + int isinit, + const char *file, + int line) /* {{{ */ +{ + text errbuf[1024] = "<>"; + char tmp_buf[2048]; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + pdo_oci_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_oci_stmt *) stmt->driver_data; + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } else { + einfo = &H->einfo; + } + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + + einfo->errmsg = NULL; + einfo->errcode = 0; + einfo->file = file; + einfo->line = line; + + if (isinit) { /* Initialization error */ + strcpy(*pdo_err, "HY000"); + slprintf(tmp_buf, sizeof(tmp_buf), "%s (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + } else { + switch (status) { + case OCI_SUCCESS: + strcpy(*pdo_err, "00000"); + break; + case OCI_ERROR: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_SUCCESS_WITH_INFO: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_SUCCESS_WITH_INFO: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NEED_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NEED_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NO_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NO_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_INVALID_HANDLE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_INVALID_HANDLE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_STILL_EXECUTING: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_STILL_EXECUTING (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_CONTINUE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_CONTINUE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + } + + if (einfo->errcode) { + switch (einfo->errcode) { + case 1013: /* user requested cancel of current operation */ + zend_bailout(); + break; + + case 12154: /* ORA-12154: TNS:could not resolve service name */ + strcpy(*pdo_err, "42S02"); + break; + + case 22: /* ORA-00022: invalid session id */ + case 378: + case 602: + case 603: + case 604: + case 609: + case 1012: /* ORA-01012: */ + case 1033: + case 1041: + case 1043: + case 1089: + case 1090: + case 1092: + case 3113: /* ORA-03133: end of file on communication channel */ + case 3114: + case 3122: + case 3135: + case 12153: + case 27146: + case 28511: + /* consider the connection closed */ + dbh->is_closed = 1; + H->attached = 0; + strcpy(*pdo_err, "01002"); /* FIXME */ + break; + + default: + strcpy(*pdo_err, "HY000"); + } + } + + if (stmt) { + /* always propagate the error code back up to the dbh, + * so that we can catch the error information when execute + * is called via query. See Bug #33707 */ + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + } + H->einfo = *einfo; + H->einfo.errmsg = einfo->errmsg ? pestrdup(einfo->errmsg, dbh->is_persistent) : NULL; + strcpy(dbh->error_code, stmt->error_code); + } + } + + /* little mini hack so that we can use this code from the dbh ctor */ + if (!dbh->methods && status != OCI_SUCCESS_WITH_INFO) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->errcode, "SQLSTATE[%s]: %s", *pdo_err, einfo->errmsg); + } + + return einfo->errcode; +} +/* }}} */ + +static void oci_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + if (H->svc) { + /* rollback any outstanding work */ + OCITransRollback(H->svc, H->err, 0); + } + + if (H->session) { + OCIHandleFree(H->session, OCI_HTYPE_SESSION); + H->session = NULL; + } + + if (H->svc) { + OCIHandleFree(H->svc, OCI_HTYPE_SVCCTX); + H->svc = NULL; + } + + if (H->server && H->attached) { + H->last_err = OCIServerDetach(H->server, H->err, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCIServerDetach"); + } + H->attached = 0; + } + + if (H->server) { + OCIHandleFree(H->server, OCI_HTYPE_SERVER); + H->server = NULL; + } + + if (H->err) { + OCIHandleFree(H->err, OCI_HTYPE_ERROR); + H->err = NULL; + } + + if (H->charset && H->env) { + OCIHandleFree(H->env, OCI_HTYPE_ENV); + H->env = NULL; + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +static bool oci_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_stmt *S = ecalloc(1, sizeof(*S)); + ub4 prefetch; + zend_string *nsql = NULL; + int ret; + +#ifdef HAVE_OCISTMTFETCH2 + S->exec_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL + ? OCI_STMT_SCROLLABLE_READONLY + : OCI_DEFAULT; +#else + S->exec_type = OCI_DEFAULT; +#endif + + S->H = H; + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + /* create an OCI statement handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->stmt, OCI_HTYPE_STMT, 0, NULL); + + /* and our own private error handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->err, OCI_HTYPE_ERROR, 0, NULL); + + if (ZSTR_LEN(sql) != 0) { + H->last_err = + OCIStmtPrepare(S->stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + efree(S); + return false; + } + } + + prefetch = H->prefetch; /* Note 0 is allowed so in future REF CURSORs can be used & then passed with no row loss*/ + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_ROWS, H->err); + if (!H->last_err) { + prefetch *= PDO_OCI_PREFETCH_ROWSIZE; + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_MEMORY, H->err); + } + + stmt->driver_data = S; + stmt->methods = &swoole_oci_stmt_methods; + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + + return true; +} +/* }}} */ + +static zend_long oci_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + OCIStmt *stmt; + ub2 stmt_type; + ub4 rowcount; + int ret = -1; + + OCIHandleAlloc(H->env, (dvoid *) &stmt, OCI_HTYPE_STMT, 0, NULL); + + H->last_err = + OCIStmtPrepare(stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(stmt, OCI_HTYPE_STMT); + return -1; + } + + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &stmt_type, 0, OCI_ATTR_STMT_TYPE, H->err); + + if (stmt_type == OCI_STMT_SELECT) { + /* invalid usage; cancel it */ + OCIHandleFree(stmt, OCI_HTYPE_STMT); + php_error_docref(NULL, E_WARNING, "issuing a SELECT query here is invalid"); + return -1; + } + + /* now we are good to go */ + H->last_err = OCIStmtExecute(H->svc, + stmt, + H->err, + 1, + 0, + NULL, + NULL, + (dbh->auto_commit && !dbh->in_txn) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); + + sword last_err = H->last_err; + + if (last_err) { + H->last_err = oci_drv_error("OCIStmtExecute"); + } + + if (!last_err || last_err == OCI_SUCCESS_WITH_INFO) { + /* return the number of affected rows */ + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, H->err); + ret = rowcount; + } + + OCIHandleFree(stmt, OCI_HTYPE_STMT); + + return ret; +} +/* }}} */ + +static zend_string *oci_handle_quoter(pdo_dbh_t *dbh, + const zend_string *unquoted, + enum pdo_param_type paramtype) /* {{{ */ +{ + int qcount = 0; + char const *cu, *l, *r; + char *c, *quoted; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* count single quotes */ + for (cu = ZSTR_VAL(unquoted); (cu = strchr(cu, '\'')); qcount++, cu++) + ; /* empty loop */ + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted = c = emalloc(quotedlen + 1); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = ZSTR_VAL(unquoted); (r = strchr(l, '\'')); l = r + 1) { + strncpy(c, l, r - l + 1); + c += (r - l + 1); + *c++ = '\''; /* add second quote */ + } + + /* Copy remainder and add enclosing quote */ + strncpy(c, l, quotedlen - (c - quoted) - 1); + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} +/* }}} */ + +static bool oci_handle_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + /* with Oracle, there is nothing special to be done */ + return true; +} +/* }}} */ + +static bool oci_handle_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransRollback(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransRollback"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + zend_long lval; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: { + bool bval; + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (dbh->in_txn) { + /* Assume they want to commit whatever is outstanding */ + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + dbh->in_txn = false; + } + + dbh->auto_commit = (unsigned int) bval; + return true; + } + case PDO_ATTR_PREFETCH: { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + H->prefetch = pdo_oci_sanitize_prefetch(lval); + return true; + } + case PDO_OCI_ATTR_ACTION: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *action = zval_try_get_string(val); + if (UNEXPECTED(!action)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(action), (ub4) ZSTR_LEN(action), OCI_ATTR_ACTION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_ACTION"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_INFO: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *client_info = zval_try_get_string(val); + if (UNEXPECTED(!client_info)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(client_info), + (ub4) ZSTR_LEN(client_info), + OCI_ATTR_CLIENT_INFO, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_INFO"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_IDENTIFIER: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *identifier = zval_try_get_string(val); + if (UNEXPECTED(!identifier)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(identifier), + (ub4) ZSTR_LEN(identifier), + OCI_ATTR_CLIENT_IDENTIFIER, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_IDENTIFIER"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_MODULE: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *module = zval_try_get_string(val); + if (UNEXPECTED(!module)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(module), (ub4) ZSTR_LEN(module), OCI_ATTR_MODULE, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_MODULE"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + if (!pdo_get_long_param(&lval, val)) { + return false; + } + ub4 timeout = (ub4) lval; + + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, (ub4) 0, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CALL_TIMEOUT"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + default: + return false; + } +} +/* }}} */ + +static int oci_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: { + text infostr[512]; + char verstr[15]; + ub4 vernum; + + if (OCIServerRelease(H->svc, H->err, infostr, (ub4) sizeof(infostr), (ub1) OCI_HTYPE_SVCCTX, &vernum)) { + ZVAL_STRING(return_value, "<>"); + } else { + if (attr == PDO_ATTR_SERVER_INFO) { + ZVAL_STRING(return_value, (char *) infostr); + } else { + slprintf(verstr, + sizeof(verstr), + "%d.%d.%d.%d.%d", + (int) ((vernum >> 24) & 0xFF), /* version number */ + (int) ((vernum >> 20) & 0x0F), /* release number*/ + (int) ((vernum >> 12) & 0xFF), /* update number */ + (int) ((vernum >> 8) & 0x0F), /* port release number */ + (int) ((vernum >> 0) & 0xFF)); /* port update number */ + + ZVAL_STRING(return_value, verstr); + } + } + return TRUE; + } + + case PDO_ATTR_CLIENT_VERSION: { +#if OCI_MAJOR_VERSION > 10 || (OCI_MAJOR_VERSION == 10 && OCI_MINOR_VERSION >= 2) + /* Run time client version */ + sword major, minor, update, patch, port_update; + char verstr[15]; + + OCIClientVersion(&major, &minor, &update, &patch, &port_update); + slprintf(verstr, sizeof(verstr), "%d.%d.%d.%d.%d", major, minor, update, patch, port_update); + ZVAL_STRING(return_value, verstr); +#elif defined(SWOOLE_PDO_OCI_CLIENT_VERSION) + /* Compile time client version */ + ZVAL_STRING(return_value, SWOOLE_PDO_OCI_CLIENT_VERSION); +#else + return FALSE; + +#endif /* Check for OCIClientVersion() support */ + + return TRUE; + } + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(return_value, dbh->auto_commit); + return TRUE; + + case PDO_ATTR_PREFETCH: + ZVAL_LONG(return_value, H->prefetch); + return TRUE; + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + ub4 timeout; + + H->last_err = OCIAttrGet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, NULL, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrGet: OCI_ATTR_CALL_TIMEOUT"); + return FALSE; + } + + ZVAL_LONG(return_value, (zend_long) timeout); + return TRUE; +#else + oci_drv_error("Unsupported attribute type"); + return FALSE; +#endif + } + default: + return FALSE; + } + return FALSE; +} +/* }}} */ + +static zend_result pdo_oci_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + sb4 error_code = 0; +#if (!((OCI_MAJOR_VERSION > 10) || ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2)))) + char version[256]; +#endif + + /* TODO move attached check to PDO level */ + if (H->attached == 0) { + return FAILURE; + } + /* TODO add persistent_timeout check at PDO level */ + + /* Use OCIPing instead of OCIServerVersion. If OCIPing returns ORA-1010 (invalid OCI operation) + * such as from Pre-10.1 servers, the error is still from the server and we would have + * successfully performed a roundtrip and validated the connection. Use OCIServerVersion for + * Pre-10.2 clients + */ +#if ((OCI_MAJOR_VERSION > 10) || \ + ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2))) /* OCIPing available 10.2 onwards */ + H->last_err = OCIPing(H->svc, H->err, OCI_DEFAULT); +#else + /* use good old OCIServerVersion() */ + H->last_err = OCIServerVersion(H->svc, H->err, (text *) version, sizeof(version), OCI_HTYPE_SVCCTX); +#endif + if (H->last_err == OCI_SUCCESS) { + return SUCCESS; + } + + OCIErrorGet(H->err, (ub4) 1, NULL, &error_code, NULL, 0, OCI_HTYPE_ERROR); + + if (error_code == 1010) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static const struct pdo_dbh_methods oci_methods = { + oci_handle_closer, + oci_handle_preparer, + oci_handle_doer, + oci_handle_quoter, + oci_handle_begin, + oci_handle_commit, + oci_handle_rollback, + oci_handle_set_attribute, + NULL, /* last_id not supported */ + pdo_oci_fetch_error_func, + oci_handle_get_attribute, + pdo_oci_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL /* get_gc */ +}; + +static int pdo_oci_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H; + int i, ret = 0; + struct pdo_data_src_parser vars[] = { + {"charset", NULL, 0}, {"dbname", "", 0}, {"user", NULL, 0}, {"password", NULL, 0}}; + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 4); + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST | 1 << PDO_PARAM_EVT_NORMALIZE; + + H->prefetch = PDO_OCI_PREFETCH_DEFAULT; + + /* allocate an environment */ +#ifdef HAVE_OCIENVNLSCREATE + if (vars[0].optval) { + H->charset = OCINlsCharSetNameToId(swoole_pdo_oci_Env, (const oratext *) vars[0].optval); + if (!H->charset) { + oci_init_error("OCINlsCharSetNameToId: unknown character set name"); + goto cleanup; + } else { + if (OCIEnvNlsCreate(&H->env, SWOOLE_PDO_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, H->charset, H->charset) != + OCI_SUCCESS) { + oci_init_error("OCIEnvNlsCreate: Check the character set is valid and that PHP has access to Oracle " + "libraries and NLS data"); + goto cleanup; + } + } + } +#endif + if (H->env == NULL) { + /* use the global environment */ + H->env = swoole_pdo_oci_Env; + } + + /* something to hold errors */ + OCIHandleAlloc(H->env, (dvoid **) &H->err, OCI_HTYPE_ERROR, 0, NULL); + + /* handle for the server */ + OCIHandleAlloc(H->env, (dvoid **) &H->server, OCI_HTYPE_SERVER, 0, NULL); + + H->last_err = + OCIServerAttach(H->server, H->err, (text *) vars[1].optval, (sb4) strlen(vars[1].optval), OCI_DEFAULT); + + if (H->last_err) { + oci_drv_error("pdo_oci_handle_factory"); + goto cleanup; + } + + H->attached = 1; + + /* create a service context */ + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->svc, OCI_HTYPE_SVCCTX, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SVCCTX"); + goto cleanup; + } + + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->session, OCI_HTYPE_SESSION, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SESSION"); + goto cleanup; + } + + /* set server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->server, 0, OCI_ATTR_SERVER, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SERVER"); + goto cleanup; + } + + /* username */ + if (!dbh->username && vars[2].optval) { + dbh->username = pestrdup(vars[2].optval, dbh->is_persistent); + } + + if (dbh->username) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->username, (ub4) strlen(dbh->username), OCI_ATTR_USERNAME, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_USERNAME"); + goto cleanup; + } + } + + /* password */ + if (!dbh->password && vars[3].optval) { + dbh->password = pestrdup(vars[3].optval, dbh->is_persistent); + } + + if (dbh->password) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->password, (ub4) strlen(dbh->password), OCI_ATTR_PASSWORD, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_PASSWORD"); + goto cleanup; + } + } + + /* Now fire up the session */ + H->last_err = OCISessionBegin(H->svc, H->err, H->session, OCI_CRED_RDBMS, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCISessionBegin"); + /* OCISessionBegin returns OCI_SUCCESS_WITH_INFO when + * user's password has expired, but is still usable. + */ + if (H->last_err != OCI_SUCCESS_WITH_INFO) { + goto cleanup; + } + } + + /* set the server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->session, 0, OCI_ATTR_SESSION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SESSION"); + goto cleanup; + } + + /* Get max character width */ + H->last_err = OCINlsNumericInfoGet(H->env, H->err, &H->max_char_width, OCI_NLS_CHARSET_MAXBYTESZ); + if (H->last_err) { + oci_drv_error("OCINlsNumericInfoGet: OCI_NLS_CHARSET_MAXBYTESZ"); + goto cleanup; + } + + dbh->methods = &oci_methods; + dbh->alloc_own_columns = 1; + dbh->native_case = PDO_CASE_UPPER; + + ret = 1; + +cleanup: + for (i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!ret) { + oci_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_oci_driver = {PDO_DRIVER_HEADER(oci), pdo_oci_handle_factory}; + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch) /* {{{ */ +{ + if (prefetch < 0) { + prefetch = 0; + } else if (prefetch > UB4MAXVAL / PDO_OCI_PREFETCH_ROWSIZE) { + prefetch = PDO_OCI_PREFETCH_DEFAULT; + } + return ((ub4) prefetch); +} +/* }}} */ diff --git a/thirdparty/pdo_oci/oci_statement.c b/thirdparty/pdo_oci/oci_statement.c new file mode 100644 index 0000000000..953cac03fb --- /dev/null +++ b/thirdparty/pdo_oci/oci_statement.c @@ -0,0 +1,1114 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_extensions.h" + +#define PDO_OCI_LOBMAXSIZE (4294967295UL) /* OCI_LOBMAXSIZE */ + +#define STMT_CALL(name, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +#define STMT_CALL_MSG(name, msg, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name ": " #msg, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +static php_stream *oci_create_lob_stream(pdo_stmt_t *stmt, OCILobLocator *lob); + +#define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \ + do { \ + boolean isTempLOB; \ + OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \ + if (isTempLOB) OCILobFreeTemporary(svchp, errhp, lob); \ + } while (0) + +static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + HashTable *BC = stmt->bound_columns; + HashTable *BP = stmt->bound_params; + + int i; + + if (S->stmt) { + /* cancel server side resources for the statement if we didn't + * fetch it all */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + + /* free the handle */ + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + S->stmt = NULL; + } + if (S->err) { + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + S->err = NULL; + } + + /* need to ensure these go away now */ + if (BC) { + zend_hash_destroy(BC); + FREE_HASHTABLE(stmt->bound_columns); + stmt->bound_columns = NULL; + } + + if (BP) { + zend_hash_destroy(BP); + FREE_HASHTABLE(stmt->bound_params); + stmt->bound_params = NULL; + } + + if (S->einfo.errmsg) { + pefree(S->einfo.errmsg, stmt->dbh->is_persistent); + S->einfo.errmsg = NULL; + } + +#if PHP_API_VERSION < 20240925 + bool server_obj_usable = + !Z_ISUNDEF(stmt->database_object_handle) && + IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) && + !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); +#else + bool server_obj_usable = php_pdo_stmt_valid_db_obj_handle(stmt); +#endif + + if (S->cols) { + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + if (server_obj_usable) { + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, (OCILobLocator *) S->cols[i].data); + } + OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB); + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + S->cols = NULL; + } + efree(S); + + stmt->driver_data = NULL; + + return 1; +} /* }}} */ + +static int oci_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + ub4 rowcount; + b4 mode; + + if (!S->stmt_type) { + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_STMT_TYPE", (S->stmt, OCI_HTYPE_STMT, &S->stmt_type, 0, OCI_ATTR_STMT_TYPE, S->err)); + } + + if (stmt->executed) { + /* ensure that we cancel the cursor from a previous fetch */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + } + +#ifdef OCI_STMT_SCROLLABLE_READONLY /* needed for oci8 ? */ + if (S->exec_type == OCI_STMT_SCROLLABLE_READONLY) { + mode = OCI_STMT_SCROLLABLE_READONLY; + } else +#endif + if (stmt->dbh->auto_commit && !stmt->dbh->in_txn) { + mode = OCI_COMMIT_ON_SUCCESS; + } else { + mode = OCI_DEFAULT; + } + + STMT_CALL( + OCIStmtExecute, + (S->H->svc, S->stmt, S->err, (S->stmt_type == OCI_STMT_SELECT && !S->have_blobs) ? 0 : 1, 0, NULL, NULL, mode)); + + if (!stmt->executed) { + ub4 colcount; + /* do first-time-only definition of bind/mapping stuff */ + + /* how many columns do we have ? */ + STMT_CALL_MSG( + OCIAttrGet, "ATTR_PARAM_COUNT", (S->stmt, OCI_HTYPE_STMT, &colcount, 0, OCI_ATTR_PARAM_COUNT, S->err)); + + stmt->column_count = (int) colcount; + + if (S->cols) { + int i; + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + /* do nothing */ + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + } + + S->cols = ecalloc(colcount, sizeof(pdo_oci_column)); + } + + STMT_CALL_MSG(OCIAttrGet, "ATTR_ROW_COUNT", (S->stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, S->err)); + stmt->row_count = (long) rowcount; + + return 1; +} /* }}} */ + +static sb4 oci_bind_input_cb( + dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + *indpp = &P->indicator; + + // TODO Duplicate dereferencing operation + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + if (P->thing) { + *bufpp = P->thing; + *alenp = sizeof(void *); + } else if (ZVAL_IS_NULL(parameter)) { + /* insert a NULL value into the column */ + P->indicator = -1; /* NULL */ + *bufpp = 0; + *alenp = -1; + } else if (!P->thing) { + /* regular string bind */ + // safe: use preconverted C buffer (no Zend API here `try_convert_to_string`) + if (!P->preconv_buf) { + return OCI_ERROR; + } + *bufpp = P->preconv_buf; + *alenp = P->preconv_len; + } + + *piecep = OCI_ONE_PIECE; + return OCI_CONTINUE; +} /* }}} */ + +/** + * This function will be called during the execution of OCIStmtExecute, + * while the OCIStmtExecute function is executed in an AIO asynchronous thread within asynchronous IO. + * + */ +static sb4 oci_bind_output_cb(dvoid *ctx, + OCIBind *bindp, + ub4 iter, + ub4 index, + dvoid **bufpp, + ub4 **alenpp, + ub1 *piecep, + dvoid **indpp, + ub2 **rcodepp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + P->actual_len = sizeof(OCILobLocator *); + *bufpp = P->thing; + *alenpp = &P->actual_len; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + return OCI_CONTINUE; + } + + if (Z_TYPE_P(parameter) == IS_OBJECT || Z_TYPE_P(parameter) == IS_RESOURCE) { + return OCI_CONTINUE; + } + + // It can be safely released in an aio thread, this is a persistent string, + // allocated with malloc instead of ZendMM emalloc. + zval_ptr_dtor(parameter); + + // Must use malloc for persistent strings + Z_STR_P(parameter) = zend_string_alloc(param->max_value_len, 1); + P->used_for_output = 1; + + P->actual_len = (ub4) Z_STRLEN_P(parameter); + *alenpp = &P->actual_len; + *bufpp = (Z_STR_P(parameter))->val; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + + return OCI_CONTINUE; +} /* }}} */ + +static int oci_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + pdo_oci_bound_param *P; + sb4 value_sz = -1; + zval *parameter; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + P = (pdo_oci_bound_param *) param->driver_data; + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P && P->preconv_buf) { + efree(P->preconv_buf); + P->preconv_buf = NULL; + P->preconv_len = 0; + } + if (P && P->thing) { + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + efree(P); + } else if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + P = (pdo_oci_bound_param *) ecalloc(1, sizeof(pdo_oci_bound_param)); + param->driver_data = P; + + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_LOB: + /* P->thing is now an OCILobLocator * */ + P->oci_type = SQLT_BLOB; + value_sz = (sb4) sizeof(OCILobLocator *); + break; + + case PDO_PARAM_STR: + default: + P->oci_type = SQLT_CHR; + value_sz = (sb4) param->max_value_len; + if (param->max_value_len == 0) { + value_sz = (sb4) 1332; /* maximum size before value is interpreted as a LONG value */ + } + } + + if (param->name) { + STMT_CALL(OCIBindByName, + (S->stmt, + &P->bind, + S->err, + (text *) param->name->val, + (sb4) param->name->len, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } else { + STMT_CALL(OCIBindByPos, + (S->stmt, + &P->bind, + S->err, + ((ub4) param->paramno) + 1, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } + + + STMT_CALL(OCIBindDynamic, (P->bind, S->err, param, oci_bind_input_cb, param, oci_bind_output_cb)); + + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + P->indicator = 0; + P->used_for_output = 0; + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + ub4 empty = 0; + STMT_CALL(OCIDescriptorAlloc, (S->H->env, &P->thing, OCI_DTYPE_LOB, 0, NULL)); + STMT_CALL(OCIAttrSet, (P->thing, OCI_DTYPE_LOB, &empty, 0, OCI_ATTR_LOBEMPTY, S->err)); + S->have_blobs = 1; + } + + /* in PDO_PARAM_EVT_EXEC_PRE, executed in PHP-context (main thread) */ + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_LOB && Z_TYPE_P(parameter) != IS_NULL) { + convert_to_string(parameter); + /* allocate persistent or temporary buffer that outlives dispatch to async thread */ + P->preconv_len = Z_STRLEN_P(parameter); + P->preconv_buf = emalloc(P->preconv_len + 1); + memcpy(P->preconv_buf, Z_STRVAL_P(parameter), P->preconv_len); + P->preconv_buf[P->preconv_len] = '\0'; + } + + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + /* fixup stuff set in motion in oci_bind_output_cb */ + if (P->used_for_output) { + if (P->indicator == -1) { + /* set up a NULL value */ + if (Z_TYPE_P(parameter) == IS_STRING) { + /* OCI likes to stick non-terminated strings in things */ + *Z_STRVAL_P(parameter) = '\0'; + } + zval_ptr_dtor_str(parameter); + ZVAL_UNDEF(parameter); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + Z_STR_P(parameter) = zend_string_init(Z_STRVAL_P(parameter), P->actual_len, 1); + } + } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) { + php_stream *stm; + + if (Z_TYPE_P(parameter) == IS_NULL) { + /* if the param is NULL, then we assume that they + * wanted to bind a lob locator into it from the query + * */ + + stm = oci_create_lob_stream(stmt, (OCILobLocator *) P->thing); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + php_stream_to_zval(stm, parameter); + } + } else { + /* we're a LOB being used for insert; transfer the data now */ + size_t n; + ub4 amt, offset = 1; + char *consume; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + do { + char buf[8192]; + n = php_stream_read(stm, buf, sizeof(buf)); + if ((int) n <= 0) { + break; + } + consume = buf; + do { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + offset += amt; + n -= amt; + consume += amt; + } while (n); + } while (1); + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + OCILobFlushBuffer(S->H->svc, S->err, (OCILobLocator *) P->thing, 0); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + /* stick the string into the LOB */ + consume = Z_STRVAL_P(parameter); + n = Z_STRLEN_P(parameter); + if (n) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + while (n) { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + consume += amt; + n -= amt; + } + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + } + } + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + } + } + + if (P->preconv_buf) { + efree(P->preconv_buf); + P->preconv_buf = NULL; + P->preconv_len = 0; + } + + return 1; + } + } + + return 1; +} /* }}} */ + +static int oci_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */ +{ +#ifdef HAVE_OCISTMTFETCH2 + ub4 ociori = OCI_FETCH_NEXT; +#endif + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + +#ifdef HAVE_OCISTMTFETCH2 + switch (ori) { + case PDO_FETCH_ORI_NEXT: + ociori = OCI_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + ociori = OCI_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + ociori = OCI_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + ociori = OCI_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + ociori = OCI_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + ociori = OCI_FETCH_RELATIVE; + break; + } + S->last_err = OCIStmtFetch2(S->stmt, S->err, 1, ociori, (sb4) offset, OCI_DEFAULT); +#else + S->last_err = OCIStmtFetch(S->stmt, S->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); +#endif + + if (S->last_err == OCI_NO_DATA) { + /* no (more) data */ + return 0; + } + + if (S->last_err == OCI_NEED_DATA) { + oci_stmt_error("OCI_NEED_DATA"); + return 0; + } + + if (S->last_err == OCI_SUCCESS_WITH_INFO || S->last_err == OCI_SUCCESS) { + return 1; + } + + oci_stmt_error("OCIStmtFetch"); + + return 0; +} /* }}} */ + +/** + * This function is thread-safe as it does not call any ZendAPI. + */ +static sb4 oci_define_callback( + dvoid *octxp, OCIDefine *define, ub4 iter, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { + pdo_oci_column *col = (pdo_oci_column *) octxp; + + switch (col->dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + *piecep = OCI_ONE_PIECE; + *bufpp = col->data; + *alenpp = &col->datalen; + *indpp = (dvoid *) &col->indicator; + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + + return OCI_CONTINUE; +} + +static int oci_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + text *colname; + ub2 dtype, data_size, precis; + ub4 namelen; + struct pdo_column_data *col = &stmt->columns[colno]; + bool dyn = FALSE; + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* what type ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* how big ? */ + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_DATA_SIZE", (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err)); + + /* precision ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* name ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME", (param, OCI_DTYPE_PARAM, &colname, &namelen, OCI_ATTR_NAME, S->err)); + + col->precision = precis; + col->maxlen = data_size; + col->name = zend_string_init((char *) colname, namelen, 0); + + S->cols[colno].dtype = dtype; + + /* how much room do we need to store the field */ + switch (dtype) { + case SQLT_LBI: + case SQLT_LNG: + if (dtype == SQLT_LBI) { + dtype = SQLT_BIN; + } else { + dtype = SQLT_CHR; + } + S->cols[colno].datalen = 512; /* XXX should be INT_MAX and fetched by pieces */ + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + break; + + case SQLT_BLOB: + case SQLT_CLOB: + STMT_CALL(OCIDescriptorAlloc, (S->H->env, (dvoid **) &S->cols[colno].data, OCI_DTYPE_LOB, 0, NULL)); + S->cols[colno].datalen = sizeof(OCILobLocator *); + dyn = TRUE; + break; + + case SQLT_BIN: + default: + if (dtype == SQLT_DAT || dtype == SQLT_NUM || dtype == SQLT_RDD +#ifdef SQLT_TIMESTAMP + || dtype == SQLT_TIMESTAMP +#endif +#ifdef SQLT_TIMESTAMP_TZ + || dtype == SQLT_TIMESTAMP_TZ +#endif + ) { + /* should be big enough for most date formats and numbers */ + S->cols[colno].datalen = 512; +#if defined(SQLT_IBFLOAT) && defined(SQLT_IBDOUBLE) + } else if (dtype == SQLT_IBFLOAT || dtype == SQLT_IBDOUBLE) { + S->cols[colno].datalen = 1024; +#endif + } else if (dtype == SQLT_BIN) { + S->cols[colno].datalen = (ub4) col->maxlen * 2; /* raw characters to hex digits */ + } else { + S->cols[colno].datalen = (ub4) (col->maxlen * S->H->max_char_width); + } + + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + dtype = SQLT_CHR; + } + + STMT_CALL(OCIDefineByPos, + (S->stmt, + &S->cols[colno].def, + S->err, + colno + 1, + S->cols[colno].data, + S->cols[colno].datalen, + dtype, + &S->cols[colno].indicator, + &S->cols[colno].fetched_len, + &S->cols[colno].retcode, + dyn ? OCI_DYNAMIC_FETCH : OCI_DEFAULT)); + + if (dyn) { + STMT_CALL(OCIDefineDynamic, (S->cols[colno].def, S->err, &S->cols[colno], oci_define_callback)); + } + + return 1; +} /* }}} */ + +struct _oci_lob_env { + OCISvcCtx *svc; + OCIError *err; +}; +typedef struct _oci_lob_env oci_lob_env; + +struct oci_lob_self { + zval dbh; + pdo_stmt_t *stmt; + pdo_oci_stmt *S; + OCILobLocator *lob; + oci_lob_env *E; + ub4 offset; + ub1 csfrm; +}; + +static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + ub4 amt; + sword r; + + amt = (ub4) count; + r = OCILobWrite(self->E->svc, + self->E->err, + self->lob, + &amt, + self->offset, + (char *) buf, + (ub4) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + + if (r != OCI_SUCCESS) { + return (ssize_t) -1; + } + + self->offset += amt; + return amt; +} + +static ssize_t oci_blob_read(php_stream *stream, char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; +#if HAVE_OCILOBREAD2 + oraub8 byte_amt = (oraub8) count; + oraub8 char_amt = 0; + + sword r = OCILobRead2(self->E->svc, + self->E->err, + self->lob, + &byte_amt, + &char_amt, + (oraub8) self->offset, + buf, + (oraub8) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + self->csfrm); +#else + ub4 byte_amt = (ub4) count; + + sword r = OCILobRead(self->E->svc, + self->E->err, + self->lob, + &byte_amt, + self->offset, + buf, + (ub4) count, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); +#endif + + if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { + return (ssize_t) -1; + } + +#if HAVE_OCILOBREAD2 + self->offset += self->csfrm == 0 ? byte_amt : char_amt; +#else + self->offset += byte_amt; +#endif + if (byte_amt < count) { + stream->eof = 1; + } + return byte_amt; +} + +static int oci_blob_close(php_stream *stream, int close_handle) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + pdo_stmt_t *stmt = self->stmt; + + if (close_handle) { + zend_object *obj = &stmt->std; + + OCILobClose(self->E->svc, self->E->err, self->lob); + zval_ptr_dtor(&self->dbh); + GC_DELREF(obj); + efree(self->E); + efree(self); + } + + /* php_pdo_free_statement(stmt); */ + return 0; +} + +static int oci_blob_flush(php_stream *stream) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + OCILobFlushBuffer(self->E->svc, self->E->err, self->lob, 0); + return 0; +} + +static int oci_blob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + + if (offset >= PDO_OCI_LOBMAXSIZE) { + return -1; + } else { + self->offset = (ub4) offset + 1; /* Oracle LOBS are 1-based, but PHP is 0-based */ + return 0; + } +} + +static const php_stream_ops oci_blob_stream_ops = {oci_blob_write, + oci_blob_read, + oci_blob_close, + oci_blob_flush, + "pdo_oci blob stream", + oci_blob_seek, + NULL, + NULL, + NULL}; + +static php_stream *oci_create_lob_stream(pdo_stmt_t *stmt, OCILobLocator *lob) { + php_stream *stm; + struct oci_lob_self *self = ecalloc(1, sizeof(*self)); + +#if PHP_API_VERSION < 20240925 + ZVAL_COPY_VALUE(&self->dbh, &stmt->database_object_handle); +#else + ZVAL_OBJ(&self->dbh, stmt->database_object_handle); +#endif + self->lob = lob; + self->offset = 1; /* 1-based */ + self->stmt = stmt; + self->S = (pdo_oci_stmt *) stmt->driver_data; + self->E = ecalloc(1, sizeof(oci_lob_env)); + self->E->svc = self->S->H->svc; + self->E->err = self->S->err; + + OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm); + + stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); + + if (stm) { + zend_object *obj; + obj = &stmt->std; + Z_ADDREF(self->dbh); + GC_ADDREF(obj); + return stm; + } + + efree(self); + return NULL; +} + +static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + pdo_oci_column *C = &S->cols[colno]; + + /* check the indicator to ensure that the data is intact */ + if (C->indicator == -1) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->indicator == 0) { + /* it was stored perfectly */ + + if (C->dtype == SQLT_BLOB || C->dtype == SQLT_CLOB) { + if (C->data) { + php_stream *stream = oci_create_lob_stream(stmt, (OCILobLocator *) C->data); + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) C->data, OCI_LOB_READONLY); + php_stream_to_zval(stream, result); + return 1; + } + return 0; + } + + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + return 1; + } else { + /* it was truncated */ + php_error_docref(NULL, E_WARNING, "Column %d data was too large for buffer and was truncated to fit it", colno); + ZVAL_STRINGL(result, C->data, C->fetched_len); + return 1; + } +} /* }}} */ + +static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + ub2 dtype, precis; + sb1 scale; + zval flags; + ub1 isnull, charset_form; + if (!S->stmt) { + return FAILURE; + } + if (colno >= stmt->column_count) { + /* error invalid column */ + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* column data type */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* column precision */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* column scale */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE", (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, S->err)); + + /* string column charset form */ + if (dtype == SQLT_CHR || dtype == SQLT_VCS || dtype == SQLT_AFC || dtype == SQLT_CLOB) { + STMT_CALL_MSG(OCIAttrGet, + "OCI_ATTR_CHARSET_FORM", + (param, OCI_DTYPE_PARAM, &charset_form, 0, OCI_ATTR_CHARSET_FORM, S->err)); + } + + if (dtype) { + /* if there is a declared type */ + switch (dtype) { +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP"); + add_assoc_string(return_value, "native_type", "TIMESTAMP"); + break; +#endif +#ifdef SQLT_TIMESTAMP_TZ + case SQLT_TIMESTAMP_TZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH TIMEZONE"); + break; +#endif +#ifdef SQLT_TIMESTAMP_LTZ + case SQLT_TIMESTAMP_LTZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + break; +#endif +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL YEAR TO MONTH"); + add_assoc_string(return_value, "native_type", "INTERVAL YEAR TO MONTH"); + break; +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL DAY TO SECOND"); + add_assoc_string(return_value, "native_type", "INTERVAL DAY TO SECOND"); + break; +#endif + case SQLT_DAT: + add_assoc_string(return_value, "oci:decl_type", "DATE"); + add_assoc_string(return_value, "native_type", "DATE"); + break; + case SQLT_FLT: + case SQLT_NUM: + /* if the precision is nonzero and scale is -127 then it is a FLOAT */ + if (scale == -127 && precis != 0) { + add_assoc_string(return_value, "oci:decl_type", "FLOAT"); + add_assoc_string(return_value, "native_type", "FLOAT"); + } else { + add_assoc_string(return_value, "oci:decl_type", "NUMBER"); + add_assoc_string(return_value, "native_type", "NUMBER"); + } + break; + case SQLT_LNG: + add_assoc_string(return_value, "oci:decl_type", "LONG"); + add_assoc_string(return_value, "native_type", "LONG"); + break; + case SQLT_BIN: + add_assoc_string(return_value, "oci:decl_type", "RAW"); + add_assoc_string(return_value, "native_type", "RAW"); + break; + case SQLT_LBI: + add_assoc_string(return_value, "oci:decl_type", "LONG RAW"); + add_assoc_string(return_value, "native_type", "LONG RAW"); + break; + case SQLT_CHR: + case SQLT_VCS: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NVARCHAR2"); + add_assoc_string(return_value, "native_type", "NVARCHAR2"); + } else { + add_assoc_string(return_value, "oci:decl_type", "VARCHAR2"); + add_assoc_string(return_value, "native_type", "VARCHAR2"); + } + break; + case SQLT_AFC: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCHAR"); + add_assoc_string(return_value, "native_type", "NCHAR"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CHAR"); + add_assoc_string(return_value, "native_type", "CHAR"); + } + break; + case SQLT_BLOB: + add_assoc_string(return_value, "oci:decl_type", "BLOB"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BLOB"); + break; + case SQLT_CLOB: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCLOB"); + add_assoc_string(return_value, "native_type", "NCLOB"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CLOB"); + add_assoc_string(return_value, "native_type", "CLOB"); + } + add_next_index_string(&flags, "blob"); + break; + case SQLT_BFILE: + add_assoc_string(return_value, "oci:decl_type", "BFILE"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BFILE"); + break; + case SQLT_RDD: + add_assoc_string(return_value, "oci:decl_type", "ROWID"); + add_assoc_string(return_value, "native_type", "ROWID"); + break; + case SQLT_BFLOAT: + case SQLT_IBFLOAT: + add_assoc_string(return_value, "oci:decl_type", "BINARY_FLOAT"); + add_assoc_string(return_value, "native_type", "BINARY_FLOAT"); + break; + case SQLT_BDOUBLE: + case SQLT_IBDOUBLE: + add_assoc_string(return_value, "oci:decl_type", "BINARY_DOUBLE"); + add_assoc_string(return_value, "native_type", "BINARY_DOUBLE"); + break; + default: + add_assoc_long(return_value, "oci:decl_type", dtype); + add_assoc_string(return_value, "native_type", "UNKNOWN"); + } + } else { + /* if the column is NULL */ + add_assoc_long(return_value, "oci:decl_type", 0); + add_assoc_string(return_value, "native_type", "NULL"); + } + + switch (dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + } + + /* column can be null */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_IS_NULL", (param, OCI_DTYPE_PARAM, &isnull, 0, OCI_ATTR_IS_NULL, S->err)); + + if (isnull) { + add_next_index_string(&flags, "nullable"); + } else { + add_next_index_string(&flags, "not_null"); + } + + /* PDO type */ + switch (dtype) { + case SQLT_BFILE: + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + } + + add_assoc_long(return_value, "scale", scale); + add_assoc_zval(return_value, "flags", &flags); + + OCIDescriptorFree(param, OCI_DTYPE_PARAM); + return SUCCESS; +} /* }}} */ + +const struct pdo_stmt_methods swoole_oci_stmt_methods = {oci_stmt_dtor, + oci_stmt_execute, + oci_stmt_fetch, + oci_stmt_describe, + oci_stmt_get_col, + oci_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + oci_stmt_col_meta, + NULL, + NULL}; + diff --git a/thirdparty/pdo_oci/pdo_oci.c b/thirdparty/pdo_oci/pdo_oci.c new file mode 100644 index 0000000000..07583133f1 --- /dev/null +++ b/thirdparty/pdo_oci/pdo_oci.c @@ -0,0 +1,144 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_oci.h" +#include "php_pdo_oci_int.h" +#ifdef ZTS +#include +#endif + +/* {{{ pdo_oci_module_entry */ + +static const zend_module_dep pdo_oci_deps[] = {ZEND_MOD_REQUIRED("pdo") ZEND_MOD_END}; + +zend_module_entry pdo_oci_module_entry = {STANDARD_MODULE_HEADER_EX, + NULL, + pdo_oci_deps, + "PDO_OCI", + NULL, + PHP_MINIT(pdo_oci), + PHP_MSHUTDOWN(pdo_oci), + PHP_RINIT(pdo_oci), + NULL, + PHP_MINFO(pdo_oci), + PHP_PDO_OCI_VERSION, + STANDARD_MODULE_PROPERTIES}; +/* }}} */ + +#ifdef COMPILE_DL_PDO_OCI +ZEND_GET_MODULE(pdo_oci) +#endif + +const ub4 PDO_OCI_INIT_MODE = +#if 0 && defined(OCI_SHARED) + /* shared mode is known to be bad for PHP */ + OCI_SHARED +#else + OCI_DEFAULT +#endif +#ifdef OCI_OBJECT + | OCI_OBJECT +#endif + // Whether PHP is ZTS or not, when used in Swoole coroutines, this option must be enabled. + | OCI_THREADED + ; + +/* true global environment */ +OCIEnv *pdo_oci_Env = NULL; + +#ifdef ZTS +/* lock for pdo_oci_Env initialization */ +static MUTEX_T pdo_oci_env_mutex; +#endif + +/* {{{ PHP_MINIT_FUNCTION */ +PHP_MINIT_FUNCTION(pdo_oci) { + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); + REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); + + if (FAILURE == php_pdo_register_driver(&pdo_oci_driver)) { + return FAILURE; + } + + // Defer OCI init to PHP_RINIT_FUNCTION because with php-fpm, + // NLS_LANG is not yet available here. + +#ifdef ZTS + pdo_oci_env_mutex = tsrm_mutex_alloc(); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION */ +PHP_RINIT_FUNCTION(pdo_oci) { + if (!pdo_oci_Env) { +#ifdef ZTS + tsrm_mutex_lock(pdo_oci_env_mutex); + if (!pdo_oci_Env) { // double-checked locking idiom +#endif +#ifdef HAVE_OCIENVCREATE + OCIEnvCreate(&pdo_oci_Env, PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL, 0, NULL); +#else + OCIInitialize(PDO_OCI_INIT_MODE, NULL, NULL, NULL, NULL); + OCIEnvInit(&pdo_oci_Env, OCI_DEFAULT, 0, NULL); +#endif +#ifdef ZTS + } + tsrm_mutex_unlock(pdo_oci_env_mutex); +#endif + } + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(pdo_oci) { + php_pdo_unregister_driver(&pdo_oci_driver); + + if (pdo_oci_Env) { + OCIHandleFree((dvoid *) pdo_oci_Env, OCI_HTYPE_ENV); + } + +#ifdef ZTS + tsrm_mutex_free(pdo_oci_env_mutex); +#endif + + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(pdo_oci) { + php_info_print_table_start(); + php_info_print_table_row(2, "PDO Driver for OCI 8 and later", "enabled"); + php_info_print_table_end(); +} +/* }}} */ diff --git a/thirdparty/pdo_oci/php_pdo_oci_int.h b/thirdparty/pdo_oci/php_pdo_oci_int.h new file mode 100644 index 0000000000..c525c37e10 --- /dev/null +++ b/thirdparty/pdo_oci/php_pdo_oci_int.h @@ -0,0 +1,120 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_OCI_INT_H +#define PHP_PDO_OCI_INT_H + +#include "zend_portability.h" + +#include + +typedef struct { + const char *file; + int line; + sb4 errcode; + char *errmsg; +} pdo_oci_error_info; + +/* stuff we use in an OCI database handle */ +typedef struct { + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; +} pdo_oci_db_handle; + +typedef struct { + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; + + char *data; + ub4 datalen; + + ub2 dtype; + +} pdo_oci_column; + +typedef struct { + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; +} pdo_oci_stmt; + +typedef struct { + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; + + ub4 actual_len; + + dvoid *thing; /* for LOBS, REFCURSORS etc. */ + + unsigned used_for_output; + + /* --- NEW: preconverted buffer for async-safe binds --- */ + char *preconv_buf; /* pointer to a NUL-terminated C buffer (owned by param) */ + ub4 preconv_len; /* length of the buffer in bytes */ +} pdo_oci_bound_param; + +extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; +extern const pdo_driver_t swoole_pdo_oci_driver; +extern OCIEnv *swoole_pdo_oci_Env; + +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_oci_stmt_methods; +extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; +extern OCIEnv *swoole_pdo_oci_Env; + +/* Default prefetch size in number of rows */ +#define PDO_OCI_PREFETCH_DEFAULT 100 + +/* Arbitrary assumed row length for prefetch memory limit calcuation */ +#define PDO_OCI_PREFETCH_ROWSIZE 1024 + +enum { + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT +}; + +#endif /* PHP_PDO_OCI_INT_H */ diff --git a/thirdparty/pdo_sqlite/php_pdo_sqlite.h b/thirdparty/pdo_sqlite/php_pdo_sqlite.h new file mode 100644 index 0000000000..c29a81ebb2 --- /dev/null +++ b/thirdparty/pdo_sqlite/php_pdo_sqlite.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_SQLITE_H +#define PHP_PDO_SQLITE_H + +extern zend_module_entry pdo_sqlite_module_entry; +#define phpext_pdo_sqlite_ptr &pdo_sqlite_module_entry + +#include "php_version.h" +#define PHP_PDO_SQLITE_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +enum pdo_sqlite_transaction_mode { + PDO_SQLITE_TRANSACTION_MODE_DEFERRED = 0, + PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE = 1, + PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE = 2 +}; + +PHP_MINIT_FUNCTION(pdo_sqlite); +PHP_MSHUTDOWN_FUNCTION(pdo_sqlite); +PHP_RINIT_FUNCTION(pdo_sqlite); +PHP_RSHUTDOWN_FUNCTION(pdo_sqlite); +PHP_MINFO_FUNCTION(pdo_sqlite); + +#endif /* PHP_PDO_SQLITE_H */ diff --git a/thirdparty/pdo_sqlite/php_pdo_sqlite_int.h b/thirdparty/pdo_sqlite/php_pdo_sqlite_int.h new file mode 100644 index 0000000000..2cb13575ba --- /dev/null +++ b/thirdparty/pdo_sqlite/php_pdo_sqlite_int.h @@ -0,0 +1,70 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_SQLITE_INT_H +#define PHP_PDO_SQLITE_INT_H + +#include "php_pdo_sqlite.h" +#include + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_sqlite_error_info; + +typedef struct { + sqlite3 *db; + pdo_sqlite_error_info einfo; + enum pdo_sqlite_transaction_mode transaction_mode; +} pdo_sqlite_db_handle; + +typedef struct { + pdo_sqlite_db_handle *H; + sqlite3_stmt *stmt; + unsigned pre_fetched:1; + unsigned done:1; +} pdo_sqlite_stmt; + +extern const pdo_driver_t pdo_sqlite_driver; + +#if PHP_VERSION_ID >= 80400 +extern int pdo_sqlite_scanner(pdo_scanner_t *s); +#endif + +extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); +#define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) +#define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_sqlite_stmt_methods; + +enum { + PDO_SQLITE_ATTR_OPEN_FLAGS = PDO_ATTR_DRIVER_SPECIFIC, + PDO_SQLITE_ATTR_READONLY_STATEMENT, + PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES, + PDO_SQLITE_ATTR_BUSY_STATEMENT, + PDO_SQLITE_ATTR_EXPLAIN_STATEMENT, + PDO_SQLITE_ATTR_TRANSACTION_MODE +}; + +typedef int pdo_sqlite_create_collation_callback(void*, int, const void*, int, const void*); + +void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqlite_create_collation_callback callback); + +#endif diff --git a/thirdparty/pdo_sqlite/sqlite_driver.c b/thirdparty/pdo_sqlite/sqlite_driver.c new file mode 100644 index 0000000000..ff53fc0841 --- /dev/null +++ b/thirdparty/pdo_sqlite/sqlite_driver.c @@ -0,0 +1,428 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" +#include "php_swoole_api.h" + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_sqlite.h" +#include "php_pdo_sqlite_int.h" +#include "zend_exceptions.h" + +int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_sqlite_error_info *einfo = &H->einfo; + + einfo->errcode = sqlite3_errcode(H->db); + einfo->file = file; + einfo->line = line; + + if (einfo->errcode != SQLITE_OK) { + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + einfo->errmsg = pestrdup((char*)sqlite3_errmsg(H->db), dbh->is_persistent); + } else { /* no error */ + strncpy(*pdo_err, PDO_ERR_NONE, sizeof(*pdo_err)); + return 0; + } + switch (einfo->errcode) { + case SQLITE_NOTFOUND: + strncpy(*pdo_err, "42S02", sizeof(*pdo_err)); + break; + + case SQLITE_INTERRUPT: + strncpy(*pdo_err, "01002", sizeof(*pdo_err)); + break; + + case SQLITE_NOLFS: + strncpy(*pdo_err, "HYC00", sizeof(*pdo_err)); + break; + + case SQLITE_TOOBIG: + strncpy(*pdo_err, "22001", sizeof(*pdo_err)); + break; + + case SQLITE_CONSTRAINT: + strncpy(*pdo_err, "23000", sizeof(*pdo_err)); + break; + + case SQLITE_ERROR: + default: + strncpy(*pdo_err, "HY000", sizeof(*pdo_err)); + break; + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return einfo->errcode; +} +/* }}} */ + +static void pdo_sqlite_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_sqlite_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} + +static void sqlite_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + if (H) { + pdo_sqlite_error_info *einfo = &H->einfo; + + if (H->db) { +#ifdef HAVE_SQLITE3_CLOSE_V2 + sqlite3_close_v2(H->db); +#else + sqlite3_close(H->db); +#endif + H->db = NULL; + } + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool sqlite_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_sqlite_stmt *S = ecalloc(1, sizeof(pdo_sqlite_stmt)); + int i; + const char *tail; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_sqlite_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL|PDO_PLACEHOLDER_NAMED; + + if (PDO_CURSOR_FWDONLY != pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY)) { + H->einfo.errcode = SQLITE_ERROR; + pdo_sqlite_error(dbh); + return false; + } + + i = sqlite3_prepare_v2(H->db, ZSTR_VAL(sql), ZSTR_LEN(sql), &S->stmt, &tail); + if (i == SQLITE_OK) { + return true; + } + + pdo_sqlite_error(dbh); + + return false; +} + +static zend_long sqlite_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + if (sqlite3_exec(H->db, ZSTR_VAL(sql), NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return -1; + } else { + return sqlite3_changes(H->db); + } +} + +static zend_string *pdo_sqlite_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + return zend_i64_to_str(sqlite3_last_insert_rowid(H->db)); +} + +/* NB: doesn't handle binary strings... use prepared stmts for that */ +static zend_string* sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + char *quoted; + if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) { + return NULL; + } +#if PHP_VERSION_ID >= 80500 + if (UNEXPECTED(zend_str_has_nul_byte(unquoted))) { + if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + zend_throw_exception_ex( + php_pdo_get_exception(), 0, + "SQLite PDO::quote does not support null bytes"); + } else if (dbh->error_mode == PDO_ERRMODE_WARNING) { + php_error_docref(NULL, E_WARNING, + "SQLite PDO::quote does not support null bytes"); + } + return NULL; + } +#endif + + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + /* TODO use %Q format? */ + sqlite3_snprintf(2*ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted)); + zend_string *quoted_str = zend_string_init(quoted, strlen(quoted), 0); + efree(quoted); + return quoted_str; +} + +static bool sqlite_handle_begin(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + const char *begin_statement = "BEGIN"; + switch (H->transaction_mode) { + case PDO_SQLITE_TRANSACTION_MODE_DEFERRED: + begin_statement = "BEGIN DEFERRED TRANSACTION"; + break; + case PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE: + begin_statement = "BEGIN IMMEDIATE TRANSACTION"; + break; + case PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE: + begin_statement = "BEGIN EXCLUSIVE TRANSACTION"; + break; + } + + if (sqlite3_exec(H->db, begin_statement, NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_commit(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + if (sqlite3_exec(H->db, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_rollback(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + if (sqlite3_exec(H->db, "ROLLBACK", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *)sqlite3_libversion()); + break; + case PDO_SQLITE_ATTR_TRANSACTION_MODE: + ZVAL_LONG(return_value, H->transaction_mode); + break; + + default: + return 0; + } + + return 1; +} + +static bool pdo_sqlite_in_transaction(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle* H = (pdo_sqlite_db_handle*) dbh->driver_data; + /* It's not possible in sqlite3 to explicitly turn autocommit off other + * than manually starting a transaction. Manual transactions always are + * the mode of operation when autocommit is off. */ + return H->db && sqlite3_get_autocommit(H->db) == 0; +} + +static bool pdo_sqlite_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + zend_long lval; + + switch (attr) { + case PDO_ATTR_TIMEOUT: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_busy_timeout(H->db, lval * 1000); + return true; + case PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_extended_result_codes(H->db, lval); + return true; + case PDO_SQLITE_ATTR_TRANSACTION_MODE: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + switch (lval) { + case PDO_SQLITE_TRANSACTION_MODE_DEFERRED: + case PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE: + case PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE: + H->transaction_mode = lval; + return true; + default: + return false; + } + } + return false; +} + +static void pdo_sqlite_request_shutdown(pdo_dbh_t *dbh) +{ +} + +static const struct pdo_dbh_methods sqlite_methods = { + sqlite_handle_closer, + sqlite_handle_preparer, + sqlite_handle_doer, + sqlite_handle_quoter, + sqlite_handle_begin, + sqlite_handle_commit, + sqlite_handle_rollback, + pdo_sqlite_set_attr, + pdo_sqlite_last_insert_id, + pdo_sqlite_fetch_error_func, + pdo_sqlite_get_attribute, + NULL, /* check_liveness: not needed */ + NULL, + pdo_sqlite_request_shutdown, + pdo_sqlite_in_transaction, + NULL, +#if PHP_VERSION_ID >= 80400 + pdo_sqlite_scanner +#endif +}; + +static char *make_filename_safe(const char *filename) +{ + if (!filename) { + return NULL; + } + if (*filename && strncasecmp(filename, "file:", 5) == 0) { + if (PG(open_basedir) && *PG(open_basedir)) { + return NULL; + } + return estrdup(filename); + } + if (*filename && strcmp(filename, ":memory:")) { + char *fullpath = expand_filepath(filename, NULL); + + if (!fullpath) { + return NULL; + } + + if (php_check_open_basedir(fullpath)) { + efree(fullpath); + return NULL; + } + return fullpath; + } + return estrdup(filename); +} + +#define ZVAL_NULLABLE_STRING(zv, str) do { \ + zval *zv_ = zv; \ + const char *str_ = str; \ + if (str_) { \ + ZVAL_STRING(zv_, str_); \ + } else { \ + ZVAL_NULL(zv_); \ + } \ +} while (0) + +static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_sqlite_db_handle *H; + int i, ret = 0; + zend_long timeout = 60, flags; + char *filename; + + H = pecalloc(1, sizeof(pdo_sqlite_db_handle), dbh->is_persistent); + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + dbh->driver_data = H; + + /* skip all but this one param event */ + dbh->skip_param_evt = 0x7F ^ (1 << PDO_PARAM_EVT_EXEC_PRE); + + filename = make_filename_safe(dbh->data_source); + + if (!filename) { + zend_throw_exception_ex(php_pdo_get_exception(), 0, + "open_basedir prohibits opening %s", + dbh->data_source); + goto cleanup; + } + + flags = pdo_attr_lval(driver_options, PDO_SQLITE_ATTR_OPEN_FLAGS, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + if (!(PG(open_basedir) && *PG(open_basedir))) { + flags |= SQLITE_OPEN_URI; + } + i = sqlite3_open_v2(filename, &H->db, flags, NULL); + + efree(filename); + + if (i != SQLITE_OK) { + pdo_sqlite_error(dbh); + goto cleanup; + } + + if (driver_options) { + timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout); + } + sqlite3_busy_timeout(H->db, timeout * 1000); + + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &sqlite_methods; + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_sqlite_driver = { + PDO_DRIVER_HEADER(sqlite), + pdo_sqlite_handle_factory +}; diff --git a/thirdparty/pdo_sqlite/sqlite_sql_parser.c b/thirdparty/pdo_sqlite/sqlite_sql_parser.c new file mode 100644 index 0000000000..47406f3b6d --- /dev/null +++ b/thirdparty/pdo_sqlite/sqlite_sql_parser.c @@ -0,0 +1,419 @@ +/* Generated by re2c 4.3 */ +#line 1 "ext/pdo_sqlite/sqlite_sql_parser.re" +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_sqlite_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + #line 34 "ext/pdo_sqlite/sqlite_sql_parser.re" + + + +#line 34 "ext/pdo_sqlite/sqlite_sql_parser.c" +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy4; + case '\'': goto yy6; + case '-': goto yy7; + case '/': goto yy8; + case ':': goto yy9; + case '?': goto yy10; + case '[': goto yy12; + case '`': goto yy13; + default: goto yy2; + } +yy1: + YYCURSOR = YYMARKER; + switch (yyaccept) { + case 0: goto yy5; + case 1: goto yy17; + case 2: goto yy21; + case 3: goto yy33; + default: goto yy37; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case '"': + case '\'': + case '-': + case '/': + case ':': + case '?': + case '[': + case '`': goto yy3; + default: goto yy2; + } +yy3: +#line 46 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 80 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy15; +yy5: +#line 44 "ext/pdo_sqlite/sqlite_sql_parser.re" + { SKIP_ONE(PDO_PARSER_TEXT); } +#line 88 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy19; +yy7: + yych = *++YYCURSOR; + switch (yych) { + case '-': goto yy22; + default: goto yy5; + } +yy8: + yych = *++YYCURSOR; + switch (yych) { + case '*': goto yy24; + default: goto yy5; + } +yy9: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy25; + case ':': goto yy27; + default: goto yy5; + } +yy10: + yych = *++YYCURSOR; + switch (yych) { + case '?': goto yy29; + default: goto yy11; + } +yy11: +#line 43 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND_POS); } +#line 184 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy12: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy31; +yy13: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy35; +yy14: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy15: + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy16; + default: goto yy14; + } +yy16: + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy17; + case '"': goto yy16; + default: goto yy14; + } +yy17: +#line 37 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 218 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy18: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy19: + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy20; + default: goto yy18; + } +yy20: + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy21; + case '\'': goto yy20; + default: goto yy18; + } +yy21: +#line 38 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 242 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy22: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\n': goto yy23; + default: goto yy22; + } +yy23: +#line 45 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 254 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy38; + default: goto yy24; + } +yy25: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy25; + default: goto yy26; + } +yy26: +#line 42 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND); } +#line 336 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy27: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case ':': goto yy27; + default: goto yy28; + } +yy28: +#line 41 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 348 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '?': goto yy29; + default: goto yy28; + } +yy30: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy31: + switch (yych) { + case 0x00: goto yy1; + case ']': goto yy32; + default: goto yy30; + } +yy32: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy33; + case ']': goto yy32; + default: goto yy30; + } +yy33: +#line 40 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 380 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy34: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy35: + switch (yych) { + case 0x00: goto yy1; + case '`': goto yy36; + default: goto yy34; + } +yy36: + yyaccept = 4; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy37; + case '`': goto yy36; + default: goto yy34; + } +yy37: +#line 39 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 404 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy38: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy38; + case '/': goto yy39; + default: goto yy24; + } +yy39: + ++YYCURSOR; + goto yy23; +} +#line 47 "ext/pdo_sqlite/sqlite_sql_parser.re" + +} diff --git a/thirdparty/pdo_sqlite/sqlite_statement.c b/thirdparty/pdo_sqlite/sqlite_statement.c new file mode 100644 index 0000000000..9e4693a82a --- /dev/null +++ b/thirdparty/pdo_sqlite/sqlite_statement.c @@ -0,0 +1,470 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" +#include "php_swoole_api.h" + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_sqlite.h" +#include "php_pdo_sqlite_int.h" + +#if defined(__APPLE__) +// If more than one usage, a Zend macro could be created +// around this runtime check +#include +#endif + +static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + if (S->stmt) { + sqlite3_finalize(S->stmt); + S->stmt = NULL; + } + efree(S); + return 1; +} + +static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + } + + S->done = 0; + switch (sqlite3_step(S->stmt)) { + case SQLITE_ROW: + S->pre_fetched = 1; + php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt)); + return 1; + + case SQLITE_DONE: + php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt)); + stmt->row_count = sqlite3_changes(S->H->db); + sqlite3_reset(S->stmt); + S->done = 1; + return 1; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + zval *parameter; + + switch (event_type) { + case PDO_PARAM_EVT_EXEC_PRE: + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + S->done = 1; + } + + if (param->is_param) { + + if (param->paramno == -1) { + param->paramno = sqlite3_bind_parameter_index(S->stmt, ZSTR_VAL(param->name)) - 1; + } + + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_NULL: + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_INT: + case PDO_PARAM_BOOL: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + convert_to_long(parameter); +#if ZEND_LONG_MAX > 2147483647 + if (SQLITE_OK == sqlite3_bind_int64(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#else + if (SQLITE_OK == sqlite3_bind_int(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#endif + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_LOB: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } else if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + } + + if (SQLITE_OK == sqlite3_bind_blob(S->stmt, param->paramno + 1, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + SQLITE_STATIC)) { + return 1; + } + return 0; + + case PDO_PARAM_STR: + default: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + if (SQLITE_OK == sqlite3_bind_text(S->stmt, param->paramno + 1, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + SQLITE_STATIC)) { + return 1; + } + } + pdo_sqlite_error_stmt(stmt); + return 0; + } + } + break; + + default: + ; + } + return 1; +} + +static int pdo_sqlite_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + int i; + if (!S->stmt) { + return 0; + } + if (S->pre_fetched) { + S->pre_fetched = 0; + return 1; + } + if (S->done) { + return 0; + } + i = sqlite3_step(S->stmt); + switch (i) { + case SQLITE_ROW: + return 1; + + case SQLITE_DONE: + S->done = 1; + sqlite3_reset(S->stmt); + return 0; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + const char *str; + + if(colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + + str = sqlite3_column_name(S->stmt, colno); + stmt->columns[colno].name = zend_string_init(str, strlen(str), 0); + stmt->columns[colno].maxlen = SIZE_MAX; + stmt->columns[colno].precision = 0; + + return 1; +} + +static int pdo_sqlite_stmt_get_col( + pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + if (!S->stmt) { + return 0; + } + if(colno >= sqlite3_data_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + ZVAL_NULL(result); + return 1; + + case SQLITE_INTEGER: { + int64_t i = sqlite3_column_int64(S->stmt, colno); +#if SIZEOF_ZEND_LONG < 8 + if (i > ZEND_LONG_MAX || i < ZEND_LONG_MIN) { + ZVAL_STRINGL(result, + (char *) sqlite3_column_text(S->stmt, colno), + sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +#endif + ZVAL_LONG(result, i); + return 1; + } + + case SQLITE_FLOAT: + ZVAL_DOUBLE(result, sqlite3_column_double(S->stmt, colno)); + return 1; + + case SQLITE_BLOB: + ZVAL_STRINGL_FAST(result, + sqlite3_column_blob(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + + default: + ZVAL_STRINGL_FAST(result, + (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +} + +static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + const char *str; + zval flags; + + if (!S->stmt || !stmt->executed) { + return FAILURE; + } + if(colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_NULL); + break; + + case SQLITE_FLOAT: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_DOUBLE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + + case SQLITE_BLOB: + add_next_index_string(&flags, "blob"); + ZEND_FALLTHROUGH; + case SQLITE_TEXT: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_STRING)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + + case SQLITE_INTEGER: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_INTEGER)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_INT); + break; + } + + str = sqlite3_column_decltype(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "sqlite:decl_type", (char *)str); + } + +#ifdef HAVE_SQLITE3_COLUMN_TABLE_NAME + str = sqlite3_column_table_name(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "table", (char *)str); + } +#endif + + add_assoc_zval(return_value, "flags", &flags); + + return SUCCESS; +} + +static int pdo_sqlite_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + sqlite3_reset(S->stmt); + return 1; +} + +static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_SQLITE_ATTR_READONLY_STATEMENT: + ZVAL_FALSE(val); + + if (sqlite3_stmt_readonly(S->stmt)) { + ZVAL_TRUE(val); + } + break; + + case PDO_SQLITE_ATTR_BUSY_STATEMENT: + ZVAL_FALSE(val); + + if (sqlite3_stmt_busy(S->stmt)) { + ZVAL_TRUE(val); + } + break; + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3043000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + ZVAL_LONG(val, (zend_long)sqlite3_stmt_isexplain(S->stmt)); + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif + default: + return 0; + } + + return 1; +} + +static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *zval) +{ + switch (attr) { + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3043000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + if (Z_TYPE_P(zval) != IS_LONG) { + zend_type_error("explain mode must be of type int, %s given", zend_zval_value_name(zval)); + return 0; + } + if (Z_LVAL_P(zval) < 0 || Z_LVAL_P(zval) > 2) { + zend_value_error("explain mode must be one of the Pdo\\Sqlite::EXPLAIN_MODE_* constants"); + return 0; + } + + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + if (sqlite3_stmt_explain(S->stmt, (int)Z_LVAL_P(zval)) != SQLITE_OK) { + return 0; + } + + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif + default: + return 0; + } + + return 1; +} + +const struct pdo_stmt_methods swoole_sqlite_stmt_methods = { + pdo_sqlite_stmt_dtor, + pdo_sqlite_stmt_execute, + pdo_sqlite_stmt_fetch, + pdo_sqlite_stmt_describe, + pdo_sqlite_stmt_get_col, + pdo_sqlite_stmt_param_hook, + pdo_sqlite_stmt_set_attribute, /* set_attr */ + pdo_sqlite_stmt_get_attribute, /* get_attr */ + pdo_sqlite_stmt_col_meta, + NULL, /* next_rowset */ + pdo_sqlite_stmt_cursor_closer +}; diff --git a/thirdparty/php/curl/curl_arginfo.h b/thirdparty/php/curl/curl_arginfo.h index 383716f081..b00ad0b874 100644 --- a/thirdparty/php/curl/curl_arginfo.h +++ b/thirdparty/php/curl/curl_arginfo.h @@ -1,9 +1,9 @@ /* This is a generated file, edit the .stub.php file instead. * Stub hash: f1d616c644ad366405816cde0384f6f391773ebf */ -#include "curl_interface.h" +#include "swoole_curl_interface.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -47,6 +47,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_swoole_native_curl_init, 0, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, url, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +#if LIBCURL_VERSION_NUM >= 0x073E00 && PHP_VERSION_ID >= 80200 /* Available since 7.62.0 */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_upkeep, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_add_handle, 0, 2, IS_LONG, 0) ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -116,6 +122,9 @@ static const zend_function_entry swoole_native_curl_functions[] = { PHP_FE(swoole_native_curl_exec, arginfo_swoole_native_curl_exec) PHP_FE(swoole_native_curl_getinfo, arginfo_swoole_native_curl_getinfo) PHP_FE(swoole_native_curl_init, arginfo_swoole_native_curl_init) +#if LIBCURL_VERSION_NUM >= 0x073E00 && PHP_VERSION_ID >= 80200 + PHP_FE(swoole_native_curl_upkeep, arginfo_swoole_native_curl_upkeep) +#endif PHP_FE(swoole_native_curl_setopt, arginfo_swoole_native_curl_setopt) PHP_FE(swoole_native_curl_setopt_array, arginfo_swoole_native_curl_setopt_array) PHP_FE(swoole_native_curl_reset, arginfo_swoole_native_curl_reset) @@ -132,7 +141,6 @@ static const zend_function_entry swoole_native_curl_functions[] = { PHP_FE(swoole_native_curl_multi_info_read, arginfo_swoole_native_curl_multi_info_read) PHP_FE(swoole_native_curl_multi_init, arginfo_swoole_native_curl_multi_init) PHP_FE(swoole_native_curl_multi_remove_handle, arginfo_swoole_native_curl_multi_remove_handle) - PHP_FE_END }; #endif diff --git a/thirdparty/php/curl/curl_interface.h b/thirdparty/php/curl/curl_interface.h deleted file mode 100644 index cdfcf44aeb..0000000000 --- a/thirdparty/php/curl/curl_interface.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "php_swoole_cxx.h" - -#ifdef SW_USE_CURL -SW_EXTERN_C_BEGIN - -#include -#include - -void swoole_native_curl_minit(int module_number); -void swoole_native_curl_mshutdown(); - -PHP_FUNCTION(swoole_native_curl_close); -PHP_FUNCTION(swoole_native_curl_copy_handle); -PHP_FUNCTION(swoole_native_curl_errno); -PHP_FUNCTION(swoole_native_curl_error); -PHP_FUNCTION(swoole_native_curl_exec); -PHP_FUNCTION(swoole_native_curl_getinfo); -PHP_FUNCTION(swoole_native_curl_init); -PHP_FUNCTION(swoole_native_curl_setopt); -PHP_FUNCTION(swoole_native_curl_setopt_array); -PHP_FUNCTION(swoole_native_curl_reset); -PHP_FUNCTION(swoole_native_curl_escape); -PHP_FUNCTION(swoole_native_curl_unescape); -PHP_FUNCTION(swoole_native_curl_pause); -PHP_FUNCTION(swoole_native_curl_multi_add_handle); -PHP_FUNCTION(swoole_native_curl_multi_close); -PHP_FUNCTION(swoole_native_curl_multi_errno); -PHP_FUNCTION(swoole_native_curl_multi_exec); -PHP_FUNCTION(swoole_native_curl_multi_select); -PHP_FUNCTION(swoole_native_curl_multi_remove_handle); -PHP_FUNCTION(swoole_native_curl_multi_setopt); -PHP_FUNCTION(swoole_native_curl_multi_getcontent); -PHP_FUNCTION(swoole_native_curl_multi_info_read); -PHP_FUNCTION(swoole_native_curl_multi_init); -SW_EXTERN_C_END -#endif diff --git a/thirdparty/php/curl/curl_private.h b/thirdparty/php/curl/curl_private.h index 19ea94fcbd..3a58284a08 100644 --- a/thirdparty/php/curl/curl_private.h +++ b/thirdparty/php/curl/curl_private.h @@ -5,7 +5,7 @@ | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -15,9 +15,7 @@ +----------------------------------------------------------------------+ */ -/* Copied from PHP-4f68662f5b61aecf90f6d8005976f5f91d4ce8d3 */ - -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #ifndef _PHP_CURL_PRIVATE_H #define _PHP_CURL_PRIVATE_H @@ -67,16 +65,21 @@ typedef struct { typedef struct { zval func_name; zend_fcall_info_cache fci_cache; - int method; -} php_curl_progress, php_curl_fnmatch, php_curlm_server_push; +} php_curl_callback; typedef struct { php_curl_write *write; php_curl_write *write_header; php_curl_read *read; zval std_err; - php_curl_progress *progress; - php_curl_fnmatch *fnmatch; + php_curl_callback *progress; +#if PHP_VERSION_ID >= 80200 + php_curl_callback *xferinfo; +#endif + php_curl_callback *fnmatch; +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + php_curl_callback *sshhostkey; +#endif } php_curl_handlers; struct _php_curl_error { @@ -88,37 +91,23 @@ struct _php_curl_send_headers { zend_string *str; }; -#if PHP_VERSION_ID >= 80100 struct _php_curl_free { zend_llist post; zend_llist stream; HashTable *slist; }; -#else -struct _php_curl_free { - zend_llist str; - zend_llist post; - zend_llist stream; - HashTable *slist; -}; -#endif typedef struct { CURL *cp; -#if PHP_VERSION_ID >= 80100 php_curl_handlers handlers; -#else - php_curl_handlers *handlers; -#endif struct _php_curl_free *to_free; struct _php_curl_send_headers header; struct _php_curl_error err; - zend_bool in_callback; + bool in_callback; uint32_t *clone; zval postfields; -#if PHP_VERSION_ID >= 80100 + /* For CURLOPT_PRIVATE */ zval private_data; -#endif /* CurlShareHandle object set using CURLOPT_SHARE. */ struct _php_curlsh *share; zend_object std; @@ -127,7 +116,7 @@ typedef struct { #define CURLOPT_SAFE_UPLOAD -1 typedef struct { - php_curlm_server_push *server_push; + php_curl_callback *server_push; } php_curlm_handlers; namespace swoole { @@ -139,16 +128,9 @@ class Multi; using swoole::curl::Multi; typedef struct { -#if PHP_VERSION_ID < 80100 - int still_running; -#endif Multi *multi; zend_llist easyh; -#if PHP_VERSION_ID >= 80100 php_curlm_handlers handlers; -#else - php_curlm_handlers *handlers; -#endif struct { int no; } err; @@ -167,17 +149,16 @@ php_curl *swoole_curl_init_handle_into_zval(zval *curl); void swoole_curl_init_handle(php_curl *ch); void swoole_curl_cleanup_handle(php_curl *); void swoole_curl_multi_cleanup_list(void *data); -void swoole_curl_verify_handlers(php_curl *ch, int reporterror); +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror); void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source); -#if PHP_VERSION_ID >= 80100 static inline php_curl_handlers *curl_handlers(php_curl *ch) { return &ch->handlers; } -#else -static inline php_curl_handlers *curl_handlers(php_curl *ch) { - return ch->handlers; -} + +#if PHP_VERSION_ID >= 80300 +/* Consumes `zv` */ +zend_long swoole_curl_get_long(zval *zv); #endif static inline php_curl *curl_from_obj(zend_object *obj) { @@ -192,9 +173,12 @@ static inline php_curlsh *curl_share_from_obj(zend_object *obj) { #define Z_CURL_SHARE_P(zv) curl_share_from_obj(Z_OBJ_P(zv)) void curl_multi_register_class(const zend_function_entry *method_entries); -int swoole_curl_cast_object(zend_object *obj, zval *result, int type); -php_curl *swoole_curl_get_handle(zval *zid, bool exclusive = true, bool required = true); +#if PHP_VERSION_ID >= 80200 +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type); +#else +int swoole_curl_cast_object(zend_object *obj, zval *result, int type); +#endif #endif /* _PHP_CURL_PRIVATE_H */ #endif diff --git a/thirdparty/php/curl/interface.cc b/thirdparty/php/curl/interface.cc index e6006748df..3ea8049166 100644 --- a/thirdparty/php/curl/interface.cc +++ b/thirdparty/php/curl/interface.cc @@ -1,13 +1,11 @@ /* - +----------------------------------------------------------------------+ - | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -19,13 +17,13 @@ #include "php_swoole_cxx.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #include "php_swoole_curl.h" using namespace swoole; SW_EXTERN_C_BEGIN -#include "curl_interface.h" +#include "swoole_curl_interface.h" #include "curl_arginfo.h" #include @@ -39,25 +37,8 @@ SW_EXTERN_C_BEGIN #define HttpPost curl_httppost #endif -#ifndef RETVAL_COPY -#define RETVAL_COPY(zv) ZVAL_COPY(return_value, zv) -#endif - -#ifndef RETURN_COPY -#define RETURN_COPY(zv) \ - do { \ - RETVAL_COPY(zv); \ - return; \ - } while (0) -#endif - /* {{{ cruft for thread safe SSL crypto locks */ -#if defined(ZTS) && defined(HAVE_CURL_SSL) -#ifdef PHP_WIN32 -#define PHP_CURL_NEED_OPENSSL_TSL -#include -#else /* !PHP_WIN32 */ -#if defined(HAVE_CURL_OPENSSL) +#if defined(ZTS) && defined(HAVE_CURL_OLD_OPENSSL) #if defined(HAVE_OPENSSL_CRYPTO_H) #define PHP_CURL_NEED_OPENSSL_TSL #include @@ -66,32 +47,39 @@ SW_EXTERN_C_BEGIN "openssl/crypto.h; thus no SSL crypto locking callbacks will be set, which may " \ "cause random crashes on SSL requests" #endif -#elif defined(HAVE_CURL_GNUTLS) -#if defined(HAVE_GCRYPT_H) -#define PHP_CURL_NEED_GNUTLS_TSL -#include -#else -#warning "libcurl was compiled with GnuTLS support, but configure could not find " \ - "gcrypt.h; thus no SSL crypto locking callbacks will be set, which may " \ - "cause random crashes on SSL requests" -#endif -#else -#warning "libcurl was compiled with SSL support, but configure could not determine which" \ - "library was used; thus no SSL crypto locking callbacks will be set, which may " \ - "cause random crashes on SSL requests" -#endif /* HAVE_CURL_OPENSSL || HAVE_CURL_GNUTLS */ -#endif /* PHP_WIN32 */ -#endif /* ZTS && HAVE_CURL_SSL */ +#endif /* ZTS && HAVE_CURL_OLD_OPENSSL */ /* }}} */ -#define SMART_STR_PREALLOC 4096 - #include "zend_smart_str.h" #include "ext/standard/info.h" #include "ext/standard/file.h" #include "ext/standard/url.h" #include "curl_private.h" +#ifdef __GNUC__ +/* don't complain about deprecated CURLOPT_* we're exposing to PHP; we + need to keep using those to avoid breaking PHP API compatibiltiy */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#ifdef PHP_CURL_NEED_OPENSSL_TSL /* {{{ */ +static MUTEX_T *php_curl_openssl_tsl = NULL; + +/* Locking callbacks are no longer used since OpenSSL 1.1. Mark the functions as unused to + * avoid warnings due to this. */ +static ZEND_ATTRIBUTE_UNUSED void php_curl_ssl_lock(int mode, int n, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + tsrm_mutex_lock(php_curl_openssl_tsl[n]); + } else { + tsrm_mutex_unlock(php_curl_openssl_tsl[n]); + } +} + +static ZEND_ATTRIBUTE_UNUSED unsigned long php_curl_ssl_id(void) { + return (unsigned long) tsrm_thread_id(); +} +#endif + static zend_class_entry *swoole_native_curl_exception_ce; static zend_object_handlers swoole_native_curl_exception_handlers; @@ -111,57 +99,12 @@ static zend_object_handlers swoole_native_curl_exception_handlers; return; #endif -void swoole_curl_set_in_coroutine(php_curl *ch, bool value) { - zend_update_property_bool(nullptr, &ch->std, ZEND_STRL("in_coroutine"), value); -} - -bool swoole_curl_is_in_coroutine(php_curl *ch) { - zval rv; - zval *zv = zend_read_property_ex(nullptr, &ch->std, SW_ZSTR_KNOWN(SW_ZEND_STR_IN_COROUTINE), 1, &rv); - return zval_is_true(zv); -} - -void swoole_curl_set_private_data(php_curl *ch, zval *zvalue) { -#if PHP_VERSION_ID >= 80100 - zval_ptr_dtor(&ch->private_data); - ZVAL_COPY(&ch->private_data, zvalue); -#else - zend_update_property_ex(nullptr, &ch->std, SW_ZSTR_KNOWN(SW_ZEND_STR_PRIVATE_DATA), zvalue); -#endif -} - -void swoole_curl_get_private_data(php_curl *ch, zval *return_value) { -#if PHP_VERSION_ID >= 80100 - if (!Z_ISUNDEF(ch->private_data)) { - RETURN_COPY(&ch->private_data); - } else { - RETURN_FALSE; - } +static zend_result php_curl_option_str(php_curl *ch, zend_long option, const char *str, const size_t len) { +#if PHP_VERSION_ID >= 80300 + if (zend_char_has_nul_byte(str, len)) { #else - zval rv; - zval *zv = zend_read_property_ex(nullptr, &ch->std, SW_ZSTR_KNOWN(SW_ZEND_STR_PRIVATE_DATA), 1, &rv); - RETURN_COPY(zv); -#endif -} - -php_curl *swoole_curl_get_handle(zval *zid, bool exclusive, bool required) { - php_curl *ch = Z_CURL_P(zid); - if (SWOOLE_G(req_status) == PHP_SWOOLE_RSHUTDOWN_END) { - exclusive = false; - } - - if (exclusive) { - swoole::curl::Handle *handle = nullptr; - curl_easy_getinfo(ch->cp, CURLINFO_PRIVATE, &handle); - if (handle && handle->multi && handle->multi->check_bound_co() == nullptr) { - return nullptr; - } - } - return ch; -} - -static int php_curl_option_str(php_curl *ch, zend_long option, const char *str, const size_t len) { if (strlen(str) != len) { +#endif zend_value_error("%s(): cURL option must not contain any null bytes", get_active_function_name()); return FAILURE; } @@ -171,79 +114,86 @@ static int php_curl_option_str(php_curl *ch, zend_long option, const char *str, return error == CURLE_OK ? SUCCESS : FAILURE; } - -static int php_curl_option_url(php_curl *ch, const char *url, const size_t len) /* {{{ */ +#if PHP_VERSION_ID >= 80300 +static zend_result php_curl_option_url(php_curl *ch, const zend_string *url) /* {{{ */ +#else +static zend_result php_curl_option_url(php_curl *ch, const char *url, const size_t len) /* {{{ */ +#endif { /* Disable file:// if open_basedir are used */ if (PG(open_basedir) && *PG(open_basedir)) { curl_easy_setopt(ch->cp, CURLOPT_PROTOCOLS, CURLPROTO_ALL & ~CURLPROTO_FILE); } +#if PHP_VERSION_ID >= 80300 + return php_curl_option_str(ch, CURLOPT_URL, ZSTR_VAL(url), ZSTR_LEN(url)); +#else return php_curl_option_str(ch, CURLOPT_URL, url, len); +#endif } /* }}} */ -void swoole_curl_verify_handlers(php_curl *ch, int reporterror) /* {{{ */ +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror) /* {{{ */ { php_stream *stream; - ZEND_ASSERT(ch && curl_handlers(ch)); + ZEND_ASSERT(ch); - if (!Z_ISUNDEF(curl_handlers(ch)->std_err)) { + if (!Z_ISUNDEF(ch->handlers.std_err)) { stream = (php_stream *) zend_fetch_resource2_ex( - &curl_handlers(ch)->std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { if (reporterror) { php_error_docref(NULL, E_WARNING, "CURLOPT_STDERR resource has gone away, resetting to stderr"); } - zval_ptr_dtor(&curl_handlers(ch)->std_err); - ZVAL_UNDEF(&curl_handlers(ch)->std_err); + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); curl_easy_setopt(ch->cp, CURLOPT_STDERR, stderr); } } - if (curl_handlers(ch)->read && !Z_ISUNDEF(curl_handlers(ch)->read->stream)) { + if (ch->handlers.read && !Z_ISUNDEF(ch->handlers.read->stream)) { stream = (php_stream *) zend_fetch_resource2_ex( - &curl_handlers(ch)->read->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + &ch->handlers.read->stream, NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { if (reporterror) { php_error_docref(NULL, E_WARNING, "CURLOPT_INFILE resource has gone away, resetting to default"); } - zval_ptr_dtor(&curl_handlers(ch)->read->stream); - ZVAL_UNDEF(&curl_handlers(ch)->read->stream); - curl_handlers(ch)->read->res = NULL; - curl_handlers(ch)->read->fp = 0; + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + ch->handlers.read->res = NULL; + ch->handlers.read->fp = 0; curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); } } - if (curl_handlers(ch)->write_header && !Z_ISUNDEF(curl_handlers(ch)->write_header->stream)) { + if (ch->handlers.write_header && !Z_ISUNDEF(ch->handlers.write_header->stream)) { stream = (php_stream *) zend_fetch_resource2_ex( - &curl_handlers(ch)->write_header->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + &ch->handlers.write_header->stream, NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { if (reporterror) { php_error_docref(NULL, E_WARNING, "CURLOPT_WRITEHEADER resource has gone away, resetting to default"); } - zval_ptr_dtor(&curl_handlers(ch)->write_header->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write_header->stream); - curl_handlers(ch)->write_header->fp = 0; + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = 0; - curl_handlers(ch)->write_header->method = PHP_CURL_IGNORE; + ch->handlers.write_header->method = PHP_CURL_IGNORE; curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); } } - if (curl_handlers(ch)->write && !Z_ISUNDEF(curl_handlers(ch)->write->stream)) { + if (ch->handlers.write && !Z_ISUNDEF(ch->handlers.write->stream)) { stream = (php_stream *) zend_fetch_resource2_ex( - &curl_handlers(ch)->write->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + &ch->handlers.write->stream, NULL, php_file_le_stream(), php_file_le_pstream()); if (stream == NULL) { if (reporterror) { php_error_docref(NULL, E_WARNING, "CURLOPT_FILE resource has gone away, resetting to default"); } - zval_ptr_dtor(&curl_handlers(ch)->write->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write->stream); - curl_handlers(ch)->write->fp = 0; + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + ch->handlers.write->fp = 0; - curl_handlers(ch)->write->method = PHP_CURL_STDOUT; + ch->handlers.write->method = PHP_CURL_STDOUT; curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); } } @@ -259,11 +209,11 @@ static zend_object_handlers swoole_coroutine_curl_handle_handlers; static zend_object *swoole_curl_create_object(zend_class_entry *class_type); static void swoole_curl_free_obj(zend_object *object); +static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n); static zend_function *swoole_curl_get_constructor(zend_object *object); static zend_object *swoole_curl_clone_obj(zend_object *object); -static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n); - -static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields); +php_curl *swoole_curl_init_handle_into_zval(zval *curl); +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields); SW_EXTERN_C_END @@ -271,8 +221,30 @@ void swoole_native_curl_minit(int module_number) { if (!SWOOLE_G(cli)) { return; } + +#ifdef PHP_CURL_NEED_OPENSSL_TSL + if (!CRYPTO_get_id_callback()) { + int i, c = CRYPTO_num_locks(); + + php_curl_openssl_tsl = (MUTEX_T *) malloc(c * sizeof(MUTEX_T)); + if (!php_curl_openssl_tsl) { + return; + } + + for (i = 0; i < c; ++i) { + php_curl_openssl_tsl[i] = tsrm_mutex_alloc(); + } + + CRYPTO_set_id_callback(php_curl_ssl_id); + CRYPTO_set_locking_callback(php_curl_ssl_lock); + } +#endif + swoole_coroutine_curl_handle_ce = curl_ce; swoole_coroutine_curl_handle_ce->create_object = swoole_curl_create_object; +#if PHP_VERSION_ID >= 80300 + swoole_coroutine_curl_handle_ce->default_object_handlers = &swoole_coroutine_curl_handle_handlers; +#endif memcpy(&swoole_coroutine_curl_handle_handlers, &std_object_handlers, sizeof(zend_object_handlers)); swoole_coroutine_curl_handle_handlers.offset = XtOffsetOf(php_curl, std); swoole_coroutine_curl_handle_handlers.free_obj = swoole_curl_free_obj; @@ -284,7 +256,6 @@ void swoole_native_curl_minit(int module_number) { swoole_coroutine_curl_handle_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES; - zend_declare_property_bool(swoole_coroutine_curl_handle_ce, ZEND_STRL("in_coroutine"), 0, ZEND_ACC_PUBLIC); zend_declare_property_null(swoole_coroutine_curl_handle_ce, ZEND_STRL("private_data"), ZEND_ACC_PUBLIC); curl_multi_register_class(nullptr); @@ -306,7 +277,9 @@ static zend_object *swoole_curl_create_object(zend_class_entry *class_type) { zend_object_std_init(&intern->std, class_type); object_properties_init(&intern->std, class_type); +#if PHP_VERSION_ID < 80300 intern->std.handlers = &swoole_coroutine_curl_handle_handlers; +#endif return &intern->std; } @@ -325,22 +298,23 @@ static zend_object *swoole_curl_clone_obj(zend_object *object) { clone_object = swoole_curl_create_object(curl_ce); clone_ch = curl_from_obj(clone_object); - swoole_curl_init_handle(clone_ch); ch = curl_from_obj(object); - swoole_curl_set_in_coroutine(clone_ch, swoole_curl_is_in_coroutine(ch)); cp = curl_easy_duphandle(ch->cp); if (!cp) { zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); return &clone_ch->std; } + swoole_curl_init_handle(clone_ch); + clone_ch->cp = cp; swoole_setup_easy_copy_handlers(clone_ch, ch); + swoole::curl::create_handle(clone_ch->cp); - postfields = &clone_ch->postfields; + postfields = &ch->postfields; if (Z_TYPE_P(postfields) != IS_UNDEF) { - if (build_mime_structure_from_hash(clone_ch, postfields) != SUCCESS) { + if (build_mime_structure_from_hash(clone_ch, postfields) == FAILURE) { zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); return &clone_ch->std; } @@ -355,39 +329,54 @@ static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n) zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); zend_get_gc_buffer_add_zval(gc_buffer, &curl->postfields); - if (curl_handlers(curl)) { - if (curl_handlers(curl)->read) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->read->func_name); - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->read->stream); - } + if (curl->handlers.read) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.read->func_name); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.read->stream); + } - if (curl_handlers(curl)->write) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->write->func_name); - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->write->stream); - } + if (curl->handlers.write) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write->func_name); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write->stream); + } - if (curl_handlers(curl)->write_header) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->write_header->func_name); - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->write_header->stream); - } + if (curl->handlers.write_header) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write_header->func_name); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write_header->stream); + } - if (curl_handlers(curl)->progress) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->progress->func_name); - } + if (curl->handlers.progress) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.progress->func_name); + } - if (curl_handlers(curl)->fnmatch) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->fnmatch->func_name); - } +#if PHP_VERSION_ID >= 80200 + if (curl->handlers.xferinfo) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.xferinfo->func_name); + } +#endif - zend_get_gc_buffer_add_zval(gc_buffer, &curl_handlers(curl)->std_err); + if (curl->handlers.fnmatch) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.fnmatch->func_name); } +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + if (curl->handlers.sshhostkey) { + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.sshhostkey->func_name); + } +#endif + + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.std_err); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->private_data); + zend_get_gc_buffer_use(gc_buffer, table, n); return zend_std_get_properties(object); } +#if PHP_VERSION_ID >= 80200 +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type) { +#else int swoole_curl_cast_object(zend_object *obj, zval *result, int type) { +#endif if (type == IS_LONG) { /* For better backward compatibility, make (int) $curl_handle return the object ID, * similar to how it previously returned the resource ID. */ @@ -408,19 +397,11 @@ static size_t fn_write_nothing(char *data, size_t size, size_t nmemb, void *ctx) } /* }}} */ -/* {{{ curl_write_nothing - * Used as a work around. See _php_curl_close_ex - */ -static size_t curl_write_nothing(char *data, size_t size, size_t nmemb, void *ctx) { - return size * nmemb; -} -/* }}} */ - /* {{{ curl_write */ static size_t fn_write(char *data, size_t size, size_t nmemb, void *ctx) { php_curl *ch = (php_curl *) ctx; - php_curl_write *t = curl_handlers(ch)->write; + php_curl_write *t = ch->handlers.write; size_t length = size * nmemb; #if PHP_CURL_DEBUG @@ -463,8 +444,12 @@ static size_t fn_write(char *data, size_t size, size_t nmemb, void *ctx) { php_error_docref(NULL, E_WARNING, "Could not call the CURLOPT_WRITEFUNCTION"); length = -1; } else if (!Z_ISUNDEF(retval)) { - swoole_curl_verify_handlers(ch, 1); + swoole_curl_verify_handlers(ch, /* reporterror */ true); +#if PHP_VERSION_ID >= 80300 + length = swoole_curl_get_long(&retval); +#else length = zval_get_long(&retval); +#endif } zval_ptr_dtor(&argv[0]); @@ -481,53 +466,52 @@ static size_t fn_write(char *data, size_t size, size_t nmemb, void *ctx) { */ static int fn_fnmatch(void *ctx, const char *pattern, const char *string) { php_curl *ch = (php_curl *) ctx; - php_curl_fnmatch *t = curl_handlers(ch)->fnmatch; + php_curl_callback *t = ch->handlers.fnmatch; int rval = CURL_FNMATCHFUNC_FAIL; - switch (t->method) { - case PHP_CURL_USER: { - zval argv[3]; - zval retval; - int error; - zend_fcall_info fci; - - GC_ADDREF(&ch->std); - ZVAL_OBJ(&argv[0], &ch->std); - ZVAL_STRING(&argv[1], pattern); - ZVAL_STRING(&argv[2], string); - - fci.size = sizeof(fci); - ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); - fci.object = NULL; - fci.retval = &retval; - fci.param_count = 3; - fci.params = argv; - fci.named_params = NULL; - - ch->in_callback = 1; - error = zend_call_function(&fci, &t->fci_cache); - ch->in_callback = 0; - if (error == FAILURE) { - php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_FNMATCH_FUNCTION"); - } else if (!Z_ISUNDEF(retval)) { - swoole_curl_verify_handlers(ch, 1); - rval = zval_get_long(&retval); - } - zval_ptr_dtor(&argv[0]); - zval_ptr_dtor(&argv[1]); - zval_ptr_dtor(&argv[2]); - break; - } + zval argv[3]; + zval retval; + zend_result error; + zend_fcall_info fci; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRING(&argv[1], pattern); + ZVAL_STRING(&argv[2], string); + + fci.size = sizeof(fci); + ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = 3; + fci.params = argv; + fci.named_params = NULL; + + ch->in_callback = 1; + error = zend_call_function(&fci, &t->fci_cache); + ch->in_callback = 0; + if (error == FAILURE) { + php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_FNMATCH_FUNCTION"); + } else if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); +#if PHP_VERSION_ID >= 80300 + rval = swoole_curl_get_long(&retval); +#else + rval = zval_get_long(&retval); +#endif } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + zval_ptr_dtor(&argv[2]); return rval; } /* }}} */ /* {{{ curl_progress */ -static size_t fn_progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { +static int fn_progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { php_curl *ch = (php_curl *) clientp; - php_curl_progress *t = curl_handlers(ch)->progress; - size_t rval = 0; + php_curl_callback *t = ch->handlers.progress; + int rval = 0; #if PHP_CURL_DEBUG fprintf(stderr, "curl_progress() called\n"); @@ -540,51 +524,171 @@ static size_t fn_progress(void *clientp, double dltotal, double dlnow, double ul ulnow); #endif - switch (t->method) { - case PHP_CURL_USER: { - zval argv[5]; - zval retval; - int error; - zend_fcall_info fci; + zval argv[5]; + zval retval; + zend_result error; + zend_fcall_info fci; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_LONG(&argv[1], (zend_long) dltotal); + ZVAL_LONG(&argv[2], (zend_long) dlnow); + ZVAL_LONG(&argv[3], (zend_long) ultotal); + ZVAL_LONG(&argv[4], (zend_long) ulnow); + + fci.size = sizeof(fci); + ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = 5; + fci.params = argv; + fci.named_params = NULL; + + ch->in_callback = 1; + error = zend_call_function(&fci, &t->fci_cache); + ch->in_callback = 0; + if (error == FAILURE) { + php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_PROGRESSFUNCTION"); + } else if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); +#if PHP_VERSION_ID >= 80300 + if (0 != swoole_curl_get_long(&retval)) { +#else + if (0 != zval_get_long(&retval)) { +#endif + rval = 1; + } + } + zval_ptr_dtor(&argv[0]); + return rval; +} +/* }}} */ - GC_ADDREF(&ch->std); - ZVAL_OBJ(&argv[0], &ch->std); - ZVAL_LONG(&argv[1], (zend_long) dltotal); - ZVAL_LONG(&argv[2], (zend_long) dlnow); - ZVAL_LONG(&argv[3], (zend_long) ultotal); - ZVAL_LONG(&argv[4], (zend_long) ulnow); +#if LIBCURL_VERSION_NUM >= 0x072000 && PHP_VERSION_ID >= 80200 +/* {{{ curl_xferinfo */ +static size_t fn_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + php_curl *ch = (php_curl *) clientp; + php_curl_callback *t = ch->handlers.xferinfo; + int rval = 0; - fci.size = sizeof(fci); - ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); - fci.object = NULL; - fci.retval = &retval; - fci.param_count = 5; - fci.params = argv; - fci.named_params = NULL; +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_xferinfo() called\n"); + fprintf(stderr, + "clientp = %x, dltotal = %ld, dlnow = %ld, ultotal = %ld, ulnow = %ld\n", + clientp, + dltotal, + dlnow, + ultotal, + ulnow); +#endif - ch->in_callback = 1; - error = zend_call_function(&fci, &t->fci_cache); - ch->in_callback = 0; - if (error == FAILURE) { - php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_PROGRESSFUNCTION"); - } else if (!Z_ISUNDEF(retval)) { - swoole_curl_verify_handlers(ch, 1); - if (0 != zval_get_long(&retval)) { - rval = 1; - } + zval argv[5]; + zval retval; + zend_result error; + zend_fcall_info fci; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_LONG(&argv[1], dltotal); + ZVAL_LONG(&argv[2], dlnow); + ZVAL_LONG(&argv[3], ultotal); + ZVAL_LONG(&argv[4], ulnow); + + fci.size = sizeof(fci); + ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = 5; + fci.params = argv; + fci.named_params = NULL; + + ch->in_callback = 1; + error = zend_call_function(&fci, &t->fci_cache); + ch->in_callback = 0; + if (error == FAILURE) { + php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_XFERINFOFUNCTION"); + } else if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); +#if PHP_VERSION_ID >= 80300 + if (0 != swoole_curl_get_long(&retval)) { +#else + if (0 != zval_get_long(&retval)) { +#endif + rval = 1; } - zval_ptr_dtor(&argv[0]); - } } + zval_ptr_dtor(&argv[0]); return rval; } /* }}} */ +#endif + +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 +static int fn_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen) { + php_curl *ch = (php_curl *) clientp; + php_curl_callback *t = ch->handlers.sshhostkey; + int rval = CURLKHMATCH_MISMATCH; /* cancel connection in case of an exception */ + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_ssh_hostkeyfunction() called\n"); + fprintf(stderr, "clientp = %x, keytype = %d, key = %s, keylen = %zu\n", clientp, keytype, key, keylen); +#endif + + zval argv[4]; + zval retval; + zend_result error; + zend_fcall_info fci; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_LONG(&argv[1], keytype); + ZVAL_STRINGL(&argv[2], key, keylen); + ZVAL_LONG(&argv[3], keylen); + + fci.size = sizeof(fci); + ZVAL_COPY_VALUE(&fci.function_name, &t->func_name); + fci.object = NULL; + fci.retval = &retval; + fci.param_count = 4; + fci.params = argv; + fci.named_params = NULL; + + ch->in_callback = 1; + error = zend_call_function(&fci, &t->fci_cache); + ch->in_callback = 0; + if (error == FAILURE) { + php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_SSH_HOSTKEYFUNCTION"); + } else if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_LONG) { + zend_long retval_long = Z_LVAL(retval); + if (retval_long == CURLKHMATCH_OK || retval_long == CURLKHMATCH_MISMATCH) { + rval = retval_long; + } else { + zend_throw_error(NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or " + "CURLKHMATCH_MISMATCH"); + } + } else { + zend_throw_error( + NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or CURLKHMATCH_MISMATCH"); +#if PHP_VERSION_ID >= 80300 + zval_ptr_dtor(&retval); +#endif + } + } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[2]); + return rval; +} +#endif /* {{{ curl_read */ static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { php_curl *ch = (php_curl *) ctx; - php_curl_read *t = curl_handlers(ch)->read; + php_curl_read *t = ch->handlers.read; int length = 0; switch (t->method) { @@ -596,7 +700,7 @@ static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { case PHP_CURL_USER: { zval argv[3]; zval retval; - int error; + zend_result error; zend_fcall_info fci; GC_ADDREF(&ch->std); @@ -624,10 +728,12 @@ static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_READFUNCTION"); length = CURL_READFUNC_ABORT; } else if (!Z_ISUNDEF(retval)) { - swoole_curl_verify_handlers(ch, 1); + swoole_curl_verify_handlers(ch, /* reporterror */ true); if (Z_TYPE(retval) == IS_STRING) { length = MIN((int) (size * nmemb), Z_STRLEN(retval)); memcpy(data, Z_STRVAL(retval), length); + } else if (Z_TYPE(retval) == IS_LONG) { + length = Z_LVAL_P(&retval); } zval_ptr_dtor(&retval); } @@ -636,8 +742,6 @@ static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { zval_ptr_dtor(&argv[1]); break; } - default: - break; } return length; @@ -648,14 +752,15 @@ static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { */ static size_t fn_write_header(char *data, size_t size, size_t nmemb, void *ctx) { php_curl *ch = (php_curl *) ctx; - php_curl_write *t = curl_handlers(ch)->write_header; + php_curl_write *t = ch->handlers.write_header; size_t length = size * nmemb; switch (t->method) { case PHP_CURL_STDOUT: - // Handle special case write when we're returning the entire transfer - if (curl_handlers(ch)->write->method == PHP_CURL_RETURN && length > 0) { - smart_str_appendl(&curl_handlers(ch)->write->buf, data, (int) length); + /* Handle special case write when we're returning the entire transfer + */ + if (ch->handlers.write->method == PHP_CURL_RETURN && length > 0) { + smart_str_appendl(&ch->handlers.write->buf, data, (int) length); } else { PHPWRITE(data, length); } @@ -665,7 +770,7 @@ static size_t fn_write_header(char *data, size_t size, size_t nmemb, void *ctx) case PHP_CURL_USER: { zval argv[2]; zval retval; - int error; + zend_result error; zend_fcall_info fci; GC_ADDREF(&ch->std); @@ -688,8 +793,12 @@ static size_t fn_write_header(char *data, size_t size, size_t nmemb, void *ctx) php_error_docref(NULL, E_WARNING, "Could not call the CURLOPT_HEADERFUNCTION"); length = -1; } else if (!Z_ISUNDEF(retval)) { - swoole_curl_verify_handlers(ch, 1); + swoole_curl_verify_handlers(ch, /* reporterror */ true); +#if PHP_VERSION_ID >= 80300 + length = swoole_curl_get_long(&retval); +#else length = zval_get_long(&retval); +#endif } zval_ptr_dtor(&argv[0]); zval_ptr_dtor(&argv[1]); @@ -713,24 +822,15 @@ static int curl_debug(CURL *cp, curl_infotype type, char *buf, size_t buf_len, v if (type == CURLINFO_HEADER_OUT) { if (ch->header.str) { - zend_string_release(ch->header.str); - } - if (buf_len > 0) { - ch->header.str = zend_string_init(buf, buf_len, 0); + zend_string_release_ex(ch->header.str, 0); } + ch->header.str = zend_string_init(buf, buf_len, 0); } return 0; } /* }}} */ -/* {{{ curl_free_string - */ -static void curl_free_string(void **string) { - efree((char *) *string); -} -/* }}} */ - /* {{{ curl_free_post */ static void curl_free_post(void **post) { @@ -776,22 +876,22 @@ php_curl *swoole_curl_init_handle_into_zval(zval *curl) { */ void swoole_curl_init_handle(php_curl *ch) { ch->to_free = (struct _php_curl_free *) ecalloc(1, sizeof(struct _php_curl_free)); -#if PHP_VERSION_ID < 80100 - ch->handlers = (php_curl_handlers *) ecalloc(1, sizeof(php_curl_handlers)); -#endif - curl_handlers(ch)->write = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); - curl_handlers(ch)->write_header = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); - curl_handlers(ch)->read = (php_curl_read *) ecalloc(1, sizeof(php_curl_read)); - curl_handlers(ch)->progress = NULL; - curl_handlers(ch)->fnmatch = NULL; + ch->handlers.write = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.write_header = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.read = (php_curl_read *) ecalloc(1, sizeof(php_curl_read)); + ch->handlers.progress = NULL; +#if PHP_VERSION_ID >= 80200 + ch->handlers.xferinfo = NULL; +#endif + ch->handlers.fnmatch = NULL; +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + ch->handlers.sshhostkey = NULL; +#endif ch->clone = (uint32_t *) emalloc(sizeof(uint32_t)); *ch->clone = 1; memset(&ch->err, 0, sizeof(struct _php_curl_error)); -#if PHP_VERSION_ID < 80100 - zend_llist_init(&ch->to_free->str, sizeof(char *), (llist_dtor_func_t) curl_free_string, 0); -#endif zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t) curl_free_post, 0); zend_llist_init(&ch->to_free->stream, sizeof(struct mime_data_cb_arg *), (llist_dtor_func_t) curl_free_cb_arg, 0); @@ -839,8 +939,8 @@ static void create_certinfo(struct curl_certinfo *ci, zval *listcode) { static void _php_curl_set_default_options(php_curl *ch) { const char *cainfo; - curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0); + curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0L); curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, fn_write); curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); @@ -849,10 +949,10 @@ static void _php_curl_set_default_options(php_curl *ch) { curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, fn_write_header); curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); #ifndef ZTS - curl_easy_setopt(ch->cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 1); + curl_easy_setopt(ch->cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 1L); #endif - curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120); - curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20); /* prevent infinite redirects */ + curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120L); + curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20L); /* prevent infinite redirects */ cainfo = INI_STR("openssl.cafile"); if (!(cainfo && cainfo[0] != '\0')) { @@ -861,7 +961,10 @@ static void _php_curl_set_default_options(php_curl *ch) { if (cainfo && cainfo[0] != '\0') { curl_easy_setopt(ch->cp, CURLOPT_CAINFO, cainfo); } - curl_easy_setopt(ch->cp, CURLOPT_NOSIGNAL, 1); + +#ifdef ZTS + curl_easy_setopt(ch->cp, CURLOPT_NOSIGNAL, 1L); +#endif } /* }}} */ @@ -886,15 +989,19 @@ PHP_FUNCTION(swoole_native_curl_init) { ch = swoole_curl_init_handle_into_zval(return_value); ch->cp = cp; - curl_handlers(ch)->write->method = PHP_CURL_STDOUT; - curl_handlers(ch)->read->method = PHP_CURL_DIRECT; - curl_handlers(ch)->write_header->method = PHP_CURL_IGNORE; + ch->handlers.write->method = PHP_CURL_STDOUT; + ch->handlers.read->method = PHP_CURL_DIRECT; + ch->handlers.write_header->method = PHP_CURL_IGNORE; - swoole_curl_set_in_coroutine(ch, true); _php_curl_set_default_options(ch); + swoole::curl::create_handle(cp); if (url) { +#if PHP_VERSION_ID >= 80300 + if (php_curl_option_url(ch, url) == FAILURE) { +#else if (php_curl_option_url(ch, ZSTR_VAL(url), ZSTR_LEN(url)) == FAILURE) { +#endif zval_ptr_dtor(return_value); RETURN_FALSE; } @@ -902,60 +1009,95 @@ PHP_FUNCTION(swoole_native_curl_init) { } /* }}} */ +#if PHP_VERSION_ID >= 80300 +static void _php_copy_callback(php_curl *ch, + php_curl_callback **new_callback, + php_curl_callback *source_callback, + CURLoption option) { + if (source_callback) { + *new_callback = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + if (!Z_ISUNDEF(source_callback->func_name)) { + ZVAL_COPY(&(*new_callback)->func_name, &source_callback->func_name); + } + curl_easy_setopt(ch->cp, (CURLoption) option, (void *) ch); + } +} +#endif + void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source) { - if (!Z_ISUNDEF(curl_handlers(source)->write->stream)) { - Z_ADDREF(curl_handlers(source)->write->stream); + if (!Z_ISUNDEF(source->handlers.write->stream)) { + Z_ADDREF(source->handlers.write->stream); } - curl_handlers(ch)->write->stream = curl_handlers(source)->write->stream; - curl_handlers(ch)->write->method = curl_handlers(source)->write->method; - if (!Z_ISUNDEF(curl_handlers(source)->read->stream)) { - Z_ADDREF(curl_handlers(source)->read->stream); + ch->handlers.write->stream = source->handlers.write->stream; + ch->handlers.write->method = source->handlers.write->method; + if (!Z_ISUNDEF(source->handlers.read->stream)) { + Z_ADDREF(source->handlers.read->stream); } - curl_handlers(ch)->read->stream = curl_handlers(source)->read->stream; - curl_handlers(ch)->read->method = curl_handlers(source)->read->method; - curl_handlers(ch)->write_header->method = curl_handlers(source)->write_header->method; - if (!Z_ISUNDEF(curl_handlers(source)->write_header->stream)) { - Z_ADDREF(curl_handlers(source)->write_header->stream); + ch->handlers.read->stream = source->handlers.read->stream; + ch->handlers.read->method = source->handlers.read->method; + ch->handlers.write_header->method = source->handlers.write_header->method; + if (!Z_ISUNDEF(source->handlers.write_header->stream)) { + Z_ADDREF(source->handlers.write_header->stream); } - curl_handlers(ch)->write_header->stream = curl_handlers(source)->write_header->stream; + ch->handlers.write_header->stream = source->handlers.write_header->stream; - curl_handlers(ch)->write->fp = curl_handlers(source)->write->fp; - curl_handlers(ch)->write_header->fp = curl_handlers(source)->write_header->fp; - curl_handlers(ch)->read->fp = curl_handlers(source)->read->fp; - curl_handlers(ch)->read->res = curl_handlers(source)->read->res; + ch->handlers.write->fp = source->handlers.write->fp; + ch->handlers.write_header->fp = source->handlers.write_header->fp; + ch->handlers.read->fp = source->handlers.read->fp; + ch->handlers.read->res = source->handlers.read->res; - if (!Z_ISUNDEF(curl_handlers(source)->write->func_name)) { - ZVAL_COPY(&curl_handlers(ch)->write->func_name, &curl_handlers(source)->write->func_name); + if (!Z_ISUNDEF(source->handlers.write->func_name)) { + ZVAL_COPY(&ch->handlers.write->func_name, &source->handlers.write->func_name); } - if (!Z_ISUNDEF(curl_handlers(source)->read->func_name)) { - ZVAL_COPY(&curl_handlers(ch)->read->func_name, &curl_handlers(source)->read->func_name); + if (!Z_ISUNDEF(source->handlers.read->func_name)) { + ZVAL_COPY(&ch->handlers.read->func_name, &source->handlers.read->func_name); } - if (!Z_ISUNDEF(curl_handlers(source)->write_header->func_name)) { - ZVAL_COPY(&curl_handlers(ch)->write_header->func_name, &curl_handlers(source)->write_header->func_name); + if (!Z_ISUNDEF(source->handlers.write_header->func_name)) { + ZVAL_COPY(&ch->handlers.write_header->func_name, &source->handlers.write_header->func_name); } curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); - - if (curl_handlers(source)->progress) { - curl_handlers(ch)->progress = (php_curl_progress *) ecalloc(1, sizeof(php_curl_progress)); - if (!Z_ISUNDEF(curl_handlers(source)->progress->func_name)) { - ZVAL_COPY(&curl_handlers(ch)->progress->func_name, &curl_handlers(source)->progress->func_name); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); + +#if PHP_VERSION_ID >= 80300 + _php_copy_callback(ch, &ch->handlers.progress, source->handlers.progress, CURLOPT_PROGRESSDATA); + _php_copy_callback(ch, &ch->handlers.xferinfo, source->handlers.xferinfo, CURLOPT_XFERINFODATA); + _php_copy_callback(ch, &ch->handlers.fnmatch, source->handlers.fnmatch, CURLOPT_FNMATCH_DATA); +#if LIBCURL_VERSION_NUM >= 0x075400 + _php_copy_callback(ch, &ch->handlers.sshhostkey, source->handlers.sshhostkey, CURLOPT_SSH_HOSTKEYDATA); +#endif +#else + if (source->handlers.progress) { + ch->handlers.progress = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + if (!Z_ISUNDEF(source->handlers.progress->func_name)) { + ZVAL_COPY(&ch->handlers.progress->func_name, &source->handlers.progress->func_name); } - curl_handlers(ch)->progress->method = curl_handlers(source)->progress->method; curl_easy_setopt(ch->cp, CURLOPT_PROGRESSDATA, (void *) ch); } - if (curl_handlers(source)->fnmatch) { - curl_handlers(ch)->fnmatch = (php_curl_fnmatch *) ecalloc(1, sizeof(php_curl_fnmatch)); - if (!Z_ISUNDEF(curl_handlers(source)->fnmatch->func_name)) { - ZVAL_COPY(&curl_handlers(ch)->fnmatch->func_name, &curl_handlers(source)->fnmatch->func_name); +#if PHP_VERSION_ID >= 80200 + if (source->handlers.xferinfo) { + ch->handlers.xferinfo = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + if (!Z_ISUNDEF(source->handlers.xferinfo->func_name)) { + ZVAL_COPY(&ch->handlers.xferinfo->func_name, &source->handlers.xferinfo->func_name); + } + curl_easy_setopt(ch->cp, CURLOPT_XFERINFODATA, (void *) ch); + } +#endif + + if (source->handlers.fnmatch) { + ch->handlers.fnmatch = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + if (!Z_ISUNDEF(source->handlers.fnmatch->func_name)) { + ZVAL_COPY(&ch->handlers.fnmatch->func_name, &source->handlers.fnmatch->func_name); } - curl_handlers(ch)->fnmatch->method = curl_handlers(source)->fnmatch->method; curl_easy_setopt(ch->cp, CURLOPT_FNMATCH_DATA, (void *) ch); } +#endif + + ZVAL_COPY(&ch->private_data, &source->private_data); efree(ch->to_free->slist); efree(ch->to_free); @@ -967,66 +1109,292 @@ void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source) { (*source->clone)++; } -static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ -{ - struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; - ssize_t numread; +#if PHP_VERSION_ID >= 80300 +zend_long swoole_curl_get_long(zval *zv) { + if (EXPECTED(Z_TYPE_P(zv) == IS_LONG)) { + return Z_LVAL_P(zv); + } else { + zend_long ret = zval_get_long(zv); + zval_ptr_dtor(zv); + return ret; + } +} +#endif + +static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + ssize_t numread; + + if (cb_arg->stream == NULL) { + if (!(cb_arg->stream = php_stream_open_wrapper(ZSTR_VAL(cb_arg->filename), "rb", IGNORE_PATH, NULL))) { + return CURL_READFUNC_ABORT; + } + } + numread = php_stream_read(cb_arg->stream, buffer, nitems * size); + if (numread < 0) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + return CURL_READFUNC_ABORT; + } + return numread; +} +/* }}} */ + +static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + int res; + + if (cb_arg->stream == NULL) { + return CURL_SEEKFUNC_CANTSEEK; + } + res = php_stream_seek(cb_arg->stream, offset, origin); + return res == SUCCESS ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; +} +/* }}} */ + +static void free_cb(void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + + if (cb_arg->stream != NULL) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + } +} +/* }}} */ + +#if PHP_VERSION_ID >= 80200 +static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key, zval *current) { + CURLcode error = CURLE_OK; + curl_mimepart *part; + CURLcode form_error; + + zend_string *postval, *tmp_postval; + + postval = zval_get_tmp_string(current, &tmp_postval); + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_tmp_string_release(tmp_postval); +#if PHP_VERSION_ID < 80300 + zend_string_release_ex(string_key, 0); +#endif + return CURLE_OUT_OF_MEMORY; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) { + error = form_error; + } + zend_tmp_string_release(tmp_postval); + + return error; +} + +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */ +{ + HashTable *postfields = Z_ARRVAL_P(zpostfields); + CURLcode error = CURLE_OK; + zval *current; + zend_string *string_key; + zend_ulong num_key; + curl_mime *mime = NULL; + curl_mimepart *part; + CURLcode form_error; + + if (zend_hash_num_elements(postfields) > 0) { + mime = curl_mime_init(ch->cp); + if (mime == NULL) { + return FAILURE; + } + } + + ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) { + zend_string *postval; + /* Pretend we have a string_key here */ + if (!string_key) { + string_key = zend_long_to_str(num_key); + } else { + zend_string_addref(string_key); + } + + ZVAL_DEREF(current); + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) { + /* new-style file upload */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + struct mime_data_cb_arg *cb_arg; + php_stream *stream; + php_stream_statbuf ssb; + size_t filesize = -1; + curl_seek_callback seekfunc = seek_cb; +#if PHP_VERSION_ID >= 80300 + prop = zend_read_property_ex( + curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv); +#else + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "name", sizeof("name") - 1, 0, &rv); +#endif + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) != IS_STRING) { + php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key)); + } else { + postval = Z_STR_P(prop); + + if (php_check_open_basedir(ZSTR_VAL(postval))) { + goto out_string; + } + + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + type = Z_STRVAL_P(prop); + } + prop = zend_read_property( + curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + filename = Z_STRVAL_P(prop); + } + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) { + if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) { + filesize = ssb.sb.st_size; + } + } else { + seekfunc = NULL; + } + + part = curl_mime_addpart(mime); + if (part == NULL) { + if (stream) { + php_stream_close(stream); + } + goto out_string; + } + + cb_arg = (struct mime_data_cb_arg *) emalloc(sizeof *cb_arg); + cb_arg->filename = zend_string_copy(postval); + cb_arg->stream = stream; + + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK || + (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK || + (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) { + error = form_error; + } + zend_llist_add_element(&ch->to_free->stream, &cb_arg); + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) { + /* new-style file upload from string */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + + prop = zend_read_property( + curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + filename = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + type = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + postval = Z_STR_P(prop); + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + part = curl_mime_addpart(mime); + if (part == NULL) { + goto out_string; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK || + (form_error = curl_mime_filename(part, filename)) != CURLE_OK || + (form_error = curl_mime_type(part, type)) != CURLE_OK) { + error = form_error; + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_ARRAY) { + zval *current_element; - if (cb_arg->stream == NULL) { - if (!(cb_arg->stream = php_stream_open_wrapper(ZSTR_VAL(cb_arg->filename), "rb", IGNORE_PATH, NULL))) { - return CURL_READFUNC_ABORT; + ZEND_HASH_FOREACH_VAL(HASH_OF(current), current_element) { + add_simple_field(mime, string_key, current_element); + } + ZEND_HASH_FOREACH_END(); + + zend_string_release_ex(string_key, 0); + continue; } + + add_simple_field(mime, string_key, current); + zend_string_release_ex(string_key, 0); } - numread = php_stream_read(cb_arg->stream, buffer, nitems * size); - if (numread < 0) { - php_stream_close(cb_arg->stream); - cb_arg->stream = NULL; - return CURL_READFUNC_ABORT; - } - return numread; -} -/* }}} */ + ZEND_HASH_FOREACH_END(); -static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */ -{ - struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; - int res; + SAVE_CURL_ERROR(ch, error); + if (error != CURLE_OK) { + goto out_mime; + } - if (cb_arg->stream == NULL) { - return CURL_SEEKFUNC_CANTSEEK; + if ((*ch->clone) == 1) { + zend_llist_clean(&ch->to_free->post); } - res = php_stream_seek(cb_arg->stream, offset, origin); - return res == SUCCESS ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; -} -/* }}} */ -static void free_cb(void *arg) /* {{{ */ -{ - struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + zend_llist_add_element(&ch->to_free->post, &mime); + error = curl_easy_setopt(ch->cp, CURLOPT_MIMEPOST, mime); + SAVE_CURL_ERROR(ch, error); + return error == CURLE_OK ? SUCCESS : FAILURE; - if (cb_arg->stream != NULL) { - php_stream_close(cb_arg->stream); - cb_arg->stream = NULL; - } +out_string: + zend_string_release_ex(string_key, false); +out_mime: + + curl_mime_free(mime); + return FAILURE; } /* }}} */ - -static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */ +#else +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */ { + HashTable *postfields = Z_ARRVAL_P(zpostfields); CURLcode error = CURLE_OK; zval *current; - HashTable *postfields = HASH_OF(zpostfields); zend_string *string_key; zend_ulong num_key; curl_mime *mime = NULL; curl_mimepart *part; CURLcode form_error; - if (!postfields) { - php_error_docref(NULL, E_WARNING, "Couldn't get HashTable in CURLOPT_POSTFIELDS"); - return FAILURE; - } - if (zend_hash_num_elements(postfields) > 0) { mime = curl_mime_init(ch->cp); if (mime == NULL) { @@ -1035,7 +1403,7 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields } ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) { - zend_string *postval; + zend_string *postval, *tmp_postval; /* Pretend we have a string_key here */ if (!string_key) { string_key = zend_long_to_str(num_key); @@ -1054,23 +1422,25 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields size_t filesize = -1; curl_seek_callback seekfunc = seek_cb; - prop = zend_read_property(curl_CURLFile_class, SW_Z8_OBJ_P(current), "name", sizeof("name") - 1, 0, &rv); + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "name", sizeof("name") - 1, 0, &rv); + ZVAL_DEREF(prop); if (Z_TYPE_P(prop) != IS_STRING) { php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key)); } else { postval = Z_STR_P(prop); if (php_check_open_basedir(ZSTR_VAL(postval))) { - return 1; + return FAILURE; } - prop = - zend_read_property(curl_CURLFile_class, SW_Z8_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + ZVAL_DEREF(prop); if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { type = Z_STRVAL_P(prop); } prop = zend_read_property( - curl_CURLFile_class, SW_Z8_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + ZVAL_DEREF(prop); if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { filename = Z_STRVAL_P(prop); } @@ -1092,7 +1462,7 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields part = curl_mime_addpart(mime); if (part == NULL) { - zend_string_release(string_key); + zend_string_release_ex(string_key, 0); return FAILURE; } if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || @@ -1104,24 +1474,79 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields zend_llist_add_element(&ch->to_free->stream, &cb_arg); } - zend_string_release(string_key); + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) { + /* new-style file upload from string */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + + prop = zend_read_property( + curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + filename = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + type = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data") - 1, 0, &rv); + if (EG(exception)) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + postval = Z_STR_P(prop); + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK || + (form_error = curl_mime_filename(part, filename)) != CURLE_OK || + (form_error = curl_mime_type(part, type)) != CURLE_OK) { + error = form_error; + } + + zend_string_release_ex(string_key, 0); continue; } - postval = zval_get_string(current); + postval = zval_get_tmp_string(current, &tmp_postval); part = curl_mime_addpart(mime); if (part == NULL) { - zend_string_release(postval); - zend_string_release(string_key); + zend_tmp_string_release(tmp_postval); + zend_string_release_ex(string_key, 0); return FAILURE; } if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) { error = form_error; } - zend_string_release(postval); - zend_string_release(string_key); + zend_tmp_string_release(tmp_postval); + zend_string_release_ex(string_key, 0); } ZEND_HASH_FOREACH_END(); @@ -1140,14 +1565,13 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields return error == CURLE_OK ? SUCCESS : FAILURE; } /* }}} */ +#endif /* {{{ proto resource curl_copy_handle(resource ch) Copy a cURL handle along with all of it's preferences */ PHP_FUNCTION(swoole_native_curl_copy_handle) { - CURL *cp; zval *zid; - php_curl *ch, *dupch; - zval *postfields; + php_curl *ch; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) @@ -1157,29 +1581,11 @@ PHP_FUNCTION(swoole_native_curl_copy_handle) { RETURN_FALSE; } - cp = curl_easy_duphandle(ch->cp); - if (!cp) { - php_error_docref(NULL, E_WARNING, "Cannot duplicate cURL handle"); - RETURN_FALSE; - } - - dupch = swoole_curl_init_handle_into_zval(return_value); - dupch->cp = cp; - - swoole_setup_easy_copy_handlers(dupch, ch); - - postfields = &ch->postfields; - if (Z_TYPE_P(postfields) != IS_UNDEF) { - if (build_mime_structure_from_hash(dupch, postfields) != SUCCESS) { - zval_ptr_dtor(return_value); - php_error_docref(NULL, E_WARNING, "Cannot rebuild mime structure"); - RETURN_FALSE; - } - } + RETURN_OBJ(swoole_curl_clone_obj(Z_OBJ_P(zid))); } /* }}} */ -static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool is_array_config) { +static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool is_array_config) { CURLcode error = CURLE_OK; zend_long lval; @@ -1190,10 +1596,10 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i if (lval == 1) { php_error_docref( NULL, E_NOTICE, "CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead"); - error = curl_easy_setopt(ch->cp, (CURLoption) option, 2); + error = curl_easy_setopt(ch->cp, (CURLoption) option, 2L); break; } - /* no break */ + ZEND_FALLTHROUGH; case CURLOPT_AUTOREFERER: case CURLOPT_BUFFERSIZE: case CURLOPT_CONNECTTIMEOUT: @@ -1273,7 +1679,6 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_RTSP_REQUEST: case CURLOPT_RTSP_SERVER_CSEQ: case CURLOPT_WILDCARDMATCH: - case CURLOPT_TLSAUTH_TYPE: case CURLOPT_GSSAPI_DELEGATION: case CURLOPT_ACCEPTTIMEOUT_MS: case CURLOPT_SSL_OPTIONS: @@ -1310,27 +1715,61 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i #if LIBCURL_VERSION_NUM >= 0x073d00 /* Available since 7.61.0 */ case CURLOPT_DISALLOW_USERNAME_IN_URL: #endif +#if LIBCURL_VERSION_NUM >= 0x073E00 && PHP_VERSION_ID >= 80200 /* Available since 7.62.0 */ + case CURLOPT_UPKEEP_INTERVAL_MS: + case CURLOPT_UPLOAD_BUFFERSIZE: +#endif #if LIBCURL_VERSION_NUM >= 0x074000 /* Available since 7.64.0 */ case CURLOPT_HTTP09_ALLOWED: +#endif +#if LIBCURL_VERSION_NUM >= 0x074001 && PHP_VERSION_ID >= 80200 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074100 && PHP_VERSION_ID >= 80200 /* Available since 7.65.0 */ + case CURLOPT_MAXAGE_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x074500 && PHP_VERSION_ID >= 80200 /* Available since 7.69.0 */ + case CURLOPT_MAIL_RCPT_ALLLOWFAILS: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 && PHP_VERSION_ID >= 80200 /* Available since 7.74.0 */ + case CURLOPT_HSTS_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074c00 && PHP_VERSION_ID >= 80200 /* Available since 7.76.0 */ + case CURLOPT_DOH_SSL_VERIFYHOST: + case CURLOPT_DOH_SSL_VERIFYPEER: + case CURLOPT_DOH_SSL_VERIFYSTATUS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 && PHP_VERSION_ID >= 80200 /* Available since 7.80.0 */ + case CURLOPT_MAXLIFETIME_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x075100 && PHP_VERSION_ID >= 80300 /* Available since 7.81.0 */ + case CURLOPT_MIME_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075600 && PHP_VERSION_ID >= 80300 /* Available since 7.86.0 */ + case CURLOPT_WS_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075700 && PHP_VERSION_ID >= 80300 /* Available since 7.87.0 */ + case CURLOPT_CA_CACHE_TIMEOUT: + case CURLOPT_QUICK_EXIT: #endif lval = zval_get_long(zvalue); if ((option == CURLOPT_PROTOCOLS || option == CURLOPT_REDIR_PROTOCOLS) && (PG(open_basedir) && *PG(open_basedir)) && (lval & CURLPROTO_FILE)) { php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set"); - return 1; + return FAILURE; } -#ifdef ZTS +#if defined(ZTS) if (option == CURLOPT_DNS_USE_GLOBAL_CACHE && lval) { php_error_docref( NULL, E_WARNING, "CURLOPT_DNS_USE_GLOBAL_CACHE cannot be activated when thread safety is enabled"); - return 1; + return FAILURE; } #endif error = curl_easy_setopt(ch->cp, (CURLoption) option, lval); break; case CURLOPT_SAFE_UPLOAD: if (!zend_is_true(zvalue)) { - php_error_docref(NULL, E_WARNING, "Disabling safe uploads is no longer supported"); + zend_value_error("%s(): Disabling safe uploads is no longer supported", get_active_function_name()); return FAILURE; } break; @@ -1352,22 +1791,28 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_SSLKEYTYPE: case CURLOPT_SSL_CIPHER_LIST: case CURLOPT_USERAGENT: +#if PHP_VERSION_ID < 80300 case CURLOPT_USERPWD: +#endif case CURLOPT_COOKIELIST: case CURLOPT_FTP_ALTERNATIVE_TO_USER: case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: +#if PHP_VERSION_ID < 80300 case CURLOPT_PASSWORD: +#endif case CURLOPT_PROXYPASSWORD: case CURLOPT_PROXYUSERNAME: +#if PHP_VERSION_ID < 80300 case CURLOPT_USERNAME: +#endif case CURLOPT_NOPROXY: case CURLOPT_SOCKS5_GSSAPI_SERVICE: case CURLOPT_MAIL_FROM: case CURLOPT_RTSP_STREAM_URI: case CURLOPT_RTSP_TRANSPORT: + case CURLOPT_TLSAUTH_TYPE: case CURLOPT_TLSAUTH_PASSWORD: case CURLOPT_TLSAUTH_USERNAME: - case CURLOPT_ACCEPT_ENCODING: case CURLOPT_TRANSFER_ENCODING: case CURLOPT_DNS_SERVERS: case CURLOPT_MAIL_AUTH: @@ -1396,13 +1841,43 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_PROXY_TLS13_CIPHERS: case CURLOPT_TLS13_CIPHERS: #endif +#if LIBCURL_VERSION_NUM >= 0x074001 && PHP_VERSION_ID >= 80200 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC: +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 && PHP_VERSION_ID >= 80200 /* Available since 7.66.0 */ + case CURLOPT_SASL_AUTHZID: +#endif #if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ case CURLOPT_PROXY_ISSUERCERT: +#endif +#if LIBCURL_VERSION_NUM >= 0x074900 && PHP_VERSION_ID >= 80200 /* Available since 7.73.0 */ + case CURLOPT_SSL_EC_CURVES: +#endif +#if LIBCURL_VERSION_NUM >= 0x074b00 && PHP_VERSION_ID >= 80200 /* Available since 7.75.0 */ + case CURLOPT_AWS_SIGV4: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 && PHP_VERSION_ID >= 80200 /* Available since 7.80.0 */ + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: +#endif +#if LIBCURL_VERSION_NUM >= 0x075500 && PHP_VERSION_ID >= 80300 /* Available since 7.85.0 */ + case CURLOPT_PROTOCOLS_STR: + case CURLOPT_REDIR_PROTOCOLS_STR: #endif { - zend_string *str = zval_get_string(zvalue); - int ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); - zend_string_release(str); + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); +#if LIBCURL_VERSION_NUM >= 0x075500 && PHP_VERSION_ID >= 80300 /* Available since 7.85.0 */ + if ((option == CURLOPT_PROTOCOLS_STR || option == CURLOPT_REDIR_PROTOCOLS_STR) && + (PG(open_basedir) && *PG(open_basedir)) && + (php_memnistr(ZSTR_VAL(str), "file", sizeof("file") - 1, ZSTR_VAL(str) + ZSTR_LEN(str)) != NULL || + php_memnistr(ZSTR_VAL(str), "all", sizeof("all") - 1, ZSTR_VAL(str) + ZSTR_LEN(str)) != NULL)) { + zend_tmp_string_release(tmp_str); + php_error_docref(NULL, E_WARNING, "The FILE protocol cannot be activated when an open_basedir is set"); + return FAILURE; + } +#endif + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); return ret; } @@ -1412,6 +1887,7 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_RANGE: case CURLOPT_FTP_ACCOUNT: case CURLOPT_RTSP_SESSION_ID: + case CURLOPT_ACCEPT_ENCODING: case CURLOPT_DNS_INTERFACE: case CURLOPT_DNS_LOCAL_IP4: case CURLOPT_DNS_LOCAL_IP6: @@ -1419,15 +1895,27 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_UNIX_SOCKET_PATH: #if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ case CURLOPT_DOH_URL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 && PHP_VERSION_ID >= 80200 /* Available since 7.74.0 */ + case CURLOPT_HSTS: #endif case CURLOPT_KRBLEVEL: +#if PHP_VERSION_ID >= 80300 + // Authorization header would be implictly set + // with an empty string thus we explictly set the option + // to null to avoid this unwarranted side effect + case CURLOPT_USERPWD: + case CURLOPT_USERNAME: + case CURLOPT_PASSWORD: +#endif { if (Z_ISNULL_P(zvalue)) { error = curl_easy_setopt(ch->cp, (CURLoption) option, NULL); } else { - zend_string *str = zval_get_string(zvalue); - int ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); - zend_string_release(str); + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); return ret; } break; @@ -1435,15 +1923,21 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i /* Curl private option */ case CURLOPT_PRIVATE: { - swoole_curl_set_private_data(ch, zvalue); + zval_ptr_dtor(&ch->private_data); + ZVAL_COPY(&ch->private_data, zvalue); return SUCCESS; } - /* Curl url option */ + /* Curl url option */ case CURLOPT_URL: { - zend_string *str = zval_get_string(zvalue); - int ret = php_curl_option_url(ch, ZSTR_VAL(str), ZSTR_LEN(str)); - zend_string_release(str); + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); +#if PHP_VERSION_ID >= 80300 + zend_result ret = php_curl_option_url(ch, str); +#else + zend_result ret = php_curl_option_url(ch, ZSTR_VAL(str), ZSTR_LEN(str)); +#endif + zend_tmp_string_release(tmp_str); return ret; } @@ -1475,17 +1969,17 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i switch (option) { case CURLOPT_FILE: if (!what) { - if (!Z_ISUNDEF(curl_handlers(ch)->write->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->write->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write->stream); + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); } - curl_handlers(ch)->write->fp = NULL; - curl_handlers(ch)->write->method = PHP_CURL_STDOUT; + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; } else if (what->mode[0] != 'r' || what->mode[1] == '+') { - zval_ptr_dtor(&curl_handlers(ch)->write->stream); - curl_handlers(ch)->write->fp = fp; - curl_handlers(ch)->write->method = PHP_CURL_FILE; - ZVAL_COPY(&curl_handlers(ch)->write->stream, zvalue); + zval_ptr_dtor(&ch->handlers.write->stream); + ch->handlers.write->fp = fp; + ch->handlers.write->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write->stream, zvalue); } else { zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); return FAILURE; @@ -1493,17 +1987,17 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i break; case CURLOPT_WRITEHEADER: if (!what) { - if (!Z_ISUNDEF(curl_handlers(ch)->write_header->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->write_header->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write_header->stream); + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); } - curl_handlers(ch)->write_header->fp = NULL; - curl_handlers(ch)->write_header->method = PHP_CURL_IGNORE; + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; } else if (what->mode[0] != 'r' || what->mode[1] == '+') { - zval_ptr_dtor(&curl_handlers(ch)->write_header->stream); - curl_handlers(ch)->write_header->fp = fp; - curl_handlers(ch)->write_header->method = PHP_CURL_FILE; - ZVAL_COPY(&curl_handlers(ch)->write_header->stream, zvalue); + zval_ptr_dtor(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = fp; + ch->handlers.write_header->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write_header->stream, zvalue); } else { zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); return FAILURE; @@ -1511,33 +2005,33 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i break; case CURLOPT_INFILE: if (!what) { - if (!Z_ISUNDEF(curl_handlers(ch)->read->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->read->stream); - ZVAL_UNDEF(&curl_handlers(ch)->read->stream); + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); } - curl_handlers(ch)->read->fp = NULL; - curl_handlers(ch)->read->res = NULL; + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; } else { - zval_ptr_dtor(&curl_handlers(ch)->read->stream); - curl_handlers(ch)->read->fp = fp; - curl_handlers(ch)->read->res = Z_RES_P(zvalue); - ZVAL_COPY(&curl_handlers(ch)->read->stream, zvalue); + zval_ptr_dtor(&ch->handlers.read->stream); + ch->handlers.read->fp = fp; + ch->handlers.read->res = Z_RES_P(zvalue); + ZVAL_COPY(&ch->handlers.read->stream, zvalue); } break; case CURLOPT_STDERR: if (!what) { - if (!Z_ISUNDEF(curl_handlers(ch)->std_err)) { - zval_ptr_dtor(&curl_handlers(ch)->std_err); - ZVAL_UNDEF(&curl_handlers(ch)->std_err); + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); } } else if (what->mode[0] != 'r' || what->mode[1] == '+') { - zval_ptr_dtor(&curl_handlers(ch)->std_err); - ZVAL_COPY(&curl_handlers(ch)->std_err, zvalue); + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_COPY(&ch->handlers.std_err, zvalue); } else { zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); return FAILURE; } - /* break omitted intentionally */ + ZEND_FALLTHROUGH; default: error = curl_easy_setopt(ch->cp, (CURLoption) option, fp); break; @@ -1555,15 +2049,13 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_MAIL_RCPT: case CURLOPT_RESOLVE: case CURLOPT_PROXYHEADER: - case CURLOPT_CONNECT_TO: - { + case CURLOPT_CONNECT_TO: { zval *current; - HashTable *ph = NULL; - zend_string *val; + HashTable *ph; + zend_string *val, *tmp_val; struct curl_slist *slist = NULL; - ph = HASH_OF(zvalue); - if (!ph) { + if (Z_TYPE_P(zvalue) != IS_ARRAY) { const char *name = NULL; switch (option) { case CURLOPT_HTTPHEADER: @@ -1601,15 +2093,27 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i return FAILURE; } + ph = Z_ARRVAL_P(zvalue); ZEND_HASH_FOREACH_VAL(ph, current) { ZVAL_DEREF(current); - val = zval_get_string(current); + val = zval_get_tmp_string(current, &tmp_val); +#if PHP_VERSION_ID >= 80300 + struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val)); + zend_tmp_string_release(tmp_val); + if (!new_slist) { + curl_slist_free_all(slist); + php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); + return FAILURE; + } + slist = new_slist; +#else slist = curl_slist_append(slist, ZSTR_VAL(val)); - zend_string_release(val); + zend_tmp_string_release(tmp_val); if (!slist) { php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); - return 1; + return FAILURE; } +#endif } ZEND_HASH_FOREACH_END(); @@ -1632,72 +2136,110 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_FOLLOWLOCATION: lval = zend_is_true(zvalue); - error = curl_easy_setopt(ch->cp, (CURLoption) option, lval); + error = curl_easy_setopt(ch->cp, (CURLoption) option, (long) lval); break; case CURLOPT_HEADERFUNCTION: - if (!Z_ISUNDEF(curl_handlers(ch)->write_header->func_name)) { - zval_ptr_dtor(&curl_handlers(ch)->write_header->func_name); - curl_handlers(ch)->write_header->fci_cache = empty_fcall_info_cache; + if (!Z_ISUNDEF(ch->handlers.write_header->func_name)) { + zval_ptr_dtor(&ch->handlers.write_header->func_name); + ch->handlers.write_header->fci_cache = empty_fcall_info_cache; } - ZVAL_COPY(&curl_handlers(ch)->write_header->func_name, zvalue); - curl_handlers(ch)->write_header->method = PHP_CURL_USER; + ZVAL_COPY(&ch->handlers.write_header->func_name, zvalue); + ch->handlers.write_header->method = PHP_CURL_USER; break; case CURLOPT_POSTFIELDS: if (Z_TYPE_P(zvalue) == IS_ARRAY) { - return build_mime_structure_from_hash(ch, zvalue); + if (zend_hash_num_elements(HASH_OF(zvalue)) == 0) { + /* no need to build the mime structure for empty hashtables; + also works around https://github.com/curl/curl/issues/6455 */ + curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDS, ""); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, 0L); + } else { + return build_mime_structure_from_hash(ch, zvalue); + } } else { - zend_string *str = zval_get_string(zvalue); + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */ error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, ZSTR_LEN(str)); error = curl_easy_setopt(ch->cp, CURLOPT_COPYPOSTFIELDS, ZSTR_VAL(str)); - zend_string_release(str); + zend_tmp_string_release(tmp_str); } break; case CURLOPT_PROGRESSFUNCTION: curl_easy_setopt(ch->cp, CURLOPT_PROGRESSFUNCTION, fn_progress); curl_easy_setopt(ch->cp, CURLOPT_PROGRESSDATA, ch); - if (curl_handlers(ch)->progress == NULL) { - curl_handlers(ch)->progress = (php_curl_progress *) ecalloc(1, sizeof(php_curl_progress)); - } else if (!Z_ISUNDEF(curl_handlers(ch)->progress->func_name)) { - zval_ptr_dtor(&curl_handlers(ch)->progress->func_name); - curl_handlers(ch)->progress->fci_cache = empty_fcall_info_cache; - } - ZVAL_COPY(&curl_handlers(ch)->progress->func_name, zvalue); - curl_handlers(ch)->progress->method = PHP_CURL_USER; + if (ch->handlers.progress == NULL) { + ch->handlers.progress = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + } else if (!Z_ISUNDEF(ch->handlers.progress->func_name)) { + zval_ptr_dtor(&ch->handlers.progress->func_name); + ch->handlers.progress->fci_cache = empty_fcall_info_cache; + } + ZVAL_COPY(&ch->handlers.progress->func_name, zvalue); + break; + +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + case CURLOPT_SSH_HOSTKEYFUNCTION: + curl_easy_setopt(ch->cp, CURLOPT_SSH_HOSTKEYFUNCTION, fn_ssh_hostkeyfunction); + curl_easy_setopt(ch->cp, CURLOPT_SSH_HOSTKEYDATA, ch); + if (ch->handlers.sshhostkey == NULL) { + ch->handlers.sshhostkey = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + } else if (!Z_ISUNDEF(ch->handlers.sshhostkey->func_name)) { + zval_ptr_dtor(&ch->handlers.sshhostkey->func_name); + ch->handlers.sshhostkey->fci_cache = empty_fcall_info_cache; + } + ZVAL_COPY(&ch->handlers.sshhostkey->func_name, zvalue); break; +#endif case CURLOPT_READFUNCTION: - if (!Z_ISUNDEF(curl_handlers(ch)->read->func_name)) { - zval_ptr_dtor(&curl_handlers(ch)->read->func_name); - curl_handlers(ch)->read->fci_cache = empty_fcall_info_cache; + if (!Z_ISUNDEF(ch->handlers.read->func_name)) { + zval_ptr_dtor(&ch->handlers.read->func_name); + ch->handlers.read->fci_cache = empty_fcall_info_cache; } - ZVAL_COPY(&curl_handlers(ch)->read->func_name, zvalue); - curl_handlers(ch)->read->method = PHP_CURL_USER; + ZVAL_COPY(&ch->handlers.read->func_name, zvalue); + ch->handlers.read->method = PHP_CURL_USER; break; case CURLOPT_RETURNTRANSFER: if (zend_is_true(zvalue)) { - curl_handlers(ch)->write->method = PHP_CURL_RETURN; + ch->handlers.write->method = PHP_CURL_RETURN; } else { - curl_handlers(ch)->write->method = PHP_CURL_STDOUT; + ch->handlers.write->method = PHP_CURL_STDOUT; } break; case CURLOPT_WRITEFUNCTION: - if (!Z_ISUNDEF(curl_handlers(ch)->write->func_name)) { - zval_ptr_dtor(&curl_handlers(ch)->write->func_name); - curl_handlers(ch)->write->fci_cache = empty_fcall_info_cache; + if (!Z_ISUNDEF(ch->handlers.write->func_name)) { + zval_ptr_dtor(&ch->handlers.write->func_name); + ch->handlers.write->fci_cache = empty_fcall_info_cache; } - ZVAL_COPY(&curl_handlers(ch)->write->func_name, zvalue); - curl_handlers(ch)->write->method = PHP_CURL_USER; + ZVAL_COPY(&ch->handlers.write->func_name, zvalue); + ch->handlers.write->method = PHP_CURL_USER; + break; + +#if PHP_VERSION_ID >= 80200 + case CURLOPT_XFERINFOFUNCTION: + curl_easy_setopt(ch->cp, CURLOPT_XFERINFOFUNCTION, fn_xferinfo); + curl_easy_setopt(ch->cp, CURLOPT_XFERINFODATA, ch); + if (ch->handlers.xferinfo == NULL) { + ch->handlers.xferinfo = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + } else if (!Z_ISUNDEF(ch->handlers.xferinfo->func_name)) { + zval_ptr_dtor(&ch->handlers.xferinfo->func_name); + ch->handlers.xferinfo->fci_cache = empty_fcall_info_cache; + } + ZVAL_COPY(&ch->handlers.xferinfo->func_name, zvalue); break; +#endif /* Curl off_t options */ case CURLOPT_MAX_RECV_SPEED_LARGE: case CURLOPT_MAX_SEND_SPEED_LARGE: +#if PHP_VERSION_ID >= 80200 + case CURLOPT_MAXFILESIZE_LARGE: +#endif #if LIBCURL_VERSION_NUM >= 0x073b00 /* Available since 7.59.0 */ case CURLOPT_TIMEVALUE_LARGE: #endif @@ -1707,7 +2249,7 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_POSTREDIR: lval = zval_get_long(zvalue); - error = curl_easy_setopt(ch->cp, CURLOPT_POSTREDIR, lval & CURL_REDIR_POST_ALL); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTREDIR, (long) (lval & CURL_REDIR_POST_ALL)); break; /* the following options deal with files, therefore the open_basedir check @@ -1722,18 +2264,18 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i case CURLOPT_SSH_PUBLIC_KEYFILE: case CURLOPT_CRLFILE: case CURLOPT_ISSUERCERT: - case CURLOPT_SSH_KNOWNHOSTS: - { - zend_string *str = zval_get_string(zvalue); - int ret; + case CURLOPT_SSH_KNOWNHOSTS: { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret; if (ZSTR_LEN(str) && php_check_open_basedir(ZSTR_VAL(str))) { - zend_string_release(str); + zend_tmp_string_release(tmp_str); return FAILURE; } ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); - zend_string_release(str); + zend_tmp_string_release(tmp_str); return ret; } @@ -1741,11 +2283,11 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i if (zend_is_true(zvalue)) { curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, curl_debug); curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); - curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 1); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 1L); } else { curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, NULL); curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, NULL); - curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0L); } break; @@ -1760,43 +2302,44 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool i GC_ADDREF(&sh->std); ch->share = sh; } - break; - } + } break; case CURLOPT_FNMATCH_FUNCTION: curl_easy_setopt(ch->cp, CURLOPT_FNMATCH_FUNCTION, fn_fnmatch); curl_easy_setopt(ch->cp, CURLOPT_FNMATCH_DATA, ch); - if (curl_handlers(ch)->fnmatch == NULL) { - curl_handlers(ch)->fnmatch = (php_curl_fnmatch *) ecalloc(1, sizeof(php_curl_fnmatch)); - } else if (!Z_ISUNDEF(curl_handlers(ch)->fnmatch->func_name)) { - zval_ptr_dtor(&curl_handlers(ch)->fnmatch->func_name); - curl_handlers(ch)->fnmatch->fci_cache = empty_fcall_info_cache; - } - ZVAL_COPY(&curl_handlers(ch)->fnmatch->func_name, zvalue); - curl_handlers(ch)->fnmatch->method = PHP_CURL_USER; + if (ch->handlers.fnmatch == NULL) { + ch->handlers.fnmatch = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); + } else if (!Z_ISUNDEF(ch->handlers.fnmatch->func_name)) { + zval_ptr_dtor(&ch->handlers.fnmatch->func_name); + ch->handlers.fnmatch->fci_cache = empty_fcall_info_cache; + } + ZVAL_COPY(&ch->handlers.fnmatch->func_name, zvalue); break; /* Curl blob options */ #if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ - case CURLOPT_ISSUERCERT_BLOB: - case CURLOPT_PROXY_ISSUERCERT_BLOB: - case CURLOPT_PROXY_SSLCERT_BLOB: - case CURLOPT_PROXY_SSLKEY_BLOB: - case CURLOPT_SSLCERT_BLOB: - case CURLOPT_SSLKEY_BLOB: - { - zend_string *tmp_str; - zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); - - struct curl_blob stblob; - stblob.data = ZSTR_VAL(str); - stblob.len = ZSTR_LEN(str); - stblob.flags = CURL_BLOB_COPY; - error = curl_easy_setopt(ch->cp, (CURLoption) option, &stblob); - - zend_tmp_string_release(tmp_str); - } - break; + case CURLOPT_ISSUERCERT_BLOB: + case CURLOPT_PROXY_ISSUERCERT_BLOB: + case CURLOPT_PROXY_SSLCERT_BLOB: + case CURLOPT_PROXY_SSLKEY_BLOB: + case CURLOPT_SSLCERT_BLOB: + case CURLOPT_SSLKEY_BLOB: +#if LIBCURL_VERSION_NUM >= 0x074d00 && PHP_VERSION_ID >= 80200 /* Available since 7.77.0 */ + case CURLOPT_CAINFO_BLOB: + case CURLOPT_PROXY_CAINFO_BLOB: +#endif + { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + + struct curl_blob stblob; + stblob.data = ZSTR_VAL(str); + stblob.len = ZSTR_LEN(str); + stblob.flags = CURL_BLOB_COPY; + error = curl_easy_setopt(ch->cp, (CURLoption) option, &stblob); + + zend_tmp_string_release(tmp_str); + } break; #endif default: @@ -1858,8 +2401,8 @@ PHP_FUNCTION(swoole_native_curl_setopt_array) { ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(arr), option, string_key, entry) { if (string_key) { - php_error_docref(NULL, E_WARNING, "Array keys must be CURLOPT constants or equivalent integer values"); - RETURN_FALSE; + zend_argument_value_error(2, "contains an invalid cURL option"); + RETURN_THROWS(); } ZVAL_DEREF(entry); if (_php_curl_setopt(ch, (zend_long) option, entry, 1) == FAILURE) { @@ -1875,9 +2418,9 @@ PHP_FUNCTION(swoole_native_curl_setopt_array) { /* {{{ _php_curl_cleanup_handle(ch) Cleanup an execution phase */ void swoole_curl_cleanup_handle(php_curl *ch) { - smart_str_free(&curl_handlers(ch)->write->buf); + smart_str_free(&ch->handlers.write->buf); if (ch->header.str) { - zend_string_release(ch->header.str); + zend_string_release_ex(ch->header.str, 0); ch->header.str = NULL; } @@ -1901,42 +2444,39 @@ PHP_FUNCTION(swoole_native_curl_exec) { RETURN_FALSE; } - swoole_curl_verify_handlers(ch, 1); - + swoole_curl_verify_handlers(ch, /* reporterror */ true); swoole_curl_cleanup_handle(ch); - - Multi multi{}; - error = multi.exec(ch); + error = swoole_curl_easy_perform(ch->cp); SAVE_CURL_ERROR(ch, error); if (error != CURLE_OK) { - smart_str_free(&curl_handlers(ch)->write->buf); + smart_str_free(&ch->handlers.write->buf); RETURN_FALSE; } - if (!Z_ISUNDEF(curl_handlers(ch)->std_err)) { + if (!Z_ISUNDEF(ch->handlers.std_err)) { php_stream *stream; stream = (php_stream *) zend_fetch_resource2_ex( - &curl_handlers(ch)->std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); if (stream) { php_stream_flush(stream); } } - if (curl_handlers(ch)->write->method == PHP_CURL_RETURN && curl_handlers(ch)->write->buf.s) { - smart_str_0(&curl_handlers(ch)->write->buf); - RETURN_STR_COPY(curl_handlers(ch)->write->buf.s); + if (ch->handlers.write->method == PHP_CURL_RETURN && ch->handlers.write->buf.s) { + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); } /* flush the file handle, so any remaining data is synched to disk */ - if (curl_handlers(ch)->write->method == PHP_CURL_FILE && curl_handlers(ch)->write->fp) { - fflush(curl_handlers(ch)->write->fp); + if (ch->handlers.write->method == PHP_CURL_FILE && ch->handlers.write->fp) { + fflush(ch->handlers.write->fp); } - if (curl_handlers(ch)->write_header->method == PHP_CURL_FILE && curl_handlers(ch)->write_header->fp) { - fflush(curl_handlers(ch)->write_header->fp); + if (ch->handlers.write_header->method == PHP_CURL_FILE && ch->handlers.write_header->fp) { + fflush(ch->handlers.write_header->fp); } - if (curl_handlers(ch)->write->method == PHP_CURL_RETURN) { + if (ch->handlers.write->method == PHP_CURL_RETURN) { RETURN_EMPTY_STRING(); } else { RETURN_TRUE; @@ -1950,7 +2490,7 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { zval *zid; php_curl *ch; zend_long option; - zend_bool option_is_null = 1; + bool option_is_null = 1; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) @@ -1973,6 +2513,9 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { double d_code; struct curl_certinfo *ci = NULL; zval listcode; +#if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */ + curl_off_t co; +#endif array_init(return_value); @@ -2075,7 +2618,6 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { CAAS("scheme", s_code); } #if LIBCURL_VERSION_NUM >= 0x073d00 /* Available since 7.61.0 */ - curl_off_t co; if (curl_easy_getinfo(ch->cp, CURLINFO_APPCONNECT_TIME_T, &co) == CURLE_OK) { CAAL("appconnect_time_us", co); } @@ -2105,6 +2647,14 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { if (curl_easy_getinfo(ch->cp, CURLINFO_EFFECTIVE_METHOD, &s_code) == CURLE_OK) { CAAS("effective_method", s_code); } +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + if (curl_easy_getinfo(ch->cp, CURLINFO_CAPATH, &s_code) == CURLE_OK) { + CAAS("capath", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CAINFO, &s_code) == CURLE_OK) { + CAAS("cainfo", s_code); + } #endif } else { switch (option) { @@ -2114,7 +2664,6 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { } else { RETURN_FALSE; } - break; case CURLINFO_CERTINFO: { struct curl_certinfo *ci = NULL; @@ -2128,8 +2677,12 @@ PHP_FUNCTION(swoole_native_curl_getinfo) { break; } case CURLINFO_PRIVATE: { - swoole_curl_get_private_data(ch, return_value); - return; + if (!Z_ISUNDEF(ch->private_data)) { + RETURN_COPY(&ch->private_data); + } else { + RETURN_FALSE; + } + break; } default: { int type = CURLINFO_TYPEMASK & option; @@ -2213,10 +2766,11 @@ PHP_FUNCTION(swoole_native_curl_error) { if (ch->err.no) { ch->err.str[CURL_ERROR_SIZE] = 0; - if (strlen(ch->err.str) == 0) { + if (strlen(ch->err.str) > 0) { + RETURN_STRING(ch->err.str); + } else { RETURN_STRING(curl_easy_strerror((CURLcode) ch->err.no)); } - RETURN_STRING(ch->err.str); } else { RETURN_EMPTY_STRING(); } @@ -2252,17 +2806,40 @@ PHP_FUNCTION(swoole_native_curl_close) { ZEND_PARSE_PARAMETERS_END(); if ((ch = swoole_curl_get_handle(zid)) == NULL) { - RETURN_FALSE; + return; } if (ch->in_callback) { - php_error_docref(NULL, E_WARNING, "Attempt to close cURL handle from a callback"); - return; + zend_throw_error(NULL, "%s(): Attempt to close cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); } } /* }}} */ -static void _php_curl_free(php_curl *ch) { +#if PHP_VERSION_ID >= 80300 +static void _php_curl_free_callback(php_curl_callback *callback) { + if (callback) { + zval_ptr_dtor(&callback->func_name); + efree(callback); + } +} +#endif + +static void swoole_curl_free_obj(zend_object *object) { + php_curl *ch = curl_from_obj(object); + +#if PHP_CURL_DEBUG + fprintf(stderr, "DTOR CALLED, ch = %x\n", ch); +#endif + + if (!ch->cp) { + /* Can happen if constructor throws. */ + zend_object_std_dtor(&ch->std); + return; + } + + swoole_curl_verify_handlers(ch, /* reporterror */ false); + /* * Libcurl is doing connection caching. When easy handle is cleaned up, * if the handle was previously used by the curl_multi_api, the connection @@ -2274,28 +2851,16 @@ static void _php_curl_free(php_curl *ch) { * * Libcurl commit d021f2e8a00 fix this issue and should be part of 7.28.2 */ - curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, curl_write_nothing); - curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, curl_write_nothing); - - swoole::curl::Handle *handle = nullptr; + curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, fn_write_nothing); + curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, fn_write_nothing); - if (curl_easy_getinfo(ch->cp, CURLINFO_PRIVATE, &handle) && handle) { - if (handle->multi) { - handle->multi->remove_handle(ch); - } - } else { - handle = nullptr; + swoole::curl::Handle *handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + handle->multi->remove_handle(handle); } /* cURL destructors should be invoked only by last curl handle */ if (--(*ch->clone) == 0) { -#if PHP_VERSION_ID < 80100 - zend_llist_clean(&ch->to_free->str); -#else -#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */ - zend_llist_clean(&ch->to_free->buffers); -#endif -#endif zend_llist_clean(&ch->to_free->post); zend_llist_clean(&ch->to_free->stream); zend_hash_destroy(ch->to_free->slist); @@ -2303,71 +2868,69 @@ static void _php_curl_free(php_curl *ch) { efree(ch->to_free); efree(ch->clone); - if (handle) { - delete handle; - } - curl_easy_setopt(ch->cp, CURLOPT_PRIVATE, nullptr); + swoole::curl::destroy_handle(ch->cp); } - if (ch->cp != NULL) { + if (ch->cp) { curl_easy_cleanup(ch->cp); } - smart_str_free(&curl_handlers(ch)->write->buf); - zval_ptr_dtor(&curl_handlers(ch)->write->func_name); - zval_ptr_dtor(&curl_handlers(ch)->read->func_name); - zval_ptr_dtor(&curl_handlers(ch)->write_header->func_name); - zval_ptr_dtor(&curl_handlers(ch)->std_err); + smart_str_free(&ch->handlers.write->buf); + zval_ptr_dtor(&ch->handlers.write->func_name); + zval_ptr_dtor(&ch->handlers.read->func_name); + zval_ptr_dtor(&ch->handlers.write_header->func_name); + zval_ptr_dtor(&ch->handlers.std_err); if (ch->header.str) { - zend_string_release(ch->header.str); + zend_string_release_ex(ch->header.str, 0); } - zval_ptr_dtor(&curl_handlers(ch)->write_header->stream); - zval_ptr_dtor(&curl_handlers(ch)->write->stream); - zval_ptr_dtor(&curl_handlers(ch)->read->stream); + zval_ptr_dtor(&ch->handlers.write_header->stream); + zval_ptr_dtor(&ch->handlers.write->stream); + zval_ptr_dtor(&ch->handlers.read->stream); - efree(curl_handlers(ch)->write); - efree(curl_handlers(ch)->write_header); - efree(curl_handlers(ch)->read); + efree(ch->handlers.write); + efree(ch->handlers.write_header); + efree(ch->handlers.read); + +#if PHP_VERSION_ID >= 80300 + _php_curl_free_callback(ch->handlers.progress); + _php_curl_free_callback(ch->handlers.xferinfo); + _php_curl_free_callback(ch->handlers.fnmatch); +#if LIBCURL_VERSION_NUM >= 0x075400 + _php_curl_free_callback(ch->handlers.sshhostkey); +#endif +#else + if (ch->handlers.progress) { + zval_ptr_dtor(&ch->handlers.progress->func_name); + efree(ch->handlers.progress); + } - if (curl_handlers(ch)->progress) { - zval_ptr_dtor(&curl_handlers(ch)->progress->func_name); - efree(curl_handlers(ch)->progress); +#if PHP_VERSION_ID >= 80200 + if (ch->handlers.xferinfo) { + zval_ptr_dtor(&ch->handlers.xferinfo->func_name); + efree(ch->handlers.xferinfo); } +#endif - if (curl_handlers(ch)->fnmatch) { - zval_ptr_dtor(&curl_handlers(ch)->fnmatch->func_name); - efree(curl_handlers(ch)->fnmatch); + if (ch->handlers.fnmatch) { + zval_ptr_dtor(&ch->handlers.fnmatch->func_name); + efree(ch->handlers.fnmatch); } -#if PHP_VERSION_ID < 80100 - efree(ch->handlers); +#if LIBCURL_VERSION_NUM >= 0x075400 && php_version_id >= 80300 + if (ch->handlers.sshhostkey) { + zval_ptr_dtor(&ch->handlers.sshhostkey->func_name); + efree(ch->handlers.sshhostkey); + } #endif +#endif + zval_ptr_dtor(&ch->postfields); -#if PHP_VERSION_ID >= 80100 zval_ptr_dtor(&ch->private_data); -#endif if (ch->share) { OBJ_RELEASE(&ch->share->std); } -} - -static void swoole_curl_free_obj(zend_object *object) { - php_curl *ch = curl_from_obj(object); - -#if PHP_CURL_DEBUG - fprintf(stderr, "DTOR CALLED, ch = %x\n", ch); -#endif - - if (!ch->cp) { - /* Can happen if constructor throws. */ - zend_object_std_dtor(&ch->std); - return; - } - - swoole_curl_verify_handlers(ch, 0); - _php_curl_free(ch); zend_object_std_dtor(&ch->std); } @@ -2375,44 +2938,61 @@ static void swoole_curl_free_obj(zend_object *object) { /* {{{ _php_curl_reset_handlers() Reset all handlers of a given php_curl */ static void _php_curl_reset_handlers(php_curl *ch) { - if (!Z_ISUNDEF(curl_handlers(ch)->write->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->write->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write->stream); + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + } + + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; + + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + } + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); } - curl_handlers(ch)->write->fp = NULL; - curl_handlers(ch)->write->method = PHP_CURL_STDOUT; + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; + ch->handlers.read->method = PHP_CURL_DIRECT; - if (!Z_ISUNDEF(curl_handlers(ch)->write_header->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->write_header->stream); - ZVAL_UNDEF(&curl_handlers(ch)->write_header->stream); + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); } - curl_handlers(ch)->write_header->fp = NULL; - curl_handlers(ch)->write_header->method = PHP_CURL_IGNORE; - if (!Z_ISUNDEF(curl_handlers(ch)->read->stream)) { - zval_ptr_dtor(&curl_handlers(ch)->read->stream); - ZVAL_UNDEF(&curl_handlers(ch)->read->stream); + if (ch->handlers.progress) { + zval_ptr_dtor(&ch->handlers.progress->func_name); + efree(ch->handlers.progress); + ch->handlers.progress = NULL; } - curl_handlers(ch)->read->fp = NULL; - curl_handlers(ch)->read->res = NULL; - curl_handlers(ch)->read->method = PHP_CURL_DIRECT; - if (!Z_ISUNDEF(curl_handlers(ch)->std_err)) { - zval_ptr_dtor(&curl_handlers(ch)->std_err); - ZVAL_UNDEF(&curl_handlers(ch)->std_err); +#if LIBCURL_VERSION_NUM >= 0x072000 && PHP_VERSION_ID >= 80200 + if (ch->handlers.xferinfo) { + zval_ptr_dtor(&ch->handlers.xferinfo->func_name); + efree(ch->handlers.xferinfo); + ch->handlers.xferinfo = NULL; } +#endif - if (curl_handlers(ch)->progress) { - zval_ptr_dtor(&curl_handlers(ch)->progress->func_name); - efree(curl_handlers(ch)->progress); - curl_handlers(ch)->progress = NULL; + if (ch->handlers.fnmatch) { + zval_ptr_dtor(&ch->handlers.fnmatch->func_name); + efree(ch->handlers.fnmatch); + ch->handlers.fnmatch = NULL; } - if (curl_handlers(ch)->fnmatch) { - zval_ptr_dtor(&curl_handlers(ch)->fnmatch->func_name); - efree(curl_handlers(ch)->fnmatch); - curl_handlers(ch)->fnmatch = NULL; +#if LIBCURL_VERSION_NUM >= 0x075400 && PHP_VERSION_ID >= 80300 + if (ch->handlers.sshhostkey) { + zval_ptr_dtor(&ch->handlers.sshhostkey->func_name); + efree(ch->handlers.sshhostkey); + ch->handlers.sshhostkey = NULL; } +#endif } /* }}} */ @@ -2431,8 +3011,8 @@ PHP_FUNCTION(swoole_native_curl_reset) { } if (ch->in_callback) { - php_error_docref(NULL, E_WARNING, "Attempt to reset cURL handle from a callback"); - return; + zend_throw_error(NULL, "%s(): Attempt to reset cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); } curl_easy_reset(ch->cp); @@ -2485,7 +3065,9 @@ PHP_FUNCTION(swoole_native_curl_unescape) { Z_PARAM_STR(str) ZEND_PARSE_PARAMETERS_END(); - ch = Z_CURL_P(zid); + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) { RETURN_FALSE; @@ -2519,4 +3101,27 @@ PHP_FUNCTION(swoole_native_curl_pause) { RETURN_LONG(curl_easy_pause(ch->cp, bitmask)); } /* }}} */ + +#if LIBCURL_VERSION_NUM >= 0x073E00 && PHP_VERSION_ID >= 80200 /* Available since 7.62.0 */ +/* {{{ perform connection upkeep checks */ +PHP_FUNCTION(swoole_native_curl_upkeep) { + CURLcode error; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + error = curl_easy_upkeep(ch->cp); + SAVE_CURL_ERROR(ch, error); + + RETURN_BOOL(error == CURLE_OK); +} +/*}}} */ +#endif #endif diff --git a/thirdparty/php/curl/multi.cc b/thirdparty/php/curl/multi.cc index a08f2b69ce..f7c5aa7985 100644 --- a/thirdparty/php/curl/multi.cc +++ b/thirdparty/php/curl/multi.cc @@ -5,7 +5,7 @@ | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -17,14 +17,14 @@ #include "php_swoole_cxx.h" #include "zend_object_handlers.h" -#ifdef SW_USE_CURL +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #include "php_swoole_curl.h" using swoole::curl::Multi; using swoole::curl::Selector; SW_EXTERN_C_BEGIN -#include "curl_interface.h" +#include "swoole_curl_interface.h" #include "curl_arginfo.h" #include @@ -46,6 +46,7 @@ bool swoole_curl_multi_is_in_coroutine(php_curlm *mh) { } /* CurlMultiHandle class */ + zend_class_entry *swoole_coroutine_curl_multi_handle_ce; static inline php_curlm *curl_multi_from_obj(zend_object *obj) { @@ -61,17 +62,12 @@ SW_EXTERN_C_END PHP_FUNCTION(swoole_native_curl_multi_init) { php_curlm *mh; -#ifdef ZEND_PARSE_PARAMETERS_NONE ZEND_PARSE_PARAMETERS_NONE(); -#endif object_init_ex(return_value, swoole_coroutine_curl_multi_handle_ce); mh = Z_CURL_MULTI_P(return_value); mh->multi = new Multi(); - mh->multi->set_selector(new Selector()); -#if PHP_VERSION_ID < 80100 - mh->handlers = (php_curlm_handlers *) ecalloc(1, sizeof(php_curlm_handlers)); -#endif + swoole_curl_multi_set_in_coroutine(mh, true); zend_llist_init(&mh->easyh, sizeof(zval), swoole_curl_multi_cleanup_list, 0); } @@ -91,7 +87,7 @@ PHP_FUNCTION(swoole_native_curl_multi_add_handle) { ZEND_PARSE_PARAMETERS_END(); mh = Z_CURL_MULTI_P(z_mh); - ch = Z_CURL_P(z_ch); + ch = swoole_curl_get_handle(z_ch); if (!(swoole_curl_multi_is_in_coroutine(mh))) { swoole_fatal_error(SW_ERROR_WRONG_OPERATION, @@ -99,16 +95,24 @@ PHP_FUNCTION(swoole_native_curl_multi_add_handle) { RETURN_FALSE; } - swoole_curl_verify_handlers(ch, 1); - + swoole_curl_verify_handlers(ch, /* reporterror */ true); swoole_curl_cleanup_handle(ch); + auto handle = swoole::curl::get_handle(ch->cp); + error = mh->multi->add_handle(handle); + SAVE_CURLM_ERROR(mh, error); + +#if PHP_VERSION_ID >= 80200 + if (error == CURLM_OK) { + Z_ADDREF_P(z_ch); + zend_llist_add_element(&mh->easyh, z_ch); + } +#else Z_ADDREF_P(z_ch); zend_llist_add_element(&mh->easyh, z_ch); +#endif - error = mh->multi->add_handle(ch->cp); - SAVE_CURLM_ERROR(mh, error); - + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); RETURN_LONG((zend_long) error); } /* }}} */ @@ -167,11 +171,23 @@ PHP_FUNCTION(swoole_native_curl_multi_remove_handle) { RETURN_FALSE; } ch = Z_CURL_P(z_ch); - error = mh->multi->remove_handle(ch->cp); - SAVE_CURLM_ERROR(mh, error); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + error = mh->multi->remove_handle(handle); + } else { + error = curl_multi_remove_handle(mh->multi, ch->cp); + } - RETVAL_LONG((zend_long) error); + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); + SAVE_CURLM_ERROR(mh, error); +#if PHP_VERSION_ID >= 80200 + if (error == CURLM_OK) { + zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *)) curl_compare_objects); + } +#else zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *)) curl_compare_objects); +#endif + RETVAL_LONG((zend_long) error); } /* }}} */ @@ -193,6 +209,18 @@ PHP_FUNCTION(swoole_native_curl_multi_select) { "The given object is not a valid coroutine CurlMultiHandle object"); RETURN_FALSE; } + +#if PHP_VERSION_ID >= 80200 + if (!(timeout >= 0.0 && timeout <= ((double) INT_MAX / 1000.0))) { + swoole_fatal_error( + SW_ERROR_WRONG_OPERATION, "timeout must be between 0 and %d", (int) ceilf((double) INT_MAX / 1000)); +#ifdef CURLM_BAD_FUNCTION_ARGUMENT + SAVE_CURLM_ERROR(mh, CURLM_BAD_FUNCTION_ARGUMENT); +#endif + RETURN_FALSE; + } +#endif + RETURN_LONG(mh->multi->select(mh, timeout)); } /* }}} */ @@ -202,7 +230,7 @@ PHP_FUNCTION(swoole_native_curl_multi_exec) { zval *z_mh; zval *z_still_running; php_curlm *mh; - int still_running = 0; + int still_running; CURLMcode error = CURLM_OK; ZEND_PARSE_PARAMETERS_START(2, 2) @@ -225,7 +253,8 @@ PHP_FUNCTION(swoole_native_curl_multi_exec) { for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { ch = Z_CURL_P(pz_ch); - swoole_curl_verify_handlers(ch, 1); + + swoole_curl_verify_handlers(ch, /* reporterror */ true); } } @@ -249,12 +278,12 @@ PHP_FUNCTION(swoole_native_curl_multi_getcontent) { ch = Z_CURL_P(z_ch); - if (curl_handlers(ch)->write->method == PHP_CURL_RETURN) { - if (!curl_handlers(ch)->write->buf.s) { + if (ch->handlers.write->method == PHP_CURL_RETURN) { + if (!ch->handlers.write->buf.s) { RETURN_EMPTY_STRING(); } - smart_str_0(&curl_handlers(ch)->write->buf); - RETURN_STR_COPY(curl_handlers(ch)->write->buf.s); + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); } RETURN_NULL(); @@ -326,17 +355,16 @@ PHP_FUNCTION(swoole_native_curl_multi_close) { mh = Z_CURL_MULTI_P(z_mh); - bool is_in_coroutine = swoole_curl_multi_is_in_coroutine(mh); - for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { php_curl *ch = Z_CURL_P(pz_ch); if (!ch) { continue; } - swoole_curl_verify_handlers(ch, 0); - if (is_in_coroutine) { - mh->multi->remove_handle(ch->cp); + swoole_curl_verify_handlers(ch, /* reporterror */ false); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle) { + mh->multi->remove_handle(handle); } else { curl_multi_remove_handle(mh->multi, ch->cp); } @@ -385,17 +413,14 @@ static int _php_server_push_callback( php_curl *parent; php_curlm *mh = (php_curlm *) userp; size_t rval = CURL_PUSH_DENY; -#if PHP_VERSION_ID < 80100 - php_curlm_server_push *t = mh->handlers->server_push; -#else - php_curlm_server_push *t = mh->handlers.server_push; -#endif + php_curl_callback *t = mh->handlers.server_push; zval *pz_parent_ch = NULL; zval pz_ch; zval headers; zval retval; char *header; - int error; + zend_result error; + zend_fcall_info fci = empty_fcall_info; pz_parent_ch = _php_curl_multi_find_easy_handle(mh, parent_ch); @@ -403,12 +428,23 @@ static int _php_server_push_callback( return rval; } + if (UNEXPECTED(zend_fcall_info_init(&t->func_name, 0, &fci, &t->fci_cache, NULL, NULL) == FAILURE)) { + php_error_docref(NULL, E_WARNING, "Cannot call the CURLMOPT_PUSHFUNCTION"); + return rval; + } + parent = Z_CURL_P(pz_parent_ch); ch = swoole_curl_init_handle_into_zval(&pz_ch); ch->cp = easy; swoole_setup_easy_copy_handlers(ch, parent); + auto parent_handle = swoole::curl::get_handle(parent->cp); + if (parent_handle) { + auto handle = swoole::curl::create_handle(easy); + handle->multi = parent_handle->multi; + } + size_t i; array_init(&headers); for (i = 0; i < num_headers; i++) { @@ -416,20 +452,33 @@ static int _php_server_push_callback( add_next_index_string(&headers, header); } - zend_fcall_info_init(&t->func_name, 0, &fci, &t->fci_cache, NULL, NULL); +#if PHP_VERSION_ID >= 80300 + ZEND_ASSERT(pz_parent_ch); + zval call_args[3] = {*pz_parent_ch, pz_ch, headers}; + fci.param_count = 3; + fci.params = call_args; + fci.retval = &retval; +#else zend_fcall_info_argn(&fci, 3, pz_parent_ch, &pz_ch, &headers); fci.retval = &retval; +#endif error = zend_call_function(&fci, &t->fci_cache); +#if PHP_VERSION_ID < 80300 zend_fcall_info_args_clear(&fci, 1); +#endif zval_ptr_dtor_nogc(&headers); if (error == FAILURE) { php_error_docref(NULL, E_WARNING, "Cannot call the CURLMOPT_PUSHFUNCTION"); } else if (!Z_ISUNDEF(retval)) { +#if PHP_VERSION_ID >= 80300 + if (CURL_PUSH_DENY != swoole_curl_get_long(&retval)) { +#else if (CURL_PUSH_DENY != zval_get_long(&retval)) { +#endif rval = CURL_PUSH_OK; zend_llist_add_element(&mh->easyh, &pz_ch); } else { @@ -442,7 +491,7 @@ static int _php_server_push_callback( } /* }}} */ -static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ +static bool _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ { CURLMcode error = CURLM_OK; @@ -453,41 +502,39 @@ static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: case CURLMOPT_MAX_HOST_CONNECTIONS: case CURLMOPT_MAX_PIPELINE_LENGTH: - case CURLMOPT_MAX_TOTAL_CONNECTIONS: { + case CURLMOPT_MAX_TOTAL_CONNECTIONS: +#if LIBCURL_VERSION_NUM >= 0x074300 && PHP_VERSION_ID >= 80200 + case CURLMOPT_MAX_CONCURRENT_STREAMS: +#endif + { zend_long lval = zval_get_long(zvalue); if (option == CURLMOPT_PIPELINING && (lval & 1)) { +#if LIBCURL_VERSION_NUM >= 0x073e00 /* 7.62.0 */ php_error_docref(NULL, E_WARNING, "CURLPIPE_HTTP1 is no longer supported"); +#else + php_error_docref(NULL, E_DEPRECATED, "CURLPIPE_HTTP1 is deprecated"); +#endif } error = curl_multi_setopt(mh->multi->get_multi_handle(), (CURLMoption) option, lval); break; } case CURLMOPT_PUSHFUNCTION: { -#if PHP_VERSION_ID < 80100 - if (mh->handlers->server_push == NULL) { - mh->handlers->server_push = (php_curlm_server_push *) ecalloc(1, sizeof(php_curlm_server_push)); - } else if (!Z_ISUNDEF(mh->handlers->server_push->func_name)) { - zval_ptr_dtor(&mh->handlers->server_push->func_name); - mh->handlers->server_push->fci_cache = empty_fcall_info_cache; - } - - ZVAL_COPY(&mh->handlers->server_push->func_name, zvalue); - mh->handlers->server_push->method = PHP_CURL_USER; -#else if (mh->handlers.server_push == NULL) { - mh->handlers.server_push = (php_curlm_server_push *) ecalloc(1, sizeof(php_curlm_server_push)); + mh->handlers.server_push = (php_curl_callback *) ecalloc(1, sizeof(php_curl_callback)); } else if (!Z_ISUNDEF(mh->handlers.server_push->func_name)) { zval_ptr_dtor(&mh->handlers.server_push->func_name); mh->handlers.server_push->fci_cache = empty_fcall_info_cache; } ZVAL_COPY(&mh->handlers.server_push->func_name, zvalue); - mh->handlers.server_push->method = PHP_CURL_USER; -#endif - +#if PHP_VERSION_ID >= 80200 + error = curl_multi_setopt(mh->multi->get_multi_handle(), CURLMOPT_PUSHFUNCTION, _php_server_push_callback); +#else error = curl_multi_setopt(mh->multi->get_multi_handle(), (CURLMoption) option, _php_server_push_callback); +#endif if (error != CURLM_OK) { - return 0; + return false; } error = curl_multi_setopt(mh->multi->get_multi_handle(), CURLMOPT_PUSHDATA, mh); break; @@ -500,7 +547,7 @@ static int _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, SAVE_CURLM_ERROR(mh, error); - return error != CURLM_OK; + return error == CURLM_OK; } /* }}} */ @@ -522,7 +569,7 @@ PHP_FUNCTION(swoole_native_curl_multi_setopt) { "The given object is not a valid coroutine CurlMultiHandle object"); RETURN_FALSE; } - if (!_php_curl_multi_setopt(mh, options, zvalue, return_value)) { + if (_php_curl_multi_setopt(mh, (CURLMoption) options, zvalue, return_value)) { RETURN_TRUE; } else { RETURN_FALSE; @@ -539,11 +586,18 @@ static zend_object *swoole_curl_multi_create_object(zend_class_entry *class_type zend_object_std_init(&intern->std, class_type); object_properties_init(&intern->std, class_type); +#if PHP_VERSION_ID < 80300 intern->std.handlers = &swoole_coroutine_curl_multi_handle_handlers; +#endif return &intern->std; } +static zend_function *swoole_curl_multi_get_constructor(zend_object *object) { + zend_throw_error(NULL, "Cannot directly construct CurlMultiHandle, use curl_multi_init() instead"); + return NULL; +} + static void swoole_curl_multi_free_obj(zend_object *object) { php_curlm *mh = (php_curlm *) curl_multi_from_obj(object); if (!mh->multi) { @@ -555,27 +609,14 @@ static void swoole_curl_multi_free_obj(zend_object *object) { zend_object_std_dtor(&mh->std); } -static zend_function *swoole_curl_multi_get_constructor(zend_object *object) { - zend_throw_error(NULL, "Cannot directly construct CurlMultiHandle, use curl_multi_init() instead"); - return NULL; -} - static HashTable *swoole_curl_multi_get_gc(zend_object *object, zval **table, int *n) { php_curlm *curl_multi = curl_multi_from_obj(object); zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); -#if PHP_VERSION_ID >= 80100 if (curl_multi->handlers.server_push) { zend_get_gc_buffer_add_zval(gc_buffer, &curl_multi->handlers.server_push->func_name); } -#else - if (curl_multi->handlers) { - if (curl_multi->handlers->server_push) { - zend_get_gc_buffer_add_zval(gc_buffer, &curl_multi->handlers->server_push->func_name); - } - } -#endif zend_llist_position pos; for (zval *pz_ch = (zval *) zend_llist_get_first_ex(&curl_multi->easyh, &pos); pz_ch; @@ -613,9 +654,12 @@ static void _php_curl_multi_free(php_curlm *mh) { continue; } if ((ch = swoole_curl_get_handle(z_ch, true, false))) { - swoole_curl_verify_handlers(ch, 0); - if (is_in_coroutine) { - mh->multi->remove_handle(ch->cp); + swoole_curl_verify_handlers(ch, /* reporterror */ false); + auto handle = swoole::curl::get_handle(ch->cp); + if (is_in_coroutine && handle) { + mh->multi->remove_handle(handle); + } else { + curl_multi_remove_handle(mh->multi, ch->cp); } } } @@ -628,20 +672,10 @@ static void _php_curl_multi_free(php_curlm *mh) { mh->multi = nullptr; } zend_llist_clean(&mh->easyh); -#if PHP_VERSION_ID < 80100 - if (mh->handlers->server_push) { - zval_ptr_dtor(&mh->handlers->server_push->func_name); - efree(mh->handlers->server_push); - } - if (mh->handlers) { - efree(mh->handlers); - } -#else if (mh->handlers.server_push) { zval_ptr_dtor(&mh->handlers.server_push->func_name); efree(mh->handlers.server_push); } -#endif } #endif diff --git a/thirdparty/php/curl/php_curl.h b/thirdparty/php/curl/php_curl.h index 046f6b7922..37d18fb4c9 100644 --- a/thirdparty/php/curl/php_curl.h +++ b/thirdparty/php/curl/php_curl.h @@ -1,13 +1,11 @@ /* - +----------------------------------------------------------------------+ - | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -16,51 +14,33 @@ | Wez Furlong | +----------------------------------------------------------------------+ */ - -/* Copied from PHP-7.4.11 */ - -#ifdef SW_USE_CURL +#include "php_swoole_cxx.h" +#if defined(SW_USE_CURL) && PHP_VERSION_ID < 80400 #ifndef _PHP_CURL_H #define _PHP_CURL_H #include "php.h" -#include "zend_smart_str.h" - -#define PHP_CURL_DEBUG 0 #ifdef PHP_WIN32 +# ifdef PHP_CURL_EXPORTS # define PHP_CURL_API __declspec(dllexport) +# else +# define PHP_CURL_API __declspec(dllimport) +# endif #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_CURL_API __attribute__ ((visibility("default"))) #else # define PHP_CURL_API #endif -#include "php_version.h" -#define PHP_CURL_VERSION PHP_VERSION - -#include -#include - -#define CURLOPT_RETURNTRANSFER 19913 -#define CURLOPT_BINARYTRANSFER 19914 /* For Backward compatibility */ -#define PHP_CURL_STDOUT 0 -#define PHP_CURL_FILE 1 -#define PHP_CURL_USER 2 -#define PHP_CURL_DIRECT 3 -#define PHP_CURL_RETURN 4 -#define PHP_CURL_IGNORE 7 - PHP_CURL_API extern zend_class_entry *curl_ce; PHP_CURL_API extern zend_class_entry *curl_share_ce; PHP_CURL_API extern zend_class_entry *curl_multi_ce; PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_handle_ce; PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_multi_handle_ce; PHP_CURL_API extern zend_class_entry *curl_CURLFile_class; +PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class; -#else -#define curl_module_ptr NULL -#endif /* HAVE_CURL */ -#define phpext_curl_ptr curl_module_ptr +#endif /* _PHP_CURL_H */ #endif diff --git a/thirdparty/php/main/SAPI.h b/thirdparty/php/main/SAPI.h new file mode 100644 index 0000000000..f434ad1dd0 --- /dev/null +++ b/thirdparty/php/main/SAPI.h @@ -0,0 +1,109 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Zeev Suraski | + +----------------------------------------------------------------------+ +*/ + +#include "main/php_variables.h" + +/** + * This only handles the cases of PARSE_STRING and PARSE_COOKIE + */ +static void swoole_php_treat_data(int arg, char *str, zval *destArray) { + char *res = NULL, *var, *val, *separator = NULL; + zval array; + int free_buffer = 0; + char *strtok_buf = NULL; + zend_long count = 0; + + ZVAL_UNDEF(&array); + ZVAL_COPY_VALUE(&array, destArray); + + res = str; + free_buffer = 1; + + if (!res) { + return; + } + + switch (arg) { + case PARSE_STRING: +#if PHP_VERSION_ID >= 80500 + separator = ZSTR_VAL(PG(arg_separator).input); +#else + separator = PG(arg_separator).input; +#endif + break; + case PARSE_COOKIE: + separator = (char *) ";\0"; + break; + } + + var = php_strtok_r(res, separator, &strtok_buf); + + while (var) { + size_t val_len; + size_t new_val_len; + + val = strchr(var, '='); + + if (arg == PARSE_COOKIE) { + /* Remove leading spaces from cookie names, needed for multi-cookie header where ; can be followed by a + * space */ + while (isspace(*var)) { + var++; + } + if (var == val || *var == '\0') { + goto next_cookie; + } + } + + if (++count > PG(max_input_vars)) { + swoole_warning("Input variables exceeded " ZEND_LONG_FMT + ". To increase the limit change max_input_vars in php.ini.", + PG(max_input_vars)); + break; + } + + if (val) { /* have a value */ + *val++ = '\0'; + if (arg == PARSE_COOKIE) { + val_len = php_raw_url_decode(val, strlen(val)); + } else { + val_len = php_url_decode(val, strlen(val)); + } + } else { + val = (char *) ""; + val_len = 0; + } + + val = estrndup(val, val_len); + if (arg != PARSE_COOKIE) { + php_url_decode(var, strlen(var)); + } + + if (sapi_module.input_filter(PARSE_STRING, var, &val, val_len, &new_val_len)) { + if (arg == PARSE_STRING || + (arg == PARSE_COOKIE && !zend_symtable_str_exists(Z_ARRVAL_P(&array), var, strlen(var)))) { + php_register_variable_safe(var, val, new_val_len, &array); + } + } + efree(val); + next_cookie: + var = php_strtok_r(NULL, separator, &strtok_buf); + } + + if (free_buffer) { + efree(res); + } +} diff --git a/thirdparty/php/sockets/conversions.cc b/thirdparty/php/sockets/conversions.cc index 3b10819b12..50c3fb7765 100644 --- a/thirdparty/php/sockets/conversions.cc +++ b/thirdparty/php/sockets/conversions.cc @@ -3,406 +3,409 @@ #include #include -# include -# include -# include -# include -# include -# include -# include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define MAX_USER_BUFF_SIZE ((size_t) (100 * 1024 * 1024)) #define DEFAULT_BUFF_SIZE 8192 -struct _ser_context -{ +struct _ser_context { HashTable params; /* stores pointers; has to be first */ struct err_s err; zend_llist keys, - /* common part to res_context ends here */ - allocations; - Socket *sock; + /* common part to res_context ends here */ + allocations; + SocketImpl *sock; }; -struct _res_context -{ +struct _res_context { HashTable params; /* stores pointers; has to be first */ struct err_s err; zend_llist keys; }; typedef struct { - /* zval info */ - const char *name; - unsigned name_size; - int required; - - /* structure info */ - size_t field_offset; /* 0 to pass full structure, e.g. when more than - one field is to be changed; in that case the - callbacks need to know the name of the fields */ - - /* callbacks */ - from_zval_write_field *from_zval; - to_zval_read_field *to_zval; + /* zval info */ + const char *name; + unsigned name_size; + int required; + + /* structure info */ + size_t field_offset; /* 0 to pass full structure, e.g. when more than + one field is to be changed; in that case the + callbacks need to know the name of the fields */ + + /* callbacks */ + from_zval_write_field *from_zval; + to_zval_read_field *to_zval; } field_descriptor; const struct key_value sw_empty_key_value_list[] = {{0}}; /* ERRORS */ -static void do_from_to_zval_err(struct err_s *err, - zend_llist *keys, - const char *what_conv, - const char *fmt, - va_list ap) -{ - smart_str path = {0}; - const char **node; - char *user_msg; - int user_msg_size; - zend_llist_position pos; - - if (err->has_error) { - return; +static void do_from_to_zval_err( + struct err_s *err, zend_llist *keys, const char *what_conv, const char *fmt, va_list ap) { + smart_str path = {0}; + const char **node; + char *user_msg; + int user_msg_size; + zend_llist_position pos; + + if (err->has_error) { + return; } - for (node = (const char **) zend_llist_get_first_ex(keys, &pos); node != NULL; node = - (const char **) zend_llist_get_next_ex(keys, &pos)) - { + for (node = (const char **) zend_llist_get_first_ex(keys, &pos); node != NULL; + node = (const char **) zend_llist_get_next_ex(keys, &pos)) { smart_str_appends(&path, *node); smart_str_appends(&path, " > "); } - if (path.s && ZSTR_LEN(path.s) > 3) { - ZSTR_LEN(path.s) -= 3; - } - smart_str_0(&path); - - user_msg_size = vspprintf(&user_msg, 0, fmt, ap); - - err->has_error = 1; - err->level = E_WARNING; - spprintf(&err->msg, 0, "error converting %s data (path: %s): %.*s", - what_conv, - path.s && *ZSTR_VAL(path.s) != '\0' ? ZSTR_VAL(path.s) : "unavailable", - user_msg_size, user_msg); - err->should_free = 1; - - efree(user_msg); - smart_str_free(&path); + if (path.s && ZSTR_LEN(path.s) > 3) { + ZSTR_LEN(path.s) -= 3; + } + smart_str_0(&path); + + user_msg_size = vspprintf(&user_msg, 0, fmt, ap); + + err->has_error = 1; + err->level = E_WARNING; + spprintf(&err->msg, + 0, + "error converting %s data (path: %s): %.*s", + what_conv, + path.s && *ZSTR_VAL(path.s) != '\0' ? ZSTR_VAL(path.s) : "unavailable", + user_msg_size, + user_msg); + err->should_free = 1; + + efree(user_msg); + smart_str_free(&path); } -ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) +ZEND_ATTRIBUTE_FORMAT(printf, 2, 3) -static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) -{ - va_list ap; +static void do_from_zval_err(ser_context *ctx, const char *fmt, ...) { + va_list ap; - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); - va_end(ap); + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "user", fmt, ap); + va_end(ap); } -ZEND_ATTRIBUTE_FORMAT(printf, 2 ,3) +ZEND_ATTRIBUTE_FORMAT(printf, 2, 3) -static void do_to_zval_err(res_context *ctx, const char *fmt, ...) -{ - va_list ap; +static void do_to_zval_err(res_context *ctx, const char *fmt, ...) { + va_list ap; - va_start(ap, fmt); - do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); - va_end(ap); + va_start(ap, fmt); + do_from_to_zval_err(&ctx->err, &ctx->keys, "native", fmt, ap); + va_end(ap); } -void err_msg_dispose(struct err_s *err) -{ - if (err->msg != NULL) { - php_error_docref(NULL, err->level, "%s", err->msg); - if (err->should_free) { - efree(err->msg); - } - } +void err_msg_dispose(struct err_s *err) { + if (err->msg != NULL) { + php_error_docref(NULL, err->level, "%s", err->msg); + if (err->should_free) { + efree(err->msg); + } + } } -void allocations_dispose(zend_llist **allocations) -{ - zend_llist_destroy(*allocations); - efree(*allocations); - *allocations = NULL; +void allocations_dispose(zend_llist **allocations) { + zend_llist_destroy(*allocations); + efree(*allocations); + *allocations = NULL; } /* Generic Aggregated conversions */ static void from_zval_write_aggregation(const zval *container, - char *structure, - const field_descriptor *descriptors, - ser_context *ctx) -{ - const field_descriptor *descr; - zval *elem; - - if (Z_TYPE_P(container) != IS_ARRAY) { - do_from_zval_err(ctx, "%s", "expected an array here"); - } - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - if ((elem = zend_hash_str_find(Z_ARRVAL_P(container), - descr->name, descr->name_size - 1)) != NULL) { - - if (descr->from_zval == NULL) { - do_from_zval_err(ctx, "No information on how to convert value " - "of key '%s'", descr->name); - break; - } - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->from_zval(elem, ((char*)structure) + descr->field_offset, ctx); - zend_llist_remove_tail(&ctx->keys); - - } else if (descr->required) { - do_from_zval_err(ctx, "The key '%s' is required", descr->name); - break; - } - } + char *structure, + const field_descriptor *descriptors, + ser_context *ctx) { + const field_descriptor *descr; + zval *elem; + + if (Z_TYPE_P(container) != IS_ARRAY) { + do_from_zval_err(ctx, "%s", "expected an array here"); + } + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + if ((elem = zend_hash_str_find(Z_ARRVAL_P(container), descr->name, descr->name_size - 1)) != NULL) { + if (descr->from_zval == NULL) { + do_from_zval_err(ctx, + "No information on how to convert value " + "of key '%s'", + descr->name); + break; + } + + zend_llist_add_element(&ctx->keys, (void *) &descr->name); + descr->from_zval(elem, ((char *) structure) + descr->field_offset, ctx); + zend_llist_remove_tail(&ctx->keys); + + } else if (descr->required) { + do_from_zval_err(ctx, "The key '%s' is required", descr->name); + break; + } + } } static void to_zval_read_aggregation(const char *structure, - zval *zarr, /* initialized array */ - const field_descriptor *descriptors, - res_context *ctx) -{ - const field_descriptor *descr; - - assert(Z_TYPE_P(zarr) == IS_ARRAY); - assert(Z_ARRVAL_P(zarr) != NULL); - - for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { - zval *new_zv, tmp; - - if (descr->to_zval == NULL) { - do_to_zval_err(ctx, "No information on how to convert native " - "field into value for key '%s'", descr->name); - break; - } - - ZVAL_NULL(&tmp); - new_zv = zend_symtable_str_update(Z_ARRVAL_P(zarr), descr->name, descr->name_size - 1, &tmp); - - zend_llist_add_element(&ctx->keys, (void*)&descr->name); - descr->to_zval(structure + descr->field_offset, new_zv, ctx); - zend_llist_remove_tail(&ctx->keys); - } + zval *zarr, /* initialized array */ + const field_descriptor *descriptors, + res_context *ctx) { + const field_descriptor *descr; + + assert(Z_TYPE_P(zarr) == IS_ARRAY); + assert(Z_ARRVAL_P(zarr) != NULL); + + for (descr = descriptors; descr->name != NULL && !ctx->err.has_error; descr++) { + zval *new_zv, tmp; + + if (descr->to_zval == NULL) { + do_to_zval_err(ctx, + "No information on how to convert native " + "field into value for key '%s'", + descr->name); + break; + } + + ZVAL_NULL(&tmp); + new_zv = zend_symtable_str_update(Z_ARRVAL_P(zarr), descr->name, descr->name_size - 1, &tmp); + + zend_llist_add_element(&ctx->keys, (void *) &descr->name); + descr->to_zval(structure + descr->field_offset, new_zv, ctx); + zend_llist_remove_tail(&ctx->keys); + } } -static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) -{ - unsigned ival; - memcpy(&ival, data, sizeof(ival)); +static void to_zval_read_unsigned(const char *data, zval *zv, res_context *ctx) { + unsigned ival; + memcpy(&ival, data, sizeof(ival)); - ZVAL_LONG(zv, (zend_long)ival); + ZVAL_LONG(zv, (zend_long) ival); } -static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) -{ - int res; - struct sockaddr_in6 saddr6 = {0}; - zend_string *addr_str; - - addr_str = zval_get_string((zval *) zaddr_str); - res = php_set_inet6_addr(&saddr6, ZSTR_VAL(addr_str), ctx->sock); - if (res) { - memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); - } else { - /* error already emitted, but let's emit another more relevant */ - do_from_zval_err(ctx, "could not resolve address '%s' to get an AF_INET6 " - "address", Z_STRVAL_P(zaddr_str)); - } - - zend_string_release(addr_str); +static void from_zval_write_sin6_addr(const zval *zaddr_str, char *addr6, ser_context *ctx) { + int res; + struct sockaddr_in6 saddr6 = {0}; + zend_string *addr_str; + + addr_str = zval_get_string((zval *) zaddr_str); + res = php_set_inet6_addr(&saddr6, ZSTR_VAL(addr_str), ctx->sock); + if (res) { + memcpy(addr6, &saddr6.sin6_addr, sizeof saddr6.sin6_addr); + } else { + /* error already emitted, but let's emit another more relevant */ + do_from_zval_err(ctx, + "could not resolve address '%s' to get an AF_INET6 " + "address", + Z_STRVAL_P(zaddr_str)); + } + + zend_string_release(addr_str); } -static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) -{ - const struct in6_addr *addr = (const struct in6_addr *)data; - socklen_t size = INET6_ADDRSTRLEN; - zend_string *str = zend_string_alloc(size - 1, 0); +static void to_zval_read_sin6_addr(const char *data, zval *zv, res_context *ctx) { + const struct in6_addr *addr = (const struct in6_addr *) data; + socklen_t size = INET6_ADDRSTRLEN; + zend_string *str = zend_string_alloc(size - 1, 0); - memset(ZSTR_VAL(str), '\0', size); + memset(ZSTR_VAL(str), '\0', size); - ZVAL_NEW_STR(zv, str); + ZVAL_NEW_STR(zv, str); - if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { - do_to_zval_err(ctx, "could not convert IPv6 address to string " - "(errno %d)", errno); - return; - } + if (inet_ntop(AF_INET6, addr, Z_STRVAL_P(zv), size) == NULL) { + do_to_zval_err(ctx, + "could not convert IPv6 address to string " + "(errno %d)", + errno); + return; + } - Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); + Z_STRLEN_P(zv) = strlen(Z_STRVAL_P(zv)); } - /* CONVERSIONS for if_index */ -static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) -{ - unsigned ret = 0; - - if (Z_TYPE_P(zv) == IS_LONG) { - if (Z_LVAL_P(zv) < 0 || (zend_ulong)Z_LVAL_P(zv) > UINT_MAX) { /* allow 0 (unspecified interface) */ - do_from_zval_err(ctx, "the interface index cannot be negative or " - "larger than %u; given " ZEND_LONG_FMT, UINT_MAX, Z_LVAL_P(zv)); - } else { - ret = (unsigned)Z_LVAL_P(zv); - } - } else { - zend_string *str; - - str = zval_get_string((zval *) zv); +static void from_zval_write_ifindex(const zval *zv, char *uinteger, ser_context *ctx) { + unsigned ret = 0; + + if (Z_TYPE_P(zv) == IS_LONG) { + if (Z_LVAL_P(zv) < 0 || (zend_ulong) Z_LVAL_P(zv) > UINT_MAX) { /* allow 0 (unspecified interface) */ + do_from_zval_err(ctx, + "the interface index cannot be negative or " + "larger than %u; given " ZEND_LONG_FMT, + UINT_MAX, + Z_LVAL_P(zv)); + } else { + ret = (unsigned) Z_LVAL_P(zv); + } + } else { + zend_string *str; + + str = zval_get_string((zval *) zv); #if HAVE_IF_NAMETOINDEX - ret = if_nametoindex(ZSTR_VAL(str)); - if (ret == 0) { - do_from_zval_err(ctx, "no interface with name \"%s\" could be found", ZSTR_VAL(str)); - } + ret = if_nametoindex(ZSTR_VAL(str)); + if (ret == 0) { + do_from_zval_err(ctx, "no interface with name \"%s\" could be found", ZSTR_VAL(str)); + } #elif defined(SIOCGIFINDEX) - { - struct ifreq ifr; - if (strlcpy(ifr.ifr_name, ZSTR_VAL(str), sizeof(ifr.ifr_name)) - >= sizeof(ifr.ifr_name)) { - do_from_zval_err(ctx, "the interface name \"%s\" is too large ", ZSTR_VAL(str)); - } else if (ioctl(ctx->sock->get_fd(), SIOCGIFINDEX, &ifr) < 0) { - if (errno == ENODEV) { - do_from_zval_err(ctx, "no interface with name \"%s\" could be " - "found", ZSTR_VAL(str)); - } else { - do_from_zval_err(ctx, "error fetching interface index for " - "interface with name \"%s\" (errno %d)", - ZSTR_VAL(str), errno); - } - } else { - ret = (unsigned)ifr.ifr_ifindex; - } - } + { + struct ifreq ifr; + if (strlcpy(ifr.ifr_name, ZSTR_VAL(str), sizeof(ifr.ifr_name)) >= sizeof(ifr.ifr_name)) { + do_from_zval_err(ctx, "the interface name \"%s\" is too large ", ZSTR_VAL(str)); + } else if (ioctl(ctx->sock->get_fd(), SIOCGIFINDEX, &ifr) < 0) { + if (errno == ENODEV) { + do_from_zval_err(ctx, + "no interface with name \"%s\" could be " + "found", + ZSTR_VAL(str)); + } else { + do_from_zval_err(ctx, + "error fetching interface index for " + "interface with name \"%s\" (errno %d)", + ZSTR_VAL(str), + errno); + } + } else { + ret = (unsigned) ifr.ifr_ifindex; + } + } #else - do_from_zval_err(ctx, - "this platform does not support looking up an interface by " - "name, an integer interface index must be supplied instead"); + do_from_zval_err(ctx, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); #endif - zend_string_release(str); - } + zend_string_release(str); + } - if (!ctx->err.has_error) { - memcpy(uinteger, &ret, sizeof(ret)); - } + if (!ctx->err.has_error) { + memcpy(uinteger, &ret, sizeof(ret)); + } } /* CONVERSIONS for struct in6_pktinfo */ #if defined(IPV6_PKTINFO) -static const field_descriptor descriptors_in6_pktinfo[] = { - {"addr", sizeof("addr"), 1, offsetof(struct in6_pktinfo, ipi6_addr), from_zval_write_sin6_addr, to_zval_read_sin6_addr}, - {"ifindex", sizeof("ifindex"), 1, offsetof(struct in6_pktinfo, ipi6_ifindex), from_zval_write_ifindex, to_zval_read_unsigned}, - {0} -}; - -void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) -{ - from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); +static const field_descriptor descriptors_in6_pktinfo[] = {{"addr", + sizeof("addr"), + 1, + offsetof(struct in6_pktinfo, ipi6_addr), + from_zval_write_sin6_addr, + to_zval_read_sin6_addr}, + {"ifindex", + sizeof("ifindex"), + 1, + offsetof(struct in6_pktinfo, ipi6_ifindex), + from_zval_write_ifindex, + to_zval_read_unsigned}, + {0}}; + +void from_zval_write_in6_pktinfo(const zval *container, char *in6_pktinfo_c, ser_context *ctx) { + from_zval_write_aggregation(container, in6_pktinfo_c, descriptors_in6_pktinfo, ctx); } -void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) -{ - array_init_size(zv, 2); +void to_zval_read_in6_pktinfo(const char *data, zval *zv, res_context *ctx) { + array_init_size(zv, 2); - to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); + to_zval_read_aggregation(data, zv, descriptors_in6_pktinfo, ctx); } #endif /* ENTRY POINT for conversions */ -static void free_from_zval_allocation(void *alloc_ptr_ptr) -{ - efree(*(void**)alloc_ptr_ptr); +static void free_from_zval_allocation(void *alloc_ptr_ptr) { + efree(*(void **) alloc_ptr_ptr); } -void *from_zval_run_conversions(const zval *container, Socket *sock, from_zval_write_field *writer, - size_t struct_size, const char *top_name, zend_llist **allocations /* out */, struct err_s *err /* in/out */) -{ - ser_context ctx; - char *structure; +void *from_zval_run_conversions(const zval *container, + SocketImpl *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */) { + ser_context ctx; + char *structure; - *allocations = NULL; + *allocations = NULL; - if (err->has_error) { - return NULL; - } + if (err->has_error) { + return NULL; + } - memset(&ctx, 0, sizeof(ctx)); - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); - ctx.sock = sock; + memset(&ctx, 0, sizeof(ctx)); + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_init(&ctx.allocations, sizeof(void *), &free_from_zval_allocation, 0); + ctx.sock = sock; structure = (char *) ecalloc(1, struct_size); - zend_llist_add_element(&ctx.keys, &top_name); - zend_llist_add_element(&ctx.allocations, &structure); + zend_llist_add_element(&ctx.keys, &top_name); + zend_llist_add_element(&ctx.allocations, &structure); - /* main call */ - writer(container, structure, &ctx); + /* main call */ + writer(container, structure, &ctx); - if (ctx.err.has_error) - { + if (ctx.err.has_error) { zend_llist_destroy(&ctx.allocations); /* deallocates structure as well */ structure = NULL; *err = ctx.err; - } - else - { + } else { *allocations = (zend_llist *) emalloc(sizeof **allocations); **allocations = ctx.allocations; } - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); - return structure; + return structure; } zval *to_zval_run_conversions(const char *structure, - to_zval_read_field *reader, - const char *top_name, - const struct key_value *key_value_pairs, - struct err_s *err, zval *zv) -{ - res_context ctx; - const struct key_value *kv; - - if (err->has_error) { - return NULL; - } - - memset(&ctx, 0, sizeof(ctx)); - zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); - zend_llist_add_element(&ctx.keys, &top_name); - - zend_hash_init(&ctx.params, 8, NULL, NULL, 0); - for (kv = key_value_pairs; kv->key != NULL; kv++) { - zend_hash_str_update_ptr(&ctx.params, kv->key, kv->key_size - 1, kv->value); - } - - ZVAL_NULL(zv); - /* main call */ - reader(structure, zv, &ctx); - - if (ctx.err.has_error) { - zval_ptr_dtor(zv); - ZVAL_UNDEF(zv); - *err = ctx.err; - } - - zend_llist_destroy(&ctx.keys); - zend_hash_destroy(&ctx.params); - - return Z_ISUNDEF_P(zv)? NULL : zv; + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err, + zval *zv) { + res_context ctx; + const struct key_value *kv; + + if (err->has_error) { + return NULL; + } + + memset(&ctx, 0, sizeof(ctx)); + zend_llist_init(&ctx.keys, sizeof(const char *), NULL, 0); + zend_llist_add_element(&ctx.keys, &top_name); + + zend_hash_init(&ctx.params, 8, NULL, NULL, 0); + for (kv = key_value_pairs; kv->key != NULL; kv++) { + zend_hash_str_update_ptr(&ctx.params, kv->key, kv->key_size - 1, kv->value); + } + + ZVAL_NULL(zv); + /* main call */ + reader(structure, zv, &ctx); + + if (ctx.err.has_error) { + zval_ptr_dtor(zv); + ZVAL_UNDEF(zv); + *err = ctx.err; + } + + zend_llist_destroy(&ctx.keys); + zend_hash_destroy(&ctx.params); + + return Z_ISUNDEF_P(zv) ? NULL : zv; } diff --git a/thirdparty/php/sockets/conversions.h b/thirdparty/php/sockets/conversions.h index 1afbf17cdc..a52c5ef42e 100644 --- a/thirdparty/php/sockets/conversions.h +++ b/thirdparty/php/sockets/conversions.h @@ -6,16 +6,14 @@ #include /* TYPE DEFINITIONS */ -struct err_s -{ +struct err_s { int has_error; char *msg; int level; int should_free; }; -struct key_value -{ +struct key_value { const char *key; unsigned key_size; void *value; @@ -27,8 +25,8 @@ typedef struct _res_context res_context; #define KEY_RECVMSG_RET "recvmsg_ret" -typedef void (from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); -typedef void (to_zval_read_field)(const char *data, zval *zv, res_context *ctx); +typedef void(from_zval_write_field)(const zval *arr_value, char *field, ser_context *ctx); +typedef void(to_zval_read_field)(const char *data, zval *zv, res_context *ctx); /* VARIABLE DECLARATIONS */ extern const struct key_value sw_empty_key_value_list[]; @@ -51,12 +49,20 @@ void from_zval_write_ucred(const zval *container, char *ucred_c, ser_context *ct void to_zval_read_ucred(const char *data, zval *zv, res_context *ctx); #endif - void from_zval_write_msghdr_recv(const zval *container, char *msghdr_c, ser_context *ctx); /* ENTRY POINTS FOR CONVERSIONS */ -void *from_zval_run_conversions(const zval *container, Socket *sock, from_zval_write_field *writer, - size_t struct_size, const char *top_name, zend_llist **allocations /* out */, struct err_s *err /* in/out */); - -zval *to_zval_run_conversions(const char *structure, to_zval_read_field *reader, const char *top_name, - const struct key_value *key_value_pairs, struct err_s *err, zval *zv); +void *from_zval_run_conversions(const zval *container, + SocketImpl *sock, + from_zval_write_field *writer, + size_t struct_size, + const char *top_name, + zend_llist **allocations /* out */, + struct err_s *err /* in/out */); + +zval *to_zval_run_conversions(const char *structure, + to_zval_read_field *reader, + const char *top_name, + const struct key_value *key_value_pairs, + struct err_s *err, + zval *zv); diff --git a/thirdparty/php/sockets/multicast.cc b/thirdparty/php/sockets/multicast.cc index 9d9f7bed98..8df0502f4b 100644 --- a/thirdparty/php/sockets/multicast.cc +++ b/thirdparty/php/sockets/multicast.cc @@ -30,18 +30,19 @@ #include "main/php_network.h" -using swoole::coroutine::Socket; +enum source_op { JOIN_SOURCE, LEAVE_SOURCE, BLOCK_SOURCE, UNBLOCK_SOURCE }; -enum source_op { - JOIN_SOURCE, - LEAVE_SOURCE, - BLOCK_SOURCE, - UNBLOCK_SOURCE -}; - -static int _php_mcast_join_leave(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index, int join); +static int _php_mcast_join_leave( + SocketImpl *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index, int join); #ifdef HAS_MCAST_EXT -static int _php_mcast_source_op(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, struct sockaddr *source, socklen_t source_len, unsigned int if_index, enum source_op sop); +static int _php_mcast_source_op(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index, + enum source_op sop); #endif #ifdef RFC3678_API @@ -51,773 +52,689 @@ static const char *_php_source_op_to_string(enum source_op sop); static int _php_source_op_to_ipv4_op(enum source_op sop); #endif -int php_string_to_if_index(const char *val, unsigned *out) -{ +int php_string_to_if_index(const char *val, unsigned *out) { #if HAVE_IF_NAMETOINDEX - unsigned int ind; - - ind = if_nametoindex(val); - if (ind == 0) { - php_error_docref(NULL, E_WARNING, - "no interface with name \"%s\" could be found", val); - return FAILURE; - } else { - *out = ind; - return SUCCESS; - } + unsigned int ind; + + ind = if_nametoindex(val); + if (ind == 0) { + php_error_docref(NULL, E_WARNING, "no interface with name \"%s\" could be found", val); + return FAILURE; + } else { + *out = ind; + return SUCCESS; + } #else - php_error_docref(NULL, E_WARNING, - "this platform does not support looking up an interface by " - "name, an integer interface index must be supplied instead"); - return FAILURE; + php_error_docref(NULL, + E_WARNING, + "this platform does not support looking up an interface by " + "name, an integer interface index must be supplied instead"); + return FAILURE; #endif } -static int php_get_if_index_from_zval(zval *val, unsigned *out) -{ - int ret; - - if (Z_TYPE_P(val) == IS_LONG) { - if (Z_LVAL_P(val) < 0 || (zend_ulong)Z_LVAL_P(val) > UINT_MAX) { - php_error_docref(NULL, E_WARNING, - "the interface index cannot be negative or larger than %u;" - " given " ZEND_LONG_FMT, UINT_MAX, Z_LVAL_P(val)); - ret = FAILURE; - } else { - *out = Z_LVAL_P(val); - ret = SUCCESS; - } - } else { - zend_string *str = zval_get_string(val); - ret = php_string_to_if_index(ZSTR_VAL(str), out); - zend_string_release(str); - } - - return ret; +static int php_get_if_index_from_zval(zval *val, unsigned *out) { + int ret; + + if (Z_TYPE_P(val) == IS_LONG) { + if (Z_LVAL_P(val) < 0 || (zend_ulong) Z_LVAL_P(val) > UINT_MAX) { + php_error_docref(NULL, + E_WARNING, + "the interface index cannot be negative or larger than %u;" + " given " ZEND_LONG_FMT, + UINT_MAX, + Z_LVAL_P(val)); + ret = FAILURE; + } else { + *out = Z_LVAL_P(val); + ret = SUCCESS; + } + } else { + zend_string *str = zval_get_string(val); + ret = php_string_to_if_index(ZSTR_VAL(str), out); + zend_string_release(str); + } + + return ret; } +static int php_get_if_index_from_array(const HashTable *ht, const char *key, SocketImpl *sock, unsigned int *if_index) { + zval *val; + if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) { + *if_index = 0; /* default: 0 */ + return SUCCESS; + } -static int php_get_if_index_from_array(const HashTable *ht, const char *key, - Socket *sock, unsigned int *if_index) -{ - zval *val; - - if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) { - *if_index = 0; /* default: 0 */ - return SUCCESS; - } - - return php_get_if_index_from_zval(val, if_index); + return php_get_if_index_from_zval(val, if_index); } -static int php_get_address_from_array(const HashTable *ht, const char *key, - Socket *sock, php_sockaddr_storage *ss, socklen_t *ss_len) -{ - zval *val; - zend_string *str; - - if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) { - php_error_docref(NULL, E_WARNING, "no key \"%s\" passed in optval", key); - return FAILURE; - } - str = zval_get_string(val); - if (!php_set_inet46_addr(ss, ss_len, ZSTR_VAL(str), sock)) { - zend_string_release(str); - return FAILURE; - } - zend_string_release(str); - return SUCCESS; +static int php_get_address_from_array( + const HashTable *ht, const char *key, SocketImpl *sock, php_sockaddr_storage *ss, socklen_t *ss_len) { + zval *val; + zend_string *str; + + if ((val = zend_hash_str_find(ht, key, strlen(key))) == NULL) { + php_error_docref(NULL, E_WARNING, "no key \"%s\" passed in optval", key); + return FAILURE; + } + str = zval_get_string(val); + if (!php_set_inet46_addr(ss, ss_len, ZSTR_VAL(str), sock)) { + zend_string_release(str); + return FAILURE; + } + zend_string_release(str); + return SUCCESS; } -static int php_do_mcast_opt(Socket *php_sock, int level, int optname, zval *arg4) -{ +static int php_do_mcast_opt(SocketImpl *php_sock, int level, int optname, zval *arg4) { HashTable *opt_ht; unsigned int if_index; int retval; - int (*mcast_req_fun)(Socket *, int, struct sockaddr *, socklen_t, unsigned); + int (*mcast_req_fun)(SocketImpl *, int, struct sockaddr *, socklen_t, unsigned); #ifdef HAS_MCAST_EXT - int (*mcast_sreq_fun)(Socket *, int, struct sockaddr *, socklen_t, struct sockaddr *, socklen_t, unsigned); + int (*mcast_sreq_fun)(SocketImpl *, int, struct sockaddr *, socklen_t, struct sockaddr *, socklen_t, unsigned); #endif - php_sockaddr_storage group = { 0 }; + php_sockaddr_storage group = {0}; socklen_t glen = 0; #ifdef HAS_MCAST_EXT - php_sockaddr_storage source = { 0 }; + php_sockaddr_storage source = {0}; socklen_t slen = 0; #endif - switch (optname) { - case PHP_MCAST_JOIN_GROUP: - mcast_req_fun = &php_mcast_join; - goto mcast_req_fun; - case PHP_MCAST_LEAVE_GROUP: - { - mcast_req_fun = &php_mcast_leave; -mcast_req_fun: - convert_to_array_ex(arg4); - opt_ht = Z_ARRVAL_P(arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index) == FAILURE) { - return FAILURE; - } - - retval = mcast_req_fun(php_sock, level, (struct sockaddr*)&group, - glen, if_index); - break; - } + switch (optname) { + case PHP_MCAST_JOIN_GROUP: + mcast_req_fun = &php_mcast_join; + goto mcast_req_fun; + case PHP_MCAST_LEAVE_GROUP: { + mcast_req_fun = &php_mcast_leave; + mcast_req_fun: + convert_to_array_ex(arg4); + opt_ht = Z_ARRVAL_P(arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, &glen) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, &if_index) == FAILURE) { + return FAILURE; + } + + retval = mcast_req_fun(php_sock, level, (struct sockaddr *) &group, glen, if_index); + break; + } #ifdef HAS_MCAST_EXT - case PHP_MCAST_BLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_block_source; - goto mcast_sreq_fun; - case PHP_MCAST_UNBLOCK_SOURCE: - mcast_sreq_fun = &php_mcast_unblock_source; - goto mcast_sreq_fun; - case PHP_MCAST_JOIN_SOURCE_GROUP: - mcast_sreq_fun = &php_mcast_join_source; - goto mcast_sreq_fun; - case PHP_MCAST_LEAVE_SOURCE_GROUP: - { - - mcast_sreq_fun = &php_mcast_leave_source; - mcast_sreq_fun: - convert_to_array_ex(arg4); - opt_ht = Z_ARRVAL_P(arg4); - - if (php_get_address_from_array(opt_ht, "group", php_sock, &group, - &glen) == FAILURE) { - return FAILURE; - } - if (php_get_address_from_array(opt_ht, "source", php_sock, &source, - &slen) == FAILURE) { - return FAILURE; - } - if (php_get_if_index_from_array(opt_ht, "interface", php_sock, - &if_index) == FAILURE) { - return FAILURE; - } - - retval = mcast_sreq_fun(php_sock, level, (struct sockaddr*)&group, - glen, (struct sockaddr*)&source, slen, if_index); - break; - } + case PHP_MCAST_BLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_block_source; + goto mcast_sreq_fun; + case PHP_MCAST_UNBLOCK_SOURCE: + mcast_sreq_fun = &php_mcast_unblock_source; + goto mcast_sreq_fun; + case PHP_MCAST_JOIN_SOURCE_GROUP: + mcast_sreq_fun = &php_mcast_join_source; + goto mcast_sreq_fun; + case PHP_MCAST_LEAVE_SOURCE_GROUP: { + mcast_sreq_fun = &php_mcast_leave_source; + mcast_sreq_fun: + convert_to_array_ex(arg4); + opt_ht = Z_ARRVAL_P(arg4); + + if (php_get_address_from_array(opt_ht, "group", php_sock, &group, &glen) == FAILURE) { + return FAILURE; + } + if (php_get_address_from_array(opt_ht, "source", php_sock, &source, &slen) == FAILURE) { + return FAILURE; + } + if (php_get_if_index_from_array(opt_ht, "interface", php_sock, &if_index) == FAILURE) { + return FAILURE; + } + + retval = mcast_sreq_fun( + php_sock, level, (struct sockaddr *) &group, glen, (struct sockaddr *) &source, slen, if_index); + break; + } #endif - default: - php_error_docref(NULL, E_WARNING, - "unexpected option in php_do_mcast_opt (level %d, option %d). " - "This is a bug.", level, optname); - return FAILURE; - } - - if (retval != 0) { - if (retval != -2) { /* error, but message already emitted */ - PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } - return FAILURE; - } - return SUCCESS; + default: + php_error_docref(NULL, + E_WARNING, + "unexpected option in php_do_mcast_opt (level %d, option %d). " + "This is a bug.", + level, + optname); + return FAILURE; + } + + if (retval != 0) { + if (retval != -2) { /* error, but message already emitted */ + PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } + return FAILURE; + } + return SUCCESS; } -int php_do_setsockopt_ip_mcast(Socket *php_sock, - int level, - int optname, - zval *arg4) -{ - unsigned int if_index; - struct in_addr if_addr; - void *opt_ptr; - socklen_t optlen; - unsigned char ipv4_mcast_ttl_lback; - int retval; - - switch (optname) { - case PHP_MCAST_JOIN_GROUP: - case PHP_MCAST_LEAVE_GROUP: +int php_do_setsockopt_ip_mcast(SocketImpl *php_sock, int level, int optname, zval *arg4) { + unsigned int if_index; + struct in_addr if_addr; + void *opt_ptr; + socklen_t optlen; + unsigned char ipv4_mcast_ttl_lback; + int retval; + + switch (optname) { + case PHP_MCAST_JOIN_GROUP: + case PHP_MCAST_LEAVE_GROUP: #ifdef HAS_MCAST_EXT - case PHP_MCAST_BLOCK_SOURCE: - case PHP_MCAST_UNBLOCK_SOURCE: - case PHP_MCAST_JOIN_SOURCE_GROUP: - case PHP_MCAST_LEAVE_SOURCE_GROUP: + case PHP_MCAST_BLOCK_SOURCE: + case PHP_MCAST_UNBLOCK_SOURCE: + case PHP_MCAST_JOIN_SOURCE_GROUP: + case PHP_MCAST_LEAVE_SOURCE_GROUP: #endif - if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) { - return FAILURE; - } else { - return SUCCESS; - } - - case IP_MULTICAST_IF: - if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) { - return FAILURE; - } - - if (php_if_index_to_addr4(if_index, php_sock, &if_addr) == FAILURE) { - return FAILURE; - } - opt_ptr = &if_addr; - optlen = sizeof(if_addr); - goto dosockopt; - - case IP_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - ipv4_mcast_ttl_lback = (unsigned char) (Z_TYPE_P(arg4) == IS_TRUE); - goto ipv4_loop_ttl; - - case IP_MULTICAST_TTL: - convert_to_long_ex(arg4); - if (Z_LVAL_P(arg4) < 0L || Z_LVAL_P(arg4) > 255L) { - php_error_docref(NULL, E_WARNING, - "Expected a value between 0 and 255"); - return FAILURE; - } - ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_P(arg4); -ipv4_loop_ttl: - opt_ptr = &ipv4_mcast_ttl_lback; - optlen = sizeof(ipv4_mcast_ttl_lback); - goto dosockopt; - } - - return 1; + if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IP_MULTICAST_IF: + if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) { + return FAILURE; + } + + if (php_if_index_to_addr4(if_index, php_sock, &if_addr) == FAILURE) { + return FAILURE; + } + opt_ptr = &if_addr; + optlen = sizeof(if_addr); + goto dosockopt; + + case IP_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + ipv4_mcast_ttl_lback = (unsigned char) (Z_TYPE_P(arg4) == IS_TRUE); + goto ipv4_loop_ttl; + + case IP_MULTICAST_TTL: + convert_to_long_ex(arg4); + if (Z_LVAL_P(arg4) < 0L || Z_LVAL_P(arg4) > 255L) { + php_error_docref(NULL, E_WARNING, "Expected a value between 0 and 255"); + return FAILURE; + } + ipv4_mcast_ttl_lback = (unsigned char) Z_LVAL_P(arg4); + ipv4_loop_ttl: + opt_ptr = &ipv4_mcast_ttl_lback; + optlen = sizeof(ipv4_mcast_ttl_lback); + goto dosockopt; + } + + return 1; dosockopt: - retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); - if (retval != 0) { - PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - return FAILURE; - } + retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + return FAILURE; + } - return SUCCESS; + return SUCCESS; } -int php_do_setsockopt_ipv6_mcast(Socket *php_sock, int level, int optname, zval *arg4) -{ - unsigned int if_index; - void *opt_ptr; - socklen_t optlen; - int ov; - int retval; - - switch (optname) { - case PHP_MCAST_JOIN_GROUP: - case PHP_MCAST_LEAVE_GROUP: +int php_do_setsockopt_ipv6_mcast(SocketImpl *php_sock, int level, int optname, zval *arg4) { + unsigned int if_index; + void *opt_ptr; + socklen_t optlen; + int ov; + int retval; + + switch (optname) { + case PHP_MCAST_JOIN_GROUP: + case PHP_MCAST_LEAVE_GROUP: #ifdef HAS_MCAST_EXT - case PHP_MCAST_BLOCK_SOURCE: - case PHP_MCAST_UNBLOCK_SOURCE: - case PHP_MCAST_JOIN_SOURCE_GROUP: - case PHP_MCAST_LEAVE_SOURCE_GROUP: + case PHP_MCAST_BLOCK_SOURCE: + case PHP_MCAST_UNBLOCK_SOURCE: + case PHP_MCAST_JOIN_SOURCE_GROUP: + case PHP_MCAST_LEAVE_SOURCE_GROUP: #endif - if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) { - return FAILURE; - } else { - return SUCCESS; - } - - case IPV6_MULTICAST_IF: - if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) { - return FAILURE; - } - - opt_ptr = &if_index; - optlen = sizeof(if_index); - goto dosockopt; - - case IPV6_MULTICAST_LOOP: - convert_to_boolean_ex(arg4); - ov = (int) Z_TYPE_P(arg4) == IS_TRUE; - goto ipv6_loop_hops; - case IPV6_MULTICAST_HOPS: - convert_to_long_ex(arg4); - if (Z_LVAL_P(arg4) < -1L || Z_LVAL_P(arg4) > 255L) { - php_error_docref(NULL, E_WARNING, - "Expected a value between -1 and 255"); - return FAILURE; - } - ov = (int) Z_LVAL_P(arg4); -ipv6_loop_hops: - opt_ptr = &ov; - optlen = sizeof(ov); - goto dosockopt; - } - - return 1; /* not handled */ + if (php_do_mcast_opt(php_sock, level, optname, arg4) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } + + case IPV6_MULTICAST_IF: + if (php_get_if_index_from_zval(arg4, &if_index) == FAILURE) { + return FAILURE; + } + + opt_ptr = &if_index; + optlen = sizeof(if_index); + goto dosockopt; + + case IPV6_MULTICAST_LOOP: + convert_to_boolean_ex(arg4); + ov = (int) Z_TYPE_P(arg4) == IS_TRUE; + goto ipv6_loop_hops; + case IPV6_MULTICAST_HOPS: + convert_to_long_ex(arg4); + if (Z_LVAL_P(arg4) < -1L || Z_LVAL_P(arg4) > 255L) { + php_error_docref(NULL, E_WARNING, "Expected a value between -1 and 255"); + return FAILURE; + } + ov = (int) Z_LVAL_P(arg4); + ipv6_loop_hops: + opt_ptr = &ov; + optlen = sizeof(ov); + goto dosockopt; + } + + return 1; /* not handled */ dosockopt: - retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); - if (retval != 0) { - PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - return FAILURE; - } + retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + return FAILURE; + } - return SUCCESS; + return SUCCESS; } -int php_mcast_join( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - unsigned int if_index) -{ - return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1); +int php_mcast_join(SocketImpl *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index) { + return _php_mcast_join_leave(sock, level, group, group_len, if_index, 1); } -int php_mcast_leave( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - unsigned int if_index) -{ - return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0); +int php_mcast_leave(SocketImpl *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index) { + return _php_mcast_join_leave(sock, level, group, group_len, if_index, 0); } #ifdef HAS_MCAST_EXT -int php_mcast_join_source( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - struct sockaddr *source, - socklen_t source_len, - unsigned int if_index) -{ - return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE); +int php_mcast_join_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index) { + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, JOIN_SOURCE); } -int php_mcast_leave_source( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - struct sockaddr *source, - socklen_t source_len, - unsigned int if_index) -{ - return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE); +int php_mcast_leave_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index) { + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, LEAVE_SOURCE); } -int php_mcast_block_source( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - struct sockaddr *source, - socklen_t source_len, - unsigned int if_index) -{ - return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE); +int php_mcast_block_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index) { + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, BLOCK_SOURCE); } -int php_mcast_unblock_source( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - struct sockaddr *source, - socklen_t source_len, - unsigned int if_index) -{ - return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE); +int php_mcast_unblock_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index) { + return _php_mcast_source_op(sock, level, group, group_len, source, source_len, if_index, UNBLOCK_SOURCE); } #endif /* HAS_MCAST_EXT */ - -static int _php_mcast_join_leave( - Socket *sock, - int level, - struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */ - socklen_t group_len, - unsigned int if_index, - int join) -{ +static int _php_mcast_join_leave(SocketImpl *sock, + int level, + struct sockaddr *group, /* struct sockaddr_in/sockaddr_in6 */ + socklen_t group_len, + unsigned int if_index, + int join) { #ifdef RFC3678_API - struct group_req greq = {0}; + struct group_req greq = {0}; - memcpy(&greq.gr_group, group, group_len); - assert(greq.gr_group.ss_family != 0); /* the caller has set this */ - greq.gr_interface = if_index; + memcpy(&greq.gr_group, group, group_len); + assert(greq.gr_group.ss_family != 0); /* the caller has set this */ + greq.gr_interface = if_index; - return setsockopt(sock->get_fd(), level, - join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char*)&greq, - sizeof(greq)); + return setsockopt(sock->get_fd(), level, join ? MCAST_JOIN_GROUP : MCAST_LEAVE_GROUP, (char *) &greq, sizeof(greq)); #else - if (sock->get_sock_type() == AF_INET) { - struct ip_mreq mreq = {{0}}; - struct in_addr addr; - - assert(group_len == sizeof(struct sockaddr_in)); - - if (if_index != 0) { - if (php_if_index_to_addr4(if_index, sock, &addr) == - FAILURE) - return -2; /* failure, but notice already emitted */ - mreq.imr_interface = addr; - } else { - mreq.imr_interface.s_addr = htonl(INADDR_ANY); - } - mreq.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr; - return setsockopt(sock->get_fd(), level, - join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char*)&mreq, - sizeof(mreq)); - } - else if (sock->get_sock_type() == AF_INET6) { - struct ipv6_mreq mreq = {{{{0}}}}; - - assert(group_len == sizeof(struct sockaddr_in6)); - - mreq.ipv6mr_multiaddr = ((struct sockaddr_in6*)group)->sin6_addr; - mreq.ipv6mr_interface = if_index; - - return setsockopt(sock->get_fd(), level, - join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char*)&mreq, - sizeof(mreq)); - } - else { - php_error_docref(NULL, E_WARNING, - "Option %s is inapplicable to this socket type", - join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP"); - return -2; - } + if (sock->get_sock_type() == AF_INET) { + struct ip_mreq mreq = {{0}}; + struct in_addr addr; + + assert(group_len == sizeof(struct sockaddr_in)); + + if (if_index != 0) { + if (php_if_index_to_addr4(if_index, sock, &addr) == FAILURE) + return -2; /* failure, but notice already emitted */ + mreq.imr_interface = addr; + } else { + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + } + mreq.imr_multiaddr = ((struct sockaddr_in *) group)->sin_addr; + return setsockopt( + sock->get_fd(), level, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char *) &mreq, sizeof(mreq)); + } else if (sock->get_sock_type() == AF_INET6) { + struct ipv6_mreq mreq = {{{{0}}}}; + + assert(group_len == sizeof(struct sockaddr_in6)); + + mreq.ipv6mr_multiaddr = ((struct sockaddr_in6 *) group)->sin6_addr; + mreq.ipv6mr_interface = if_index; + + return setsockopt( + sock->get_fd(), level, join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, (char *) &mreq, sizeof(mreq)); + } else { + php_error_docref(NULL, + E_WARNING, + "Option %s is inapplicable to this socket type", + join ? "MCAST_JOIN_GROUP" : "MCAST_LEAVE_GROUP"); + return -2; + } #endif } #ifdef HAS_MCAST_EXT -static int _php_mcast_source_op( - Socket *sock, - int level, - struct sockaddr *group, - socklen_t group_len, - struct sockaddr *source, - socklen_t source_len, - unsigned int if_index, - enum source_op sop) -{ +static int _php_mcast_source_op(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index, + enum source_op sop) { #ifdef RFC3678_API - struct group_source_req gsreq = {0}; + struct group_source_req gsreq = {0}; - memcpy(&gsreq.gsr_group, group, group_len); - assert(gsreq.gsr_group.ss_family != 0); - memcpy(&gsreq.gsr_source, source, source_len); - assert(gsreq.gsr_source.ss_family != 0); - gsreq.gsr_interface = if_index; + memcpy(&gsreq.gsr_group, group, group_len); + assert(gsreq.gsr_group.ss_family != 0); + memcpy(&gsreq.gsr_source, source, source_len); + assert(gsreq.gsr_source.ss_family != 0); + gsreq.gsr_interface = if_index; - return setsockopt(sock->get_fd(), level, - _php_source_op_to_rfc3678_op(sop), (char*)&gsreq, sizeof(gsreq)); + return setsockopt(sock->get_fd(), level, _php_source_op_to_rfc3678_op(sop), (char *) &gsreq, sizeof(gsreq)); #else - if (sock->type == AF_INET) { - struct ip_mreq_source mreqs = {0}; - struct in_addr addr; - - mreqs.imr_multiaddr = ((struct sockaddr_in*)group)->sin_addr; - mreqs.imr_sourceaddr = ((struct sockaddr_in*)source)->sin_addr; - - assert(group_len == sizeof(struct sockaddr_in)); - assert(source_len == sizeof(struct sockaddr_in)); - - if (if_index != 0) { - if (php_if_index_to_addr4(if_index, sock, &addr) == - FAILURE) - return -2; /* failure, but notice already emitted */ - mreqs.imr_interface = addr; - } else { - mreqs.imr_interface.s_addr = htonl(INADDR_ANY); - } - - return setsockopt(sock->get_fd(), level, - _php_source_op_to_ipv4_op(sop), (char*)&mreqs, sizeof(mreqs)); - } - else if (sock->type == AF_INET6) { - php_error_docref(NULL, E_WARNING, - "This platform does not support %s for IPv6 sockets", - _php_source_op_to_string(sop)); - return -2; - } - else { - php_error_docref(NULL, E_WARNING, - "Option %s is inapplicable to this socket type", - _php_source_op_to_string(sop)); - return -2; - } + if (sock->type == AF_INET) { + struct ip_mreq_source mreqs = {0}; + struct in_addr addr; + + mreqs.imr_multiaddr = ((struct sockaddr_in *) group)->sin_addr; + mreqs.imr_sourceaddr = ((struct sockaddr_in *) source)->sin_addr; + + assert(group_len == sizeof(struct sockaddr_in)); + assert(source_len == sizeof(struct sockaddr_in)); + + if (if_index != 0) { + if (php_if_index_to_addr4(if_index, sock, &addr) == FAILURE) + return -2; /* failure, but notice already emitted */ + mreqs.imr_interface = addr; + } else { + mreqs.imr_interface.s_addr = htonl(INADDR_ANY); + } + + return setsockopt(sock->get_fd(), level, _php_source_op_to_ipv4_op(sop), (char *) &mreqs, sizeof(mreqs)); + } else if (sock->type == AF_INET6) { + php_error_docref( + NULL, E_WARNING, "This platform does not support %s for IPv6 sockets", _php_source_op_to_string(sop)); + return -2; + } else { + php_error_docref( + NULL, E_WARNING, "Option %s is inapplicable to this socket type", _php_source_op_to_string(sop)); + return -2; + } #endif } #if RFC3678_API -static int _php_source_op_to_rfc3678_op(enum source_op sop) -{ - switch (sop) { - case JOIN_SOURCE: - return MCAST_JOIN_SOURCE_GROUP; - case LEAVE_SOURCE: - return MCAST_LEAVE_SOURCE_GROUP; - case BLOCK_SOURCE: - return MCAST_BLOCK_SOURCE; - case UNBLOCK_SOURCE: - return MCAST_UNBLOCK_SOURCE; - } - - abort(); - return 0; +static int _php_source_op_to_rfc3678_op(enum source_op sop) { + switch (sop) { + case JOIN_SOURCE: + return MCAST_JOIN_SOURCE_GROUP; + case LEAVE_SOURCE: + return MCAST_LEAVE_SOURCE_GROUP; + case BLOCK_SOURCE: + return MCAST_BLOCK_SOURCE; + case UNBLOCK_SOURCE: + return MCAST_UNBLOCK_SOURCE; + } + + abort(); + return 0; } #else -static const char *_php_source_op_to_string(enum source_op sop) -{ - switch (sop) { - case JOIN_SOURCE: - return "MCAST_JOIN_SOURCE_GROUP"; - case LEAVE_SOURCE: - return "MCAST_LEAVE_SOURCE_GROUP"; - case BLOCK_SOURCE: - return "MCAST_BLOCK_SOURCE"; - case UNBLOCK_SOURCE: - return "MCAST_UNBLOCK_SOURCE"; - } - - abort(); - return ""; +static const char *_php_source_op_to_string(enum source_op sop) { + switch (sop) { + case JOIN_SOURCE: + return "MCAST_JOIN_SOURCE_GROUP"; + case LEAVE_SOURCE: + return "MCAST_LEAVE_SOURCE_GROUP"; + case BLOCK_SOURCE: + return "MCAST_BLOCK_SOURCE"; + case UNBLOCK_SOURCE: + return "MCAST_UNBLOCK_SOURCE"; + } + + abort(); + return ""; } -static int _php_source_op_to_ipv4_op(enum source_op sop) -{ - switch (sop) { - case JOIN_SOURCE: - return IP_ADD_SOURCE_MEMBERSHIP; - case LEAVE_SOURCE: - return IP_DROP_SOURCE_MEMBERSHIP; - case BLOCK_SOURCE: - return IP_BLOCK_SOURCE; - case UNBLOCK_SOURCE: - return IP_UNBLOCK_SOURCE; - } - - abort(); - return 0; +static int _php_source_op_to_ipv4_op(enum source_op sop) { + switch (sop) { + case JOIN_SOURCE: + return IP_ADD_SOURCE_MEMBERSHIP; + case LEAVE_SOURCE: + return IP_DROP_SOURCE_MEMBERSHIP; + case BLOCK_SOURCE: + return IP_BLOCK_SOURCE; + case UNBLOCK_SOURCE: + return IP_UNBLOCK_SOURCE; + } + + abort(); + return 0; } #endif #endif /* HAS_MCAST_EXT */ #ifdef PHP_WIN32 -int php_if_index_to_addr4(unsigned if_index, Socket *php_sock, struct in_addr *out_addr) -{ - MIB_IPADDRTABLE *addr_table; +int php_if_index_to_addr4(unsigned if_index, SocketImpl *php_sock, struct in_addr *out_addr) { + MIB_IPADDRTABLE *addr_table; ULONG size; DWORD retval; - DWORD i; + DWORD i; - (void) php_sock; /* not necessary */ + (void) php_sock; /* not necessary */ - if (if_index == 0) { - out_addr->s_addr = INADDR_ANY; - return SUCCESS; - } + if (if_index == 0) { + out_addr->s_addr = INADDR_ANY; + return SUCCESS; + } - size = 4 * (sizeof *addr_table); - addr_table = emalloc(size); + size = 4 * (sizeof *addr_table); + addr_table = emalloc(size); retry: - retval = GetIpAddrTable(addr_table, &size, 0); - if (retval == ERROR_INSUFFICIENT_BUFFER) { - efree(addr_table); - addr_table = emalloc(size); - goto retry; - } - if (retval != NO_ERROR) { - php_error_docref(NULL, E_WARNING, - "GetIpAddrTable failed with error %lu", retval); - return FAILURE; - } - for (i = 0; i < addr_table->dwNumEntries; i++) { - MIB_IPADDRROW r = addr_table->table[i]; - if (r.dwIndex == if_index) { - out_addr->s_addr = r.dwAddr; - return SUCCESS; - } - } - php_error_docref(NULL, E_WARNING, - "No interface with index %u was found", if_index); - return FAILURE; + retval = GetIpAddrTable(addr_table, &size, 0); + if (retval == ERROR_INSUFFICIENT_BUFFER) { + efree(addr_table); + addr_table = emalloc(size); + goto retry; + } + if (retval != NO_ERROR) { + php_error_docref(NULL, E_WARNING, "GetIpAddrTable failed with error %lu", retval); + return FAILURE; + } + for (i = 0; i < addr_table->dwNumEntries; i++) { + MIB_IPADDRROW r = addr_table->table[i]; + if (r.dwIndex == if_index) { + out_addr->s_addr = r.dwAddr; + return SUCCESS; + } + } + php_error_docref(NULL, E_WARNING, "No interface with index %u was found", if_index); + return FAILURE; } -int php_add4_to_if_index(struct in_addr *addr, Socket *php_sock, unsigned *if_index) -{ - MIB_IPADDRTABLE *addr_table; +int php_add4_to_if_index(struct in_addr *addr, SocketImpl *php_sock, unsigned *if_index) { + MIB_IPADDRTABLE *addr_table; ULONG size; DWORD retval; - DWORD i; + DWORD i; - (void) php_sock; /* not necessary */ + (void) php_sock; /* not necessary */ - if (addr->s_addr == INADDR_ANY) { - *if_index = 0; - return SUCCESS; - } + if (addr->s_addr == INADDR_ANY) { + *if_index = 0; + return SUCCESS; + } - size = 4 * (sizeof *addr_table); - addr_table = emalloc(size); + size = 4 * (sizeof *addr_table); + addr_table = emalloc(size); retry: - retval = GetIpAddrTable(addr_table, &size, 0); - if (retval == ERROR_INSUFFICIENT_BUFFER) { - efree(addr_table); - addr_table = emalloc(size); - goto retry; - } - if (retval != NO_ERROR) { - php_error_docref(NULL, E_WARNING, - "GetIpAddrTable failed with error %lu", retval); - return FAILURE; - } - for (i = 0; i < addr_table->dwNumEntries; i++) { - MIB_IPADDRROW r = addr_table->table[i]; - if (r.dwAddr == addr->s_addr) { - *if_index = r.dwIndex; - return SUCCESS; - } - } - - { - char addr_str[17] = {0}; - inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); - php_error_docref(NULL, E_WARNING, - "The interface with IP address %s was not found", addr_str); - } - return FAILURE; + retval = GetIpAddrTable(addr_table, &size, 0); + if (retval == ERROR_INSUFFICIENT_BUFFER) { + efree(addr_table); + addr_table = emalloc(size); + goto retry; + } + if (retval != NO_ERROR) { + php_error_docref(NULL, E_WARNING, "GetIpAddrTable failed with error %lu", retval); + return FAILURE; + } + for (i = 0; i < addr_table->dwNumEntries; i++) { + MIB_IPADDRROW r = addr_table->table[i]; + if (r.dwAddr == addr->s_addr) { + *if_index = r.dwIndex; + return SUCCESS; + } + } + + { + char addr_str[17] = {0}; + inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); + php_error_docref(NULL, E_WARNING, "The interface with IP address %s was not found", addr_str); + } + return FAILURE; } #else -int php_if_index_to_addr4(unsigned if_index, Socket *php_sock, struct in_addr *out_addr) -{ - struct ifreq if_req; +int php_if_index_to_addr4(unsigned if_index, SocketImpl *php_sock, struct in_addr *out_addr) { + struct ifreq if_req; - if (if_index == 0) { - out_addr->s_addr = INADDR_ANY; - return SUCCESS; - } + if (if_index == 0) { + out_addr->s_addr = INADDR_ANY; + return SUCCESS; + } #if !defined(ifr_ifindex) && defined(ifr_index) #define ifr_ifindex ifr_index #endif #if defined(SIOCGIFNAME) - if_req.ifr_ifindex = if_index; - if (ioctl(php_sock->get_fd(), SIOCGIFNAME, &if_req) == -1) { + if_req.ifr_ifindex = if_index; + if (ioctl(php_sock->get_fd(), SIOCGIFNAME, &if_req) == -1) { #elif defined(HAVE_IF_INDEXTONAME) - if (if_indextoname(if_index, if_req.ifr_name) == NULL) { + if (if_indextoname(if_index, if_req.ifr_name) == NULL) { #else #error Neither SIOCGIFNAME nor if_indextoname are available #endif - php_error_docref(NULL, E_WARNING, - "Failed obtaining address for interface %u: error %d", if_index, errno); - return FAILURE; - } - - if (ioctl(php_sock->get_fd(), SIOCGIFADDR, &if_req) == -1) { - php_error_docref(NULL, E_WARNING, - "Failed obtaining address for interface %u: error %d", if_index, errno); - return FAILURE; - } - - memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr, - sizeof *out_addr); - return SUCCESS; + php_error_docref(NULL, E_WARNING, "Failed obtaining address for interface %u: error %d", if_index, errno); + return FAILURE; + } + + if (ioctl(php_sock->get_fd(), SIOCGIFADDR, &if_req) == -1) { + php_error_docref(NULL, E_WARNING, "Failed obtaining address for interface %u: error %d", if_index, errno); + return FAILURE; + } + + memcpy(out_addr, &((struct sockaddr_in *) &if_req.ifr_addr)->sin_addr, sizeof *out_addr); + return SUCCESS; } -int php_add4_to_if_index(struct in_addr *addr, Socket *php_sock, unsigned *if_index) -{ - struct ifconf if_conf = {0}; - char *buf = NULL, - *p; - int size = 0, - lastsize = 0; - size_t entry_len; - - if (addr->s_addr == INADDR_ANY) { - *if_index = 0; - return SUCCESS; - } - - for (;;) - { +int php_add4_to_if_index(struct in_addr *addr, SocketImpl *php_sock, unsigned *if_index) { + struct ifconf if_conf = {0}; + char *buf = NULL, *p; + int size = 0, lastsize = 0; + size_t entry_len; + + if (addr->s_addr == INADDR_ANY) { + *if_index = 0; + return SUCCESS; + } + + for (;;) { size += 5 * sizeof(struct ifreq); - buf = (char*) ecalloc(size, 1); + buf = (char *) ecalloc(size, 1); if_conf.ifc_len = size; if_conf.ifc_buf = (caddr_t) buf; - if (ioctl(php_sock->get_fd(), SIOCGIFCONF, (char*)&if_conf) == -1 && - (errno != EINVAL || lastsize != 0)) { - php_error_docref(NULL, E_WARNING, - "Failed obtaining interfaces list: error %d", errno); - goto err; - } - - if (if_conf.ifc_len == lastsize) - /* not increasing anymore */ - break; - else { - lastsize = if_conf.ifc_len; - efree(buf); - buf = NULL; - } - } - - for (p = (char *) if_conf.ifc_buf; - p < ((char *) if_conf.ifc_buf + if_conf.ifc_len); - p += entry_len) { - struct ifreq *cur_req; - - /* let's hope the pointer is aligned */ - cur_req = (struct ifreq*) p; + if (ioctl(php_sock->get_fd(), SIOCGIFCONF, (char *) &if_conf) == -1 && (errno != EINVAL || lastsize != 0)) { + php_error_docref(NULL, E_WARNING, "Failed obtaining interfaces list: error %d", errno); + goto err; + } + + if (if_conf.ifc_len == lastsize) /* not increasing anymore */ + break; + else { + lastsize = if_conf.ifc_len; + efree(buf); + buf = NULL; + } + } + + for (p = (char *) if_conf.ifc_buf; p < ((char *) if_conf.ifc_buf + if_conf.ifc_len); p += entry_len) { + struct ifreq *cur_req; + + /* let's hope the pointer is aligned */ + cur_req = (struct ifreq *) p; #ifdef HAVE_SOCKADDR_SA_LEN - entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name); + entry_len = cur_req->ifr_addr.sa_len + sizeof(cur_req->ifr_name); #else - /* if there's no sa_len, assume the ifr_addr field is a sockaddr */ - entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name); + /* if there's no sa_len, assume the ifr_addr field is a sockaddr */ + entry_len = sizeof(struct sockaddr) + sizeof(cur_req->ifr_name); #endif - entry_len = MAX(entry_len, sizeof(*cur_req)); + entry_len = MAX(entry_len, sizeof(*cur_req)); - if ((((struct sockaddr*)&cur_req->ifr_addr)->sa_family == AF_INET) && - (((struct sockaddr_in*)&cur_req->ifr_addr)->sin_addr.s_addr == - addr->s_addr)) { + if ((((struct sockaddr *) &cur_req->ifr_addr)->sa_family == AF_INET) && + (((struct sockaddr_in *) &cur_req->ifr_addr)->sin_addr.s_addr == addr->s_addr)) { #if defined(SIOCGIFINDEX) - if (ioctl(php_sock->get_fd(), SIOCGIFINDEX, (char*)cur_req) - == -1) { + if (ioctl(php_sock->get_fd(), SIOCGIFINDEX, (char *) cur_req) == -1) { #elif defined(HAVE_IF_NAMETOINDEX) - unsigned index_tmp; - if ((index_tmp = if_nametoindex(cur_req->ifr_name)) == 0) { + unsigned index_tmp; + if ((index_tmp = if_nametoindex(cur_req->ifr_name)) == 0) { #else #error Neither SIOCGIFINDEX nor if_nametoindex are available #endif - php_error_docref(NULL, E_WARNING, - "Error converting interface name to index: error %d", - errno); - goto err; - } else { + php_error_docref(NULL, E_WARNING, "Error converting interface name to index: error %d", errno); + goto err; + } else { #if defined(SIOCGIFINDEX) - *if_index = cur_req->ifr_ifindex; + *if_index = cur_req->ifr_ifindex; #else - *if_index = index_tmp; + *if_index = index_tmp; #endif - efree(buf); - return SUCCESS; - } - } - } - - { - char addr_str[17] = {0}; - inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); - php_error_docref(NULL, E_WARNING, - "The interface with IP address %s was not found", addr_str); - } + efree(buf); + return SUCCESS; + } + } + } + + { + char addr_str[17] = {0}; + inet_ntop(AF_INET, addr, addr_str, sizeof(addr_str)); + php_error_docref(NULL, E_WARNING, "The interface with IP address %s was not found", addr_str); + } err: - if (buf != NULL) - efree(buf); - return FAILURE; + if (buf != NULL) efree(buf); + return FAILURE; } #endif diff --git a/thirdparty/php/sockets/multicast.h b/thirdparty/php/sockets/multicast.h index 20d57b57ac..b6127a0fe8 100644 --- a/thirdparty/php/sockets/multicast.h +++ b/thirdparty/php/sockets/multicast.h @@ -20,55 +20,72 @@ #include "php_swoole_cxx.h" -using swoole::coroutine::Socket; - #if defined(MCAST_JOIN_GROUP) && !defined(__APPLE__) -# define RFC3678_API 1 +#define RFC3678_API 1 /* has block/unblock and source membership, in this case for both IPv4 and IPv6 */ -# define HAS_MCAST_EXT 1 +#define HAS_MCAST_EXT 1 #elif defined(IP_ADD_SOURCE_MEMBERSHIP) && !defined(__APPLE__) /* has block/unblock and source membership, but only for IPv4 */ -# define HAS_MCAST_EXT 1 +#define HAS_MCAST_EXT 1 #endif #ifndef RFC3678_API -# define PHP_MCAST_JOIN_GROUP IP_ADD_MEMBERSHIP -# define PHP_MCAST_LEAVE_GROUP IP_DROP_MEMBERSHIP -# ifdef HAS_MCAST_EXT -# define PHP_MCAST_BLOCK_SOURCE IP_BLOCK_SOURCE -# define PHP_MCAST_UNBLOCK_SOURCE IP_UNBLOCK_SOURCE -# define PHP_MCAST_JOIN_SOURCE_GROUP IP_ADD_SOURCE_MEMBERSHIP -# define PHP_MCAST_LEAVE_SOURCE_GROUP IP_DROP_SOURCE_MEMBERSHIP -# endif +#define PHP_MCAST_JOIN_GROUP IP_ADD_MEMBERSHIP +#define PHP_MCAST_LEAVE_GROUP IP_DROP_MEMBERSHIP +#ifdef HAS_MCAST_EXT +#define PHP_MCAST_BLOCK_SOURCE IP_BLOCK_SOURCE +#define PHP_MCAST_UNBLOCK_SOURCE IP_UNBLOCK_SOURCE +#define PHP_MCAST_JOIN_SOURCE_GROUP IP_ADD_SOURCE_MEMBERSHIP +#define PHP_MCAST_LEAVE_SOURCE_GROUP IP_DROP_SOURCE_MEMBERSHIP +#endif #else -# define PHP_MCAST_JOIN_GROUP MCAST_JOIN_GROUP -# define PHP_MCAST_LEAVE_GROUP MCAST_LEAVE_GROUP -# define PHP_MCAST_BLOCK_SOURCE MCAST_BLOCK_SOURCE -# define PHP_MCAST_UNBLOCK_SOURCE MCAST_UNBLOCK_SOURCE -# define PHP_MCAST_JOIN_SOURCE_GROUP MCAST_JOIN_SOURCE_GROUP -# define PHP_MCAST_LEAVE_SOURCE_GROUP MCAST_LEAVE_SOURCE_GROUP +#define PHP_MCAST_JOIN_GROUP MCAST_JOIN_GROUP +#define PHP_MCAST_LEAVE_GROUP MCAST_LEAVE_GROUP +#define PHP_MCAST_BLOCK_SOURCE MCAST_BLOCK_SOURCE +#define PHP_MCAST_UNBLOCK_SOURCE MCAST_UNBLOCK_SOURCE +#define PHP_MCAST_JOIN_SOURCE_GROUP MCAST_JOIN_SOURCE_GROUP +#define PHP_MCAST_LEAVE_SOURCE_GROUP MCAST_LEAVE_SOURCE_GROUP #endif -int php_do_setsockopt_ip_mcast(Socket *php_sock, int level, int optname, zval *arg4); -int php_do_setsockopt_ipv6_mcast(Socket *php_sock, int level, int optname, zval *arg4); -int php_if_index_to_addr4(unsigned if_index, Socket *php_sock, struct in_addr *out_addr); -int php_add4_to_if_index(struct in_addr *addr, Socket *php_sock, unsigned *if_index); +int php_do_setsockopt_ip_mcast(SocketImpl *php_sock, int level, int optname, zval *arg4); +int php_do_setsockopt_ipv6_mcast(SocketImpl *php_sock, int level, int optname, zval *arg4); +int php_if_index_to_addr4(unsigned if_index, SocketImpl *php_sock, struct in_addr *out_addr); +int php_add4_to_if_index(struct in_addr *addr, SocketImpl *php_sock, unsigned *if_index); int php_string_to_if_index(const char *val, unsigned *out); -int php_mcast_join(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index); +int php_mcast_join(SocketImpl *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index); -int php_mcast_leave(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, - unsigned int if_index); +int php_mcast_leave(SocketImpl *sock, int level, struct sockaddr *group, socklen_t group_len, unsigned int if_index); #ifdef HAS_MCAST_EXT -int php_mcast_join_source(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, - struct sockaddr *source, socklen_t source_len, unsigned int if_index); +int php_mcast_join_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index); -int php_mcast_leave_source(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, - struct sockaddr *source, socklen_t source_len, unsigned int if_index); +int php_mcast_leave_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index); -int php_mcast_block_source(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, - struct sockaddr *source, socklen_t source_len, unsigned int if_index); +int php_mcast_block_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index); -int php_mcast_unblock_source(Socket *sock, int level, struct sockaddr *group, socklen_t group_len, - struct sockaddr *source, socklen_t source_len, unsigned int if_index); +int php_mcast_unblock_source(SocketImpl *sock, + int level, + struct sockaddr *group, + socklen_t group_len, + struct sockaddr *source, + socklen_t source_len, + unsigned int if_index); #endif diff --git a/thirdparty/php/sockets/php_sockets_cxx.h b/thirdparty/php/sockets/php_sockets_cxx.h index f27dc8d4f9..e7fe88bad0 100644 --- a/thirdparty/php/sockets/php_sockets_cxx.h +++ b/thirdparty/php/sockets/php_sockets_cxx.h @@ -8,19 +8,17 @@ #include "thirdparty/php/sockets/multicast.h" #include "thirdparty/php/sockets/conversions.h" -using swoole::coroutine::Socket; - -#define PHP_SWOOLE_SOCKET_ERROR(socket, msg, errn) \ - do { \ - int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ - (socket)->errCode = _err; \ - if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ - php_error_docref(NULL, E_WARNING, "%s [%d]: %s", msg, _err, strerror(_err)); \ - } \ - } while (0) - -int php_do_setsockopt_ipv6_rfc3542(Socket *php_sock, int level, int optname, zval *arg4); -int php_do_getsockopt_ipv6_rfc3542(Socket *php_sock, int level, int optname, zval *result); +#define PHP_SWOOLE_SOCKET_ERROR(socket, msg, errn) \ + do { \ + int _err = (errn); /* save value to avoid repeated calls to WSAGetLastError() on Windows */ \ + (socket)->errCode = _err; \ + if (_err != EAGAIN && _err != EWOULDBLOCK && _err != EINPROGRESS) { \ + php_error_docref(NULL, E_WARNING, "%s [%d]: %s", msg, _err, strerror(_err)); \ + } \ + } while (0) + +int php_do_setsockopt_ipv6_rfc3542(SocketImpl *php_sock, int level, int optname, zval *arg4); +int php_do_getsockopt_ipv6_rfc3542(SocketImpl *php_sock, int level, int optname, zval *result); int php_string_to_if_index(const char *val, unsigned *out); @@ -29,14 +27,14 @@ int php_string_to_if_index(const char *val, unsigned *out); * The IPv6 literal can be a IPv4 mapped address (like ::ffff:127.0.0.1). * If the hostname yields no IPv6 addresses, a mapped IPv4 address may be returned (AI_V4MAPPED) */ -int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, Socket *php_sock); +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, SocketImpl *php_sock); /* * Convert an IPv4 literal or a hostname into a sockaddr_in. */ -int php_set_inet_addr(struct sockaddr_in *sin, char *string, Socket *php_sock); +int php_set_inet_addr(struct sockaddr_in *sin, char *string, SocketImpl *php_sock); /* * Calls either php_set_inet6_addr() or php_set_inet_addr(), depending on the type of the socket. */ -int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, Socket *php_sock); +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, SocketImpl *php_sock); diff --git a/thirdparty/php/sockets/sendrecvmsg.cc b/thirdparty/php/sockets/sendrecvmsg.cc index 325774bc69..690b3ae7e0 100644 --- a/thirdparty/php/sockets/sendrecvmsg.cc +++ b/thirdparty/php/sockets/sendrecvmsg.cc @@ -24,101 +24,99 @@ #include #endif -#define MAX_USER_BUFF_SIZE ((size_t)(100*1024*1024)) +#define MAX_USER_BUFF_SIZE ((size_t) (100 * 1024 * 1024)) #define DEFAULT_BUFF_SIZE 8192 #define MAX_ARRAY_KEY_SIZE 128 -#define LONG_CHECK_VALID_INT(l) \ - do { \ - if ((l) < INT_MIN && (l) > INT_MAX) { \ - php_error_docref(NULL, E_WARNING, "The value " ZEND_LONG_FMT " does not fit inside " \ - "the boundaries of a native integer", (l)); \ - return; \ - } \ - } while (0) - - -int php_do_setsockopt_ipv6_rfc3542(Socket *php_sock, int level, int optname, zval *arg4) -{ +#define LONG_CHECK_VALID_INT(l) \ + do { \ + if ((l) < INT_MIN && (l) > INT_MAX) { \ + php_error_docref(NULL, \ + E_WARNING, \ + "The value " ZEND_LONG_FMT " does not fit inside " \ + "the boundaries of a native integer", \ + (l)); \ + return; \ + } \ + } while (0) + +int php_do_setsockopt_ipv6_rfc3542(SocketImpl *php_sock, int level, int optname, zval *arg4) { #ifdef IPV6_PKTINFO - struct err_s err = {0}; + struct err_s err = {0}; #endif - zend_llist *allocations = NULL; - void *opt_ptr; - socklen_t optlen; - int retval; + zend_llist *allocations = NULL; + void *opt_ptr; + socklen_t optlen; + int retval; - assert(level == IPPROTO_IPV6); + assert(level == IPPROTO_IPV6); - switch (optname) { + switch (optname) { #ifdef IPV6_PKTINFO - case IPV6_PKTINFO: - opt_ptr = from_zval_run_conversions(arg4, php_sock, from_zval_write_in6_pktinfo, - sizeof(struct in6_pktinfo), "in6_pktinfo", &allocations, &err); - if (err.has_error) { - err_msg_dispose(&err); - return FAILURE; - } - - optlen = sizeof(struct in6_pktinfo); - goto dosockopt; + case IPV6_PKTINFO: + opt_ptr = from_zval_run_conversions( + arg4, php_sock, from_zval_write_in6_pktinfo, sizeof(struct in6_pktinfo), "in6_pktinfo", &allocations, &err); + if (err.has_error) { + err_msg_dispose(&err); + return FAILURE; + } + + optlen = sizeof(struct in6_pktinfo); + goto dosockopt; #endif - } + } - /* we also support IPV6_TCLASS, but that can be handled by the default - * integer optval handling in the caller */ - return 1; + /* we also support IPV6_TCLASS, but that can be handled by the default + * integer optval handling in the caller */ + return 1; #ifdef IPV6_PKTINFO dosockopt: #endif - retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); - if (retval != 0) { - PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); - } - allocations_dispose(&allocations); + retval = setsockopt(php_sock->get_fd(), level, optname, opt_ptr, optlen); + if (retval != 0) { + PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to set socket option", errno); + } + allocations_dispose(&allocations); - return retval != 0 ? FAILURE : SUCCESS; + return retval != 0 ? FAILURE : SUCCESS; } -int php_do_getsockopt_ipv6_rfc3542(Socket *php_sock, int level, int optname, zval *result) -{ - struct err_s err = - { 0 }; +int php_do_getsockopt_ipv6_rfc3542(SocketImpl *php_sock, int level, int optname, zval *result) { + struct err_s err = {0}; char *buffer; socklen_t size; int res; to_zval_read_field *reader; - assert(level == IPPROTO_IPV6); + assert(level == IPPROTO_IPV6); - switch (optname) { + switch (optname) { #ifdef IPV6_PKTINFO - case IPV6_PKTINFO: - size = sizeof(struct in6_pktinfo); - reader = &to_zval_read_in6_pktinfo; - break; + case IPV6_PKTINFO: + size = sizeof(struct in6_pktinfo); + reader = &to_zval_read_in6_pktinfo; + break; #endif - default: - return 1; - } - - buffer = (char*) ecalloc(1, size); - res = getsockopt(php_sock->get_fd(), level, optname, buffer, &size); - if (res != 0) { - PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to get socket option", errno); - } else { - zval tmp; - zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", - sw_empty_key_value_list, &err, &tmp); - if (err.has_error) { - err_msg_dispose(&err); - res = -1; - } else { - ZVAL_COPY_VALUE(result, zv); - } - } - efree(buffer); - - return res == 0 ? SUCCESS : FAILURE; + default: + return 1; + } + + buffer = (char *) ecalloc(1, size); + res = getsockopt(php_sock->get_fd(), level, optname, buffer, &size); + if (res != 0) { + PHP_SWOOLE_SOCKET_ERROR(php_sock, "unable to get socket option", errno); + } else { + zval tmp; + zval *zv = to_zval_run_conversions(buffer, reader, "in6_pktinfo", sw_empty_key_value_list, &err, &tmp); + if (err.has_error) { + err_msg_dispose(&err); + res = -1; + } else { + ZVAL_COPY_VALUE(result, zv); + } + } + efree(buffer); + + return res == 0 ? SUCCESS : FAILURE; } diff --git a/thirdparty/php/sockets/sockaddr_conv.cc b/thirdparty/php/sockets/sockaddr_conv.cc index a5599a265d..a4f183e24f 100644 --- a/thirdparty/php/sockets/sockaddr_conv.cc +++ b/thirdparty/php/sockets/sockaddr_conv.cc @@ -4,123 +4,121 @@ #include /* Sets addr by hostname, or by ip in string form (AF_INET6) */ -int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, Socket *php_sock) /* {{{ */ +int php_set_inet6_addr(struct sockaddr_in6 *sin6, char *string, SocketImpl *php_sock) /* {{{ */ { - struct in6_addr tmp; + struct in6_addr tmp; #if HAVE_GETADDRINFO - struct addrinfo hints; - struct addrinfo *addrinfo = NULL; + struct addrinfo hints; + struct addrinfo *addrinfo = NULL; #endif - char *scope = strchr(string, '%'); + char *scope = strchr(string, '%'); - if (inet_pton(AF_INET6, string, &tmp)) { - memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); - } else { + if (inet_pton(AF_INET6, string, &tmp)) { + memcpy(&(sin6->sin6_addr.s6_addr), &(tmp.s6_addr), sizeof(struct in6_addr)); + } else { #if HAVE_GETADDRINFO - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_INET6; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET6; #if HAVE_AI_V4MAPPED - hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; #else - hints.ai_flags = AI_ADDRCONFIG; + hints.ai_flags = AI_ADDRCONFIG; #endif - getaddrinfo(string, NULL, &hints, &addrinfo); - if (!addrinfo) { + getaddrinfo(string, NULL, &hints, &addrinfo); + if (!addrinfo) { #ifdef PHP_WIN32 - PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); + PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", WSAGetLastError()); #else - PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); + PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); #endif - return 0; - } - if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { - php_error_docref(NULL, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); - freeaddrinfo(addrinfo); - return 0; - } + return 0; + } + if (addrinfo->ai_family != PF_INET6 || addrinfo->ai_addrlen != sizeof(struct sockaddr_in6)) { + php_error_docref(NULL, E_WARNING, "Host lookup failed: Non AF_INET6 domain returned on AF_INET6 socket"); + freeaddrinfo(addrinfo); + return 0; + } - memcpy(&(sin6->sin6_addr.s6_addr), ((struct sockaddr_in6*)(addrinfo->ai_addr))->sin6_addr.s6_addr, sizeof(struct in6_addr)); - freeaddrinfo(addrinfo); + memcpy(&(sin6->sin6_addr.s6_addr), + ((struct sockaddr_in6 *) (addrinfo->ai_addr))->sin6_addr.s6_addr, + sizeof(struct in6_addr)); + freeaddrinfo(addrinfo); #else - /* No IPv6 specific hostname resolution is available on this system? */ - php_error_docref(NULL, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); - return 0; + /* No IPv6 specific hostname resolution is available on this system? */ + php_error_docref(NULL, E_WARNING, "Host lookup failed: getaddrinfo() not available on this system"); + return 0; #endif + } - } + if (scope++) { + zend_long lval = 0; + double dval = 0; + unsigned scope_id = 0; - if (scope++) { - zend_long lval = 0; - double dval = 0; - unsigned scope_id = 0; + if (IS_LONG == is_numeric_string(scope, strlen(scope), &lval, &dval, 0)) { + if (lval > 0 && (zend_ulong) lval <= UINT_MAX) { + scope_id = lval; + } + } else { + php_string_to_if_index(scope, &scope_id); + } - if (IS_LONG == is_numeric_string(scope, strlen(scope), &lval, &dval, 0)) { - if (lval > 0 && (zend_ulong)lval <= UINT_MAX) { - scope_id = lval; - } - } else { - php_string_to_if_index(scope, &scope_id); - } + sin6->sin6_scope_id = scope_id; + } - sin6->sin6_scope_id = scope_id; - } - - return 1; + return 1; } /* }}} */ /* Sets addr by hostname, or by ip in string form (AF_INET) */ -int php_set_inet_addr(struct sockaddr_in *sin, char *string, Socket *php_sock) /* {{{ */ +int php_set_inet_addr(struct sockaddr_in *sin, char *string, SocketImpl *php_sock) /* {{{ */ { - struct in_addr tmp; - struct hostent *host_entry; + struct in_addr tmp; + struct hostent *host_entry; - if (inet_pton(AF_INET, string, &tmp)) { - sin->sin_addr.s_addr = tmp.s_addr; - } else { - if (strlen(string) > MAXFQDNLEN || ! (host_entry = php_network_gethostbyname(string))) { - /* Note: < -10000 indicates a host lookup error */ - PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); - return 0; - } - if (host_entry->h_addrtype != AF_INET) { - php_error_docref(NULL, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); - return 0; - } - memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); - } + if (inet_pton(AF_INET, string, &tmp)) { + sin->sin_addr.s_addr = tmp.s_addr; + } else { + if (strlen(string) > MAXFQDNLEN || !(host_entry = php_network_gethostbyname(string))) { + /* Note: < -10000 indicates a host lookup error */ + PHP_SWOOLE_SOCKET_ERROR(php_sock, "Host lookup failed", (-10000 - h_errno)); + return 0; + } + if (host_entry->h_addrtype != AF_INET) { + php_error_docref(NULL, E_WARNING, "Host lookup failed: Non AF_INET domain returned on AF_INET socket"); + return 0; + } + memcpy(&(sin->sin_addr.s_addr), host_entry->h_addr_list[0], host_entry->h_length); + } - return 1; + return 1; } /* }}} */ /* Sets addr by hostname or by ip in string form (AF_INET or AF_INET6, * depending on the socket) */ -int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, Socket *php_sock) /* {{{ */ +int php_set_inet46_addr(php_sockaddr_storage *ss, socklen_t *ss_len, char *string, SocketImpl *php_sock) /* {{{ */ { - if (php_sock->get_sock_domain() == AF_INET) { - struct sockaddr_in t = {0}; - if (php_set_inet_addr(&t, string, php_sock)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET; - *ss_len = sizeof(t); - return 1; - } - } - else if (php_sock->get_sock_domain() == AF_INET6) { - struct sockaddr_in6 t = {0}; - if (php_set_inet6_addr(&t, string, php_sock)) { - memcpy(ss, &t, sizeof t); - ss->ss_family = AF_INET6; - *ss_len = sizeof(t); - return 1; - } - } - else { - php_error_docref(NULL, E_WARNING, - "IP address used in the context of an unexpected type of socket"); - } - return 0; + if (php_sock->get_sock_domain() == AF_INET) { + struct sockaddr_in t = {0}; + if (php_set_inet_addr(&t, string, php_sock)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET; + *ss_len = sizeof(t); + return 1; + } + } else if (php_sock->get_sock_domain() == AF_INET6) { + struct sockaddr_in6 t = {0}; + if (php_set_inet6_addr(&t, string, php_sock)) { + memcpy(ss, &t, sizeof t); + ss->ss_family = AF_INET6; + *ss_len = sizeof(t); + return 1; + } + } else { + php_error_docref(NULL, E_WARNING, "IP address used in the context of an unexpected type of socket"); + } + return 0; } diff --git a/thirdparty/php/ssh2/LICENSE b/thirdparty/php/ssh2/LICENSE new file mode 100644 index 0000000000..3111a9f2a4 --- /dev/null +++ b/thirdparty/php/ssh2/LICENSE @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2012 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . + +PHP includes the Zend Engine, freely available at +. \ No newline at end of file diff --git a/thirdparty/php/ssh2/php_ssh2.h b/thirdparty/php/ssh2/php_ssh2.h new file mode 100644 index 0000000000..baf332ad13 --- /dev/null +++ b/thirdparty/php/ssh2/php_ssh2.h @@ -0,0 +1,146 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_SSH2_H +#define PHP_SSH2_H + +#include "php_swoole_ssh2.h" + +#include "ext/standard/url.h" +#include "main/php_network.h" + +#define PHP_SSH2_DEFAULT_PORT 22 + +/* Exported Constants */ +#define PHP_SSH2_FINGERPRINT_MD5 0x0000 +#define PHP_SSH2_FINGERPRINT_SHA1 0x0001 +#define PHP_SSH2_FINGERPRINT_HEX 0x0000 +#define PHP_SSH2_FINGERPRINT_RAW 0x0002 + +#define PHP_SSH2_TERM_UNIT_CHARS 0x0000 +#define PHP_SSH2_TERM_UNIT_PIXELS 0x0001 + +#define PHP_SSH2_DEFAULT_TERMINAL "vanilla" +#define PHP_SSH2_DEFAULT_TERM_WIDTH 80 +#define PHP_SSH2_DEFAULT_TERM_HEIGHT 25 +#define PHP_SSH2_DEFAULT_TERM_UNIT PHP_SSH2_TERM_UNIT_CHARS + +#define PHP_SSH2_SESSION_RES_NAME "SSH2 Session" +#define PHP_SSH2_CHANNEL_STREAM_NAME "SSH2 Channel" +#define PHP_SSH2_LISTENER_RES_NAME "SSH2 Listener" +#define PHP_SSH2_SFTP_RES_NAME "SSH2 SFTP" +#define PHP_SSH2_PKEY_SUBSYS_RES_NAME "SSH2 Publickey Subsystem" + +#define PHP_SSH2_SFTP_STREAM_NAME "SSH2 SFTP File" +#define PHP_SSH2_SFTP_DIRSTREAM_NAME "SSH2 SFTP Directory" +#define PHP_SSH2_SFTP_WRAPPER_NAME "SSH2 SFTP" + +#define PHP_SSH2_LISTEN_MAX_QUEUED 128 + +typedef struct _php_ssh2_sftp_data { + LIBSSH2_SESSION *session; + LIBSSH2_SFTP *sftp; + + zend_resource *session_rsrc; +} php_ssh2_sftp_data; + +typedef struct _php_ssh2_listener_data { + LIBSSH2_SESSION *session; + LIBSSH2_LISTENER *listener; + + zend_resource *session_rsrc; +} php_ssh2_listener_data; + +typedef struct _php_ssh2_pkey_subsys_data { + LIBSSH2_SESSION *session; + LIBSSH2_PUBLICKEY *pkey; + + zend_resource *session_rsrc; +} php_ssh2_pkey_subsys_data; + +#define SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession) \ + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( \ + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { \ + RETURN_FALSE; \ + } \ + if (libssh2_userauth_authenticated(session)) { \ + php_error_docref(NULL, E_WARNING, "Connection already authenticated"); \ + RETURN_FALSE; \ + } + +#define SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession) \ + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( \ + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { \ + RETURN_FALSE; \ + } \ + if (!libssh2_userauth_authenticated(session)) { \ + php_error_docref(NULL, E_WARNING, "Connection not authenticated"); \ + RETURN_FALSE; \ + } + +typedef struct _php_ssh2_channel_data { + LIBSSH2_CHANNEL *channel; + + /* Distinguish which stream we should read/write from/to */ + unsigned int streamid; + char is_blocking; + long timeout; + + /* Resource */ + zend_resource *session_rsrc; + + /* Allow one stream to be closed while the other is kept open */ + unsigned char *refcount; + +} php_ssh2_channel_data; + +LIBSSH2_SESSION *php_ssh2_session_connect(const char *host, int port, zval *methods, zval *callbacks); +void php_ssh2_sftp_dtor(zend_resource *rsrc); +php_url *php_ssh2_fopen_wraper_parse_path(const char *path, + const char *type, + php_stream_context *context, + LIBSSH2_SESSION **psession, + zend_resource **presource, + LIBSSH2_SFTP **psftp, + zend_resource **psftp_rsrc); + +extern php_stream_ops php_ssh2_channel_stream_ops; + +extern php_stream_wrapper php_ssh2_stream_wrapper_shell; +extern php_stream_wrapper php_ssh2_stream_wrapper_exec; +extern php_stream_wrapper php_ssh2_stream_wrapper_tunnel; +extern php_stream_wrapper php_ssh2_stream_wrapper_scp; +extern php_stream_wrapper php_ssh2_sftp_wrapper; + +/* Resource list entries */ +extern int le_ssh2_session; +extern int le_ssh2_sftp; + +static inline LIBSSH2_SESSION *ssh2_get_session(php_ssh2_channel_data *abstract) { + return (LIBSSH2_SESSION *) zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); +} + +#endif /* PHP_SSH2_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/thirdparty/php/ssh2/ssh2.cc b/thirdparty/php/ssh2/ssh2.cc new file mode 100644 index 0000000000..234d35ae1e --- /dev/null +++ b/thirdparty/php/ssh2/ssh2.cc @@ -0,0 +1,1300 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ + +#include "php_ssh2.h" +#include "php_swoole_ssh2_hook.h" + +#include "ext/standard/info.h" +#include "ext/standard/file.h" + +#include "main/php_network.h" + +/* Internal Constants */ +#ifndef SHA_DIGEST_LENGTH +#define SHA_DIGEST_LENGTH 20 +#endif + +#ifndef MD5_DIGEST_LENGTH +#define MD5_DIGEST_LENGTH 16 +#endif + +/* True global resources - no need for thread safety here */ +int le_ssh2_session; +int le_ssh2_listener; +int le_ssh2_sftp; +int le_ssh2_pkey_subsys; + +/* ************* + * Callbacks * + ************* */ + +/* {{{ php_ssh2_alloc_cb + * Wrap emalloc() + */ +static LIBSSH2_ALLOC_FUNC(php_ssh2_alloc_cb) { + return emalloc(count); +} +/* }}} */ + +/* {{{ php_ssh2_free_cb + * Wrap efree() + */ +static LIBSSH2_FREE_FUNC(php_ssh2_free_cb) { + efree(ptr); +} +/* }}} */ + +/* {{{ php_ssh2_realloc_cb + * Wrap erealloc() + */ +static LIBSSH2_REALLOC_FUNC(php_ssh2_realloc_cb) { + return erealloc(ptr, count); +} +/* }}} */ + +/* {{{ php_ssh2_debug_cb + * Debug packets + */ +LIBSSH2_DEBUG_FUNC(php_ssh2_debug_cb) { + php_ssh2_session_data *data; + zval args[3]; + + if (!abstract || !*abstract) { + return; + } + data = (php_ssh2_session_data *) *abstract; + if (!data->debug_cb) { + return; + } + + ZVAL_STRINGL(&args[0], message, message_len); + ZVAL_STRINGL(&args[1], language, language_len); + ZVAL_LONG(&args[2], always_display); + + zval retval; + if (FAILURE == call_user_function(NULL, NULL, data->debug_cb, &retval, 3, args)) { + php_error_docref(NULL, E_WARNING, "Failure calling debug callback"); + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } +} +/* }}} */ + +/* {{{ php_ssh2_ignore_cb + * Ignore packets + */ +LIBSSH2_IGNORE_FUNC(php_ssh2_ignore_cb) { + php_ssh2_session_data *data; + zval zretval; + zval args[1]; + + if (!abstract || !*abstract) { + return; + } + data = (php_ssh2_session_data *) *abstract; + if (!data->ignore_cb) { + return; + } + + ZVAL_STRINGL(&args[0], message, message_len); + + if (FAILURE == call_user_function(NULL, NULL, data->ignore_cb, &zretval, 1, args)) { + php_error_docref(NULL, E_WARNING, "Failure calling ignore callback"); + } + if (Z_TYPE_P(&zretval) != IS_UNDEF) { + zval_ptr_dtor(&zretval); + } +} +/* }}} */ + +/* {{{ php_ssh2_macerror_cb + * Called when a MAC error occurs, offers the chance to ignore + * WHY ARE YOU IGNORING MAC ERRORS?????? + */ +LIBSSH2_MACERROR_FUNC(php_ssh2_macerror_cb) { + php_ssh2_session_data *data; + zval zretval; + zval args[1]; + int retval = -1; + + if (!abstract || !*abstract) { + return -1; + } + data = (php_ssh2_session_data *) *abstract; + if (!data->macerror_cb) { + return -1; + } + + ZVAL_STRINGL(&args[0], packet, packet_len); + + if (FAILURE == call_user_function(NULL, NULL, data->macerror_cb, &zretval, 1, args)) { + php_error_docref(NULL, E_WARNING, "Failure calling macerror callback"); + } else { + retval = zval_is_true(&zretval) ? 0 : -1; + } + if (Z_TYPE_P(&zretval) != IS_UNDEF) { + zval_ptr_dtor(&zretval); + } + + return retval; +} +/* }}} */ + +/* {{{ php_ssh2_disconnect_cb + * Connection closed by foreign host + */ +LIBSSH2_DISCONNECT_FUNC(php_ssh2_disconnect_cb) { + php_ssh2_session_data *data; + zval args[3]; + + if (!abstract || !*abstract) { + return; + } + data = (php_ssh2_session_data *) *abstract; + if (!data->disconnect_cb) { + return; + } + + ZVAL_LONG(&args[0], reason); + ZVAL_STRINGL(&args[1], message, message_len); + ZVAL_STRINGL(&args[2], language, language_len); + + zval retval; + if (FAILURE == call_user_function(NULL, NULL, data->disconnect_cb, &retval, 3, args)) { + php_error_docref(NULL, E_WARNING, "Failure calling disconnect callback"); + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } +} +/* }}} */ + +/* ***************** + * Userspace API * + ***************** */ + +/* {{{ php_ssh2_set_callback + * Try to set a method if it's passed in with the hash table + */ +static int php_ssh2_set_callback(LIBSSH2_SESSION *session, + HashTable *ht, + const char *callback, + int callback_len, + int callback_type, + php_ssh2_session_data *data) { + zval *handler, *copyval; + void *internal_handler; + zend_string *callback_zstring; + + callback_zstring = zend_string_init(callback, callback_len, 0); + if ((handler = zend_hash_find(ht, callback_zstring)) == NULL) { + zend_string_release(callback_zstring); + return 0; + } + zend_string_release(callback_zstring); + + if (!zend_is_callable(handler, 0, NULL)) { + return -1; + } + + copyval = (zval *) emalloc(sizeof(zval)); + ZVAL_COPY(copyval, handler); + + switch (callback_type) { + case LIBSSH2_CALLBACK_IGNORE: + internal_handler = (void *) php_ssh2_ignore_cb; + if (data->ignore_cb) { + zval_ptr_dtor(data->ignore_cb); + } + data->ignore_cb = copyval; + break; + case LIBSSH2_CALLBACK_DEBUG: + internal_handler = (void *) php_ssh2_debug_cb; + if (data->debug_cb) { + zval_ptr_dtor(data->debug_cb); + } + data->debug_cb = copyval; + break; + case LIBSSH2_CALLBACK_MACERROR: + internal_handler = (void *) php_ssh2_macerror_cb; + if (data->macerror_cb) { + zval_ptr_dtor(data->macerror_cb); + } + data->macerror_cb = copyval; + break; + case LIBSSH2_CALLBACK_DISCONNECT: + internal_handler = (void *) php_ssh2_disconnect_cb; + if (data->disconnect_cb) { + zval_ptr_dtor(data->disconnect_cb); + } + data->disconnect_cb = copyval; + break; + default: + zval_ptr_dtor(copyval); + return -1; + } + + libssh2_session_callback_set(session, callback_type, internal_handler); + + return 0; +} +/* }}} */ + +/* {{{ php_ssh2_set_method + * Try to set a method if it's passed in with the hash table + */ +static int php_ssh2_set_method( + LIBSSH2_SESSION *session, HashTable *ht, const char *method, int method_len, int method_type) { + zval *value; + zend_string *method_zstring; + + method_zstring = zend_string_init(method, method_len, 0); + if ((value = zend_hash_find(ht, method_zstring)) == NULL) { + zend_string_release(method_zstring); + return 0; + } + zend_string_release(method_zstring); + + if ((Z_TYPE_P(value) != IS_STRING)) { + return -1; + } + + return libssh2_session_method_pref(session, method_type, Z_STRVAL_P(value)); +} +/* }}} */ + +/* {{{ php_ssh2_session_connect + * Connect to an SSH server with requested methods + */ +LIBSSH2_SESSION *php_ssh2_session_connect(const char *host, int port, zval *methods, zval *callbacks) { + LIBSSH2_SESSION *session; + php_ssh2_session_data *data; + zend_string *hash_lookup_zstring; + + int domain = swoole::network::Address::verify_ip(AF_INET6, host) ? AF_INET6 : AF_INET; + + auto sock = new SocketImpl(domain, SOCK_STREAM, 0); + if (sock->get_fd() < 0 || !sock->connect(host, port)) { + php_error_docref(NULL, E_WARNING, "Unable to connect to %s on port %d", host, port); + delete sock; + return NULL; + } + + data = (php_ssh2_session_data *) ecalloc(1, sizeof(php_ssh2_session_data)); + data->socket = sock; + + session = libssh2_session_init_ex(php_ssh2_alloc_cb, php_ssh2_free_cb, php_ssh2_realloc_cb, data); + if (!session) { + php_error_docref(NULL, E_WARNING, "Unable to initialize SSH2 session"); + efree(data); + delete sock; + return NULL; + } + + libssh2_banner_set(session, LIBSSH2_SSH_DEFAULT_BANNER " swoole-" SWOOLE_VERSION); + libssh2_session_set_blocking(session, 0); + + /* Override method preferences */ + if (methods) { + zval *container; + + if (php_ssh2_set_method(session, HASH_OF(methods), "kex", sizeof("kex") - 1, LIBSSH2_METHOD_KEX)) { + php_error_docref(NULL, E_WARNING, "Failed overriding KEX method"); + } + if (php_ssh2_set_method(session, HASH_OF(methods), "hostkey", sizeof("hostkey") - 1, LIBSSH2_METHOD_HOSTKEY)) { + php_error_docref(NULL, E_WARNING, "Failed overriding HOSTKEY method"); + } + + hash_lookup_zstring = zend_string_init("client_to_server", sizeof("client_to_server") - 1, 0); + if ((container = zend_hash_find(HASH_OF(methods), hash_lookup_zstring)) != NULL && + Z_TYPE_P(container) == IS_ARRAY) { + if (php_ssh2_set_method( + session, HASH_OF(container), "crypt", sizeof("crypt") - 1, LIBSSH2_METHOD_CRYPT_CS)) { + php_error_docref(NULL, E_WARNING, "Failed overriding client to server CRYPT method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "mac", sizeof("mac") - 1, LIBSSH2_METHOD_MAC_CS)) { + php_error_docref(NULL, E_WARNING, "Failed overriding client to server MAC method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "comp", sizeof("comp") - 1, LIBSSH2_METHOD_COMP_CS)) { + php_error_docref(NULL, E_WARNING, "Failed overriding client to server COMP method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "lang", sizeof("lang") - 1, LIBSSH2_METHOD_LANG_CS)) { + php_error_docref(NULL, E_WARNING, "Failed overriding client to server LANG method"); + } + } + zend_string_release(hash_lookup_zstring); + + hash_lookup_zstring = zend_string_init("server_to_client", sizeof("server_to_client") - 1, 0); + if ((container = zend_hash_find(HASH_OF(methods), hash_lookup_zstring)) != NULL && + Z_TYPE_P(container) == IS_ARRAY) { + if (php_ssh2_set_method( + session, HASH_OF(container), "crypt", sizeof("crypt") - 1, LIBSSH2_METHOD_CRYPT_SC)) { + php_error_docref(NULL, E_WARNING, "Failed overriding server to client CRYPT method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "mac", sizeof("mac") - 1, LIBSSH2_METHOD_MAC_SC)) { + php_error_docref(NULL, E_WARNING, "Failed overriding server to client MAC method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "comp", sizeof("comp") - 1, LIBSSH2_METHOD_COMP_SC)) { + php_error_docref(NULL, E_WARNING, "Failed overriding server to client COMP method"); + } + if (php_ssh2_set_method(session, HASH_OF(container), "lang", sizeof("lang") - 1, LIBSSH2_METHOD_LANG_SC)) { + php_error_docref(NULL, E_WARNING, "Failed overriding server to client LANG method"); + } + } + zend_string_release(hash_lookup_zstring); + } + + /* Register Callbacks */ + if (callbacks) { + /* ignore debug disconnect macerror */ + + if (php_ssh2_set_callback( + session, HASH_OF(callbacks), "ignore", sizeof("ignore") - 1, LIBSSH2_CALLBACK_IGNORE, data)) { + php_error_docref(NULL, E_WARNING, "Failed setting IGNORE callback"); + } + + if (php_ssh2_set_callback( + session, HASH_OF(callbacks), "debug", sizeof("debug") - 1, LIBSSH2_CALLBACK_DEBUG, data)) { + php_error_docref(NULL, E_WARNING, "Failed setting DEBUG callback"); + } + + if (php_ssh2_set_callback( + session, HASH_OF(callbacks), "macerror", sizeof("macerror") - 1, LIBSSH2_CALLBACK_MACERROR, data)) { + php_error_docref(NULL, E_WARNING, "Failed setting MACERROR callback"); + } + + if (php_ssh2_set_callback(session, + HASH_OF(callbacks), + "disconnect", + sizeof("disconnect") - 1, + LIBSSH2_CALLBACK_DISCONNECT, + data)) { + php_error_docref(NULL, E_WARNING, "Failed setting DISCONNECT callback"); + } + } + + if (libssh2_session_handshake(session, sock->get_fd())) { + int last_error = 0; + char *error_msg = NULL; + + last_error = libssh2_session_last_error(session, &error_msg, NULL, 0); + php_error_docref(NULL, E_WARNING, "Error starting up SSH connection(%d): %s", last_error, error_msg); + libssh2_session_free(session); + efree(data); + delete sock; + return NULL; + } + + return session; +} +/* }}} */ + +/* {{{ proto resource ssh2_connect(string host[, int port[, array methods[, array callbacks]]]) + * Establish a connection to a remote SSH server and return a resource on success, false on error + */ +PHP_FUNCTION(ssh2_connect) { + LIBSSH2_SESSION *session; + zval *methods = NULL, *callbacks = NULL; + char *host; + zend_long port = PHP_SSH2_DEFAULT_PORT; + size_t host_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|la!a!", &host, &host_len, &port, &methods, &callbacks) == FAILURE) { + return; + } + + session = php_ssh2_session_connect(host, port, methods, callbacks); + if (!session) { + php_error_docref(NULL, E_WARNING, "Unable to connect to %s", host); + RETURN_FALSE; + } + + RETURN_RES(zend_register_resource(session, le_ssh2_session)); +} +/* }}} */ + +/* {{{ proto resource ssh2_disconnect(resource session) + * close a connection to a remote SSH server and return a true on success, false on error. + */ +PHP_FUNCTION(ssh2_disconnect) { + zval *zsession; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + RETURN_FALSE; + } + + zend_list_close(Z_RES_P(zsession)); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array ssh2_methods_negotiated(resource session) + * Return list of negotiaed methods + */ +PHP_FUNCTION(ssh2_methods_negotiated) { + LIBSSH2_SESSION *session; + zval *zsession, endpoint; + char *kex, *hostkey, *crypt_cs, *crypt_sc, *mac_cs, *mac_sc, *comp_cs, *comp_sc, *lang_cs, *lang_sc; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + RETURN_FALSE; + } + + kex = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_KEX); + hostkey = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_HOSTKEY); + crypt_cs = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_CRYPT_CS); + crypt_sc = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_CRYPT_SC); + mac_cs = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_MAC_CS); + mac_sc = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_MAC_SC); + comp_cs = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_COMP_CS); + comp_sc = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_COMP_SC); + lang_cs = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_LANG_CS); + lang_sc = (char *) libssh2_session_methods(session, LIBSSH2_METHOD_LANG_SC); + + array_init(return_value); + add_assoc_string(return_value, "kex", kex); + add_assoc_string(return_value, "hostkey", hostkey); + + array_init(&endpoint); + add_assoc_string(&endpoint, "crypt", crypt_cs); + add_assoc_string(&endpoint, "mac", mac_cs); + add_assoc_string(&endpoint, "comp", comp_cs); + add_assoc_string(&endpoint, "lang", lang_cs); + add_assoc_zval(return_value, "client_to_server", &endpoint); + + array_init(&endpoint); + add_assoc_string(&endpoint, "crypt", crypt_sc); + add_assoc_string(&endpoint, "mac", mac_sc); + add_assoc_string(&endpoint, "comp", comp_sc); + add_assoc_string(&endpoint, "lang", lang_sc); + add_assoc_zval(return_value, "server_to_client", &endpoint); +} +/* }}} */ + +/* {{{ proto string ssh2_fingerprint(resource session[, int flags]) + * Returns a server hostkey hash from an active session + * Defaults to MD5 fingerprint encoded as ASCII hex values + */ +PHP_FUNCTION(ssh2_fingerprint) { + LIBSSH2_SESSION *session; + zval *zsession; + const char *fingerprint; + zend_long flags = 0; + size_t i, fingerprint_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|l", &zsession, &flags) == FAILURE) { + return; + } + fingerprint_len = (flags & PHP_SSH2_FINGERPRINT_SHA1) ? SHA_DIGEST_LENGTH : MD5_DIGEST_LENGTH; + + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + RETURN_FALSE; + } + + fingerprint = (char *) libssh2_hostkey_hash( + session, (flags & PHP_SSH2_FINGERPRINT_SHA1) ? LIBSSH2_HOSTKEY_HASH_SHA1 : LIBSSH2_HOSTKEY_HASH_MD5); + if (!fingerprint) { + php_error_docref(NULL, E_WARNING, "Unable to retrieve fingerprint from specified session"); + RETURN_FALSE; + } + + for (i = 0; i < fingerprint_len; i++) { + if (fingerprint[i] != '\0') { + goto fingerprint_good; + } + } + php_error_docref(NULL, E_WARNING, "No fingerprint available using specified hash"); + RETURN_NULL(); +fingerprint_good: + if (flags & PHP_SSH2_FINGERPRINT_RAW) { + RETURN_STRINGL(fingerprint, fingerprint_len); + } else { + char *hexchars; + + hexchars = (char *) emalloc((fingerprint_len * 2) + 1); + for (i = 0; i < fingerprint_len; i++) { + snprintf(hexchars + (2 * i), 3, "%02X", (unsigned char) fingerprint[i]); + } + ZVAL_STRINGL(return_value, hexchars, 2 * fingerprint_len); + efree(hexchars); + } +} +/* }}} */ + +/* {{{ proto array ssh2_auth_none(resource session, string username) + * Attempt "none" authentication, returns a list of allowed methods on failed authentication, + * false on utter failure, or true on success + */ +PHP_FUNCTION(ssh2_auth_none) { + LIBSSH2_SESSION *session; + zval *zsession; + char *username, *methods, *s, *p; + size_t username_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &zsession, &username, &username_len) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + RETURN_FALSE; + } + + s = methods = libssh2_userauth_list(session, username, username_len); + if (!methods) { + /* Either bad failure, or unexpected success */ + RETURN_BOOL(libssh2_userauth_authenticated(session)); + } + + array_init(return_value); + while ((p = strchr(s, ','))) { + if ((p - s) > 0) { + add_next_index_stringl(return_value, s, p - s); + } + s = p + 1; + } + if (strlen(s)) { + add_next_index_string(return_value, s); + } +} +/* }}} */ + +char *password_for_kbd_callback; + +static void kbd_callback(const char *name, + int name_len, + const char *instruction, + int instruction_len, + int num_prompts, + const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts, + LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses, + void **abstract) { + (void) name; + (void) name_len; + (void) instruction; + (void) instruction_len; + if (num_prompts == 1) { + responses[0].text = estrdup(password_for_kbd_callback); + responses[0].length = strlen(password_for_kbd_callback); + } + (void) prompts; + (void) abstract; +} + +/* {{{ proto bool ssh2_auth_password(resource session, string username, string password) + * Authenticate over SSH using a plain password + */ +PHP_FUNCTION(ssh2_auth_password) { + LIBSSH2_SESSION *session; + zval *zsession; + zend_string *username, *password; + char *userauthlist; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &zsession, &username, &password) == FAILURE) { + return; + } + + SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession); + + userauthlist = libssh2_userauth_list(session, username->val, username->len); + + if (userauthlist != NULL) { + password_for_kbd_callback = password->val; + if (strstr(userauthlist, "keyboard-interactive") != NULL) { + if (libssh2_userauth_keyboard_interactive(session, username->val, &kbd_callback) == 0) { + RETURN_TRUE; + } + } + + /* TODO: Support password change callback */ + if (libssh2_userauth_password_ex(session, username->val, username->len, password->val, password->len, NULL)) { + php_error_docref(NULL, E_WARNING, "Authentication failed for %s using password", username->val); + RETURN_FALSE; + } + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ssh2_auth_pubkey_file(resource session, string username, string pubkeyfile, string privkeyfile[, + * string passphrase]) Authenticate using a public key + */ +PHP_FUNCTION(ssh2_auth_pubkey_file) { + LIBSSH2_SESSION *session; + zval *zsession; + zend_string *username, *pubkey, *privkey, *passphrase = nullptr; +#ifndef PHP_WIN32 + zend_string *newpath; + struct passwd *pws; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS|S", &zsession, &username, &pubkey, &privkey, &passphrase) == + FAILURE) { + return; + } + + if (php_check_open_basedir(ZSTR_VAL(pubkey)) || php_check_open_basedir(ZSTR_VAL(privkey))) { + RETURN_FALSE; + } + + SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession); +#ifndef PHP_WIN32 + /* Explode '~/paths' stopgap fix because libssh2 does not accept tilde for homedir + This should be ifdef'ed when a fix is available to support older libssh2 versions*/ + pws = getpwuid(geteuid()); + if (ZSTR_LEN(pubkey) >= 2 && *(ZSTR_VAL(pubkey)) == '~' && *(ZSTR_VAL(pubkey) + 1) == '/') { + newpath = zend_string_alloc(strlen(pws->pw_dir) + ZSTR_LEN(pubkey), 0); + strcpy(ZSTR_VAL(newpath), pws->pw_dir); + strcat(ZSTR_VAL(newpath), ZSTR_VAL(pubkey) + 1); + zend_string_release(pubkey); + pubkey = newpath; + } + if (ZSTR_LEN(privkey) >= 2 && *(ZSTR_VAL(privkey)) == '~' && *(ZSTR_VAL(privkey) + 1) == '/') { + newpath = zend_string_alloc(strlen(pws->pw_dir) + ZSTR_LEN(privkey), 0); + strcpy(ZSTR_VAL(newpath), pws->pw_dir); + strcat(ZSTR_VAL(newpath), ZSTR_VAL(privkey) + 1); + zend_string_release(privkey); + privkey = newpath; + } +#endif + + auto passphrase_ptr = passphrase ? ZSTR_VAL(passphrase) : nullptr; + + /* TODO: Support passphrase callback */ + if (libssh2_userauth_publickey_fromfile_ex( + session, ZSTR_VAL(username), ZSTR_LEN(username), ZSTR_VAL(pubkey), ZSTR_VAL(privkey), passphrase_ptr)) { + char *buf; + int len; + libssh2_session_last_error(session, &buf, &len, 0); + php_error_docref(NULL, E_WARNING, "Authentication failed for %s using public key: %s", ZSTR_VAL(username), buf); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ssh2_auth_pubkey(resource session, string username, string pubkey, string privkey[, string + * passphrase]) Authenticate using a public key + */ +PHP_FUNCTION(ssh2_auth_pubkey) { + LIBSSH2_SESSION *session; + zval *zsession; + zend_string *username, *pubkey, *privkey, *passphrase = nullptr; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSSS|S", &zsession, &username, &pubkey, &privkey, &passphrase) == + FAILURE) { + return; + } + + auto passphrase_ptr = passphrase ? ZSTR_VAL(passphrase) : nullptr; + + SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession); + + if (libssh2_userauth_publickey_frommemory(session, + ZSTR_VAL(username), + ZSTR_LEN(username), + ZSTR_VAL(pubkey), + ZSTR_LEN(pubkey), + ZSTR_VAL(privkey), + ZSTR_LEN(privkey), + passphrase_ptr)) { + char *buf; + int len; + libssh2_session_last_error(session, &buf, &len, 0); + php_error_docref(NULL, E_WARNING, "Authentication failed for %s using public key: %s", ZSTR_VAL(username), buf); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool ssh2_auth_hostbased_file(resource session, string username, string hostname, string pubkeyfile, string + * privkeyfile[, string passphrase[, string local_username]]) Authenticate using a hostkey + */ +PHP_FUNCTION(ssh2_auth_hostbased_file) { + LIBSSH2_SESSION *session; + zval *zsession; + char *username, *hostname, *pubkey, *privkey, *passphrase = NULL, *local_username = NULL; + size_t username_len, hostname_len, pubkey_len, privkey_len, passphrase_len, local_username_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "rssss|s!s!", + &zsession, + &username, + &username_len, + &hostname, + &hostname_len, + &pubkey, + &pubkey_len, + &privkey, + &privkey_len, + &passphrase, + &passphrase_len, + &local_username, + &local_username_len) == FAILURE) { + return; + } + + if (php_check_open_basedir(pubkey) || php_check_open_basedir(privkey)) { + RETURN_FALSE; + } + + SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession); + + if (!local_username) { + local_username = username; + local_username_len = username_len; + } + + /* TODO: Support passphrase callback */ + if (libssh2_userauth_hostbased_fromfile_ex(session, + username, + username_len, + pubkey, + privkey, + passphrase, + hostname, + hostname_len, + local_username, + local_username_len)) { + php_error_docref(NULL, E_WARNING, "Authentication failed for %s using hostbased public key", username); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto resource ssh2_forward_listen(resource session, int port[, string host[, long max_connections]]) + * Bind a port on the remote server and listen for connections + */ +PHP_FUNCTION(ssh2_forward_listen) { + zval *zsession; + LIBSSH2_SESSION *session; + LIBSSH2_LISTENER *listener; + php_ssh2_listener_data *data; + zend_long port; + char *host = NULL; + size_t host_len; + zend_long max_connections = PHP_SSH2_LISTEN_MAX_QUEUED; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl|sl", &zsession, &port, &host, &host_len, &max_connections) == + FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + listener = libssh2_channel_forward_listen_ex(session, host, port, NULL, max_connections); + + if (!listener) { + php_error_docref(NULL, E_WARNING, "Failure listening on remote port"); + RETURN_FALSE; + } + + data = (php_ssh2_listener_data *) emalloc(sizeof(php_ssh2_listener_data)); + data->session = session; + data->session_rsrc = Z_RES_P(zsession); + Z_ADDREF_P(zsession); + data->listener = listener; + + RETURN_RES(zend_register_resource(data, le_ssh2_listener)); +} +/* }}} */ + +/* {{{ proto stream ssh2_forward_accept(resource listener[, string &shost[, long &sport]]) + * Accept a connection created by a listener + */ +PHP_FUNCTION(ssh2_forward_accept) { + zval *zlistener; + php_ssh2_listener_data *data; + LIBSSH2_CHANNEL *channel; + php_ssh2_channel_data *channel_data; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zlistener) == FAILURE) { + return; + } + + if ((data = (php_ssh2_listener_data *) zend_fetch_resource( + Z_RES_P(zlistener), PHP_SSH2_LISTENER_RES_NAME, le_ssh2_listener)) == NULL) { + RETURN_FALSE; + } + + auto session = data->session; + channel = libssh2_channel_forward_accept(data->listener); + + if (!channel) { + RETURN_FALSE; + } + + channel_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + channel_data->channel = channel; + channel_data->streamid = 0; + channel_data->is_blocking = 0; + channel_data->session_rsrc = data->session_rsrc; + channel_data->refcount = NULL; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+"); + if (!stream) { + php_error_docref(NULL, E_WARNING, "Failure allocating stream"); + efree(channel_data); + libssh2_channel_free(channel); + RETURN_FALSE; + } + + GC_ADDREF(channel_data->session_rsrc); + + php_stream_to_zval(stream, return_value); +} +/* }}} */ + +/* *********************** + * Publickey Subsystem * + *********************** */ + +/* {{{ proto resource ssh2_publickey_init(resource connection) +Initialize the publickey subsystem */ +PHP_FUNCTION(ssh2_publickey_init) { + zval *zsession; + LIBSSH2_SESSION *session; + LIBSSH2_PUBLICKEY *pkey; + php_ssh2_pkey_subsys_data *data; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + pkey = libssh2_publickey_init(session); + + if (!pkey) { + int last_error = 0; + char *error_msg = NULL; + + last_error = libssh2_session_last_error(session, &error_msg, NULL, 0); + php_error_docref(NULL, E_WARNING, "Unable to initialize publickey subsystem(%d) %s", last_error, error_msg); + RETURN_FALSE; + } + + data = (php_ssh2_pkey_subsys_data *) emalloc(sizeof(php_ssh2_pkey_subsys_data)); + data->session = session; + data->session_rsrc = Z_RES_P(zsession); + Z_ADDREF_P(zsession); + data->pkey = pkey; + + RETURN_RES(zend_register_resource(data, le_ssh2_pkey_subsys)); +} +/* }}} */ + +/* {{{ proto bool ssh2_publickey_add(resource pkey, string algoname, string blob[, bool overwrite=FALSE [,array +attributes=NULL]]) Add an additional publickey */ +PHP_FUNCTION(ssh2_publickey_add) { + zval *zpkey_data, *zattrs = NULL; + php_ssh2_pkey_subsys_data *data; + char *algo, *blob; + size_t algo_len, blob_len; + unsigned long num_attrs = 0; + libssh2_publickey_attribute *attrs = NULL; + zend_bool overwrite = 0; + + if (zend_parse_parameters( + ZEND_NUM_ARGS(), "rss|ba", &zpkey_data, &algo, &algo_len, &blob, &blob_len, &overwrite, &zattrs) == + FAILURE) { + return; + } + + if ((data = (php_ssh2_pkey_subsys_data *) zend_fetch_resource( + Z_RES_P(zpkey_data), PHP_SSH2_PKEY_SUBSYS_RES_NAME, le_ssh2_pkey_subsys)) == NULL) { + RETURN_FALSE; + } + + if (zattrs) { + HashPosition pos; + zval *attr_val; + unsigned long current_attr = 0; + + num_attrs = zend_hash_num_elements(Z_ARRVAL_P(zattrs)); + attrs = (libssh2_publickey_attribute *) safe_emalloc(num_attrs, sizeof(libssh2_publickey_attribute), 0); + + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(zattrs), &pos); + (attr_val = zend_hash_get_current_data_ex(Z_ARRVAL_P(zattrs), &pos)) != NULL; + zend_hash_move_forward_ex(Z_ARRVAL_P(zattrs), &pos)) { + zend_string *key; + int type; + zend_ulong idx; + zval copyval = *attr_val; + + type = zend_hash_get_current_key_ex(Z_ARRVAL_P(zattrs), &key, &idx, &pos); + if (type == HASH_KEY_NON_EXISTENT) { + /* All but impossible */ + break; + } + if (type == HASH_KEY_IS_LONG) { + /* Malformed, ignore */ + php_error_docref(NULL, E_WARNING, "Malformed attirbute array, contains numeric index"); + num_attrs--; + continue; + } + + if (!key || (key->len == 1 && key->val[0] == '*')) { + /* Empty key, ignore */ + php_error_docref(NULL, E_WARNING, "Empty attribute key"); + num_attrs--; + continue; + } + + zval_copy_ctor(©val); + // TODO Sean-Der + // Z_UNSET_ISREF_P(©val); + // Z_SET_REFCOUNT_P(©val, 1); + convert_to_string(©val); + + if (key->val[0] == '*') { + attrs[current_attr].mandatory = 1; + attrs[current_attr].name = key->val + 1; + attrs[current_attr].name_len = key->len - 2; + } else { + attrs[current_attr].mandatory = 0; + attrs[current_attr].name = key->val; + attrs[current_attr].name_len = key->len - 1; + } + attrs[current_attr].value_len = Z_STRLEN(copyval); + attrs[current_attr].value = Z_STRVAL(copyval); + + /* copyval deliberately not dtor'd, we're stealing the string */ + current_attr++; + } + } + + if (libssh2_publickey_add_ex(data->pkey, + (unsigned char *) algo, + algo_len, + (unsigned char *) blob, + blob_len, + overwrite, + num_attrs, + attrs)) { + php_error_docref(NULL, E_WARNING, "Unable to add %s key", algo); + RETVAL_FALSE; + } else { + RETVAL_TRUE; + } + + if (attrs) { + unsigned long i; + + for (i = 0; i < num_attrs; i++) { + /* name doesn't need freeing */ + // TODO Sean-Der + // efree(attrs[i].value); + } + efree(attrs); + } +} +/* }}} */ + +/* {{{ proto bool ssh2_publickey_remove(resource pkey, string algoname, string blob) +Remove a publickey entry */ +PHP_FUNCTION(ssh2_publickey_remove) { + zval *zpkey_data; + php_ssh2_pkey_subsys_data *data; + char *algo, *blob; + size_t algo_len, blob_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rss", &zpkey_data, &algo, &algo_len, &blob, &blob_len) == FAILURE) { + return; + } + + if ((data = (php_ssh2_pkey_subsys_data *) zend_fetch_resource( + Z_RES_P(zpkey_data), PHP_SSH2_PKEY_SUBSYS_RES_NAME, le_ssh2_pkey_subsys)) == NULL) { + RETURN_FALSE; + } + + if (libssh2_publickey_remove_ex(data->pkey, (unsigned char *) algo, algo_len, (unsigned char *) blob, blob_len)) { + php_error_docref(NULL, E_WARNING, "Unable to remove %s key", algo); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array ssh2_publickey_list(resource pkey) +List currently installed publickey entries */ +PHP_FUNCTION(ssh2_publickey_list) { + zval *zpkey_data; + php_ssh2_pkey_subsys_data *data; + unsigned long num_keys, i; + libssh2_publickey_list *keys; + zend_string *hash_key_zstring; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zpkey_data) == FAILURE) { + return; + } + + if ((data = (php_ssh2_pkey_subsys_data *) zend_fetch_resource( + Z_RES_P(zpkey_data), PHP_SSH2_PKEY_SUBSYS_RES_NAME, le_ssh2_pkey_subsys)) == NULL) { + RETURN_FALSE; + } + + if (libssh2_publickey_list_fetch(data->pkey, &num_keys, &keys)) { + php_error_docref(NULL, E_WARNING, "Unable to list keys on remote server"); + RETURN_FALSE; + } + + array_init(return_value); + for (i = 0; i < num_keys; i++) { + zval key, attrs; + unsigned long j; + + array_init(&key); + + add_assoc_stringl(&key, "name", (char *) keys[i].name, keys[i].name_len); + add_assoc_stringl(&key, "blob", (char *) keys[i].blob, keys[i].blob_len); + + array_init(&attrs); + for (j = 0; j < keys[i].num_attrs; j++) { + zval attr; + + ZVAL_STRINGL(&attr, keys[i].attrs[j].value, keys[i].attrs[j].value_len); + hash_key_zstring = zend_string_init(keys[i].attrs[j].name, keys[i].attrs[j].name_len, 0); + zend_hash_add(Z_ARRVAL_P(&attrs), hash_key_zstring, &attr); + zend_string_release(hash_key_zstring); + } + add_assoc_zval(&key, "attrs", &attrs); + + add_next_index_zval(return_value, &key); + } + + libssh2_publickey_list_free(data->pkey, keys); +} +/* }}} */ + +/* {{{ proto array ssh2_auth_agent(resource session, string username) +Authenticate using the ssh agent */ +PHP_FUNCTION(ssh2_auth_agent) { +#ifdef PHP_SSH2_AGENT_AUTH + zval *zsession; + char *username; + size_t username_len; + + LIBSSH2_SESSION *session; + char *userauthlist; + LIBSSH2_AGENT *agent = NULL; + int rc; + struct libssh2_agent_publickey *identity, *prev_identity = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rs", &zsession, &username, &username_len) == FAILURE) { + return; + } + + SSH2_FETCH_NONAUTHENTICATED_SESSION(session, zsession); + + /* check what authentication methods are available */ + userauthlist = libssh2_userauth_list(session, username, username_len); + + if (userauthlist != NULL && strstr(userauthlist, "publickey") == NULL) { + php_error_docref(NULL, E_WARNING, "\"publickey\" authentication is not supported"); + RETURN_FALSE; + } + + /* Connect to the ssh-agent */ + agent = libssh2_agent_init(session); + + if (!agent) { + php_error_docref(NULL, E_WARNING, "Failure initializing ssh-agent support"); + RETURN_FALSE; + } + + if (libssh2_agent_connect(agent)) { + php_error_docref(NULL, E_WARNING, "Failure connecting to ssh-agent"); + libssh2_agent_free(agent); + RETURN_FALSE; + } + + if (libssh2_agent_list_identities(agent)) { + php_error_docref(NULL, E_WARNING, "Failure requesting identities to ssh-agent"); + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + RETURN_FALSE; + } + + while (1) { + rc = libssh2_agent_get_identity(agent, &identity, prev_identity); + + if (rc == 1) { + php_error_docref(NULL, E_WARNING, "Couldn't continue authentication"); + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + RETURN_FALSE; + } + + if (rc < 0) { + php_error_docref(NULL, E_WARNING, "Failure obtaining identity from ssh-agent support"); + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + RETURN_FALSE; + } + + if (!libssh2_agent_userauth(agent, username, identity)) { + libssh2_agent_disconnect(agent); + libssh2_agent_free(agent); + RETURN_TRUE; + } + prev_identity = identity; + } +#else + php_error_docref( + NULL, + E_WARNING, + "Upgrade the libssh2 library (needs 1.2.3 or higher) and reinstall the ssh2 extension for ssh2 agent support"); + RETURN_FALSE; +#endif /* PHP_SSH2_AGENT_AUTH */ +} +/* }}} */ + +/* *********************** + * Module Housekeeping * + *********************** */ + +static void php_ssh2_session_dtor(zend_resource *rsrc) { + LIBSSH2_SESSION *session = (LIBSSH2_SESSION *) rsrc->ptr; + php_ssh2_session_data **data = (php_ssh2_session_data **) libssh2_session_abstract(session); + + libssh2_session_disconnect(session, "swoole_ssh2 (https://github.com/swoole/swoole-src)"); + + if (*data) { + if ((*data)->ignore_cb) { + zval_ptr_dtor((*data)->ignore_cb); + } + if ((*data)->debug_cb) { + zval_ptr_dtor((*data)->debug_cb); + } + if ((*data)->macerror_cb) { + zval_ptr_dtor((*data)->macerror_cb); + } + if ((*data)->disconnect_cb) { + zval_ptr_dtor((*data)->disconnect_cb); + } + + delete (*data)->socket; + efree(*data); + *data = NULL; + } + + libssh2_session_free(session); +} + +static void php_ssh2_listener_dtor(zend_resource *rsrc) { + php_ssh2_listener_data *data = (php_ssh2_listener_data *) rsrc->ptr; + LIBSSH2_LISTENER *listener = data->listener; + auto session = data->session; + + libssh2_channel_forward_cancel(listener); + zend_list_delete(data->session_rsrc); + efree(data); +} + +static void php_ssh2_pkey_subsys_dtor(zend_resource *rsrc) { + php_ssh2_pkey_subsys_data *data = (php_ssh2_pkey_subsys_data *) rsrc->ptr; + LIBSSH2_PUBLICKEY *pkey = data->pkey; + + libssh2_publickey_shutdown(pkey); + zend_list_delete(data->session_rsrc); + efree(data); +} + +/* {{{ PHP_MINIT_FUNCTION + */ +int php_swoole_ssh2_minit(int module_number) { + le_ssh2_session = + zend_register_list_destructors_ex(php_ssh2_session_dtor, NULL, PHP_SSH2_SESSION_RES_NAME, module_number); + le_ssh2_listener = + zend_register_list_destructors_ex(php_ssh2_listener_dtor, NULL, PHP_SSH2_LISTENER_RES_NAME, module_number); + le_ssh2_sftp = zend_register_list_destructors_ex(php_ssh2_sftp_dtor, NULL, PHP_SSH2_SFTP_RES_NAME, module_number); + le_ssh2_pkey_subsys = zend_register_list_destructors_ex( + php_ssh2_pkey_subsys_dtor, NULL, PHP_SSH2_PKEY_SUBSYS_RES_NAME, module_number); + + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_MD5", PHP_SSH2_FINGERPRINT_MD5, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_SHA1", PHP_SSH2_FINGERPRINT_SHA1, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_HEX", PHP_SSH2_FINGERPRINT_HEX, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_FINGERPRINT_RAW", PHP_SSH2_FINGERPRINT_RAW, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SSH2_TERM_UNIT_CHARS", PHP_SSH2_TERM_UNIT_CHARS, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_TERM_UNIT_PIXELS", PHP_SSH2_TERM_UNIT_PIXELS, CONST_CS | CONST_PERSISTENT); + + REGISTER_STRING_CONSTANT("SSH2_DEFAULT_TERMINAL", PHP_SSH2_DEFAULT_TERMINAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_DEFAULT_TERM_WIDTH", PHP_SSH2_DEFAULT_TERM_WIDTH, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_DEFAULT_TERM_HEIGHT", PHP_SSH2_DEFAULT_TERM_HEIGHT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_DEFAULT_TERM_UNIT", PHP_SSH2_DEFAULT_TERM_UNIT, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("SSH2_STREAM_STDIO", 0, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_STREAM_STDERR", SSH_EXTENDED_DATA_STDERR, CONST_CS | CONST_PERSISTENT); + + /* events/revents */ + REGISTER_LONG_CONSTANT("SSH2_POLLIN", LIBSSH2_POLLFD_POLLIN, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLLEXT", LIBSSH2_POLLFD_POLLEXT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLLOUT", LIBSSH2_POLLFD_POLLOUT, CONST_CS | CONST_PERSISTENT); + + /* revents only */ + REGISTER_LONG_CONSTANT("SSH2_POLLERR", LIBSSH2_POLLFD_POLLERR, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLLHUP", LIBSSH2_POLLFD_POLLHUP, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLLNVAL", LIBSSH2_POLLFD_POLLNVAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLL_SESSION_CLOSED", LIBSSH2_POLLFD_SESSION_CLOSED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLL_CHANNEL_CLOSED", LIBSSH2_POLLFD_CHANNEL_CLOSED, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("SSH2_POLL_LISTENER_CLOSED", LIBSSH2_POLLFD_LISTENER_CLOSED, CONST_CS | CONST_PERSISTENT); + + return (php_register_url_stream_wrapper("ssh2.shell", &php_ssh2_stream_wrapper_shell) == SUCCESS && + php_register_url_stream_wrapper("ssh2.exec", &php_ssh2_stream_wrapper_exec) == SUCCESS && + php_register_url_stream_wrapper("ssh2.tunnel", &php_ssh2_stream_wrapper_tunnel) == SUCCESS && + php_register_url_stream_wrapper("ssh2.scp", &php_ssh2_stream_wrapper_scp) == SUCCESS && + php_register_url_stream_wrapper("ssh2.sftp", &php_ssh2_sftp_wrapper) == SUCCESS) + ? SUCCESS + : FAILURE; +} +/* }}} */ + +int php_swoole_ssh2_mshutdown() { + return (php_unregister_url_stream_wrapper("ssh2.shell") == SUCCESS && + php_unregister_url_stream_wrapper("ssh2.exec") == SUCCESS && + php_unregister_url_stream_wrapper("ssh2.tunnel") == SUCCESS && + php_unregister_url_stream_wrapper("ssh2.scp") == SUCCESS && + php_unregister_url_stream_wrapper("ssh2.sftp") == SUCCESS) + ? SUCCESS + : FAILURE; +} + +void php_swoole_ssh2_minfo() { + php_info_print_table_row(2, "SSH2 support", "enabled"); + php_info_print_table_row(2, "libssh2 banner", LIBSSH2_SSH_BANNER); +} diff --git a/thirdparty/php/ssh2/ssh2_fopen_wrappers.cc b/thirdparty/php/ssh2/ssh2_fopen_wrappers.cc new file mode 100644 index 0000000000..cb85abc2b1 --- /dev/null +++ b/thirdparty/php/ssh2/ssh2_fopen_wrappers.cc @@ -0,0 +1,1484 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ + +#include "php_ssh2.h" +#include "php_swoole_ssh2_hook.h" + +void *php_ssh2_zval_from_resource_handle(int handle) { + zval *val; + zend_resource *zr; + ZEND_HASH_FOREACH_VAL(&EG(regular_list), val) { + zr = Z_RES_P(val); + if (zr->handle == handle) { + return val; + } + } + ZEND_HASH_FOREACH_END(); + return NULL; +} + +/* ********************** + * channel_stream_ops * + ********************** */ + +static ssize_t php_ssh2_channel_stream_write(php_stream *stream, const char *buf, size_t count) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + ssize_t writestate; + LIBSSH2_SESSION *session; + + session = + (LIBSSH2_SESSION *) zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + +#ifdef PHP_SSH2_SESSION_TIMEOUT + if (abstract->is_blocking) { + ssh2_set_socket_timeout(session, abstract->timeout); + } +#endif + + writestate = libssh2_channel_write_ex(abstract->channel, abstract->streamid, buf, count); + +#ifdef PHP_SSH2_SESSION_TIMEOUT + if (abstract->is_blocking) { + ssh2_set_socket_timeout(session, -1); + } +#endif + + if (writestate < 0) { + char *error_msg = NULL; + if (libssh2_session_last_error(session, &error_msg, NULL, 0) == writestate) { + php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, writestate); + } + + stream->eof = 1; + } + + return writestate; +} + +static ssize_t php_ssh2_channel_stream_read(php_stream *stream, char *buf, size_t count) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + ssize_t readstate; + auto session = ssh2_get_session(abstract); + + stream->eof = libssh2_channel_eof(abstract->channel); + +#ifdef PHP_SSH2_SESSION_TIMEOUT + if (abstract->is_blocking) { + ssh2_set_socket_timeout(session, abstract->timeout); + } +#endif + + readstate = libssh2_channel_read_ex(abstract->channel, abstract->streamid, buf, count); + +#ifdef PHP_SSH2_SESSION_TIMEOUT + if (abstract->is_blocking) { + ssh2_set_socket_timeout(session, -1); + } +#endif + + if (readstate < 0) { + char *error_msg = NULL; + if (libssh2_session_last_error(session, &error_msg, NULL, 0) == readstate) { + php_error_docref(NULL, E_WARNING, "Failure '%s' (%ld)", error_msg, readstate); + } + + stream->eof = 1; + readstate = 0; + } + return readstate; +} + +static int php_ssh2_channel_stream_close(php_stream *stream, int close_handle) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + + if (!abstract->refcount || (--(*(abstract->refcount)) == 0)) { + /* Last one out, turn off the lights */ + if (abstract->refcount) { + efree(abstract->refcount); + } + auto session = ssh2_get_session(abstract); + libssh2_channel_eof(abstract->channel); + libssh2_channel_free(abstract->channel); + zend_list_delete(abstract->session_rsrc); + } + efree(abstract); + + return 0; +} + +static int php_ssh2_channel_stream_flush(php_stream *stream) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + auto session = ssh2_get_session(abstract); + + return libssh2_channel_flush_ex(abstract->channel, abstract->streamid); +} + +static int php_ssh2_channel_stream_cast(php_stream *stream, int castas, void **ret) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + LIBSSH2_SESSION *session; + php_ssh2_session_data **session_data; + + session = + (LIBSSH2_SESSION *) zend_fetch_resource(abstract->session_rsrc, PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + session_data = (php_ssh2_session_data **) libssh2_session_abstract(session); + + switch (castas) { + case PHP_STREAM_AS_FD: + case PHP_STREAM_AS_FD_FOR_SELECT: + case PHP_STREAM_AS_SOCKETD: + if (ret) { + *(php_socket_t *) ret = (*session_data)->socket->get_fd(); + } + return SUCCESS; + default: + return FAILURE; + } +} + +static int php_ssh2_channel_stream_set_option(php_stream *stream, int option, int value, void *ptrparam) { + php_ssh2_channel_data *abstract = (php_ssh2_channel_data *) stream->abstract; + auto session = ssh2_get_session(abstract); + int ret; + + switch (option) { + case PHP_STREAM_OPTION_BLOCKING: { + ret = abstract->is_blocking; + abstract->is_blocking = value; + return ret; + } + case PHP_STREAM_OPTION_META_DATA_API: { + add_assoc_long((zval *) ptrparam, "exit_status", libssh2_channel_get_exit_status(abstract->channel)); + break; + } + case PHP_STREAM_OPTION_READ_TIMEOUT: { + ret = abstract->timeout; +#ifdef PHP_SSH2_SESSION_TIMEOUT + struct timeval tv = *(struct timeval *) ptrparam; + abstract->timeout = tv.tv_sec * 1000 + (tv.tv_usec / 1000); +#else + php_error_docref(NULL, E_WARNING, "No support for ssh2 stream timeout. Please recompile with libssh2 >= 1.2.9"); +#endif + return ret; + } + case PHP_STREAM_OPTION_CHECK_LIVENESS: { + return stream->eof = libssh2_channel_eof(abstract->channel); + } + } + + return -1; +} + +php_stream_ops php_ssh2_channel_stream_ops = { + php_ssh2_channel_stream_write, + php_ssh2_channel_stream_read, + php_ssh2_channel_stream_close, + php_ssh2_channel_stream_flush, + PHP_SSH2_CHANNEL_STREAM_NAME, + NULL, /* seek */ + php_ssh2_channel_stream_cast, + NULL, /* stat */ + php_ssh2_channel_stream_set_option, +}; + +/* ********************* + * Magic Path Helper * + ********************* */ + +/* {{{ php_ssh2_fopen_wraper_parse_path + * Parse an ssh2.*:// path + */ +php_url *php_ssh2_fopen_wraper_parse_path(const char *path, + const char *type, + php_stream_context *context, + LIBSSH2_SESSION **psession, + zend_resource **presource, + LIBSSH2_SFTP **psftp, + zend_resource **psftp_rsrc) { + php_ssh2_sftp_data *sftp_data = NULL; + LIBSSH2_SESSION *session; + php_url *resource; + zval *methods = NULL, *callbacks = NULL, zsession, *tmpzval; + zend_long resource_id; + const char *h; + char *username = NULL, *password = NULL, *pubkey_file = NULL, *privkey_file = NULL; + int username_len = 0, password_len = 0; + + h = strstr(path, "Resource id #"); + if (h) { + /* Starting with 5.6.28, 7.0.13 need to be clean, else php_url_parse will fail */ + char *tmp = estrdup(path); + + strncpy(tmp + (h - path), h + sizeof("Resource id #") - 1, strlen(tmp) - sizeof("Resource id #")); + resource = php_url_parse(tmp); + efree(tmp); + } else { + resource = php_url_parse(path); + } + if (!resource || !resource->path) { + return NULL; + } + + if (strncmp(ZSTR_VAL(resource->scheme), "ssh2.", sizeof("ssh2.") - 1)) { + /* Not an ssh wrapper */ + php_url_free(resource); + return NULL; + } + + if (strcmp(ZSTR_VAL(resource->scheme) + sizeof("ssh2.") - 1, type)) { + /* Wrong ssh2. wrapper type */ + php_url_free(resource); + return NULL; + } + + if (!resource->host) { + return NULL; + } + + /* + * Find resource->path in the path string, then copy the entire string from the original path. + * This includes ?query#fragment in the path string + */ + + /* Look for a resource ID to reuse a session */ + if (is_numeric_string(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), &resource_id, NULL, 0) == IS_LONG) { + php_ssh2_sftp_data *sftp_data; + zval *zresource; + + if ((zresource = (zval *) php_ssh2_zval_from_resource_handle(resource_id)) == NULL) { + php_url_free(resource); + return NULL; + } + + if (psftp) { + /* suppress potential warning by passing NULL as resource_type_name */ + sftp_data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zresource), NULL, le_ssh2_sftp); + if (sftp_data) { + /* Want the sftp layer */ + Z_ADDREF_P(zresource); + *psftp_rsrc = Z_RES_P(zresource); + *psftp = sftp_data->sftp; + *presource = sftp_data->session_rsrc; + *psession = sftp_data->session; + return resource; + } + } + session = + (LIBSSH2_SESSION *) zend_fetch_resource(Z_RES_P(zresource), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (session) { + if (psftp) { + /* We need an sftp layer too */ + LIBSSH2_SFTP *sftp = libssh2_sftp_init(session); + + if (!sftp) { + php_url_free(resource); + return NULL; + } + sftp_data = (php_ssh2_sftp_data *) emalloc(sizeof(php_ssh2_sftp_data)); + sftp_data->sftp = sftp; + sftp_data->session = session; + sftp_data->session_rsrc = Z_RES_P(zresource); + Z_ADDREF_P(zresource); + *psftp_rsrc = zend_register_resource(sftp_data, le_ssh2_sftp); + *psftp = sftp; + *presource = Z_RES_P(zresource); + *psession = session; + return resource; + } + Z_ADDREF_P(zresource); + *presource = Z_RES_P(zresource); + *psession = session; + return resource; + } + } + + /* Fallback on finding it in the context */ + if (ZSTR_VAL(resource->host)[0] == 0 && context && psftp && + (tmpzval = php_stream_context_get_option(context, "ssh2", "sftp")) != NULL && + Z_TYPE_P(tmpzval) == IS_RESOURCE) { + php_ssh2_sftp_data *sftp_data; + sftp_data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(tmpzval), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp); + if (sftp_data) { + Z_ADDREF_P(tmpzval); + *psftp_rsrc = Z_RES_P(tmpzval); + *psftp = sftp_data->sftp; + *presource = sftp_data->session_rsrc; + *psession = sftp_data->session; + return resource; + } + } + if (ZSTR_VAL(resource->host)[0] == 0 && context && + (tmpzval = php_stream_context_get_option(context, "ssh2", "session")) != NULL && + Z_TYPE_P(tmpzval) == IS_RESOURCE) { + session = (LIBSSH2_SESSION *) zend_fetch_resource(Z_RES_P(tmpzval), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session); + if (session) { + if (psftp) { + /* We need an SFTP layer too! */ + LIBSSH2_SFTP *sftp = libssh2_sftp_init(session); + php_ssh2_sftp_data *sftp_data; + + if (!sftp) { + php_url_free(resource); + return NULL; + } + sftp_data = (php_ssh2_sftp_data *) emalloc(sizeof(php_ssh2_sftp_data)); + sftp_data->sftp = sftp; + sftp_data->session = session; + sftp_data->session_rsrc = Z_RES_P(tmpzval); + Z_ADDREF_P(tmpzval); + *psftp_rsrc = zend_register_resource(sftp_data, le_ssh2_sftp); + *psftp = sftp; + *presource = Z_RES_P(tmpzval); + *psession = session; + return resource; + } + Z_ADDREF_P(tmpzval); + *psession = session; + *presource = Z_RES_P(tmpzval); + return resource; + } + } + + /* Make our own connection then */ + if (!resource->port) { + resource->port = 22; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "methods")) != NULL && + Z_TYPE_P(tmpzval) == IS_ARRAY) { + methods = tmpzval; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "callbacks")) != NULL && + Z_TYPE_P(tmpzval) == IS_ARRAY) { + callbacks = tmpzval; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "username")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + username = Z_STRVAL_P(tmpzval); + username_len = Z_STRLEN_P(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "password")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + password = Z_STRVAL_P(tmpzval); + password_len = Z_STRLEN_P(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "pubkey_file")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + pubkey_file = Z_STRVAL_P(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "privkey_file")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + privkey_file = Z_STRVAL_P(tmpzval); + } + + if (resource->user) { + int len = ZSTR_LEN(resource->user); + + if (len) { + username = ZSTR_VAL(resource->user); + username_len = len; + } + } + + if (resource->pass) { + int len = ZSTR_LEN(resource->pass); + + if (len) { + password = ZSTR_VAL(resource->pass); + password_len = len; + } + } + + if (!username) { + /* username is a minimum */ + php_url_free(resource); + return NULL; + } + + session = php_ssh2_session_connect(ZSTR_VAL(resource->host), resource->port, methods, callbacks); + if (!session) { + /* Unable to connect! */ + php_url_free(resource); + return NULL; + } + + /* Authenticate */ + if (pubkey_file && privkey_file) { + if (php_check_open_basedir(pubkey_file) || php_check_open_basedir(privkey_file)) { + php_url_free(resource); + return NULL; + } + + /* Attempt pubkey authentication */ + if (!libssh2_userauth_publickey_fromfile(session, username, pubkey_file, privkey_file, password)) { + goto session_authed; + } + } + + if (password) { + /* Attempt password authentication */ + if (libssh2_userauth_password_ex(session, username, username_len, password, password_len, NULL) == 0) { + goto session_authed; + } + } + + /* Auth failure */ + php_url_free(resource); + if (Z_RES(zsession)) { + zend_list_delete(Z_RES(zsession)); + } + return NULL; + +session_authed: + ZVAL_RES(&zsession, zend_register_resource(session, le_ssh2_session)); + + if (psftp) { + LIBSSH2_SFTP *sftp; + zval zsftp{}; + + sftp = libssh2_sftp_init(session); + if (!sftp) { + php_url_free(resource); + zend_list_delete(Z_RES(zsession)); + return NULL; + } + + sftp_data = (php_ssh2_sftp_data *) emalloc(sizeof(php_ssh2_sftp_data)); + sftp_data->session = session; + sftp_data->sftp = sftp; + sftp_data->session_rsrc = Z_RES(zsession); + + // TODO Sean-Der + // ZEND_REGISTER_RESOURCE(sftp_data, le_ssh2_sftp); + *psftp_rsrc = Z_RES(zsftp); + *psftp = sftp; + } + + *presource = Z_RES(zsession); + *psession = session; + + return resource; +} +/* }}} */ + +/* ***************** + * Shell Wrapper * + ***************** */ + +/* {{{ php_ssh2_shell_open + * Make a stream from a session + */ +static php_stream *php_ssh2_shell_open(LIBSSH2_SESSION *session, + zend_resource *resource, + const char *term, + int term_len, + zval *environment, + long width, + long height, + long type) { + LIBSSH2_CHANNEL *channel; + php_ssh2_channel_data *channel_data; + php_stream *stream; + + channel = libssh2_channel_open_session(session); + if (!channel) { + php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host"); + return NULL; + } + + if (environment) { + zend_string *key; + int key_type; + zend_ulong idx; + + for (zend_hash_internal_pointer_reset(HASH_OF(environment)); + (key_type = zend_hash_get_current_key(HASH_OF(environment), &key, &idx)) != HASH_KEY_NON_EXISTENT; + zend_hash_move_forward(HASH_OF(environment))) { + if (key_type == HASH_KEY_IS_STRING) { + zval *value; + + if ((value = zend_hash_get_current_data(HASH_OF(environment))) != NULL) { + zval copyval = *value; + + zval_copy_ctor(©val); + convert_to_string(©val); + + if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) { + php_error_docref( + NULL, E_WARNING, "Failed setting %s=%s on remote end", ZSTR_VAL(key), Z_STRVAL(copyval)); + } + zval_dtor(©val); + } + } else { + php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array"); + } + } + } + + if (type == PHP_SSH2_TERM_UNIT_CHARS) { + if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, width, height, 0, 0)) { + php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld characters", term, width, height); + libssh2_channel_free(channel); + return NULL; + } + } else { + if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, 0, 0, width, height)) { + php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld pixels", term, width, height); + libssh2_channel_free(channel); + return NULL; + } + } + + if (libssh2_channel_shell(channel)) { + php_error_docref(NULL, E_WARNING, "Unable to request shell from remote host"); + libssh2_channel_free(channel); + return NULL; + } + + /* Turn it into a stream */ + channel_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + channel_data->channel = channel; + channel_data->streamid = 0; + channel_data->is_blocking = 0; + channel_data->timeout = 0; + channel_data->session_rsrc = resource; + channel_data->refcount = NULL; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+"); + + return stream; +} +/* }}} */ + +/* {{{ php_ssh2_fopen_wrapper_shell + * ssh2.shell:// fopen wrapper + */ +static php_stream *php_ssh2_fopen_wrapper_shell(php_stream_wrapper *wrapper, + const char *path, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + LIBSSH2_SESSION *session = NULL; + php_stream *stream; + zval *tmpzval, *environment = NULL; + const char *terminal = PHP_SSH2_DEFAULT_TERMINAL; + zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH; + zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT; + zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT; + zend_resource *rsrc = NULL; + int terminal_len = sizeof(PHP_SSH2_DEFAULT_TERMINAL) - 1; + php_url *resource; + char *s; + + resource = php_ssh2_fopen_wraper_parse_path(path, "shell", context, &session, &rsrc, NULL, NULL); + if (!resource || !session) { + return NULL; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "env")) != NULL && + Z_TYPE_P(tmpzval) == IS_ARRAY) { + environment = tmpzval; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + terminal = Z_STRVAL_P(tmpzval); + terminal_len = Z_STRLEN_P(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_width")) != NULL) { + zval copyval; + copyval = *tmpzval; + convert_to_long(©val); + width = Z_LVAL_P(©val); + zval_ptr_dtor(©val); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_height")) != NULL) { + zval copyval; + copyval = *tmpzval; + convert_to_long(©val); + height = Z_LVAL_P(©val); + zval_ptr_dtor(©val); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_units")) != NULL) { + zval copyval; + copyval = *tmpzval; + convert_to_long(©val); + type = Z_LVAL_P(©val); + zval_ptr_dtor(©val); + } + + s = resource->path ? ZSTR_VAL(resource->path) : NULL; + + if (s && s[0] == '/') { + /* Terminal type encoded into URL overrides context terminal type */ + char *p; + + s++; + p = strchr(s, '/'); + if (p) { + if (p - s) { + terminal = s; + terminal_len = p - terminal; + s += terminal_len + 1; + } else { + /* "null" terminal given, skip it */ + s++; + } + } else { + int len; + + if ((len = strlen(path + 1))) { + terminal = s; + terminal_len = len; + s += len; + } + } + } + + /* TODO: Accept resolution and environment vars as URL style parameters + * ssh2.shell://hostorresource/terminal/99x99c?envvar=envval&envvar=envval.... + */ + stream = php_ssh2_shell_open(session, rsrc, terminal, terminal_len, environment, width, height, type); + if (!stream) { + zend_list_delete(rsrc); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +static php_stream_wrapper_ops php_ssh2_shell_stream_wops = {php_ssh2_fopen_wrapper_shell, + NULL, /* stream_close */ + NULL, /* stat */ + NULL, /* stat_url */ + NULL, /* opendir */ + "ssh2.shell"}; + +php_stream_wrapper php_ssh2_stream_wrapper_shell = {&php_ssh2_shell_stream_wops, NULL, 0}; + +/* {{{ proto stream ssh2_shell(resource session[, string term_type[, array env[, int width, int height[, int + * width_height_type]]]]) Open a shell at the remote end and allocate a channel for it + */ +PHP_FUNCTION(ssh2_shell) { + LIBSSH2_SESSION *session; + php_stream *stream; + zval *zsession; + zval *environment = NULL; + const char *term = PHP_SSH2_DEFAULT_TERMINAL; + size_t term_len = sizeof(PHP_SSH2_DEFAULT_TERMINAL) - 1; + zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH; + zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT; + zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT; + int argc = ZEND_NUM_ARGS(); + + if (argc == 5) { + php_error_docref(NULL, E_ERROR, "width specified without height parameter"); + RETURN_FALSE; + } + + if (zend_parse_parameters(argc, "r|sa!lll", &zsession, &term, &term_len, &environment, &width, &height, &type) == + FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + stream = php_ssh2_shell_open(session, Z_RES_P(zsession), term, term_len, environment, width, height, type); + if (!stream) { + RETURN_FALSE; + } + + /* Ensure that channels are freed BEFORE the sessions they belong to */ + Z_ADDREF_P(zsession); + + php_stream_to_zval(stream, return_value); +} +/* }}} */ + +PHP_FUNCTION(ssh2_shell_resize) { + zend_long width; + zend_long height; + zend_long width_px = 0; + zend_long height_px = 0; + zval *zparent; + php_stream *parent; + php_ssh2_channel_data *data; + + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, "rll|ll", &zparent, &width, &height, &width_px, &height_px) == FAILURE) { + return; + } + + php_stream_from_zval(parent, zparent); + + if (parent->ops != &php_ssh2_channel_stream_ops) { + php_error_docref(NULL, E_WARNING, "Provided stream is not of type " PHP_SSH2_CHANNEL_STREAM_NAME); + RETURN_FALSE; + } + + data = (php_ssh2_channel_data *) parent->abstract; + auto session = ssh2_get_session(data); + + libssh2_channel_request_pty_size_ex(data->channel, width, height, width_px, height_px); + + RETURN_TRUE; +} + +/* **************** + * Exec Wrapper * + **************** */ + +/* {{{ php_ssh2_exec_command + * Make a stream from a session + */ +static php_stream *php_ssh2_exec_command(LIBSSH2_SESSION *session, + zend_resource *rsrc, + char *command, + char *term, + int term_len, + zval *environment, + long width, + long height, + long type) { + LIBSSH2_CHANNEL *channel; + php_ssh2_channel_data *channel_data; + php_stream *stream; + + channel = libssh2_channel_open_session(session); + if (!channel) { + php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host"); + return NULL; + } + + if (environment) { + zend_string *key = NULL; + int key_type; + zend_ulong idx = 0; + HashPosition pos; + + for (zend_hash_internal_pointer_reset_ex(HASH_OF(environment), &pos); + (key_type = zend_hash_get_current_key_ex(HASH_OF(environment), &key, &idx, &pos)) != HASH_KEY_NON_EXISTENT; + zend_hash_move_forward_ex(HASH_OF(environment), &pos)) { + if (key_type == HASH_KEY_IS_STRING) { + zval *value; + + if ((value = zend_hash_get_current_data(HASH_OF(environment))) != NULL) { + zval copyval = *value; + + zval_copy_ctor(©val); + convert_to_string(©val); + if (libssh2_channel_setenv_ex(channel, key->val, key->len, Z_STRVAL(copyval), Z_STRLEN(copyval))) { + php_error_docref( + NULL, E_WARNING, "Failed setting %s=%s on remote end", ZSTR_VAL(key), Z_STRVAL(copyval)); + } + zval_dtor(©val); + } + } else { + php_error_docref(NULL, E_NOTICE, "Skipping numeric index in environment array"); + } + } + } + + if (term) { + if (type == PHP_SSH2_TERM_UNIT_CHARS) { + if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, width, height, 0, 0)) { + php_error_docref( + NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld characters", term, width, height); + libssh2_channel_free(channel); + return NULL; + } + } else { + if (libssh2_channel_request_pty_ex(channel, term, term_len, NULL, 0, 0, 0, width, height)) { + php_error_docref(NULL, E_WARNING, "Failed allocating %s pty at %ldx%ld pixels", term, width, height); + libssh2_channel_free(channel); + return NULL; + } + } + } + + if (libssh2_channel_exec(channel, command)) { + php_error_docref(NULL, E_WARNING, "Unable to request command execution on remote host"); + libssh2_channel_free(channel); + return NULL; + } + + /* Turn it into a stream */ + channel_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + channel_data->channel = channel; + channel_data->streamid = 0; + channel_data->is_blocking = 0; + channel_data->timeout = 0; + channel_data->session_rsrc = rsrc; + channel_data->refcount = NULL; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+"); + + return stream; +} +/* }}} */ + +/* {{{ php_ssh2_fopen_wrapper_exec + * ssh2.exec:// fopen wrapper + */ +static php_stream *php_ssh2_fopen_wrapper_exec(php_stream_wrapper *wrapper, + const char *path, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + LIBSSH2_SESSION *session = NULL; + php_stream *stream; + zval *tmpzval, *environment = NULL; + zend_resource *rsrc = NULL; + php_url *resource; + char *terminal = NULL; + int terminal_len = 0; + long width = PHP_SSH2_DEFAULT_TERM_WIDTH; + long height = PHP_SSH2_DEFAULT_TERM_HEIGHT; + long type = PHP_SSH2_DEFAULT_TERM_UNIT; + + resource = php_ssh2_fopen_wraper_parse_path(path, "exec", context, &session, &rsrc, NULL, NULL); + if (!resource || !session) { + return NULL; + } + if (!resource->path) { + php_url_free(resource); + zend_list_delete(rsrc); + return NULL; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "env")) != NULL && + Z_TYPE_P(tmpzval) == IS_ARRAY) { + environment = tmpzval; + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term")) != NULL && + Z_TYPE_P(tmpzval) == IS_STRING) { + terminal = Z_STRVAL_P(tmpzval); + terminal_len = Z_STRLEN_P(tmpzval); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_width")) != NULL) { + zval copyval; + copyval = *tmpzval; + convert_to_long(©val); + width = Z_LVAL_P(©val); + zval_ptr_dtor(©val); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_height")) != NULL) { + zval copyval; + copyval = *tmpzval; + convert_to_long(©val); + height = Z_LVAL_P(©val); + zval_ptr_dtor(©val); + } + + if (context && (tmpzval = php_stream_context_get_option(context, "ssh2", "term_units")) != NULL) { + zval *copyval; + copyval = tmpzval; + convert_to_long(copyval); + type = Z_LVAL_P(copyval); + zval_ptr_dtor(copyval); + } + + stream = php_ssh2_exec_command( + session, rsrc, ZSTR_VAL(resource->path) + 1, terminal, terminal_len, environment, width, height, type); + if (!stream) { + zend_list_delete(rsrc); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +static php_stream_wrapper_ops php_ssh2_exec_stream_wops = {php_ssh2_fopen_wrapper_exec, + NULL, /* stream_close */ + NULL, /* stat */ + NULL, /* stat_url */ + NULL, /* opendir */ + "ssh2.exec"}; + +php_stream_wrapper php_ssh2_stream_wrapper_exec = {&php_ssh2_exec_stream_wops, NULL, 0}; + +/* {{{ proto stream ssh2_exec(resource session, string command[, string pty[, array env[, int width[, int height[, int + * width_height_type]]]]]) Execute a command at the remote end and allocate a channel for it + * + * This function has a dirty little secret.... pty and env can be in either order.... shhhh... don't tell anyone + */ +PHP_FUNCTION(ssh2_exec) { + LIBSSH2_SESSION *session; + php_stream *stream; + zval *zsession; + zval *environment = NULL; + zval *zpty = NULL; + char *command; + size_t command_len; + zend_long width = PHP_SSH2_DEFAULT_TERM_WIDTH; + zend_long height = PHP_SSH2_DEFAULT_TERM_HEIGHT; + zend_long type = PHP_SSH2_DEFAULT_TERM_UNIT; + char *term = NULL; + int term_len = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "rs|z!z!lll", + &zsession, + &command, + &command_len, + &zpty, + &environment, + &width, + &height, + &type) == FAILURE) { + return; + } + + if (zpty && Z_TYPE_P(zpty) == IS_ARRAY) { + /* Swap pty and environment -- old call style */ + zval *tmp = zpty; + zpty = environment; + environment = tmp; + } + + if (environment && Z_TYPE_P(environment) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "ssh2_exec() expects arg 4 to be of type array"); + RETURN_FALSE; + } + + if (zpty) { + convert_to_string(zpty); + term = Z_STRVAL_P(zpty); + term_len = Z_STRLEN_P(zpty); + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + stream = + php_ssh2_exec_command(session, Z_RES_P(zsession), command, term, term_len, environment, width, height, type); + if (!stream) { + RETURN_FALSE; + } + + /* Ensure that channels are freed BEFORE the sessions they belong to */ + Z_ADDREF_P(zsession); + + php_stream_to_zval(stream, return_value); +} +/* }}} */ + +/* *************** + * SCP Wrapper * + *************** */ + +/* {{{ php_ssh2_scp_xfer + * Make a stream from a session + */ +static php_stream *php_ssh2_scp_xfer(LIBSSH2_SESSION *session, zend_resource *rsrc, char *filename) { + LIBSSH2_CHANNEL *channel; + php_ssh2_channel_data *channel_data; + php_stream *stream; + +#ifdef SW_USE_SSH2_ASYNC_HOOK + php_ssh2_session_data *session_res = libssh2_session_abstract(session); +#endif + channel = libssh2_scp_recv(session, filename, NULL); + if (!channel) { + char *error; + libssh2_session_last_error(session, &error, NULL, 0); + php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host: %s", error); + return NULL; + } + + /* Turn it into a stream */ + channel_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + channel_data->channel = channel; + channel_data->streamid = 0; + channel_data->is_blocking = 0; + channel_data->timeout = 0; + channel_data->session_rsrc = rsrc; + channel_data->refcount = NULL; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r"); + + return stream; +} +/* }}} */ + +/* {{{ php_ssh2_fopen_wrapper_scp + * ssh2.scp:// fopen wrapper (Read mode only, if you want to know why write mode isn't supported as a stream, take a + * look at the SCP protocol) + */ +static php_stream *php_ssh2_fopen_wrapper_scp(php_stream_wrapper *wrapper, + const char *path, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + LIBSSH2_SESSION *session = NULL; + php_stream *stream; + zend_resource *rsrc = NULL; + php_url *resource; + + if (strchr(mode, '+') || strchr(mode, 'a') || strchr(mode, 'w')) { + return NULL; + } + + resource = php_ssh2_fopen_wraper_parse_path(path, "scp", context, &session, &rsrc, NULL, NULL); + if (!resource || !session) { + return NULL; + } + if (!resource->path) { + php_url_free(resource); + zend_list_delete(rsrc); + return NULL; + } + + stream = php_ssh2_scp_xfer(session, rsrc, ZSTR_VAL(resource->path)); + if (!stream) { + zend_list_delete(rsrc); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +static php_stream_wrapper_ops php_ssh2_scp_stream_wops = {php_ssh2_fopen_wrapper_scp, + NULL, /* stream_close */ + NULL, /* stat */ + NULL, /* stat_url */ + NULL, /* opendir */ + "ssh2.scp"}; + +php_stream_wrapper php_ssh2_stream_wrapper_scp = {&php_ssh2_scp_stream_wops, NULL, 0}; + +/* {{{ proto bool ssh2_scp_recv(resource session, string remote_file, string local_file) + * Request a file via SCP + */ +PHP_FUNCTION(ssh2_scp_recv) { + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *remote_file; + struct stat sb; + php_stream *local_file; + zval *zsession; + char *remote_filename, *local_filename; + size_t remote_filename_len, local_filename_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "rss", + &zsession, + &remote_filename, + &remote_filename_len, + &local_filename, + &local_filename_len) == FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + remote_file = libssh2_scp_recv(session, remote_filename, &sb); + if (!remote_file) { + php_error_docref(NULL, E_WARNING, "Unable to receive remote file"); + RETURN_FALSE; + } + + local_file = php_stream_open_wrapper(local_filename, "wb", REPORT_ERRORS, NULL); + if (!local_file) { + php_error_docref(NULL, E_WARNING, "Unable to write to local file"); + libssh2_channel_free(remote_file); + RETURN_FALSE; + } + + while (sb.st_size) { + char buffer[8192]; + int bytes_read; + + bytes_read = libssh2_channel_read(remote_file, buffer, sb.st_size > 8192 ? 8192 : sb.st_size); + if (bytes_read < 0) { + php_error_docref(NULL, E_WARNING, "Error reading from remote file"); + libssh2_channel_free(remote_file); + php_stream_close(local_file); + RETURN_FALSE; + } + php_stream_write(local_file, buffer, bytes_read); + sb.st_size -= bytes_read; + } + + libssh2_channel_free(remote_file); + php_stream_close(local_file); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto stream ssh2_scp_send(resource session, string local_file, string remote_file[, int create_mode = 0644]) + * Send a file via SCP + */ +PHP_FUNCTION(ssh2_scp_send) { + LIBSSH2_SESSION *session; + LIBSSH2_CHANNEL *remote_file; + php_stream *local_file; + zval *zsession; + char *local_filename, *remote_filename; + size_t local_filename_len, remote_filename_len; + zend_long create_mode = 0644; + php_stream_statbuf ssb; + int argc = ZEND_NUM_ARGS(); + + if (zend_parse_parameters(argc, + "rss|l", + &zsession, + &local_filename, + &local_filename_len, + &remote_filename, + &remote_filename_len, + &create_mode) == FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + local_file = php_stream_open_wrapper(local_filename, "rb", REPORT_ERRORS, NULL); + if (!local_file) { + php_error_docref(NULL, E_WARNING, "Unable to read source file"); + RETURN_FALSE; + } + + if (php_stream_stat(local_file, &ssb)) { + php_error_docref(NULL, E_WARNING, "Failed statting local file"); + php_stream_close(local_file); + RETURN_FALSE; + } + + if (argc < 4) { + create_mode = ssb.sb.st_mode & 0777; + } + + remote_file = + libssh2_scp_send_ex(session, remote_filename, create_mode, ssb.sb.st_size, ssb.sb.st_atime, ssb.sb.st_mtime); + if (!remote_file) { + int last_error = 0; + char *error_msg = NULL; + + last_error = libssh2_session_last_error(session, &error_msg, NULL, 0); + php_error_docref(NULL, E_WARNING, "Failure creating remote file: %s (%d)", error_msg, last_error); + php_stream_close(local_file); + RETURN_FALSE; + } + + while (ssb.sb.st_size) { + char buffer[8192]; + ssize_t toread = MIN(8192, ssb.sb.st_size); + ssize_t bytesread = php_stream_read(local_file, buffer, toread); + ssize_t sent = 0; + ssize_t justsent = 0; + + if (bytesread <= 0 || bytesread > toread) { + php_error_docref(NULL, E_WARNING, "Failed copying file 2"); + php_stream_close(local_file); + libssh2_channel_free(remote_file); + RETURN_FALSE; + } + + while (bytesread - sent > 0) { + if ((justsent = libssh2_channel_write(remote_file, (buffer + sent), bytesread - sent)) < 0) { + switch (justsent) { + case LIBSSH2_ERROR_EAGAIN: + php_error_docref(NULL, E_WARNING, "Operation would block"); + break; + + case LIBSSH2_ERROR_ALLOC: + php_error_docref(NULL, E_WARNING, "An internal memory allocation call failed"); + break; + + case LIBSSH2_ERROR_SOCKET_SEND: + php_error_docref(NULL, E_WARNING, "Unable to send data on socket"); + break; + + case LIBSSH2_ERROR_CHANNEL_CLOSED: + php_error_docref(NULL, E_WARNING, "The channel has been closed"); + break; + + case LIBSSH2_ERROR_CHANNEL_EOF_SENT: + php_error_docref(NULL, E_WARNING, "The channel has been requested to be closed"); + break; + } + + php_stream_close(local_file); + libssh2_channel_free(remote_file); + RETURN_FALSE; + } + sent = sent + justsent; + } + ssb.sb.st_size -= bytesread; + } + + libssh2_channel_flush_ex(remote_file, LIBSSH2_CHANNEL_FLUSH_ALL); + php_stream_close(local_file); + libssh2_channel_free(remote_file); + RETURN_TRUE; +} +/* }}} */ + +/* *************************** + * Direct TCP/IP Transport * + *************************** */ + +/* {{{ php_ssh2_direct_tcpip + * Make a stream from a session + */ +static php_stream *php_ssh2_direct_tcpip(LIBSSH2_SESSION *session, zend_resource *rsrc, char *host, int port) { + LIBSSH2_CHANNEL *channel; + php_ssh2_channel_data *channel_data; + php_stream *stream; + + channel = libssh2_channel_direct_tcpip(session, host, port); + if (!channel) { + php_error_docref(NULL, E_WARNING, "Unable to request a channel from remote host"); + return NULL; + } + + /* Turn it into a stream */ + channel_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + channel_data->channel = channel; + channel_data->streamid = 0; + channel_data->is_blocking = 0; + channel_data->timeout = 0; + channel_data->session_rsrc = rsrc; + channel_data->refcount = NULL; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, channel_data, 0, "r+"); + + return stream; +} +/* }}} */ + +/* {{{ php_ssh2_fopen_wrapper_tunnel + * ssh2.tunnel:// fopen wrapper + */ +static php_stream *php_ssh2_fopen_wrapper_tunnel(php_stream_wrapper *wrapper, + const char *path, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + LIBSSH2_SESSION *session = NULL; + php_stream *stream = NULL; + php_url *resource; + char *host = NULL; + int port = 0; + zend_resource *rsrc; + + resource = php_ssh2_fopen_wraper_parse_path(path, "tunnel", context, &session, &rsrc, NULL, NULL); + if (!resource || !session) { + return NULL; + } + + if (resource->path && ZSTR_VAL(resource->path)[0] == '/') { + char *colon; + + host = ZSTR_VAL(resource->path) + 1; + if (*host == '[') { + /* IPv6 Encapsulated Format */ + host++; + colon = strstr(host, "]:"); + if (colon) { + *colon = 0; + colon += 2; + } + } else { + colon = strchr(host, ':'); + if (colon) { + *(colon++) = 0; + } + } + if (colon) { + port = atoi(colon); + } + } + + if ((port <= 0) || (port > 65535) || !host || (strlen(host) == 0)) { + /* Invalid connection criteria */ + php_url_free(resource); + zend_list_delete(rsrc); + return NULL; + } + + stream = php_ssh2_direct_tcpip(session, rsrc, host, port); + if (!stream) { + zend_list_delete(rsrc); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +static php_stream_wrapper_ops php_ssh2_tunnel_stream_wops = {php_ssh2_fopen_wrapper_tunnel, + NULL, /* stream_close */ + NULL, /* stat */ + NULL, /* stat_url */ + NULL, /* opendir */ + "ssh2.tunnel"}; + +php_stream_wrapper php_ssh2_stream_wrapper_tunnel = {&php_ssh2_tunnel_stream_wops, NULL, 0}; + +/* {{{ proto stream ssh2_tunnel(resource session, string host, int port) + * Tunnel to remote TCP/IP host/port + */ +PHP_FUNCTION(ssh2_tunnel) { + LIBSSH2_SESSION *session; + php_stream *stream; + zval *zsession; + char *host; + size_t host_len; + zend_long port; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rsl", &zsession, &host, &host_len, &port) == FAILURE) { + return; + } + + SSH2_FETCH_AUTHENTICATED_SESSION(session, zsession); + + stream = php_ssh2_direct_tcpip(session, Z_RES_P(zsession), host, port); + if (!stream) { + RETURN_FALSE; + } + + /* Ensure that channels are freed BEFORE the sessions they belong to */ + Z_ADDREF_P(zsession); + + php_stream_to_zval(stream, return_value); +} +/* }}} */ + +/* ****************** + * Generic Helper * + ****************** */ + +/* {{{ proto stream ssh2_fetch_stream(stream channel, int streamid) + * Fetch an extended data stream + */ +PHP_FUNCTION(ssh2_fetch_stream) { + php_ssh2_channel_data *data, *stream_data; + php_stream *parent, *stream; + zval *zparent; + zend_long streamid; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rl", &zparent, &streamid) == FAILURE) { + return; + } + + if (streamid < 0) { + php_error_docref(NULL, E_WARNING, "Invalid stream ID requested"); + RETURN_FALSE; + } + + php_stream_from_zval(parent, zparent); + + if (parent->ops != &php_ssh2_channel_stream_ops) { + php_error_docref(NULL, E_WARNING, "Provided stream is not of type " PHP_SSH2_CHANNEL_STREAM_NAME); + RETURN_FALSE; + } + + data = (php_ssh2_channel_data *) parent->abstract; + + if (!data->refcount) { + data->refcount = (uchar *) emalloc(sizeof(uchar)); + *(data->refcount) = 1; + } + + if (*(data->refcount) == 255) { + php_error_docref(NULL, E_WARNING, "Too many streams associated to a single channel"); + RETURN_FALSE; + } + + (*(data->refcount))++; + + stream_data = (php_ssh2_channel_data *) emalloc(sizeof(php_ssh2_channel_data)); + memcpy(stream_data, data, sizeof(php_ssh2_channel_data)); + stream_data->streamid = streamid; + + stream = php_stream_alloc(&php_ssh2_channel_stream_ops, stream_data, 0, "r+"); + if (!stream) { + php_error_docref(NULL, E_WARNING, "Error opening substream"); + efree(stream_data); + (data->refcount)--; + RETURN_FALSE; + } + + php_stream_to_zval(stream, return_value); +} +/* }}} */ + +/* {{{ proto stream ssh2_send_eof(stream channel) + * Sends EOF to a stream. Primary use is to close stdin of an stdio stream. + */ +PHP_FUNCTION(ssh2_send_eof) { + php_ssh2_channel_data *data; + php_stream *parent; + zval *zparent; + int ssh2_ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zparent) == FAILURE) { + return; + } + + php_stream_from_zval(parent, zparent); + if (parent->ops != &php_ssh2_channel_stream_ops) { + php_error_docref(NULL, E_WARNING, "Provided stream is not of type " PHP_SSH2_CHANNEL_STREAM_NAME); + RETURN_FALSE; + } + + data = (php_ssh2_channel_data *) parent->abstract; + if (!data) { + php_error_docref(NULL, E_WARNING, "Abstract in stream is null"); + RETURN_FALSE; + } + + auto session = ssh2_get_session(data); + + ssh2_ret = libssh2_channel_send_eof(data->channel); + if (ssh2_ret < 0) { + php_error_docref(NULL, E_WARNING, "Couldn't send EOF to channel (Return code %d)", ssh2_ret); + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/thirdparty/php/ssh2/ssh2_sftp.cc b/thirdparty/php/ssh2/ssh2_sftp.cc new file mode 100644 index 0000000000..99f904f77e --- /dev/null +++ b/thirdparty/php/ssh2/ssh2_sftp.cc @@ -0,0 +1,907 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2016 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sara Golemon | + +----------------------------------------------------------------------+ +*/ + +#include "php_ssh2.h" + +#include "php_swoole_ssh2_hook.h" + +BEGIN_EXTERN_C() +#include "ext/standard/php_string.h" +END_EXTERN_C() + +/* ************************* + * Resource Housekeeping * + ************************* */ + +void php_ssh2_sftp_dtor(zend_resource *rsrc) { + php_ssh2_sftp_data *data = (php_ssh2_sftp_data *) rsrc->ptr; + + if (!data) { + return; + } + + if (data->session_rsrc->ptr != NULL) { + libssh2_sftp_shutdown(data->sftp); + } + + zend_list_delete(data->session_rsrc); + + efree(data); +} + +/* ***************** + * SFTP File Ops * + ***************** */ + +unsigned long php_ssh2_parse_fopen_modes(char *openmode) { + unsigned long flags = 0; + + if (strchr(openmode, 'a')) { + flags |= LIBSSH2_FXF_APPEND; + } + + if (strchr(openmode, 'w')) { + flags |= LIBSSH2_FXF_WRITE | LIBSSH2_FXF_TRUNC | LIBSSH2_FXF_CREAT; + } + + if (strchr(openmode, 'r')) { + flags |= LIBSSH2_FXF_READ; + } + + if (strchr(openmode, '+')) { + flags |= LIBSSH2_FXF_READ | LIBSSH2_FXF_WRITE; + } + + if (strchr(openmode, 'x')) { + flags |= LIBSSH2_FXF_WRITE | LIBSSH2_FXF_TRUNC | LIBSSH2_FXF_EXCL | LIBSSH2_FXF_CREAT; + } + + return flags; +} + +static inline int php_ssh2_sftp_attr2ssb(php_stream_statbuf *ssb, LIBSSH2_SFTP_ATTRIBUTES *attrs) { + memset(ssb, 0, sizeof(php_stream_statbuf)); + if (attrs->flags & LIBSSH2_SFTP_ATTR_SIZE) { + ssb->sb.st_size = attrs->filesize; + } + + if (attrs->flags & LIBSSH2_SFTP_ATTR_UIDGID) { + ssb->sb.st_uid = attrs->uid; + ssb->sb.st_gid = attrs->gid; + } + if (attrs->flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + ssb->sb.st_mode = attrs->permissions; + } + if (attrs->flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + ssb->sb.st_atime = attrs->atime; + ssb->sb.st_mtime = attrs->mtime; + } + + return 0; +} + +typedef struct _php_ssh2_sftp_handle_data { + LIBSSH2_SFTP_HANDLE *handle; + // The sftp_rsrc resource holds a reference to session_rsrc, + // so you can store the pointer directly without worrying about its lifetime. + // In the destructor, the session reference is dropped only after the sftp_rsrc resource has been freed. + LIBSSH2_SESSION *session; + LIBSSH2_SFTP *sftp; + zend_resource *sftp_rsrc; +} php_ssh2_sftp_handle_data; + +/* {{{ php_ssh2_sftp_stream_write + */ +static ssize_t php_ssh2_sftp_stream_write(php_stream *stream, const char *buf, size_t count) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + ssize_t bytes_written; + + auto session = data->session; + bytes_written = libssh2_sftp_write(data->handle, buf, count); + + return bytes_written; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_stream_read + */ +static ssize_t php_ssh2_sftp_stream_read(php_stream *stream, char *buf, size_t count) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + ssize_t bytes_read; + + auto session = data->session; + bytes_read = libssh2_sftp_read(data->handle, buf, count); + + stream->eof = (bytes_read <= 0 && bytes_read != LIBSSH2_ERROR_EAGAIN); + + return bytes_read; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_stream_close + */ +static int php_ssh2_sftp_stream_close(php_stream *stream, int close_handle) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + auto session = data->session; + + libssh2_sftp_close(data->handle); + zend_list_delete(data->sftp_rsrc); + efree(data); + + return 0; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_stream_seek + */ +static int php_ssh2_sftp_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + auto session = data->session; + + switch (whence) { + case SEEK_END: { + LIBSSH2_SFTP_ATTRIBUTES attrs; + if (libssh2_sftp_fstat(data->handle, &attrs)) { + return -1; + } + if ((attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) == 0) { + return -1; + } + offset += attrs.filesize; + break; + } + case SEEK_CUR: { + zend_off_t current_offset = libssh2_sftp_tell(data->handle); + + if (current_offset < 0) { + return -1; + } + + offset += current_offset; + break; + } + default: + return -1; + } + + libssh2_sftp_seek(data->handle, offset); + + if (newoffset) { + *newoffset = offset; + } + + return 0; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_stream_fstat + */ +static int php_ssh2_sftp_stream_fstat(php_stream *stream, php_stream_statbuf *ssb) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + LIBSSH2_SFTP_ATTRIBUTES attrs; + auto session = data->session; + + if (libssh2_sftp_fstat(data->handle, &attrs)) { + return -1; + } + + return php_ssh2_sftp_attr2ssb(ssb, &attrs); +} +/* }}} */ + +static php_stream_ops php_ssh2_sftp_stream_ops = { + php_ssh2_sftp_stream_write, + php_ssh2_sftp_stream_read, + php_ssh2_sftp_stream_close, + NULL, /* flush */ + PHP_SSH2_SFTP_STREAM_NAME, + php_ssh2_sftp_stream_seek, + NULL, /* cast */ + php_ssh2_sftp_stream_fstat, + NULL, /* set_option */ +}; + +/* {{{ php_ssh2_sftp_stream_opener + */ + +static php_stream *php_ssh2_sftp_stream_opener(php_stream_wrapper *wrapper, + const char *filename, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + php_ssh2_sftp_handle_data *data; + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + LIBSSH2_SFTP_HANDLE *handle; + php_stream *stream; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + unsigned long flags; + long perms = 0644; + + resource = php_ssh2_fopen_wraper_parse_path(filename, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !sftp_rsrc) { + return NULL; + } + + flags = php_ssh2_parse_fopen_modes((char *) mode); + + handle = libssh2_sftp_open(sftp, ZSTR_VAL(resource->path), flags, perms); + if (!handle) { + php_error_docref(NULL, E_WARNING, "Unable to open %s on remote host", filename); + php_url_free(resource); + zend_list_delete(sftp_rsrc); + return NULL; + } + + data = (php_ssh2_sftp_handle_data *) emalloc(sizeof(php_ssh2_sftp_handle_data)); + data->handle = handle; + data->session = session; + data->sftp = sftp; + data->sftp_rsrc = sftp_rsrc; + + stream = php_stream_alloc(&php_ssh2_sftp_stream_ops, data, 0, mode); + if (!stream) { + libssh2_sftp_close(handle); + zend_list_delete(sftp_rsrc); + efree(data); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +/* ********************** + * SFTP Directory Ops * + ********************** */ + +/* {{{ php_ssh2_sftp_dirstream_read + */ +static ssize_t php_ssh2_sftp_dirstream_read(php_stream *stream, char *buf, size_t count) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + php_stream_dirent *ent = (php_stream_dirent *) buf; + auto session = data->session; + int bytesread = libssh2_sftp_readdir(data->handle, ent->d_name, sizeof(ent->d_name) - 1, NULL); + zend_string *basename; + + if (bytesread <= 0) { + return 0; + } + ent->d_name[bytesread] = 0; + + basename = php_basename(ent->d_name, bytesread, NULL, 0); + if (!basename) { + return 0; + } + + bytesread = MIN(sizeof(ent->d_name) - 1, basename->len); + memcpy(ent->d_name, basename->val, bytesread); + ent->d_name[bytesread] = 0; + zend_string_release(basename); + + return sizeof(php_stream_dirent); +} +/* }}} */ + +/* {{{ php_ssh2_sftp_dirstream_close + */ +static int php_ssh2_sftp_dirstream_close(php_stream *stream, int close_handle) { + php_ssh2_sftp_handle_data *data = (php_ssh2_sftp_handle_data *) stream->abstract; + auto session = data->session; + + libssh2_sftp_close(data->handle); + zend_list_delete(data->sftp_rsrc); + efree(data); + + return 0; +} +/* }}} */ + +static php_stream_ops php_ssh2_sftp_dirstream_ops = { + NULL, /* write */ + php_ssh2_sftp_dirstream_read, + php_ssh2_sftp_dirstream_close, + NULL, /* flush */ + PHP_SSH2_SFTP_DIRSTREAM_NAME, + NULL, /* seek */ + NULL, /* cast */ + NULL, /* fstat */ + NULL, /* set_option */ +}; + +/* {{{ php_ssh2_sftp_dirstream_opener + */ +static php_stream *php_ssh2_sftp_dirstream_opener(php_stream_wrapper *wrapper, + const char *filename, + const char *mode, + int options, + zend_string **opened_path, + php_stream_context *context STREAMS_DC) { + php_ssh2_sftp_handle_data *data; + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + LIBSSH2_SFTP_HANDLE *handle; + php_stream *stream; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + + resource = php_ssh2_fopen_wraper_parse_path(filename, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp) { + return NULL; + } + + handle = libssh2_sftp_opendir(sftp, ZSTR_VAL(resource->path)); + if (!handle) { + php_error_docref(NULL, E_WARNING, "Unable to open %s on remote host", filename); + php_url_free(resource); + zend_list_delete(sftp_rsrc); + return NULL; + } + + data = (php_ssh2_sftp_handle_data *) emalloc(sizeof(php_ssh2_sftp_handle_data)); + data->handle = handle; + data->sftp_rsrc = sftp_rsrc; + + stream = php_stream_alloc(&php_ssh2_sftp_dirstream_ops, data, 0, mode); + if (!stream) { + libssh2_sftp_close(handle); + zend_list_delete(sftp_rsrc); + efree(data); + } + php_url_free(resource); + + return stream; +} +/* }}} */ + +/* **************** + * SFTP Wrapper * + **************** */ + +/* {{{ php_ssh2_sftp_urlstat + */ +static int php_ssh2_sftp_urlstat( + php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context) { + LIBSSH2_SFTP_ATTRIBUTES attrs; + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + + resource = php_ssh2_fopen_wraper_parse_path(url, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !resource->path) { + return -1; + } + + if (libssh2_sftp_stat_ex(sftp, + ZSTR_VAL(resource->path), + ZSTR_LEN(resource->path), + (flags & PHP_STREAM_URL_STAT_LINK) ? LIBSSH2_SFTP_LSTAT : LIBSSH2_SFTP_STAT, + &attrs)) { + php_url_free(resource); + // zend_list_delete(sftp_rsrcid); + return -1; + } + + php_url_free(resource); + + /* parse_path addrefs the resource, but we're not holding on to it so we have to delref it before we leave */ + // zend_list_delete(sftp_rsrcid); + + return php_ssh2_sftp_attr2ssb(ssb, &attrs); +} +/* }}} */ + +/* {{{ php_ssh2_sftp_unlink + */ +static int php_ssh2_sftp_unlink(php_stream_wrapper *wrapper, + const char *url, + int options, + php_stream_context *context) { + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + int result; + + resource = php_ssh2_fopen_wraper_parse_path(url, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !resource->path) { + if (resource) { + php_url_free(resource); + } + return 0; + } + + result = libssh2_sftp_unlink(sftp, ZSTR_VAL(resource->path)); + php_url_free(resource); + + // zend_list_delete(sftp_rsrcid); + + /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ + return (result == 0) ? -1 : 0; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_rename + */ +static int php_ssh2_sftp_rename( + php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context) { + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource, *resource_to; + int result; + + if (strncmp(url_from, "ssh2.sftp://", sizeof("ssh2.sftp://") - 1) || + strncmp(url_to, "ssh2.sftp://", sizeof("ssh2.sftp://") - 1)) { + return 0; + } + + resource_to = php_url_parse(url_to); + if (!resource_to || !resource_to->path) { + if (resource_to) { + php_url_free(resource_to); + } + return 0; + } + + resource = php_ssh2_fopen_wraper_parse_path(url_from, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !resource->path) { + if (resource) { + php_url_free(resource); + } + php_url_free(resource_to); + return 0; + } + + result = libssh2_sftp_rename(sftp, ZSTR_VAL(resource->path), ZSTR_VAL(resource_to->path)); + php_url_free(resource); + php_url_free(resource_to); + + // zend_list_delete(sftp_rsrcid); + + /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ + return (result == 0) ? -1 : 0; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_mkdir + */ +static int php_ssh2_sftp_mkdir( + php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context) { + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + int result; + + resource = php_ssh2_fopen_wraper_parse_path(url, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !resource->path) { + if (resource) { + php_url_free(resource); + } + return 0; + } + + if (options & PHP_STREAM_MKDIR_RECURSIVE) { + /* Just attempt to make every directory, some will fail, but we only care about the last success/failure */ + char *p = ZSTR_VAL(resource->path); + while ((p = strchr(p + 1, '/'))) { + libssh2_sftp_mkdir_ex(sftp, ZSTR_VAL(resource->path), p - ZSTR_VAL(resource->path), mode); + } + } + + result = libssh2_sftp_mkdir(sftp, ZSTR_VAL(resource->path), mode); + php_url_free(resource); + + // zend_list_delete(sftp_rsrcid); + + /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ + return (result == 0) ? -1 : 0; +} +/* }}} */ + +/* {{{ php_ssh2_sftp_rmdir + */ +static int php_ssh2_sftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) { + LIBSSH2_SESSION *session = NULL; + LIBSSH2_SFTP *sftp = NULL; + zend_resource *rsrc = NULL, *sftp_rsrc = NULL; + php_url *resource; + int result; + + resource = php_ssh2_fopen_wraper_parse_path(url, "sftp", context, &session, &rsrc, &sftp, &sftp_rsrc); + if (!resource || !session || !sftp || !resource->path) { + if (resource) { + php_url_free(resource); + } + return 0; + } + + result = libssh2_sftp_rmdir(sftp, ZSTR_VAL(resource->path)); + php_url_free(resource); + + // zend_list_delete(sftp_rsrcid); + + /* libssh2 uses 0 for success and the streams API uses 0 for failure, so invert */ + return (result == 0) ? -1 : 0; +} +/* }}} */ + +static php_stream_wrapper_ops php_ssh2_sftp_wrapper_ops = { + php_ssh2_sftp_stream_opener, + NULL, /* close */ + NULL, /* stat */ + php_ssh2_sftp_urlstat, + php_ssh2_sftp_dirstream_opener, + PHP_SSH2_SFTP_WRAPPER_NAME, + php_ssh2_sftp_unlink, + php_ssh2_sftp_rename, + php_ssh2_sftp_mkdir, + php_ssh2_sftp_rmdir, +}; + +php_stream_wrapper php_ssh2_sftp_wrapper = { + &php_ssh2_sftp_wrapper_ops, + NULL, + 1, +}; + +/* ***************** + * Userspace API * + ***************** */ + +/* {{{ proto resource ssh2_sftp(resource session) + * Request the SFTP subsystem from an already connected SSH2 server + */ +PHP_FUNCTION(ssh2_sftp) { + LIBSSH2_SESSION *session; + LIBSSH2_SFTP *sftp; + php_ssh2_sftp_data *data; + zval *zsession; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zsession) == FAILURE) { + return; + } + + if ((session = (LIBSSH2_SESSION *) zend_fetch_resource( + Z_RES_P(zsession), PHP_SSH2_SESSION_RES_NAME, le_ssh2_session)) == NULL) { + RETURN_FALSE; + } + + sftp = libssh2_sftp_init(session); + if (!sftp) { + char *sess_err; + libssh2_session_last_error(session, &sess_err, NULL, 0); + php_error_docref(NULL, E_WARNING, "Unable to startup SFTP subsystem: %s", sess_err); + RETURN_FALSE; + } + + data = (php_ssh2_sftp_data *) emalloc(sizeof(php_ssh2_sftp_data)); + data->session = session; + data->sftp = sftp; + data->session_rsrc = Z_RES_P(zsession); + Z_ADDREF_P(zsession); + + RETURN_RES(zend_register_resource(data, le_ssh2_sftp)); +} +/* }}} */ + +/* Much of the stuff below can be done via wrapper ops as of PHP5, but is included here for PHP 4.3 users */ + +/* {{{ proto bool ssh2_sftp_rename(resource sftp, string from, string to) + */ +PHP_FUNCTION(ssh2_sftp_rename) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *src, *dst; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &zsftp, &src, &dst) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + + RETURN_BOOL(!libssh2_sftp_rename_ex( + data->sftp, + src->val, + src->len, + dst->val, + dst->len, + LIBSSH2_SFTP_RENAME_OVERWRITE | LIBSSH2_SFTP_RENAME_ATOMIC | LIBSSH2_SFTP_RENAME_NATIVE)); +} +/* }}} */ + +/* {{{ proto bool ssh2_sftp_unlink(resource sftp, string filename) + */ +PHP_FUNCTION(ssh2_sftp_unlink) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *filename; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &zsftp, &filename) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + RETURN_BOOL(!libssh2_sftp_unlink_ex(data->sftp, filename->val, filename->len)); +} +/* }}} */ + +/* {{{ proto bool ssh2_sftp_mkdir(resource sftp, string dirname[, int mode[, int recursive]]) + */ +PHP_FUNCTION(ssh2_sftp_mkdir) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *dirname; + zend_long mode = 0777; + zend_bool recursive = 0; + const char *p; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS|lb", &zsftp, &dirname, &mode, &recursive) == FAILURE) { + return; + } + + if (!dirname) { + RETURN_FALSE; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + + if (recursive) { + /* Just attempt to make every directory, some will fail, but we only care about the last success/failure */ + p = dirname->val; + while ((p = strchr(p + 1, '/'))) { + if ((p - dirname->val) + 1 == (long) dirname->len) { + break; + } + libssh2_sftp_mkdir_ex(data->sftp, dirname->val, p - dirname->val, mode); + } + } + + RETURN_BOOL(!libssh2_sftp_mkdir_ex(data->sftp, dirname->val, dirname->len, mode)); +} +/* }}} */ + +/* {{{ proto bool ssh2_sftp_rmdir(resource sftp, string dirname) + */ +PHP_FUNCTION(ssh2_sftp_rmdir) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *dirname; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &zsftp, &dirname) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + RETURN_BOOL(!libssh2_sftp_rmdir_ex(data->sftp, dirname->val, dirname->len)); +} +/* }}} */ + +/* {{{ proto bool ssh2_sftp_chmod(resource sftp, string filename, int mode) + */ +PHP_FUNCTION(ssh2_sftp_chmod) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *filename; + zend_long mode; + LIBSSH2_SFTP_ATTRIBUTES attrs; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSl", &zsftp, &filename, &mode) == FAILURE) { + return; + } + + if (ZSTR_LEN(filename) < 1) { + RETURN_FALSE; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + attrs.permissions = mode; + attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; + + auto session = data->session; + + RETURN_BOOL(!libssh2_sftp_stat_ex(data->sftp, filename->val, filename->len, LIBSSH2_SFTP_SETSTAT, &attrs)); +} +/* }}} */ + +/* {{{ php_ssh2_sftp_stat_func + * In PHP4.3 this is the only way to request stat into, in PHP >= 5 you can use the fopen wrapper approach + * Both methods will return identical structures + * (well, the other one will include other values set to 0 but they don't count) + */ +static void php_ssh2_sftp_stat_func(INTERNAL_FUNCTION_PARAMETERS, int stat_type) { + php_ssh2_sftp_data *data; + LIBSSH2_SFTP_ATTRIBUTES attrs; + zval *zsftp; + zend_string *path; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &zsftp, &path) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + + if (libssh2_sftp_stat_ex(data->sftp, path->val, path->len, stat_type, &attrs)) { + php_error_docref(NULL, E_WARNING, "Failed to stat remote file"); + RETURN_FALSE; + } + + array_init(return_value); + + if (attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) { + add_index_long(return_value, 7, attrs.filesize); + add_assoc_long(return_value, "size", attrs.filesize); + } + if (attrs.flags & LIBSSH2_SFTP_ATTR_UIDGID) { + add_index_long(return_value, 4, attrs.uid); + add_assoc_long(return_value, "uid", attrs.uid); + + add_index_long(return_value, 5, attrs.gid); + add_assoc_long(return_value, "gid", attrs.gid); + } + if (attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) { + add_index_long(return_value, 2, attrs.permissions); + add_assoc_long(return_value, "mode", attrs.permissions); + } + if (attrs.flags & LIBSSH2_SFTP_ATTR_ACMODTIME) { + add_index_long(return_value, 8, attrs.atime); + add_assoc_long(return_value, "atime", attrs.atime); + + add_index_long(return_value, 9, attrs.mtime); + add_assoc_long(return_value, "mtime", attrs.mtime); + } +} +/* }}} */ + +/* {{{ proto array ssh2_sftp_stat(resource sftp, string path) + */ +PHP_FUNCTION(ssh2_sftp_stat) { + php_ssh2_sftp_stat_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, LIBSSH2_SFTP_STAT); +} +/* }}} */ + +/* {{{ proto array ssh2_sftp_lstat(resource sftp, string path) + */ +PHP_FUNCTION(ssh2_sftp_lstat) { + php_ssh2_sftp_stat_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, LIBSSH2_SFTP_LSTAT); +} +/* }}} */ + +/* {{{ proto bool ssh2_sftp_symlink(resource sftp, string target, string link) + */ +PHP_FUNCTION(ssh2_sftp_symlink) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *targ, *link; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rSS", &zsftp, &targ, &link) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + RETURN_BOOL(!libssh2_sftp_symlink_ex(data->sftp, targ->val, targ->len, link->val, link->len, LIBSSH2_SFTP_SYMLINK)); +} +/* }}} */ + +/* {{{ proto string ssh2_sftp_readlink(resource sftp, string link) + */ +PHP_FUNCTION(ssh2_sftp_readlink) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *link; + int targ_len = 0; + char targ[8192]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &zsftp, &link) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + auto session = data->session; + + if ((targ_len = libssh2_sftp_symlink_ex(data->sftp, link->val, link->len, targ, 8192, LIBSSH2_SFTP_READLINK)) < 0) { + php_error_docref(NULL, E_WARNING, "Unable to read link '%s'", ZSTR_VAL(link)); + RETURN_FALSE; + } + + RETURN_STRINGL(targ, targ_len); +} +/* }}} */ + +/* {{{ proto string ssh2_sftp_realpath(resource sftp, string filename) + */ +PHP_FUNCTION(ssh2_sftp_realpath) { + php_ssh2_sftp_data *data; + zval *zsftp; + zend_string *link; + int targ_len = 0; + char targ[8192]; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "rS", &zsftp, &link) == FAILURE) { + return; + } + + if ((data = (php_ssh2_sftp_data *) zend_fetch_resource(Z_RES_P(zsftp), PHP_SSH2_SFTP_RES_NAME, le_ssh2_sftp)) == + NULL) { + RETURN_FALSE; + } + + if (data->session_rsrc->ptr == NULL) { + RETURN_FALSE; + } + + auto session = data->session; + + if ((targ_len = libssh2_sftp_symlink_ex(data->sftp, link->val, link->len, targ, 8192, LIBSSH2_SFTP_REALPATH)) < 0) { + php_error_docref(NULL, E_WARNING, "Unable to resolve realpath for '%s'", link->val); + RETURN_FALSE; + } + + RETURN_STRINGL(targ, targ_len); +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/thirdparty/php/standard/proc_open.cc b/thirdparty/php/standard/proc_open.cc index ccdafefaa3..7e8251951e 100644 --- a/thirdparty/php/standard/proc_open.cc +++ b/thirdparty/php/standard/proc_open.cc @@ -1,13 +1,11 @@ /* - +----------------------------------------------------------------------+ - | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | https://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | @@ -17,32 +15,63 @@ */ #include "thirdparty/php/standard/proc_open.h" -#include "swoole_coroutine_c_api.h" -using namespace std; using swoole::Coroutine; using swoole::PHPCoroutine; -using swoole::coroutine::Socket; +using swoole::coroutine::System; -#if HAVE_SYS_STAT_H -#include +#ifdef HAVE_SYS_WAIT_H +#include #endif -#if HAVE_FCNTL_H + +#ifdef HAVE_FCNTL_H #include #endif +#ifdef HAVE_PTY_H +#include +#elif defined(__FreeBSD__) +/* FreeBSD defines `openpty` in */ +#include +#elif defined(__NetBSD__) || defined(__DragonFly__) +/* On recent NetBSD/DragonFlyBSD releases the emalloc, estrdup ... calls had been introduced in libutil */ +#if defined(__NetBSD__) +#include +#else +#include +#endif +extern int openpty(int *, int *, char *, struct termios *, struct winsize *); +#elif defined(__sun) +#include +#else +/* Mac OS X (and some BSDs) define `openpty` in */ +#include +#endif + static int le_proc_open; static const char *le_proc_name = "process/coroutine"; -/* {{{ _php_array_to_envp */ -static proc_co_env_t _php_array_to_envp(zval *environment) { +static pid_t _co_waitpid(pid_t __pid, int *__stat_loc, int __options) { +#ifdef SW_THREAD + return System::waitpid_safe(__pid, __stat_loc, __options); +#else + return System::waitpid(__pid, __stat_loc, __options); +#endif +} + +/* {{{ _php_array_to_envp + * Process the `environment` argument to `proc_open` + * Convert into data structures which can be passed to underlying OS APIs like `exec` on POSIX or + * `CreateProcessW` on Win32 */ +static sw_php_process_env _php_array_to_envp(zval *environment) { zval *element; - proc_co_env_t env; - zend_string *key, *str; + sw_php_process_env env; +#ifndef PHP_WIN32 char **ep; +#endif char *p; - size_t cnt, sizeenv = 0; - HashTable *env_hash; + size_t sizeenv = 0; + HashTable *env_hash; /* temporary PHP array used as helper */ memset(&env, 0, sizeof(env)); @@ -50,10 +79,12 @@ static proc_co_env_t _php_array_to_envp(zval *environment) { return env; } - cnt = zend_hash_num_elements(Z_ARRVAL_P(environment)); + uint32_t cnt = zend_hash_num_elements(Z_ARRVAL_P(environment)); if (cnt < 1) { +#ifndef PHP_WIN32 env.envarray = (char **) ecalloc(1, sizeof(char *)); +#endif env.envp = (char *) ecalloc(4, 1); return env; } @@ -61,12 +92,15 @@ static proc_co_env_t _php_array_to_envp(zval *environment) { ALLOC_HASHTABLE(env_hash); zend_hash_init(env_hash, cnt, NULL, NULL, 0); + void *_key, *_str; + zend_string *key, *str; /* first, we have to get the size of all the elements in the hash */ - ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) { + ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), _key, element) { + key = (zend_string *) _key; str = zval_get_string(element); if (ZSTR_LEN(str) == 0) { - zend_string_release(str); + zend_string_release_ex(str, 0); continue; } @@ -81,15 +115,18 @@ static proc_co_env_t _php_array_to_envp(zval *environment) { } ZEND_HASH_FOREACH_END(); +#ifndef PHP_WIN32 ep = env.envarray = (char **) ecalloc(cnt + 1, sizeof(char *)); +#endif p = env.envp = (char *) ecalloc(sizeenv + 4, 1); - void *v1, *v2; - ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, v1, v2) { - key = (zend_string *) v1; - str = (zend_string *) v2; + ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, _key, _str) { + key = (zend_string *) _key; + str = (zend_string *) _str; +#ifndef PHP_WIN32 *ep = p; ++ep; +#endif if (key) { memcpy(p, ZSTR_VAL(key), ZSTR_LEN(key)); @@ -100,11 +137,11 @@ static proc_co_env_t _php_array_to_envp(zval *environment) { memcpy(p, ZSTR_VAL(str), ZSTR_LEN(str)); p += ZSTR_LEN(str); *p++ = '\0'; - zend_string_release(str); + zend_string_release_ex(str, 0); } ZEND_HASH_FOREACH_END(); - assert((uint32_t)(p - env.envp) <= sizeenv); + assert((uint32_t) (p - env.envp) <= sizeenv); zend_hash_destroy(env_hash); FREE_HASHTABLE(env_hash); @@ -113,112 +150,118 @@ static proc_co_env_t _php_array_to_envp(zval *environment) { } /* }}} */ -/* {{{ _php_free_envp */ -static void _php_free_envp(proc_co_env_t env, int is_persistent) { +/* {{{ _php_free_envp + * Free the structures allocated by `_php_array_to_envp` */ +static void _php_free_envp(sw_php_process_env env) { if (env.envarray) { - pefree(env.envarray, is_persistent); + efree(env.envarray); } if (env.envp) { - pefree(env.envp, is_persistent); + efree(env.envp); } } /* }}} */ static void proc_co_rsrc_dtor(zend_resource *rsrc) { - proc_co_t *proc = (proc_co_t *) rsrc->ptr; - int i; + sw_php_process_handle *proc = (sw_php_process_handle *) rsrc->ptr; int wstatus = 0; /* Close all handles to avoid a deadlock */ - for (i = 0; i < proc->npipes; i++) { - if (proc->pipes[i] != 0) { + for (int i = 0; i < proc->npipes; i++) { + if (proc->pipes[i] != NULL) { GC_DELREF(proc->pipes[i]); zend_list_close(proc->pipes[i]); - proc->pipes[i] = 0; + proc->pipes[i] = NULL; } } if (proc->running) { - if (::waitpid(proc->child, &wstatus, WNOHANG) == 0) { - swoole_coroutine_waitpid(proc->child, &wstatus, 0); - } + _co_waitpid(proc->child, &wstatus, 0); } if (proc->wstatus) { *proc->wstatus = wstatus; } - _php_free_envp(proc->env, proc->is_persistent); + _php_free_envp(proc->env); efree(proc->pipes); - efree(proc->command); + zend_string_release_ex(proc->command, false); efree(proc); } +/* }}} */ +/* {{{ PHP_MINIT_FUNCTION(proc_open) */ void swoole_proc_open_init(int module_number) { le_proc_open = zend_register_list_destructors_ex(proc_co_rsrc_dtor, NULL, le_proc_name, module_number); } +/* }}} */ -/* {{{ proto bool proc_terminate(resource process [, int signal]) - kill a process opened by proc_open */ +/* {{{ Kill a process opened by `proc_open` */ PHP_FUNCTION(swoole_proc_terminate) { zval *zproc; - proc_co_t *proc; + sw_php_process_handle *proc; zend_long sig_no = SIGTERM; ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_RESOURCE(zproc) Z_PARAM_OPTIONAL Z_PARAM_LONG(sig_no) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + ZEND_PARSE_PARAMETERS_END(); - if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) { - RETURN_FALSE; + proc = (sw_php_process_handle *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open); + if (proc == NULL) { + RETURN_THROWS(); } +#ifdef PHP_WIN32 + RETURN_BOOL(TerminateProcess(proc->childHandle, 255)); +#else RETURN_BOOL(kill(proc->child, sig_no) == 0); +#endif } /* }}} */ +/* {{{ Close a process opened by `proc_open` */ PHP_FUNCTION(swoole_proc_close) { zval *zproc; - proc_co_t *proc; int wstatus = 0; + sw_php_process_handle *proc; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_RESOURCE(zproc) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + ZEND_PARSE_PARAMETERS_END(); - if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) { - RETURN_FALSE; + if ((proc = (sw_php_process_handle *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) { + RETURN_THROWS(); } - proc->wstatus = &wstatus; zend_list_close(Z_RES_P(zproc)); RETURN_LONG(wstatus); } +/* }}} */ +/* {{{ Get information about a process opened by `proc_open` */ PHP_FUNCTION(swoole_proc_get_status) { zval *zproc; - proc_co_t *proc; + sw_php_process_handle *proc; int wstatus; pid_t wait_pid; - int running = 1, signaled = 0, stopped = 0; + bool running = 1, signaled = 0, stopped = 0; int exitcode = -1, termsig = 0, stopsig = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_RESOURCE(zproc) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + ZEND_PARSE_PARAMETERS_END(); - if ((proc = (proc_co_t *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) { - RETURN_FALSE; + if ((proc = (sw_php_process_handle *) zend_fetch_resource(Z_RES_P(zproc), le_proc_name, le_proc_open)) == NULL) { + RETURN_THROWS(); } array_init(return_value); - - add_assoc_string(return_value, "command", proc->command); + add_assoc_str(return_value, "command", zend_string_copy(proc->command)); add_assoc_long(return_value, "pid", (zend_long) proc->child); errno = 0; - wait_pid = swoole_coroutine_waitpid(proc->child, &wstatus, WNOHANG | WUNTRACED); + wait_pid = _co_waitpid(proc->child, &wstatus, WNOHANG | WUNTRACED); if (wait_pid == proc->child) { if (WIFEXITED(wstatus)) { @@ -228,7 +271,6 @@ PHP_FUNCTION(swoole_proc_get_status) { if (WIFSIGNALED(wstatus)) { running = 0; signaled = 1; - termsig = WTERMSIG(wstatus); } if (WIFSTOPPED(wstatus)) { @@ -236,6 +278,8 @@ PHP_FUNCTION(swoole_proc_get_status) { stopsig = WSTOPSIG(wstatus); } } else if (wait_pid == -1) { + /* The only error which could occur here is ECHILD, which means that the PID we were + * looking for either does not exist or is not a child of this process */ running = 0; } @@ -250,18 +294,50 @@ PHP_FUNCTION(swoole_proc_get_status) { } /* }}} */ -#define DESC_PIPE 1 -#define DESC_FILE 2 -#define DESC_REDIRECT 3 -#define DESC_PARENT_MODE_WRITE 8 - -struct php_proc_open_descriptor_item { - int index; /* desired fd number in child process */ - int parentend, childend; /* fds for pipes in parent/child */ - int mode; /* mode for proc_open code */ - int mode_flags; /* mode flags for opening fds */ -}; -/* }}} */ +#ifdef PHP_WIN32 + +/* We use this to allow child processes to inherit handles + * One static instance can be shared and used for all calls to `proc_open`, since the values are + * never changed */ +SECURITY_ATTRIBUTES php_proc_open_security = { + .nLength = sizeof(SECURITY_ATTRIBUTES), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE}; + +#define pipe(pair) (CreatePipe(&pair[0], &pair[1], &php_proc_open_security, 0) ? 0 : -1) + +#define COMSPEC_NT "cmd.exe" + +static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig) { + HANDLE copy, self = GetCurrentProcess(); + + if (!DuplicateHandle( + self, src, self, ©, 0, inherit, DUPLICATE_SAME_ACCESS | (closeorig ? DUPLICATE_CLOSE_SOURCE : 0))) + return NULL; + return copy; +} + +static inline HANDLE dup_fd_as_handle(int fd) { + return dup_handle((HANDLE) _get_osfhandle(fd), TRUE, FALSE); +} + +#define close_descriptor(fd) CloseHandle(fd) +#else /* !PHP_WIN32 */ +#define close_descriptor(fd) close(fd) +#endif + +/* Determines the type of a descriptor item. */ +typedef enum _descriptor_type { DESCRIPTOR_TYPE_STD, DESCRIPTOR_TYPE_PIPE, DESCRIPTOR_TYPE_SOCKET } descriptor_type; + +/* One instance of this struct is created for each item in `$descriptorspec` argument to `proc_open` + * They are used within `proc_open` and freed before it returns */ +typedef struct _descriptorspec_item { + int index; /* desired FD # in child process */ + descriptor_type type; + php_file_descriptor_t childend; /* FD # opened for use in child + * (will be copied to `index` in child) */ + php_file_descriptor_t parentend; /* FD # opened for use in parent + * (for pipes only; will be 0 otherwise) */ + int mode_flags; /* mode for opening FDs: r/o, r/w, binary (on Win32), etc */ +} descriptorspec_item; static zend_string *get_valid_arg_string(zval *zv, int elem_num) { zend_string *str = zval_get_string(zv); @@ -269,8 +345,14 @@ static zend_string *get_valid_arg_string(zval *zv, int elem_num) { return NULL; } + if (elem_num == 1 && ZSTR_LEN(str) == 0) { + zend_value_error("First element must contain a non-empty program name"); + zend_string_release(str); + return NULL; + } + if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) { - php_error_docref(NULL, E_WARNING, "Command array element %d contains a null byte", elem_num); + zend_value_error("Command array element %d contains a null byte", elem_num); zend_string_release(str); return NULL; } @@ -278,76 +360,627 @@ static zend_string *get_valid_arg_string(zval *zv, int elem_num) { return str; } -/* {{{ proto resource proc_open(string|array command, array descriptorspec, array &pipes [, string cwd [, array env [, - array other_options]]]) Run a process with more control over it's file descriptors */ +#ifdef PHP_WIN32 +static void append_backslashes(smart_str *str, size_t num_bs) { + for (size_t i = 0; i < num_bs; i++) { + smart_str_appendc(str, '\\'); + } +} + +/* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments */ +static void append_win_escaped_arg(smart_str *str, zend_string *arg) { + size_t num_bs = 0; + + smart_str_appendc(str, '"'); + for (size_t i = 0; i < ZSTR_LEN(arg); ++i) { + char c = ZSTR_VAL(arg)[i]; + if (c == '\\') { + num_bs++; + continue; + } + + if (c == '"') { + /* Backslashes before " need to be doubled. */ + num_bs = num_bs * 2 + 1; + } + append_backslashes(str, num_bs); + smart_str_appendc(str, c); + num_bs = 0; + } + append_backslashes(str, num_bs * 2); + smart_str_appendc(str, '"'); +} + +static zend_string *create_win_command_from_args(HashTable *args) { + smart_str str = {0}; + zval *arg_zv; + bool is_prog_name = 1; + int elem_num = 0; + + ZEND_HASH_FOREACH_VAL(args, arg_zv) { + zend_string *arg_str = get_valid_arg_string(arg_zv, ++elem_num); + if (!arg_str) { + smart_str_free(&str); + return NULL; + } + + if (!is_prog_name) { + smart_str_appendc(&str, ' '); + } + + append_win_escaped_arg(&str, arg_str); + + is_prog_name = 0; + zend_string_release(arg_str); + } + ZEND_HASH_FOREACH_END(); + smart_str_0(&str); + return str.s; +} + +/* Get a boolean option from the `other_options` array which can be passed to `proc_open`. + * (Currently, all options apply on Windows only.) */ +static bool get_option(zval *other_options, char *opt_name, size_t opt_name_len) { + HashTable *opt_ary = Z_ARRVAL_P(other_options); + zval *item = zend_hash_str_find_deref(opt_ary, opt_name, opt_name_len); + return item != NULL && (Z_TYPE_P(item) == IS_TRUE || ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item))); +} + +/* Initialize STARTUPINFOW struct, used on Windows when spawning a process. + * Ref: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow */ +static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors, int ndesc) { + memset(si, 0, sizeof(STARTUPINFOW)); + si->cb = sizeof(STARTUPINFOW); + si->dwFlags = STARTF_USESTDHANDLES; + + si->hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si->hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si->hStdError = GetStdHandle(STD_ERROR_HANDLE); + + /* redirect stdin/stdout/stderr if requested */ + for (int i = 0; i < ndesc; i++) { + switch (descriptors[i].index) { + case 0: + si->hStdInput = descriptors[i].childend; + break; + case 1: + si->hStdOutput = descriptors[i].childend; + break; + case 2: + si->hStdError = descriptors[i].childend; + break; + } + } +} + +static void init_process_info(PROCESS_INFORMATION *pi) { + memset(&pi, 0, sizeof(pi)); +} + +static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len) { + size_t len = sizeof(COMSPEC_NT) + sizeof(" /s /c ") + cmdw_len + 3; + wchar_t *cmdw_shell = (wchar_t *) malloc(len * sizeof(wchar_t)); + + if (cmdw_shell == NULL) { + php_error_docref(NULL, E_WARNING, "Command conversion failed"); + return FAILURE; + } + + if (_snwprintf(cmdw_shell, len, L"%hs /s /c \"%s\"", COMSPEC_NT, *cmdw) == -1) { + free(cmdw_shell); + php_error_docref(NULL, E_WARNING, "Command conversion failed"); + return FAILURE; + } + + free(*cmdw); + *cmdw = cmdw_shell; + + return SUCCESS; +} +#endif + +/* Convert command parameter array passed as first argument to `proc_open` into command string */ +static zend_string *get_command_from_array(HashTable *array, char ***argv, int num_elems) { + zval *arg_zv; + zend_string *command = NULL; + int i = 0; + + *argv = (char **) safe_emalloc(sizeof(char *), num_elems + 1, 0); + + ZEND_HASH_FOREACH_VAL(array, arg_zv) { + zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1); + if (!arg_str) { + /* Terminate with NULL so exit_fail code knows how many entries to free */ + (*argv)[i] = NULL; + if (command != NULL) { + zend_string_release_ex(command, false); + } + return NULL; + } + + if (i == 0) { + command = zend_string_copy(arg_str); + } + + (*argv)[i++] = (char *) estrdup(ZSTR_VAL(arg_str)); + zend_string_release(arg_str); + } + ZEND_HASH_FOREACH_END(); + + (*argv)[i] = NULL; + return command; +} + +static descriptorspec_item *alloc_descriptor_array(HashTable *descriptorspec) { + uint32_t ndescriptors = zend_hash_num_elements(descriptorspec); + return (descriptorspec_item *) ecalloc(sizeof(descriptorspec_item), ndescriptors); +} + +static zend_string *get_string_parameter(zval *array, int index, const char *param_name) { + zval *array_item; + if ((array_item = zend_hash_index_find(Z_ARRVAL_P(array), index)) == NULL) { + zend_value_error("Missing %s", param_name); + return NULL; + } + return zval_try_get_string(array_item); +} + +static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc) { +#ifdef PHP_WIN32 + desc->childend = CreateFileA( + "nul", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (desc->childend == NULL) { + php_error_docref(NULL, E_WARNING, "Failed to open nul"); + return FAILURE; + } +#else + desc->childend = open("/dev/null", O_RDWR); + if (desc->childend < 0) { + php_error_docref(NULL, E_WARNING, "Failed to open /dev/null: %s", strerror(errno)); + return FAILURE; + } +#endif + return SUCCESS; +} + +static zend_result set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd, int *slave_fd) { +#ifdef HAVE_OPENPTY + /* All FDs set to PTY in the child process will go to the slave end of the same PTY. + * Likewise, all the corresponding entries in `$pipes` in the parent will all go to the master + * end of the same PTY. + * If this is the first descriptorspec set to 'pty', find an available PTY and get master and + * slave FDs. */ + if (*master_fd == -1) { + if (openpty(master_fd, slave_fd, NULL, NULL, NULL)) { + php_error_docref(NULL, E_WARNING, "Could not open PTY (pseudoterminal): %s", strerror(errno)); + return FAILURE; + } + } + + desc->type = DESCRIPTOR_TYPE_PIPE; + desc->childend = dup(*slave_fd); + desc->parentend = dup(*master_fd); + desc->mode_flags = O_RDWR; + return SUCCESS; +#else + php_error_docref(NULL, E_WARNING, "PTY (pseudoterminal) not supported on this system"); + return FAILURE; +#endif +} + +/* Mark the descriptor close-on-exec, so it won't be inherited by children */ +static php_file_descriptor_t make_descriptor_cloexec(php_file_descriptor_t fd) { +#ifdef PHP_WIN32 + return dup_handle(fd, FALSE, TRUE); +#else +#if defined(F_SETFD) && defined(FD_CLOEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + return fd; +#endif +} + +static zend_result set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *zmode) { + php_file_descriptor_t newpipe[2]; + + if (pipe(newpipe) != 0) { + php_error_docref(NULL, E_WARNING, "Unable to create pipe %s", strerror(errno)); + return FAILURE; + } + + desc->type = DESCRIPTOR_TYPE_PIPE; + + if (strncmp(ZSTR_VAL(zmode), "w", 1) != 0) { + desc->parentend = newpipe[1]; + desc->childend = newpipe[0]; + desc->mode_flags = O_WRONLY; + } else { + desc->parentend = newpipe[0]; + desc->childend = newpipe[1]; + desc->mode_flags = O_RDONLY; + } + + desc->parentend = make_descriptor_cloexec(desc->parentend); + +#ifdef PHP_WIN32 + if (ZSTR_LEN(zmode) >= 2 && ZSTR_VAL(zmode)[1] == 'b') desc->mode_flags |= O_BINARY; +#endif + + return SUCCESS; +} + +#ifdef PHP_WIN32 +#define create_socketpair(socks) socketpair_win32(AF_INET, SOCK_STREAM, 0, (socks), 0) +#else +#define create_socketpair(socks) socketpair(AF_UNIX, SOCK_STREAM, 0, (socks)) +#endif + +static zend_result set_proc_descriptor_to_socket(descriptorspec_item *desc) { + php_socket_t sock[2]; + + if (create_socketpair(sock)) { + zend_string *err = php_socket_error_str(php_socket_errno()); + php_error_docref(NULL, E_WARNING, "Unable to create socket pair: %s", ZSTR_VAL(err)); + zend_string_release(err); + return FAILURE; + } + + desc->type = DESCRIPTOR_TYPE_SOCKET; + desc->parentend = make_descriptor_cloexec((php_file_descriptor_t) sock[0]); + + /* Pass sock[1] to child because it will never use overlapped IO on Windows. */ + desc->childend = (php_file_descriptor_t) sock[1]; + + return SUCCESS; +} + +static zend_result set_proc_descriptor_to_file(descriptorspec_item *desc, + zend_string *file_path, + zend_string *file_mode) { + php_socket_t fd; + + /* try a wrapper */ + php_stream *stream = + php_stream_open_wrapper(ZSTR_VAL(file_path), ZSTR_VAL(file_mode), REPORT_ERRORS | STREAM_WILL_CAST, NULL); + if (stream == NULL) { + return FAILURE; + } + + /* force into an fd */ + if (php_stream_cast(stream, PHP_STREAM_CAST_RELEASE | PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS) == FAILURE) { + return FAILURE; + } + +#ifdef PHP_WIN32 + desc->childend = dup_fd_as_handle((int) fd); + _close((int) fd); + + /* Simulate the append mode by fseeking to the end of the file + * This introduces a potential race condition, but it is the best we can do */ + if (strchr(ZSTR_VAL(file_mode), 'a')) { + SetFilePointer(desc->childend, 0, NULL, FILE_END); + } +#else + desc->childend = fd; +#endif + return SUCCESS; +} + +static zend_result dup_proc_descriptor(php_file_descriptor_t from, php_file_descriptor_t *to, zend_ulong nindex) { +#ifdef PHP_WIN32 + *to = dup_handle(from, TRUE, FALSE); + if (*to == NULL) { + php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex); + return FAILURE; + } +#else + *to = dup(from); + if (*to < 0) { + php_error_docref( + NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT ": %s", nindex, strerror(errno)); + return FAILURE; + } +#endif + return SUCCESS; +} + +static zend_result redirect_proc_descriptor( + descriptorspec_item *desc, int target, descriptorspec_item *descriptors, int ndesc, int nindex) { + php_file_descriptor_t redirect_to = PHP_INVALID_FD; + + for (int i = 0; i < ndesc; i++) { + if (descriptors[i].index == target) { + redirect_to = descriptors[i].childend; + break; + } + } + + if (redirect_to == PHP_INVALID_FD) { /* Didn't find the index we wanted */ + if (target < 0 || target > 2) { + php_error_docref(NULL, E_WARNING, "Redirection target %d not found", target); + return FAILURE; + } + + /* Support referring to a stdin/stdout/stderr pipe adopted from the parent, + * which happens whenever an explicit override is not provided. */ +#ifndef PHP_WIN32 + redirect_to = target; +#else + switch (target) { + case 0: + redirect_to = GetStdHandle(STD_INPUT_HANDLE); + break; + case 1: + redirect_to = GetStdHandle(STD_OUTPUT_HANDLE); + break; + case 2: + redirect_to = GetStdHandle(STD_ERROR_HANDLE); + break; + EMPTY_SWITCH_DEFAULT_CASE() + } +#endif + } + + return dup_proc_descriptor(redirect_to, &desc->childend, nindex); +} + +/* Process one item from `$descriptorspec` argument to `proc_open` */ +static zend_result set_proc_descriptor_from_array( + zval *descitem, descriptorspec_item *descriptors, int ndesc, int nindex, int *pty_master_fd, int *pty_slave_fd) { + zend_string *ztype = get_string_parameter(descitem, 0, "handle qualifier"); + if (!ztype) { + return FAILURE; + } + + zend_string *zmode = NULL, *zfile = NULL; + zend_result retval = FAILURE; + +#if 0 + if (zend_string_equals_literal(ztype, "pipe")) { + /* Set descriptor to pipe */ + zmode = get_string_parameter(descitem, 1, "mode parameter for 'pipe'"); + if (zmode == NULL) { + goto finish; + } + retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode); + } else +#endif + if (zend_string_equals_literal(ztype, "socket") || zend_string_equals_literal(ztype, "pipe")) { + /* Set descriptor to socketpair */ + retval = set_proc_descriptor_to_socket(&descriptors[ndesc]); + } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_FILE))) { + /* Set descriptor to file */ + if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) { + goto finish; + } + if ((zmode = get_string_parameter(descitem, 2, "mode parameter for 'file'")) == NULL) { + goto finish; + } + retval = set_proc_descriptor_to_file(&descriptors[ndesc], zfile, zmode); + } else if (zend_string_equals_literal(ztype, "redirect")) { + /* Redirect descriptor to whatever another descriptor is set to */ + zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1); + if (!ztarget) { + zend_value_error("Missing redirection target"); + goto finish; + } + if (Z_TYPE_P(ztarget) != IS_LONG) { + zend_value_error("Redirection target must be of type int, %s given", zend_zval_type_name(ztarget)); + goto finish; + } + + retval = redirect_proc_descriptor(&descriptors[ndesc], (int) Z_LVAL_P(ztarget), descriptors, ndesc, nindex); + } else if (zend_string_equals(ztype, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE))) { + /* Set descriptor to blackhole (discard all data written) */ + retval = set_proc_descriptor_to_blackhole(&descriptors[ndesc]); + } else if (zend_string_equals_literal(ztype, "pty")) { + /* Set descriptor to slave end of PTY */ + retval = set_proc_descriptor_to_pty(&descriptors[ndesc], pty_master_fd, pty_slave_fd); + } else { + php_error_docref(NULL, E_WARNING, "%s is not a valid descriptor spec/mode", ZSTR_VAL(ztype)); + goto finish; + } + +finish: + if (zmode) zend_string_release(zmode); + if (zfile) zend_string_release(zfile); + zend_string_release(ztype); + return retval; +} + +static zend_result set_proc_descriptor_from_resource(zval *resource, descriptorspec_item *desc, int nindex) { + /* Should be a stream - try and dup the descriptor */ + php_stream *stream = (php_stream *) zend_fetch_resource(Z_RES_P(resource), "stream", php_file_le_stream()); + if (stream == NULL) { + return FAILURE; + } + + php_socket_t fd; + zend_result status = (zend_result) php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS); + if (status == FAILURE) { + return FAILURE; + } + +#ifdef PHP_WIN32 + php_file_descriptor_t fd_t = (php_file_descriptor_t) _get_osfhandle(fd); +#else + php_file_descriptor_t fd_t = fd; +#endif + return dup_proc_descriptor(fd_t, &desc->childend, nindex); +} + +#ifndef PHP_WIN32 +#if defined(USE_POSIX_SPAWN) +static zend_result close_parentends_of_pipes(posix_spawn_file_actions_t *actions, + descriptorspec_item *descriptors, + int ndesc) { + int r; + for (int i = 0; i < ndesc; i++) { + if (descriptors[i].type != DESCRIPTOR_TYPE_STD) { + r = posix_spawn_file_actions_addclose(actions, descriptors[i].parentend); + if (r != 0) { + php_error_docref( + NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].parentend, strerror(r)); + return FAILURE; + } + } + if (descriptors[i].childend != descriptors[i].index) { + r = posix_spawn_file_actions_adddup2(actions, descriptors[i].childend, descriptors[i].index); + if (r != 0) { + php_error_docref(NULL, + E_WARNING, + "Unable to copy file descriptor %d (for pipe) into " + "file descriptor %d: %s", + descriptors[i].childend, + descriptors[i].index, + strerror(r)); + return FAILURE; + } + r = posix_spawn_file_actions_addclose(actions, descriptors[i].childend); + if (r != 0) { + php_error_docref( + NULL, E_WARNING, "Cannot close file descriptor %d: %s", descriptors[i].childend, strerror(r)); + return FAILURE; + } + } + } + + return SUCCESS; +} +#else +static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc) { + /* We are running in child process + * Close the 'parent end' of pipes which were opened before forking/spawning + * Also, dup() the child end of all pipes as necessary so they will use the FD + * number which the user requested */ + for (int i = 0; i < ndesc; i++) { + if (descriptors[i].type != DESCRIPTOR_TYPE_STD) { + close(descriptors[i].parentend); + } + if (descriptors[i].childend != descriptors[i].index) { + if (dup2(descriptors[i].childend, descriptors[i].index) < 0) { + php_error_docref(NULL, + E_WARNING, + "Unable to copy file descriptor %d (for pipe) into " + "file descriptor %d: %s", + descriptors[i].childend, + descriptors[i].index, + strerror(errno)); + return FAILURE; + } + close(descriptors[i].childend); + } + } + + return SUCCESS; +} +#endif +#endif + +static void close_all_descriptors(descriptorspec_item *descriptors, int ndesc) { + for (int i = 0; i < ndesc; i++) { + close_descriptor(descriptors[i].childend); + if (descriptors[i].parentend) close_descriptor(descriptors[i].parentend); + } +} + +static void efree_argv(char **argv) { + if (argv) { + char **arg = argv; + while (*arg != NULL) { + efree(*arg); + arg++; + } + efree(argv); + } +} + +/* {{{ Execute a command, with specified files used for input/output */ PHP_FUNCTION(swoole_proc_open) { - zval *command_zv; - char *command = NULL, *cwd = NULL; - size_t cwd_len = 0; - zval *descriptorspec; - zval *pipes; - zval *environment = NULL; - zval *other_options = NULL; - proc_co_env_t env; + zend_string *command_str; + HashTable *command_ht; + HashTable *descriptorspec; /* Mandatory argument */ + zval *pipes; /* Mandatory argument */ + char *cwd = NULL; /* Optional argument */ + size_t cwd_len = 0; /* Optional argument */ + zval *environment = NULL, *other_options = NULL; /* Optional arguments */ + + sw_php_process_env env; int ndesc = 0; int i; zval *descitem = NULL; zend_string *str_index; zend_ulong nindex; - struct php_proc_open_descriptor_item *descriptors = NULL; - int ndescriptors_array; - + descriptorspec_item *descriptors = NULL; +#ifdef PHP_WIN32 + PROCESS_INFORMATION pi; + HANDLE childHandle; + STARTUPINFOW si; + BOOL newprocok; + DWORD dwCreateFlags = 0; + UINT old_error_mode; + char cur_cwd[MAXPATHLEN]; + wchar_t *cmdw = NULL, *cwdw = NULL, *envpw = NULL; + size_t cmdw_len; + bool suppress_errors = 0; + bool bypass_shell = 0; + bool blocking_pipes = 0; + bool create_process_group = 0; + bool create_new_console = 0; +#else char **argv = NULL; - - pid_t child; - proc_co_t *proc; - int is_persistent = 0; /* TODO: ensure that persistent procs will work */ +#endif + int pty_master_fd = -1, pty_slave_fd = -1; + php_process_id_t child; + sw_php_process_handle *proc; ZEND_PARSE_PARAMETERS_START(3, 6) - Z_PARAM_ZVAL(command_zv) - Z_PARAM_ARRAY(descriptorspec) + Z_PARAM_ARRAY_HT_OR_STR(command_ht, command_str) + Z_PARAM_ARRAY_HT(descriptorspec) Z_PARAM_ZVAL(pipes) Z_PARAM_OPTIONAL - Z_PARAM_STRING_EX(cwd, cwd_len, 1, 0) - Z_PARAM_ARRAY_EX(environment, 1, 0) - Z_PARAM_ARRAY_EX(other_options, 1, 0) - ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); + Z_PARAM_STRING_OR_NULL(cwd, cwd_len) + Z_PARAM_ARRAY_OR_NULL(environment) + Z_PARAM_ARRAY_OR_NULL(other_options) + ZEND_PARSE_PARAMETERS_END(); memset(&env, 0, sizeof(env)); - if (Z_TYPE_P(command_zv) == IS_ARRAY) { - zval *arg_zv; - uint32_t num_elems = zend_hash_num_elements(Z_ARRVAL_P(command_zv)); + if (command_ht) { + uint32_t num_elems = zend_hash_num_elements(command_ht); if (num_elems == 0) { - php_error_docref(NULL, E_WARNING, "Command array must have at least one element"); - RETURN_FALSE; + zend_argument_value_error(1, "must have at least one element"); + RETURN_THROWS(); } - argv = (char **) safe_emalloc(sizeof(char *), num_elems + 1, 0); - i = 0; - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(command_zv), arg_zv) { - zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1); - if (!arg_str) { - argv[i] = NULL; - goto exit_fail; - } - - if (i == 0) { - command = pestrdup(ZSTR_VAL(arg_str), is_persistent); - } +#ifdef PHP_WIN32 + /* Automatically bypass shell if command is given as an array */ + bypass_shell = 1; + command_str = create_win_command_from_args(command_ht); +#else + command_str = get_command_from_array(command_ht, &argv, num_elems); +#endif - argv[i++] = estrdup(ZSTR_VAL(arg_str)); - zend_string_release(arg_str); + if (!command_str) { +#ifndef PHP_WIN32 + efree_argv(argv); +#endif + RETURN_FALSE; } - ZEND_HASH_FOREACH_END(); - argv[i] = NULL; - - /* As the array is non-empty, we should have found a command. */ - ZEND_ASSERT(command); } else { - convert_to_string(command_zv); - command = pestrdup(Z_STRVAL_P(command_zv), is_persistent); + zend_string_addref(command_str); + } + +#ifdef PHP_WIN32 + if (other_options) { + suppress_errors = get_option(other_options, "suppress_errors", strlen("suppress_errors")); + /* TODO: Deprecate in favor of array command? */ + bypass_shell = bypass_shell || get_option(other_options, "bypass_shell", strlen("bypass_shell")); + blocking_pipes = get_option(other_options, "blocking_pipes", strlen("blocking_pipes")); + create_process_group = get_option(other_options, "create_process_group", strlen("create_process_group")); + create_new_console = get_option(other_options, "create_new_console", strlen("create_new_console")); } +#endif php_swoole_check_reactor(); if (php_swoole_signal_isset_handler(SIGCHLD)) { @@ -355,251 +988,157 @@ PHP_FUNCTION(swoole_proc_open) { RETURN_FALSE; } - Coroutine::get_current_safe(); + swoole::Coroutine::get_current_safe(); if (environment) { env = _php_array_to_envp(environment); - } else { - memset(&env, 0, sizeof(env)); } - ndescriptors_array = zend_hash_num_elements(Z_ARRVAL_P(descriptorspec)); - - descriptors = (struct php_proc_open_descriptor_item *) safe_emalloc( - sizeof(struct php_proc_open_descriptor_item), ndescriptors_array, 0); - - memset(descriptors, 0, sizeof(struct php_proc_open_descriptor_item) * ndescriptors_array); - - /* walk the descriptor spec and set up files/pipes */ - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(descriptorspec), nindex, str_index, descitem) { - zval *ztype; + descriptors = alloc_descriptor_array(descriptorspec); + /* Walk the descriptor spec and set up files/pipes */ + ZEND_HASH_FOREACH_KEY_VAL(descriptorspec, nindex, str_index, descitem) { if (str_index) { - php_swoole_fatal_error(E_WARNING, "descriptor spec must be an integer indexed array"); + zend_argument_value_error(2, "must be an integer indexed array"); goto exit_fail; } descriptors[ndesc].index = (int) nindex; + ZVAL_DEREF(descitem); if (Z_TYPE_P(descitem) == IS_RESOURCE) { - /* should be a stream - try and dup the descriptor */ - php_stream *stream; - php_socket_t fd; - - php_stream_from_zval(stream, descitem); - - if (FAILURE == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) { + if (set_proc_descriptor_from_resource(descitem, &descriptors[ndesc], ndesc) == FAILURE) { goto exit_fail; } - - descriptors[ndesc].childend = dup(fd); - if (descriptors[ndesc].childend < 0) { - php_swoole_fatal_error(E_WARNING, - "unable to dup File-Handle for descriptor " ZEND_ULONG_FMT " - %s", - nindex, - strerror(errno)); + } else if (Z_TYPE_P(descitem) == IS_ARRAY) { + if (set_proc_descriptor_from_array( + descitem, descriptors, ndesc, (int) nindex, &pty_master_fd, &pty_slave_fd) == FAILURE) { goto exit_fail; } + } else { + php_swoole_fatal_error(E_WARNING, "Descriptor item must be either an array or a File-Handle"); + goto exit_fail; + } + ndesc++; + } + ZEND_HASH_FOREACH_END(); + +#ifdef PHP_WIN32 + if (cwd == NULL) { + char *getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN); + if (!getcwd_result) { + php_error_docref(NULL, E_WARNING, "Cannot get current directory"); + goto exit_fail; + } + cwd = cur_cwd; + } + cwdw = php_win32_cp_any_to_w(cwd); + if (!cwdw) { + php_error_docref(NULL, E_WARNING, "CWD conversion failed"); + goto exit_fail; + } - descriptors[ndesc].mode = DESC_FILE; + init_startup_info(&si, descriptors, ndesc); + init_process_info(&pi); - } else if (Z_TYPE_P(descitem) != IS_ARRAY) { - php_swoole_fatal_error(E_WARNING, "Descriptor item must be either an array or a File-Handle"); + if (suppress_errors) { + old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + } + + dwCreateFlags = NORMAL_PRIORITY_CLASS; + if (strcmp(sapi_module.name, "cli") != 0) { + dwCreateFlags |= CREATE_NO_WINDOW; + } + if (create_process_group) { + dwCreateFlags |= CREATE_NEW_PROCESS_GROUP; + } + if (create_new_console) { + dwCreateFlags |= CREATE_NEW_CONSOLE; + } + envpw = php_win32_cp_env_any_to_w(env.envp); + if (envpw) { + dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT; + } else { + if (env.envp) { + php_error_docref(NULL, E_WARNING, "ENV conversion failed"); goto exit_fail; - } else { - if ((ztype = zend_hash_index_find(Z_ARRVAL_P(descitem), 0)) != NULL) { - convert_to_string_ex(ztype); - } else { - php_swoole_fatal_error(E_WARNING, "Missing handle qualifier in array"); - goto exit_fail; - } + } + } - if (strcmp(Z_STRVAL_P(ztype), "pipe") == 0) { - php_file_descriptor_t newpipe[2]; - zval *zmode; - - if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) { - convert_to_string_ex(zmode); - } else { - php_swoole_fatal_error(E_WARNING, "Missing mode parameter for 'pipe'"); - goto exit_fail; - } - - descriptors[ndesc].mode = DESC_PIPE; - - if (0 != socketpair(AF_UNIX, SOCK_STREAM, 0, newpipe)) { - php_swoole_fatal_error(E_WARNING, "unable to create pipe %s", strerror(errno)); - goto exit_fail; - } - - if (strncmp(Z_STRVAL_P(zmode), "w", 1) != 0) { - descriptors[ndesc].parentend = newpipe[1]; - descriptors[ndesc].childend = newpipe[0]; - descriptors[ndesc].mode |= DESC_PARENT_MODE_WRITE; - } else { - descriptors[ndesc].parentend = newpipe[0]; - descriptors[ndesc].childend = newpipe[1]; - } - descriptors[ndesc].mode_flags = descriptors[ndesc].mode & DESC_PARENT_MODE_WRITE ? O_WRONLY : O_RDONLY; - - } else if (strcmp(Z_STRVAL_P(ztype), "file") == 0) { - zval *zfile, *zmode; - php_socket_t fd; - php_stream *stream; - - descriptors[ndesc].mode = DESC_FILE; - - if ((zfile = zend_hash_index_find(Z_ARRVAL_P(descitem), 1)) != NULL) { - if (!try_convert_to_string(zfile)) { - goto exit_fail; - } - } else { - php_swoole_fatal_error(E_WARNING, "Missing file name parameter for 'file'"); - goto exit_fail; - } - - if ((zmode = zend_hash_index_find(Z_ARRVAL_P(descitem), 2)) != NULL) { - if (!try_convert_to_string(zmode)) { - goto exit_fail; - } - } else { - php_swoole_fatal_error(E_WARNING, "Missing mode parameter for 'file'"); - goto exit_fail; - } - - /* try a wrapper */ - stream = php_stream_open_wrapper( - Z_STRVAL_P(zfile), Z_STRVAL_P(zmode), REPORT_ERRORS | STREAM_WILL_CAST, NULL); - - /* force into an fd */ - if (stream == NULL || - FAILURE == php_stream_cast( - stream, PHP_STREAM_CAST_RELEASE | PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) { - goto exit_fail; - } - - descriptors[ndesc].childend = fd; - } else if (strcmp(Z_STRVAL_P(ztype), "redirect") == 0) { - zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1); - struct php_proc_open_descriptor_item *target = NULL; - php_file_descriptor_t childend; - - if (!ztarget) { - php_error_docref(NULL, E_WARNING, "Missing redirection target"); - goto exit_fail; - } - if (Z_TYPE_P(ztarget) != IS_LONG) { - php_error_docref(NULL, E_WARNING, "Redirection target must be an integer"); - goto exit_fail; - } - - for (i = 0; i < ndesc; i++) { - if (descriptors[i].index == Z_LVAL_P(ztarget)) { - target = &descriptors[i]; - break; - } - } - if (target) { - childend = target->childend; - } else { - if (Z_LVAL_P(ztarget) < 0 || Z_LVAL_P(ztarget) > 2) { - php_error_docref( - NULL, E_WARNING, "Redirection target " ZEND_LONG_FMT " not found", Z_LVAL_P(ztarget)); - goto exit_fail; - } - - /* Support referring to a stdin/stdout/stderr pipe adopted from the parent, - * which happens whenever an explicit override is not provided. */ -#ifndef PHP_WIN32 - childend = Z_LVAL_P(ztarget); -#else - switch (Z_LVAL_P(ztarget)) { - case 0: - childend = GetStdHandle(STD_INPUT_HANDLE); - break; - case 1: - childend = GetStdHandle(STD_OUTPUT_HANDLE); - break; - case 2: - childend = GetStdHandle(STD_ERROR_HANDLE); - break; - EMPTY_SWITCH_DEFAULT_CASE() - } -#endif - } + cmdw = php_win32_cp_conv_any_to_w(ZSTR_VAL(command_str), ZSTR_LEN(command_str), &cmdw_len); + if (!cmdw) { + php_error_docref(NULL, E_WARNING, "Command conversion failed"); + goto exit_fail; + } -#ifdef PHP_WIN32 - descriptors[ndesc].childend = dup_handle(childend, TRUE, FALSE); - if (descriptors[ndesc].childend == NULL) { - php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex); - goto exit_fail; - } -#else - descriptors[ndesc].childend = dup(childend); - if (descriptors[ndesc].childend < 0) { - php_error_docref(NULL, - E_WARNING, - "Failed to dup() for descriptor " ZEND_LONG_FMT " - %s", - nindex, - strerror(errno)); - goto exit_fail; - } -#endif - descriptors[ndesc].mode = DESC_REDIRECT; - } else if (strcmp(Z_STRVAL_P(ztype), "null") == 0) { -#ifndef PHP_WIN32 - descriptors[ndesc].childend = open("/dev/null", O_RDWR); - if (descriptors[ndesc].childend < 0) { - php_error_docref(NULL, E_WARNING, "Failed to open /dev/null - %s", strerror(errno)); - goto exit_fail; - } -#else - descriptors[ndesc].childend = CreateFileA("nul", - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - 0, - NULL); - if (descriptors[ndesc].childend == NULL) { - php_error_docref(NULL, E_WARNING, "Failed to open nul"); - goto exit_fail; - } -#endif - descriptors[ndesc].mode = DESC_FILE; - } else if (strcmp(Z_STRVAL_P(ztype), "pty") == 0) { - php_swoole_fatal_error(E_WARNING, "pty pseudo terminal not supported on this system"); - goto exit_fail; - } else { - php_swoole_fatal_error(E_WARNING, "%s is not a valid descriptor spec/mode", Z_STRVAL_P(ztype)); - goto exit_fail; - } + if (!bypass_shell) { + if (convert_command_to_use_shell(&cmdw, cmdw_len) == FAILURE) { + goto exit_fail; } - ndesc++; } - ZEND_HASH_FOREACH_END(); + newprocok = CreateProcessW( + NULL, cmdw, &php_proc_open_security, &php_proc_open_security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi); + + if (suppress_errors) { + SetErrorMode(old_error_mode); + } + + if (newprocok == FALSE) { + DWORD dw = GetLastError(); + close_all_descriptors(descriptors, ndesc); + php_error_docref(NULL, E_WARNING, "CreateProcess failed, error code: %u", dw); + goto exit_fail; + } + + childHandle = pi.hProcess; + child = pi.dwProcessId; + CloseHandle(pi.hThread); +#elif defined(USE_POSIX_SPAWN) + posix_spawn_file_actions_t factions; + int r; + posix_spawn_file_actions_init(&factions); + + if (close_parentends_of_pipes(&factions, descriptors, ndesc) == FAILURE) { + posix_spawn_file_actions_destroy(&factions); + close_all_descriptors(descriptors, ndesc); + goto exit_fail; + } - /* the unix way */ + if (cwd) { + r = posix_spawn_file_actions_addchdir_np(&factions, cwd); + if (r != 0) { + php_error_docref(NULL, E_WARNING, "posix_spawn_file_actions_addchdir_np() failed: %s", strerror(r)); + } + } + + if (argv) { + r = posix_spawnp(&child, ZSTR_VAL(command_str), &factions, NULL, argv, (env.envarray ? env.envarray : environ)); + } else { + r = posix_spawn(&child, + "/bin/sh", + &factions, + NULL, + (char *const[]){"sh", "-c", ZSTR_VAL(command_str), NULL}, + env.envarray ? env.envarray : environ); + } + posix_spawn_file_actions_destroy(&factions); + if (r != 0) { + close_all_descriptors(descriptors, ndesc); + php_error_docref(NULL, E_WARNING, "posix_spawn() failed: %s", strerror(r)); + goto exit_fail; + } +#elif HAVE_FORK + /* the Unix way */ child = swoole_fork(SW_FORK_EXEC); if (child == 0) { - /* this is the child process */ - - /* close those descriptors that we just opened for the parent stuff, - * dup new descriptors into required descriptors and close the original - * cruft */ - for (i = 0; i < ndesc; i++) { - switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) { - case DESC_PIPE: - close(descriptors[i].parentend); - break; - } - if (dup2(descriptors[i].childend, descriptors[i].index) < 0) { - perror("dup2"); - } - if (descriptors[i].childend != descriptors[i].index) { - close(descriptors[i].childend); - } + /* This is the child process */ + + if (close_parentends_of_pipes(descriptors, ndesc) == FAILURE) { + /* We are already in child process and can't do anything to make + * `proc_open` return an error in the parent + * All we can do is exit with a non-zero (error) exit code */ + _exit(127); } if (cwd) { @@ -611,107 +1150,129 @@ PHP_FUNCTION(swoole_proc_open) { if (env.envarray) { environ = env.envarray; } - execvp(command, argv); + execvp(ZSTR_VAL(command_str), argv); } else { if (env.envarray) { - execle("/bin/sh", "sh", "-c", command, NULL, env.envarray); + execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray); } else { - execl("/bin/sh", "sh", "-c", command, NULL); + execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL); } } - _exit(127); + /* If execvp/execle/execl are successful, we will never reach here + * Display error and exit with non-zero (error) status code */ + php_error_docref(NULL, E_WARNING, "Exec failed: %s", strerror(errno)); + _exit(127); } else if (child < 0) { - /* failed to fork() */ - - /* clean up all the descriptors */ - for (i = 0; i < ndesc; i++) { - close(descriptors[i].childend); - if (descriptors[i].parentend) { - close(descriptors[i].parentend); - } - } - - php_swoole_fatal_error(E_WARNING, "fork failed - %s", strerror(errno)); - + /* Failed to fork() */ + close_all_descriptors(descriptors, ndesc); + php_error_docref(NULL, E_WARNING, "Fork failed: %s", strerror(errno)); goto exit_fail; } +#else +#error You lose (configure should not have let you get here) +#endif + + /* We forked/spawned and this is the parent */ - /* we forked/spawned and this is the parent */ pipes = zend_try_array_init(pipes); if (!pipes) { goto exit_fail; } - proc = (proc_co_t *) pemalloc(sizeof(proc_co_t), is_persistent); - proc->is_persistent = is_persistent; + proc = (sw_php_process_handle *) emalloc(sizeof(sw_php_process_handle)); + proc->command = zend_string_copy(command_str); proc->wstatus = nullptr; proc->running = true; - proc->command = command; proc->pipes = (zend_resource **) emalloc(sizeof(zend_resource *) * ndesc); proc->npipes = ndesc; proc->child = child; proc->env = env; - /* clean up all the child ends and then open streams on the parent - * ends, where appropriate */ + /* Clean up all the child ends and then open streams on the parent + * ends, where appropriate */ for (i = 0; i < ndesc; i++) { php_stream *stream = NULL; - close(descriptors[i].childend); + close_descriptor(descriptors[i].childend); - switch (descriptors[i].mode & ~DESC_PARENT_MODE_WRITE) { - case DESC_PIPE: - stream = php_swoole_create_stream_from_socket(descriptors[i].parentend, AF_UNIX, SOCK_STREAM, 0 STREAMS_CC); - /* mark the descriptor close-on-exec, so that it won't be inherited by potential other children */ - fcntl(descriptors[i].parentend, F_SETFD, FD_CLOEXEC); - if (stream) { - zval retfp; + if (descriptors[i].type == DESCRIPTOR_TYPE_PIPE) { + const char *mode_string = NULL; - /* nasty hack; don't copy it */ - stream->flags |= PHP_STREAM_FLAG_NO_SEEK; - - php_stream_to_zval(stream, &retfp); - (void) add_index_zval(pipes, descriptors[i].index, &retfp); - - proc->pipes[i] = Z_RES(retfp); - Z_ADDREF(retfp); + switch (descriptors[i].mode_flags) { +#ifdef PHP_WIN32 + case O_WRONLY | O_BINARY: + mode_string = "wb"; + break; + case O_RDONLY | O_BINARY: + mode_string = "rb"; + break; +#endif + case O_WRONLY: + mode_string = "w"; + break; + case O_RDONLY: + mode_string = "r"; + break; + case O_RDWR: + mode_string = "r+"; + break; } - break; - default: + +#ifdef PHP_WIN32 + stream = php_stream_fopen_from_fd( + _open_osfhandle((intptr_t) descriptors[i].parentend, descriptors[i].mode_flags), mode_string, NULL); + php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL); +#else + stream = php_swoole_create_stream_from_pipe(descriptors[i].parentend, mode_string, NULL STREAMS_CC); +#endif + } else if (descriptors[i].type == DESCRIPTOR_TYPE_SOCKET) { + stream = php_swoole_create_stream_from_socket( + (php_socket_t) descriptors[i].parentend, AF_UNIX, SOCK_STREAM, 0 STREAMS_CC); + } else { proc->pipes[i] = NULL; } - } - if (argv) { - char **arg = argv; - while (*arg != NULL) { - efree(*arg); - arg++; + if (stream) { + zval retfp; + + /* nasty hack; don't copy it */ + stream->flags |= PHP_STREAM_FLAG_NO_SEEK; + + php_stream_to_zval(stream, &retfp); + add_index_zval(pipes, descriptors[i].index, &retfp); + + proc->pipes[i] = Z_RES(retfp); + Z_ADDREF(retfp); } - efree(argv); } - efree(descriptors); - ZVAL_RES(return_value, zend_register_resource(proc, le_proc_open)); - return; + if (1) { + RETVAL_RES(zend_register_resource(proc, le_proc_open)); + } else { + exit_fail: + _php_free_envp(env); + RETVAL_FALSE; + } -exit_fail: - if (descriptors) { - efree(descriptors); + zend_string_release_ex(command_str, false); +#ifdef PHP_WIN32 + free(cwdw); + free(cmdw); + free(envpw); +#else + efree_argv(argv); +#endif +#ifdef HAVE_OPENPTY + if (pty_master_fd != -1) { + close(pty_master_fd); } - _php_free_envp(env, is_persistent); - if (command) { - pefree(command, is_persistent); + if (pty_slave_fd != -1) { + close(pty_slave_fd); } - if (argv) { - char **arg = argv; - while (*arg != NULL) { - efree(*arg); - arg++; - } - efree(argv); +#endif + if (descriptors) { + efree(descriptors); } - RETURN_FALSE; } /* }}} */ diff --git a/thirdparty/php/standard/proc_open.h b/thirdparty/php/standard/proc_open.h index 393c72591c..5747f5968b 100644 --- a/thirdparty/php/standard/proc_open.h +++ b/thirdparty/php/standard/proc_open.h @@ -1,31 +1,64 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ + */ + #include "php_swoole_cxx.h" -extern "C" -{ +SW_EXTERN_C_BEGIN +void swoole_proc_open_init(int module_number); PHP_FUNCTION(swoole_proc_open); PHP_FUNCTION(swoole_proc_close); PHP_FUNCTION(swoole_proc_get_status); PHP_FUNCTION(swoole_proc_terminate); -} +SW_EXTERN_C_END +#ifdef PHP_WIN32 +typedef HANDLE php_file_descriptor_t; +typedef DWORD php_process_id_t; +#define PHP_INVALID_FD INVALID_HANDLE_VALUE +#else typedef int php_file_descriptor_t; +typedef pid_t php_process_id_t; +#define PHP_INVALID_FD (-1) +#endif -void swoole_proc_open_init(int module_number); - -struct proc_co_env_t -{ +/* Environment block under Win32 is a NUL terminated sequence of NUL terminated + * name=value strings. + * Under Unix, it is an argv style array. */ +typedef struct { char *envp; +#ifndef PHP_WIN32 char **envarray; -}; +#endif +} sw_php_process_env; -struct proc_co_t -{ - pid_t child; +typedef struct { bool running; - int npipes; int *wstatus; + php_process_id_t child; +#ifdef PHP_WIN32 + HANDLE childHandle; +#endif + int npipes; zend_resource **pipes; - char *command; - int is_persistent; - proc_co_env_t env; -}; + zend_string *command; + sw_php_process_env env; +#if HAVE_SYS_WAIT_H + /* We can only request the status once before it becomes unavailable. + * Cache the result so we can request it multiple times. */ + int cached_exit_wait_status_value; + bool has_cached_exit_wait_status; +#endif +} sw_php_process_handle; diff --git a/thirdparty/php/standard/var_decoder.cc b/thirdparty/php/standard/var_decoder.cc index 1e4814d2ce..29b1da5cdf 100644 --- a/thirdparty/php/standard/var_decoder.cc +++ b/thirdparty/php/standard/var_decoder.cc @@ -36,10 +36,13 @@ static const char *php_json_get_error_msg(php_json_error_code error_code) /* {{{ return "The decoded property name is invalid"; case PHP_JSON_ERROR_UTF16: return "Single unpaired UTF-16 surrogate in unicode escape"; + case PHP_JSON_ERROR_NON_BACKED_ENUM: + return "Non-backed enums have no default serialization"; default: return "Unknown error"; } } +/* }}} */ void json_decode(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth) { if (!(options & PHP_JSON_THROW_ON_ERROR)) { diff --git a/thirdparty/php/streams/plain_wrapper.c b/thirdparty/php/streams/plain_wrapper.c index 3399d86f82..d5a8bdcf2c 100644 --- a/thirdparty/php/streams/plain_wrapper.c +++ b/thirdparty/php/streams/plain_wrapper.c @@ -1,22 +1,18 @@ /* - +----------------------------------------------------------------------+ - | PHP Version 7 | - +----------------------------------------------------------------------+ - | Copyright (c) 1997-2017 The PHP Group | - +----------------------------------------------------------------------+ - | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | - | If you did not receive a copy of the PHP license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Authors: Wez Furlong | - +----------------------------------------------------------------------+ - */ - -/* $Id$ */ + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Wez Furlong | + +----------------------------------------------------------------------+ +*/ #include "php_swoole.h" #include "php_open_temporary_file.h" @@ -25,12 +21,23 @@ #include "ext/standard/php_filestat.h" #include + +#if PHP_VERSION_ID >= 80400 +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef HAVE_SYS_FILE_H +#include +#endif +#else #if HAVE_SYS_WAIT_H #include #endif #if HAVE_SYS_FILE_H #include #endif +#endif + #ifdef HAVE_SYS_MMAN_H #include #endif @@ -39,15 +46,40 @@ #include "swoole_file_hook.h" -#if !defined(WINDOWS) && !defined(NETWARE) +#define sw_php_stream_fopen_from_fd_rel(fd, mode, persistent_id, zero_position) \ + _sw_php_stream_fopen_from_fd((fd), (mode), (persistent_id), (zero_position) STREAMS_REL_CC) + +#define sw_php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id) \ + _sw_php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC) + +#define sw_php_stream_fopen_rel(path, mode, opened_path, options) \ + _sw_php_stream_fopen(path, mode, opened_path, options STREAMS_REL_CC) + +#ifndef PHP_WIN32 extern int php_get_uid_by_name(const char *name, uid_t *uid); extern int php_get_gid_by_name(const char *name, gid_t *gid); #endif #if defined(PHP_WIN32) #define PLAIN_WRAP_BUF_SIZE(st) (((st) > UINT_MAX) ? UINT_MAX : (unsigned int) (st)) + +#if PHP_VERSION_ID >= 80100 +#define fsync _commit +#define fdatasync fsync +#endif + #else #define PLAIN_WRAP_BUF_SIZE(st) (st) + +#if PHP_VERSION_ID >= 80100 +#if !defined(HAVE_FDATASYNC) +#define fdatasync fsync +#elif defined(__APPLE__) +// The symbol is present, however not in the headers +extern int fdatasync(int); +#endif +#endif + #endif static php_stream_size_t sw_php_stdiop_write(php_stream *stream, const char *buf, size_t count); @@ -58,16 +90,15 @@ static int sw_php_stdiop_flush(php_stream *stream); static int sw_php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset); static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, void *ptrparam); static int sw_php_stdiop_cast(php_stream *stream, int castas, void **ret); - static void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result); static php_stream *_sw_php_stream_fopen_from_fd_int(int fd, const char *mode, const char *persistent_id STREAMS_DC); +#if PHP_VERSION_ID >= 80200 +static php_stream *_sw_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id, bool zero_position STREAMS_DC); +#else static php_stream *_sw_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC); +#endif static int sw_php_mkdir(const char *dir, zend_long mode); -static inline zend_bool file_can_poll(zend_stat_t *_stat) { - return S_ISCHR(_stat->st_mode) || S_ISSOCK(_stat->st_mode) || S_ISFIFO(_stat->st_mode); -} - static int sw_php_stream_parse_fopen_modes(const char *mode, int *open_flags) { int flags; @@ -124,34 +155,29 @@ static int sw_php_stream_parse_fopen_modes(const char *mode, int *open_flags) { return SUCCESS; } -#define stream_fopen_from_fd_rel(fd, mode, persistent_id) \ - _sw_php_stream_fopen_from_fd((fd), (mode), (persistent_id) STREAMS_REL_CC) -#define sw_php_stream_fopen_from_fd_int(fd, mode, persistent_id) \ - _sw_php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_CC) -#define sw_php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id) \ - _sw_php_stream_fopen_from_fd_int((fd), (mode), (persistent_id) STREAMS_REL_CC) /* {{{ ------- STDIO stream implementation -------*/ typedef struct { FILE *file; - int fd; /* underlying file descriptor */ - unsigned is_process_pipe : 1; /* use pclose instead of fclose */ - unsigned is_pipe : 1; /* stream is an actual pipe, currently Windows only*/ - unsigned cached_fstat : 1; /* sb is valid */ - unsigned is_pipe_blocking : 1; /* allow blocking read() on pipes, currently Windows only */ - unsigned no_forced_fstat : 1; /* Use fstat cache even if forced */ - unsigned is_seekable : 1; /* don't try and seek, if not set */ - unsigned _reserved : 26; - - int lock_flag; /* stores the lock state */ - zend_string *temp_name; /* if non-null, this is the path to a temporary file that - * is to be deleted when the stream is closed */ -#if HAVE_FLUSHIO + int fd; /* underlying file descriptor */ + unsigned is_process_pipe:1; /* use pclose instead of fclose */ + unsigned is_pipe:1; /* stream is an actual pipe, currently Windows only*/ + unsigned cached_fstat:1; /* sb is valid */ + unsigned is_pipe_blocking:1; /* allow blocking read() on pipes, currently Windows only */ + unsigned no_forced_fstat:1; /* Use fstat cache even if forced */ + unsigned is_seekable:1; /* don't try and seek, if not set */ + unsigned can_poll:1; + unsigned _reserved:25; + + int lock_flag; /* stores the lock state */ + zend_string *temp_name; /* if non-null, this is the path to a temporary file that + * is to be deleted when the stream is closed */ +#ifdef HAVE_FLUSHIO char last_op; #endif -#if HAVE_MMAP +#ifdef HAVE_MMAP char *last_mapped_addr; size_t last_mapped_len; #endif @@ -162,8 +188,7 @@ typedef struct { zend_stat_t sb; } php_stdio_stream_data; - -#define PHP_STDIOP_GET_FD(anfd, data) anfd = (data)->file ? fileno((data)->file) : (data)->fd +#define PHP_STDIOP_GET_FD(anfd, data) anfd = (data)->file ? fileno((data)->file) : (data)->fd static php_stream_ops sw_php_stream_stdio_ops = { sw_php_stdiop_write, @@ -195,46 +220,195 @@ static php_stream *_sw_php_stream_fopen_from_fd_int(int fd, const char *mode, co php_stdio_stream_data *self = (php_stdio_stream_data *) pemalloc_rel_orig(sizeof(*self), persistent_id); memset(self, 0, sizeof(*self)); self->file = NULL; + self->is_seekable = 1; self->is_pipe = 0; self->lock_flag = LOCK_UN; self->is_process_pipe = 0; self->temp_name = NULL; self->fd = fd; +#ifdef PHP_WIN32 + self->is_pipe_blocking = 0; +#endif return php_stream_alloc_rel(&sw_php_stream_stdio_ops, self, persistent_id, mode); } +static void _sw_detect_is_seekable(php_stdio_stream_data *self) { +#if defined(S_ISFIFO) && defined(S_ISCHR) + if (self->fd >= 0 && do_fstat(self, 0) == 0) { + self->is_seekable = !(S_ISFIFO(self->sb.st_mode) || S_ISCHR(self->sb.st_mode)); + self->is_pipe = S_ISFIFO(self->sb.st_mode); + self->can_poll = S_ISFIFO(self->sb.st_mode) || S_ISSOCK(self->sb.st_mode) || S_ISCHR(self->sb.st_mode); + if (self->can_poll) { + swoole_coroutine_socket_create(self->fd); + } + } +#elif defined(PHP_WIN32) +#if PHP_VERSION_ID >= 80300 + uintptr_t handle = _get_osfhandle(self->fd); + + if (handle != (uintptr_t)INVALID_HANDLE_VALUE) { +#else + zend_uintptr_t handle = _get_osfhandle(self->fd); + + if (handle != (zend_uintptr_t)INVALID_HANDLE_VALUE) { +#endif + DWORD file_type = GetFileType((HANDLE)handle); + + self->is_seekable = !(file_type == FILE_TYPE_PIPE || file_type == FILE_TYPE_CHAR); + self->is_pipe = file_type == FILE_TYPE_PIPE; + + /* Additional check needed to distinguish between pipes and sockets. */ + if (self->is_pipe && !GetNamedPipeInfo((HANDLE) handle, NULL, NULL, NULL, NULL)) { + self->is_pipe = 0; + } + } +#endif +} + +static php_stream *_sw_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id, bool zero_position STREAMS_DC) { + php_stream *stream = sw_php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id); + + if (stream) { + php_stdio_stream_data *self = (php_stdio_stream_data *) stream->abstract; + + _sw_detect_is_seekable(self); + if (!self->is_seekable) { + stream->flags |= PHP_STREAM_FLAG_NO_SEEK; + stream->position = -1; + } else if (zero_position) { + ZEND_ASSERT(lseek(self->fd, 0, SEEK_CUR) == 0); + stream->position = 0; + } + else { + stream->position = lseek(self->fd, 0, SEEK_CUR); +#ifdef ESPIPE + /* FIXME: Is this code still needed? */ + if (stream->position == (zend_off_t)-1 && errno == ESPIPE) { + stream->flags |= PHP_STREAM_FLAG_NO_SEEK; + self->is_seekable = 0; + } +#endif + } + } + + return stream; +} + static php_stream_size_t sw_php_stdiop_write(php_stream *stream, const char *buf, size_t count) { php_stdio_stream_data *data = (php_stdio_stream_data *) stream->abstract; assert(data != NULL); if (data->fd >= 0) { - return write(data->fd, buf, count); +#ifdef PHP_WIN32 + ssize_t bytes_written; + if (ZEND_SIZE_T_UINT_OVFL(count)) { + count = UINT_MAX; + } + bytes_written = _write(data->fd, buf, (unsigned int)count); +#else + ssize_t bytes_written = write(data->fd, buf, count); +#endif + if (bytes_written < 0) { + if (PHP_IS_TRANSIENT_ERROR(errno)) { + return 0; + } + if (errno == EINTR) { + /* TODO: Should this be treated as a proper error or not? */ + return bytes_written; + } + if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { + php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); + } + } + return bytes_written; } else { - return fwrite(buf, 1, count, data->file); +#ifdef HAVE_FLUSHIO + if (data->is_seekable && data->last_op == 'r') { + fseek(data->file, 0, SEEK_CUR); + } + data->last_op = 'w'; +#endif + + return (ssize_t) fwrite(buf, 1, count, data->file); } } static php_stream_size_t sw_php_stdiop_read(php_stream *stream, char *buf, size_t count) { php_stdio_stream_data *data = (php_stdio_stream_data *) stream->abstract; + ssize_t ret; assert(data != NULL); if (data->fd >= 0) { - ssize_t ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); - if (ret == -1 && errno == EINTR) { +#ifdef PHP_WIN32 + php_stdio_stream_data *self = (php_stdio_stream_data*)stream->abstract; + + if ((self->is_pipe || self->is_process_pipe) && !self->is_pipe_blocking) { + HANDLE ph = (HANDLE)_get_osfhandle(data->fd); + int retry = 0; + DWORD avail_read = 0; + + do { + /* Look ahead to get the available data amount to read. Do the same + as read() does, however not blocking forever. In case it failed, + no data will be read (better than block). */ + if (!PeekNamedPipe(ph, NULL, 0, NULL, &avail_read, NULL)) { + break; + } + /* If there's nothing to read, wait in 10us periods. */ + if (0 == avail_read) { + usleep(10); + } + } while (0 == avail_read && retry++ < 3200000); + + /* Reduce the required data amount to what is available, otherwise read() + will block.*/ + if (avail_read < count) { + count = avail_read; + } + } +#endif + ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); + + if (ret == (ssize_t) -1 && errno == EINTR) { /* Read was interrupted, retry once, - If read still fails, giveup with feof==0 - so script can retry if desired */ + If read still fails, giveup with feof==0 + so script can retry if desired */ ret = read(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); } - stream->eof = (ret == 0 || (ret == -1 && errno != EWOULDBLOCK && errno != EINTR && errno != EBADF)); - return ret; + + if (ret < 0) { + if (PHP_IS_TRANSIENT_ERROR(errno)) { + /* Not an error. */ + ret = 0; + } else if (errno == EINTR) { + /* TODO: Should this be treated as a proper error or not? */ + } else { + if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) { + php_error_docref(NULL, E_NOTICE, "Read of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); + } + + /* TODO: Remove this special-case? */ + if (errno != EBADF) { + stream->eof = 1; + } + } + } else if (ret == 0) { + stream->eof = 1; + } + } else { - size_t ret = fread(buf, 1, count, data->file); +#ifdef HAVE_FLUSHIO + if (data->is_seekable && data->last_op == 'w') + fseek(data->file, 0, SEEK_CUR); + data->last_op = 'r'; +#endif + ret = fread(buf, 1, count, data->file); + stream->eof = feof(data->file); - return ret; } + return ret; } static int sw_php_stdiop_close(php_stream *stream, int close_handle) { @@ -243,10 +417,21 @@ static int sw_php_stdiop_close(php_stream *stream, int close_handle) { assert(data != NULL); +#ifdef HAVE_MMAP if (data->last_mapped_addr) { munmap(data->last_mapped_addr, data->last_mapped_len); data->last_mapped_addr = NULL; } +#elif defined(PHP_WIN32) + if (data->last_mapped_addr) { + UnmapViewOfFile(data->last_mapped_addr); + data->last_mapped_addr = NULL; + } + if (data->file_mapping) { + CloseHandle(data->file_mapping); + data->file_mapping = NULL; + } +#endif if (close_handle) { if (data->file) { @@ -265,7 +450,7 @@ static int sw_php_stdiop_close(php_stream *stream, int close_handle) { } } else if (data->fd != -1) { if ((data->lock_flag & LOCK_EX) || (data->lock_flag & LOCK_SH)) { - swoole_coroutine_flock_ex(stream->orig_path, data->fd, LOCK_UN); + swoole_coroutine_flock(data->fd, LOCK_UN); } ret = close(data->fd); data->fd = -1; @@ -273,9 +458,13 @@ static int sw_php_stdiop_close(php_stream *stream, int close_handle) { return 0; /* everything should be closed already -> success */ } if (data->temp_name) { +#ifdef PHP_WIN32 + php_win32_ioutil_unlink(ZSTR_VAL(data->temp_name)); +#else unlink(ZSTR_VAL(data->temp_name)); +#endif /* temporary streams are never persistent */ - zend_string_release(data->temp_name); + zend_string_release_ex(data->temp_name, 0); data->temp_name = NULL; } } else { @@ -305,14 +494,37 @@ static int sw_php_stdiop_flush(php_stream *stream) { return 0; } +#if PHP_VERSION_ID >= 80100 +static int php_stdiop_sync(php_stream *stream, bool dataonly) +{ + php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract; + FILE *fp; + int fd; + + if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void**)&fp, REPORT_ERRORS) == FAILURE) { + return -1; + } + + if (sw_php_stdiop_flush(stream) == 0) { + PHP_STDIOP_GET_FD(fd, data); + if (dataonly) { + return fdatasync(fd); + } else { + return fsync(fd); + } + } + return -1; +} +#endif + static int sw_php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { php_stdio_stream_data *data = (php_stdio_stream_data *) stream->abstract; int ret; assert(data != NULL); - if (data->is_pipe) { - php_error_docref(NULL, E_WARNING, "cannot seek on a pipe"); + if (!data->is_seekable) { + php_error_docref(NULL, E_WARNING, "Cannot seek on this stream"); return -1; } @@ -326,8 +538,8 @@ static int sw_php_stdiop_seek(php_stream *stream, zend_off_t offset, int whence, return 0; } else { - ret = zend_fseek(data->file, offset, whence); - *newoffset = zend_ftell(data->file); + ret = fseek(data->file, offset, whence); + *newoffset = ftell(data->file); return ret; } } @@ -459,12 +671,17 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v if (fd == -1) { return -1; } - +#if PHP_VERSION_ID >= 80300 + if ((uintptr_t) ptrparam == PHP_STREAM_LOCK_SUPPORTED) { + return 0; + } +#else if ((zend_uintptr_t) ptrparam == PHP_STREAM_LOCK_SUPPORTED) { return 0; } +#endif - if (!swoole_coroutine_flock_ex(stream->orig_path, fd, value)) { + if (!swoole_coroutine_flock(fd, value)) { data->lock_flag = value; return 0; } else { @@ -475,7 +692,7 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v case PHP_STREAM_OPTION_MMAP_API: #if HAVE_MMAP { - php_stream_mmap_range *range = (php_stream_mmap_range *) ptrparam; + php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam; int prot, flags; switch (value) { @@ -486,15 +703,12 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v if (do_fstat(data, 1) != 0) { return PHP_STREAM_OPTION_RETURN_ERR; } - if (range->length == 0 && range->offset > 0 && range->offset < (size_t) data->sb.st_size) { - range->length = data->sb.st_size - range->offset; - } - if (range->length == 0 || range->length > (size_t) data->sb.st_size) { - range->length = data->sb.st_size; - } - if (range->offset >= (size_t) data->sb.st_size) { + if (range->offset > (size_t) data->sb.st_size) { range->offset = data->sb.st_size; - range->length = 0; + } + if (range->length == 0 || + range->length > data->sb.st_size - range->offset) { + range->length = data->sb.st_size - range->offset; } switch (range->mode) { case PHP_STREAM_MAP_MODE_READONLY: @@ -516,8 +730,8 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v default: return PHP_STREAM_OPTION_RETURN_ERR; } - range->mapped = (char *) mmap(NULL, range->length, prot, flags, fd, range->offset); - if (range->mapped == (char *) MAP_FAILED) { + range->mapped = (char*)mmap(NULL, range->length, prot, flags, fd, range->offset); + if (range->mapped == (char*)MAP_FAILED) { range->mapped = NULL; return PHP_STREAM_OPTION_RETURN_ERR; } @@ -538,9 +752,10 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v } #elif defined(PHP_WIN32) { - php_stream_mmap_range *range = (php_stream_mmap_range *) ptrparam; - HANDLE hfile = (HANDLE) _get_osfhandle(fd); - DWORD prot, acc, loffs = 0, delta = 0; + php_stream_mmap_range *range = (php_stream_mmap_range*)ptrparam; + HANDLE hfile = (HANDLE)_get_osfhandle(fd); + DWORD prot, acc, loffs = 0, hoffs = 0, delta = 0; + LARGE_INTEGER file_size; switch (value) { case PHP_STREAM_MMAP_SUPPORTED: @@ -577,16 +792,27 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v return PHP_STREAM_OPTION_RETURN_ERR; } - size = GetFileSize(hfile, NULL); - if (range->length == 0 && range->offset > 0 && range->offset < size) { - range->length = size - range->offset; + if (!GetFileSizeEx(hfile, &file_size)) { + CloseHandle(data->file_mapping); + data->file_mapping = NULL; + return PHP_STREAM_OPTION_RETURN_ERR; } - if (range->length == 0 || range->length > size) { - range->length = size; +# if defined(_WIN64) + size = file_size.QuadPart; +# else + if (file_size.HighPart) { + CloseHandle(data->file_mapping); + data->file_mapping = NULL; + return PHP_STREAM_OPTION_RETURN_ERR; + } else { + size = file_size.LowPart; } - if (range->offset >= size) { +# endif + if (range->offset > size) { range->offset = size; - range->length = 0; + } + if (range->length == 0 || range->length > size - range->offset) { + range->length = size - range->offset; } /* figure out how big a chunk to map to be able to view the part that we need */ @@ -596,11 +822,19 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v GetSystemInfo(&info); gran = info.dwAllocationGranularity; - loffs = ((DWORD) range->offset / gran) * gran; - delta = (DWORD) range->offset - loffs; + ZEND_ASSERT(gran != 0 && (gran & (gran - 1)) == 0); + size_t rounded_offset = (range->offset / gran) * gran; + delta = range->offset - rounded_offset; + loffs = (DWORD)rounded_offset; + hoffs = (DWORD)(rounded_offset >> 32); } - data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, 0, loffs, range->length + delta); + /* MapViewOfFile()ing zero bytes would map to the end of the file; match *nix behavior instead */ + if (range->length + delta == 0) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + + data->last_mapped_addr = MapViewOfFile(data->file_mapping, acc, hoffs, loffs, range->length + delta); if (data->last_mapped_addr) { /* give them back the address of the start offset they requested */ @@ -627,23 +861,68 @@ static int sw_php_stdiop_set_option(php_stream *stream, int option, int value, v return PHP_STREAM_OPTION_RETURN_ERR; } } - #endif return PHP_STREAM_OPTION_RETURN_NOTIMPL; +#if PHP_VERSION_ID >= 80100 + case PHP_STREAM_OPTION_SYNC_API: + switch (value) { + case PHP_STREAM_SYNC_SUPPORTED: + return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; + case PHP_STREAM_SYNC_FSYNC: + return php_stdiop_sync(stream, 0) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + case PHP_STREAM_SYNC_FDSYNC: + return php_stdiop_sync(stream, 1) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + /* Invalid option passed */ + return PHP_STREAM_OPTION_RETURN_ERR; +#endif + case PHP_STREAM_OPTION_TRUNCATE_API: switch (value) { case PHP_STREAM_TRUNCATE_SUPPORTED: return fd == -1 ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; case PHP_STREAM_TRUNCATE_SET_SIZE: { - ptrdiff_t new_size = *(ptrdiff_t *) ptrparam; + ptrdiff_t new_size = *(ptrdiff_t*)ptrparam; if (new_size < 0) { return PHP_STREAM_OPTION_RETURN_ERR; } +#ifdef PHP_WIN32 + HANDLE h = (HANDLE) _get_osfhandle(fd); + if (INVALID_HANDLE_VALUE == h) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + + LARGE_INTEGER sz, old_sz; + sz.QuadPart = 0; + + if (!SetFilePointerEx(h, sz, &old_sz, FILE_CURRENT)) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + +#ifdef _WIN64 + sz.QuadPart = new_size; +#else + sz.HighPart = 0; + sz.LowPart = new_size; +#endif + if (!SetFilePointerEx(h, sz, NULL, FILE_BEGIN)) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (0 == SetEndOfFile(h)) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (!SetFilePointerEx(h, old_sz, NULL, FILE_BEGIN)) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + return PHP_STREAM_OPTION_RETURN_OK; +#else return ftruncate(fd, new_size) == 0 ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; +#endif } } + return PHP_STREAM_OPTION_RETURN_NOTIMPL; #ifdef PHP_WIN32 case PHP_STREAM_OPTION_PIPE_BLOCKING: @@ -676,11 +955,19 @@ static php_stream_size_t php_plain_files_dirstream_read(php_stream *stream, char /* avoid problems if someone mis-uses the stream */ if (count != sizeof(php_stream_dirent)) { - return 0; + return -1; } - if ((result = readdir(dir))) { + result = readdir(dir); + if (result) { PHP_STRLCPY(ent->d_name, result->d_name, sizeof(ent->d_name), strlen(result->d_name)); +#if PHP_VERSION_ID >= 80300 +#ifdef _DIRENT_HAVE_D_TYPE + ent->d_type = result->d_type; +#else + ent->d_type = DT_UNKNOWN; +#endif +#endif return sizeof(php_stream_dirent); } return 0; @@ -748,7 +1035,7 @@ static php_stream *php_plain_files_dir_opener(php_stream_wrapper *wrapper, } /* }}} */ -static php_stream *stream_fopen_rel(const char *filename, +static php_stream *_sw_php_stream_fopen(const char *filename, const char *mode, zend_string **opened_path, int options STREAMS_DC) { @@ -760,9 +1047,7 @@ static php_stream *stream_fopen_rel(const char *filename, char *persistent_id = NULL; if (FAILURE == sw_php_stream_parse_fopen_modes(mode, &open_flags)) { - if (options & REPORT_ERRORS) { - php_error_docref(NULL, E_WARNING, "`%s' is not a valid mode for fopen", mode); - } + php_stream_wrapper_log_error(&php_plain_files_wrapper, options, "`%s' is not a valid mode for fopen", mode); return NULL; } @@ -783,6 +1068,9 @@ static php_stream *stream_fopen_rel(const char *filename, *opened_path = zend_string_init(_realpath, strlen(_realpath), 0); } /* fall through */ +#if PHP_VERSION_ID >= 80100 + ZEND_FALLTHROUGH; +#endif case PHP_STREAM_PERSISTENT_FAILURE: efree(persistent_id); @@ -795,7 +1083,7 @@ static php_stream *stream_fopen_rel(const char *filename, fd = open(_realpath, open_flags, 0666); #endif if (fd != -1) { - ret = stream_fopen_from_fd_rel(fd, mode, persistent_id); + ret = sw_php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0); if (ret) { if (opened_path) { *opened_path = zend_string_init(_realpath, strlen(_realpath), 0); @@ -816,12 +1104,16 @@ static php_stream *stream_fopen_rel(const char *filename, r = do_fstat(self, 0); if ((r == 0 && !S_ISREG(self->sb.st_mode))) { if (opened_path) { - zend_string_release(*opened_path); + zend_string_release_ex(*opened_path, 0); *opened_path = NULL; } php_stream_close(ret); return NULL; } + + /* Make sure the fstat result is reused when we later try to get the + * file size. */ + self->no_forced_fstat = 1; } if (options & STREAM_USE_BLOCKING_PIPE) { @@ -840,12 +1132,15 @@ static php_stream *stream_fopen_rel(const char *filename, return NULL; } -static php_stream *stream_opener(php_stream_wrapper *wrapper, +static php_stream *php_plain_files_stream_opener(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC) { + if (SW_STR_ISTARTS_WITH(path, strlen(path), SW_ASYNC_FILE_PROTOCOL "://")) { + path += sizeof(SW_ASYNC_FILE_PROTOCOL "://") - 1; + } if (((options & STREAM_DISABLE_OPEN_BASEDIR) == 0) && php_check_open_basedir(path)) { return NULL; } @@ -871,18 +1166,24 @@ static php_stream *stream_opener(php_stream_wrapper *wrapper, return stream; } - return stream_fopen_rel(path, mode, opened_path, options STREAMS_REL_CC); + return sw_php_stream_fopen_rel(path, mode, opened_path, options); } static int php_plain_files_url_stater( php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context) { - if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) { - url += sizeof("file://") - 1; - } +#if PHP_VERSION_ID >= 80100 + if (!(flags & PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR)) { +#endif + if (strncasecmp(url, "file://", sizeof("file://") - 1) == 0) { + url += sizeof("file://") - 1; + } - if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) { - return -1; + if (php_check_open_basedir_ex(url, (flags & PHP_STREAM_URL_STAT_QUIET) ? 0 : 1)) { + return -1; + } +#if PHP_VERSION_ID >= 80100 } +#endif #ifdef PHP_WIN32 if (flags & PHP_STREAM_URL_STAT_LINK) { @@ -964,34 +1265,51 @@ static int php_plain_files_rename( #ifdef EXDEV if (errno == EXDEV) { zend_stat_t sb; +#if !defined(ZTS) && !defined(TSRM_WIN32) + /* not sure what to do in ZTS case, umask is not thread-safe */ + int oldmask = umask(077); +#endif + int success = 0; if (php_copy_file(url_from, url_to) == SUCCESS) { if (stat(url_from, &sb) == 0) { -#if !defined(TSRM_WIN32) && !defined(NETWARE) - if (chmod(url_to, sb.st_mode)) { - if (errno == EPERM) { - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - unlink(url_from); - return 1; - } + success = 1; +#ifndef TSRM_WIN32 + /* + * Try to set user and permission info on the target. + * If we're not root, then some of these may fail. + * We try chown first, to set proper group info, relying + * on the system environment to have proper umask to not allow + * access to the file in the meantime. + */ + if (chown(url_to, sb.st_uid, sb.st_gid)) { php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; + if (errno != EPERM) { + success = 0; + } } - if (chown(url_to, sb.st_uid, sb.st_gid)) { - if (errno == EPERM) { + + if (success) { + if (chmod(url_to, sb.st_mode)) { php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - unlink(url_from); - return 1; + if (errno != EPERM) { + success = 0; + } } - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; } #endif - unlink(url_from); - return 1; + if (success) { + unlink(url_from); + } + } else { + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); } + } else { + php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); } - php_error_docref2(NULL, url_from, url_to, E_WARNING, "%s", strerror(errno)); - return 0; +#if !defined(ZTS) && !defined(TSRM_WIN32) + umask(oldmask); +#endif + return success; } #endif #endif @@ -1134,18 +1452,15 @@ static int php_plain_files_rmdir(php_stream_wrapper *wrapper, static int php_plain_files_metadata( php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context) { struct utimbuf *newtime; -#if !defined(WINDOWS) && !defined(NETWARE) +#ifndef PHP_WIN32 uid_t uid; gid_t gid; #endif mode_t mode; int ret = 0; -#ifdef PHP_WIN32 - int url_len = (int) strlen(url); -#endif #ifdef PHP_WIN32 - if (!php_win32_check_trailing_space(url, url_len)) { + if (!php_win32_check_trailing_space(url, strlen(url))) { php_error_docref1(NULL, url, E_WARNING, "%s", strerror(ENOENT)); return 0; } @@ -1173,7 +1488,7 @@ static int php_plain_files_metadata( ret = utime(url, newtime); break; -#if !defined(WINDOWS) && !defined(NETWARE) +#ifndef PHP_WIN32 case PHP_STREAM_META_OWNER_NAME: case PHP_STREAM_META_OWNER: if (option == PHP_STREAM_META_OWNER_NAME) { @@ -1204,7 +1519,7 @@ static int php_plain_files_metadata( ret = chmod(url, mode); break; default: - php_error_docref1(NULL, url, E_WARNING, "Unknown option %d for stream_metadata", option); + zend_value_error("Unknown option %d for stream_metadata", option); return 0; } if (ret == -1) { @@ -1215,47 +1530,25 @@ static int php_plain_files_metadata( return 1; } -static php_stream *_sw_php_stream_fopen_from_fd(int fd, const char *mode, const char *persistent_id STREAMS_DC) { - php_stream *stream = sw_php_stream_fopen_from_fd_int_rel(fd, mode, persistent_id); - - if (stream) { - php_stdio_stream_data *self = (php_stdio_stream_data *) stream->abstract; - - /* detect if this is a pipe */ - if (self->fd >= 0) { - self->is_pipe = (do_fstat(self, 0) == 0 && S_ISFIFO(self->sb.st_mode)) ? 1 : 0; - } - - if (self->is_pipe) { - stream->flags |= PHP_STREAM_FLAG_NO_SEEK; - } else { - stream->position = zend_lseek(self->fd, 0, SEEK_CUR); -#ifdef ESPIPE - if (stream->position == (zend_off_t) -1 && errno == ESPIPE) { - stream->position = 0; - stream->flags |= PHP_STREAM_FLAG_NO_SEEK; - self->is_pipe = 1; - } -#endif - } - } - - return stream; -} - -static int sw_php_mkdir(const char *dir, zend_long mode) { - int ret; - - if (php_check_open_basedir(dir)) { - return -1; - } - - if ((ret = mkdir(dir, (mode_t) mode)) < 0) { - php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); - } +static php_stream_wrapper_ops sw_php_plain_files_wrapper_ops = { + php_plain_files_stream_opener, + NULL, + NULL, + php_plain_files_url_stater, + php_plain_files_dir_opener, + "plainfile/coroutine", + php_plain_files_unlink, + php_plain_files_rename, + php_plain_files_mkdir, + php_plain_files_rmdir, + php_plain_files_metadata +}; - return ret; -} +PHPAPI php_stream_wrapper sw_php_plain_files_wrapper = { + &sw_php_plain_files_wrapper_ops, + NULL, + 0 +}; static void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *result) { /* replace modes not supported by fdopen and fopencookie, but supported @@ -1293,25 +1586,16 @@ static void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char result[res_curs] = '\0'; } -static php_stream_wrapper_ops wrapper_ops = {stream_opener, - NULL, - NULL, - php_plain_files_url_stater, - php_plain_files_dir_opener, - "plainfile/coroutine", - php_plain_files_unlink, - php_plain_files_rename, - php_plain_files_mkdir, - php_plain_files_rmdir, - php_plain_files_metadata}; +static int sw_php_mkdir(const char *dir, zend_long mode) { + int ret; -PHPAPI php_stream_wrapper sw_php_plain_files_wrapper = {&wrapper_ops, NULL, 0}; + if (php_check_open_basedir(dir)) { + return -1; + } -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ + if ((ret = mkdir(dir, (mode_t) mode)) < 0) { + php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); + } + + return ret; +} diff --git a/thirdparty/php/zend/zend_execute.c b/thirdparty/php/zend/zend_execute.c new file mode 100644 index 0000000000..22b9de1836 --- /dev/null +++ b/thirdparty/php/zend/zend_execute.c @@ -0,0 +1,643 @@ + +#include "zend.h" +#include "zend_compile.h" + +#if defined(ZEND_VM_FP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) +#define EXECUTE_DATA_D void +#define EXECUTE_DATA_C +#define EXECUTE_DATA_DC +#define EXECUTE_DATA_CC +#define NO_EXECUTE_DATA_CC +#else +#define EXECUTE_DATA_D zend_execute_data *execute_data +#define EXECUTE_DATA_C execute_data +#define EXECUTE_DATA_DC , EXECUTE_DATA_D +#define EXECUTE_DATA_CC , EXECUTE_DATA_C +#define NO_EXECUTE_DATA_CC , NULL +#endif + +#if defined(ZEND_VM_FP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)) +#define OPLINE_D void +#define OPLINE_C +#define OPLINE_DC +#define OPLINE_CC +#else +#define OPLINE_D const zend_op *opline +#define OPLINE_C opline +#define OPLINE_DC , OPLINE_D +#define OPLINE_CC , OPLINE_C +#endif + +#define FREE_OP(type, var) \ + if ((type) & (IS_TMP_VAR | IS_VAR)) { \ + zval_ptr_dtor_nogc(EX_VAR(var)); \ + } + +#define RETURN_VALUE_USED(opline) ((opline)->result_type != IS_UNUSED) + +#define CV_DEF_OF(i) (EX(func)->op_array.vars[i]) + +#define get_zval_ptr(op_type, node, type) _get_zval_ptr(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_deref(op_type, node, type) _get_zval_ptr_deref(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_undef(op_type, node, type) _get_zval_ptr_undef(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_op_data_zval_ptr_r(op_type, node) _get_op_data_zval_ptr_r(op_type, node EXECUTE_DATA_CC OPLINE_CC) +#define get_op_data_zval_ptr_deref_r(op_type, node) \ + _get_op_data_zval_ptr_deref_r(op_type, node EXECUTE_DATA_CC OPLINE_CC) +#define get_zval_ptr_ptr(op_type, node, type) _get_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) +#define get_zval_ptr_ptr_undef(op_type, node, type) _get_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) +#define get_obj_zval_ptr(op_type, node, type) _get_obj_zval_ptr(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_obj_zval_ptr_undef(op_type, node, type) \ + _get_obj_zval_ptr_undef(op_type, node, type EXECUTE_DATA_CC OPLINE_CC) +#define get_obj_zval_ptr_ptr(op_type, node, type) _get_obj_zval_ptr_ptr(op_type, node, type EXECUTE_DATA_CC) + +#if PHP_VERSION_ID < 80300 +static ZEND_COLD void zend_illegal_container_offset(zend_string *container, const zval *offset, int type) { + switch (type) { + case BP_VAR_IS: + zend_type_error("Cannot access offset of type %s in isset or empty", + zend_zval_type_name(offset)); + return; + case BP_VAR_UNSET: + /* Consistent error for when trying to unset a string offset */ + if (zend_string_equals(container, ZSTR_KNOWN(ZEND_STR_STRING))) { + zend_throw_error(NULL, "Cannot unset string offsets"); + } else { + zend_type_error("Cannot unset offset of type %s on %s", zend_zval_type_name(offset), ZSTR_VAL(container)); + } + return; + default: + zend_type_error("Cannot access offset of type %s on %s", + zend_zval_type_name(offset), ZSTR_VAL(container)); + return; + } +} +#endif + +static zend_always_inline zval *_get_zval_ptr_tmp(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + ZEND_ASSERT(Z_TYPE_P(ret) != IS_REFERENCE); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_var(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_var_deref(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + ZVAL_DEREF(ret); + return ret; +} + +static zend_never_inline ZEND_COLD zval *zval_undefined_cv(uint32_t var EXECUTE_DATA_DC) { + if (EXPECTED(EG(exception) == NULL)) { + zend_string *cv = CV_DEF_OF(EX_VAR_TO_NUM(var)); + zend_error(E_WARNING, "Undefined variable $%s", ZSTR_VAL(cv)); + } + return &EG(uninitialized_zval); +} + +static zend_never_inline ZEND_COLD zval *ZEND_FASTCALL _zval_undefined_op1(EXECUTE_DATA_D) { + return zval_undefined_cv(EX(opline)->op1.var EXECUTE_DATA_CC); +} + +static zend_never_inline ZEND_COLD zval *ZEND_FASTCALL _zval_undefined_op2(EXECUTE_DATA_D) { + return zval_undefined_cv(EX(opline)->op2.var EXECUTE_DATA_CC); +} + +#define ZVAL_UNDEFINED_OP1() _zval_undefined_op1(EXECUTE_DATA_C) +#define ZVAL_UNDEFINED_OP2() _zval_undefined_op2(EXECUTE_DATA_C) + +static zend_never_inline ZEND_COLD zval *_get_zval_cv_lookup(zval *ptr, uint32_t var, int type EXECUTE_DATA_DC) { + switch (type) { + case BP_VAR_R: + case BP_VAR_UNSET: + ptr = zval_undefined_cv(var EXECUTE_DATA_CC); + break; + case BP_VAR_IS: + ptr = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + zval_undefined_cv(var EXECUTE_DATA_CC); + ZEND_FALLTHROUGH; + case BP_VAR_W: + ZVAL_NULL(ptr); + break; + } + return ptr; +} + +static zend_always_inline zval *_get_zval_ptr_cv(uint32_t var, int type EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + if (type == BP_VAR_W) { + ZVAL_NULL(ret); + } else { + return _get_zval_cv_lookup(ret, var, type EXECUTE_DATA_CC); + } + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_deref(uint32_t var, int type EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + if (type == BP_VAR_W) { + ZVAL_NULL(ret); + return ret; + } else { + return _get_zval_cv_lookup(ret, var, type EXECUTE_DATA_CC); + } + } + ZVAL_DEREF(ret); + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_R(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + return zval_undefined_cv(var EXECUTE_DATA_CC); + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_deref_BP_VAR_R(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + return zval_undefined_cv(var EXECUTE_DATA_CC); + } + ZVAL_DEREF(ret); + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_IS(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_RW(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (UNEXPECTED(Z_TYPE_P(ret) == IS_UNDEF)) { + zval_undefined_cv(var EXECUTE_DATA_CC); + ZVAL_NULL(ret); + return ret; + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr_cv_BP_VAR_W(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (Z_TYPE_P(ret) == IS_UNDEF) { + ZVAL_NULL(ret); + } + return ret; +} + +static zend_always_inline zval *_get_zval_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv(node.var, type EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_op_data_zval_ptr_r(int op_type, znode_op node EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline + 1, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_BP_VAR_R(node.var EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline ZEND_ATTRIBUTE_UNUSED zval *_get_zval_ptr_deref(int op_type, + znode_op node, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (op_type == IS_TMP_VAR) { + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_var_deref(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_deref(node.var, type EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline ZEND_ATTRIBUTE_UNUSED zval *_get_op_data_zval_ptr_deref_r( + int op_type, znode_op node EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (op_type == IS_TMP_VAR) { + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_var_deref(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline + 1, node); + } else if (op_type == IS_CV) { + return _get_zval_ptr_cv_deref_BP_VAR_R(node.var EXECUTE_DATA_CC); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_zval_ptr_undef(int op_type, znode_op node, int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type & (IS_TMP_VAR | IS_VAR)) { + if (!ZEND_DEBUG || op_type == IS_VAR) { + return _get_zval_ptr_var(node.var EXECUTE_DATA_CC); + } else { + ZEND_ASSERT(op_type == IS_TMP_VAR); + return _get_zval_ptr_tmp(node.var EXECUTE_DATA_CC); + } + } else { + if (op_type == IS_CONST) { + return RT_CONSTANT(opline, node); + } else if (op_type == IS_CV) { + return EX_VAR(node.var); + } else { + return NULL; + } + } +} + +static zend_always_inline zval *_get_zval_ptr_ptr_var(uint32_t var EXECUTE_DATA_DC) { + zval *ret = EX_VAR(var); + + if (EXPECTED(Z_TYPE_P(ret) == IS_INDIRECT)) { + ret = Z_INDIRECT_P(ret); + } + return ret; +} + +static inline zval *_get_zval_ptr_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC) { + if (op_type == IS_CV) { + return _get_zval_ptr_cv(node.var, type EXECUTE_DATA_CC); + } else /* if (op_type == IS_VAR) */ { + ZEND_ASSERT(op_type == IS_VAR); + return _get_zval_ptr_ptr_var(node.var EXECUTE_DATA_CC); + } +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr(int op_type, + znode_op op, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr(op_type, op, type); +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr_undef(int op_type, + znode_op op, + int type EXECUTE_DATA_DC OPLINE_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr_undef(op_type, op, type); +} + +static inline ZEND_ATTRIBUTE_UNUSED zval *_get_obj_zval_ptr_ptr(int op_type, znode_op node, int type EXECUTE_DATA_DC) { + if (op_type == IS_UNUSED) { + return &EX(This); + } + return get_zval_ptr_ptr(op_type, node, type); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_index(const zend_string *offset) { + zend_error(E_WARNING, "Undefined array key \"%s\"", ZSTR_VAL(offset)); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_undefined_offset(zend_long lval) { + zend_error(E_WARNING, "Undefined array key " ZEND_LONG_FMT, lval); +} + +static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_illegal_array_offset_access(const zval *offset) { + zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_ARRAY), offset, BP_VAR_RW); +} + +static zend_never_inline uint8_t slow_index_convert(HashTable *ht, const zval *dim, zend_value *value EXECUTE_DATA_DC) { + switch (Z_TYPE_P(dim)) { + case IS_UNDEF: { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + ZVAL_UNDEFINED_OP2(); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + ZEND_FALLTHROUGH; + } + case IS_NULL: + value->str = ZSTR_EMPTY_ALLOC(); + return IS_STRING; + case IS_DOUBLE: + value->lval = zend_dval_to_lval(Z_DVAL_P(dim)); + if (!zend_is_long_compatible(Z_DVAL_P(dim), value->lval)) { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_incompatible_double_to_long_error(Z_DVAL_P(dim)); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + } + return IS_LONG; + case IS_RESOURCE: + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_use_resource_as_offset(dim); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && !GC_DELREF(ht)) { + zend_array_destroy(ht); + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + value->lval = Z_RES_HANDLE_P(dim); + return IS_LONG; + case IS_FALSE: + value->lval = 0; + return IS_LONG; + case IS_TRUE: + value->lval = 1; + return IS_LONG; + default: + zend_illegal_array_offset_access(dim); + return IS_NULL; + } +} + +static zend_never_inline uint8_t slow_index_convert_w(HashTable *ht, + const zval *dim, + zend_value *value EXECUTE_DATA_DC) { + switch (Z_TYPE_P(dim)) { + case IS_UNDEF: { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + ZVAL_UNDEFINED_OP2(); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + ZEND_FALLTHROUGH; + } + case IS_NULL: + value->str = ZSTR_EMPTY_ALLOC(); + return IS_STRING; + case IS_DOUBLE: + value->lval = zend_dval_to_lval(Z_DVAL_P(dim)); + if (!zend_is_long_compatible(Z_DVAL_P(dim), value->lval)) { + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_incompatible_double_to_long_error(Z_DVAL_P(dim)); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + } + return IS_LONG; + case IS_RESOURCE: + /* The array may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(ht); + } + zend_use_resource_as_offset(dim); + if (!(GC_FLAGS(ht) & IS_ARRAY_IMMUTABLE) && GC_DELREF(ht) != 1) { + if (!GC_REFCOUNT(ht)) { + zend_array_destroy(ht); + } + return IS_NULL; + } + if (EG(exception)) { + return IS_NULL; + } + value->lval = Z_RES_HANDLE_P(dim); + return IS_LONG; + case IS_FALSE: + value->lval = 0; + return IS_LONG; + case IS_TRUE: + value->lval = 1; + return IS_LONG; + default: + zend_illegal_array_offset_access(dim); + return IS_NULL; + } +} + +static zend_always_inline zval *zend_fetch_dimension_address_inner(HashTable *ht, + const zval *dim, + int dim_type, + int type EXECUTE_DATA_DC) { + zval *retval = NULL; + zend_string *offset_key; + zend_ulong hval; + +try_again: + if (EXPECTED(Z_TYPE_P(dim) == IS_LONG)) { + hval = Z_LVAL_P(dim); + num_index: + if (type != BP_VAR_W) { + ZEND_HASH_INDEX_FIND(ht, hval, retval, num_undef); + return retval; + num_undef: + switch (type) { + case BP_VAR_R: + zend_undefined_offset(hval); + ZEND_FALLTHROUGH; + case BP_VAR_UNSET: + case BP_VAR_IS: + retval = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + retval = zend_undefined_offset_write(ht, hval); + break; + } + } else { + ZEND_HASH_INDEX_LOOKUP(ht, hval, retval); + } + } else if (EXPECTED(Z_TYPE_P(dim) == IS_STRING)) { + offset_key = Z_STR_P(dim); + if (ZEND_CONST_COND(dim_type != IS_CONST, 1)) { + if (ZEND_HANDLE_NUMERIC(offset_key, hval)) { + goto num_index; + } + } + str_index: + if (type != BP_VAR_W) { + retval = zend_hash_find_ex(ht, offset_key, ZEND_CONST_COND(dim_type == IS_CONST, 0)); + if (!retval) { + switch (type) { + case BP_VAR_R: + zend_undefined_index(offset_key); + ZEND_FALLTHROUGH; + case BP_VAR_UNSET: + case BP_VAR_IS: + retval = &EG(uninitialized_zval); + break; + case BP_VAR_RW: + retval = zend_undefined_index_write(ht, offset_key); + break; + } + } + } else { + retval = zend_hash_lookup(ht, offset_key); + } + } else if (EXPECTED(Z_TYPE_P(dim) == IS_REFERENCE)) { + dim = Z_REFVAL_P(dim); + goto try_again; + } else { + zend_value val; + uint8_t t; + + if (type != BP_VAR_W && type != BP_VAR_RW) { + t = slow_index_convert(ht, dim, &val EXECUTE_DATA_CC); + } else { + t = slow_index_convert_w(ht, dim, &val EXECUTE_DATA_CC); + } + if (t == IS_STRING) { + offset_key = val.str; + goto str_index; + } else if (t == IS_LONG) { + hval = val.lval; + goto num_index; + } else { + retval = (type == BP_VAR_W || type == BP_VAR_RW) ? NULL : &EG(uninitialized_zval); + } + } + return retval; +} + +static zend_never_inline zval *ZEND_FASTCALL +zend_fetch_dimension_address_inner_RW_CONST(HashTable *ht, const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_CONST, BP_VAR_RW EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL zend_fetch_dimension_address_inner_RW(HashTable *ht, + const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_TMP_VAR, BP_VAR_RW EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL zend_fetch_dimension_address_inner_W(HashTable *ht, + const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_TMP_VAR, BP_VAR_W EXECUTE_DATA_CC); +} + +static zend_never_inline zval *ZEND_FASTCALL +zend_fetch_dimension_address_inner_W_CONST(HashTable *ht, const zval *dim EXECUTE_DATA_DC) { + return zend_fetch_dimension_address_inner(ht, dim, IS_CONST, BP_VAR_W EXECUTE_DATA_CC); +} + +static zend_always_inline int zend_binary_op(zval *ret, zval *op1, zval *op2 OPLINE_DC) { + static const binary_op_type zend_binary_ops[] = {add_function, + sub_function, + mul_function, + div_function, + mod_function, + shift_left_function, + shift_right_function, + concat_function, + bitwise_or_function, + bitwise_and_function, + bitwise_xor_function, + pow_function}; + /* size_t cast makes GCC to better optimize 64-bit PIC code */ + size_t opcode = (size_t) opline->extended_value; + + return zend_binary_ops[opcode - ZEND_ADD](ret, op1, op2); +} + +static zend_never_inline void zend_binary_assign_op_typed_ref(zend_reference *ref, + zval *value OPLINE_DC EXECUTE_DATA_DC) { + zval z_copy; + + /* Make sure that in-place concatenation is used if the LHS is a string. */ + if (opline->extended_value == ZEND_CONCAT && Z_TYPE(ref->val) == IS_STRING) { + concat_function(&ref->val, &ref->val, value); + ZEND_ASSERT(Z_TYPE(ref->val) == IS_STRING && "Concat should return string"); + return; + } + + zend_binary_op(&z_copy, &ref->val, value OPLINE_CC); + if (EXPECTED(zend_verify_ref_assignable_zval(ref, &z_copy, EX_USES_STRICT_TYPES()))) { + zval_ptr_dtor(&ref->val); + ZVAL_COPY_VALUE(&ref->val, &z_copy); + } else { + zval_ptr_dtor(&z_copy); + } +} diff --git a/thirdparty/php82/pdo_odbc/odbc_driver.c b/thirdparty/php82/pdo_odbc/odbc_driver.c new file mode 100644 index 0000000000..6ce2fcfcd9 --- /dev/null +++ b/thirdparty/php82/pdo_odbc/odbc_driver.c @@ -0,0 +1,605 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "zend_exceptions.h" +#include + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, + "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, + (long) einfo->last_error, + einfo->file, + einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + +void pdo_odbc_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, + eh, + recno++, + (SQLCHAR *) einfo->last_state, + &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, + sizeof(einfo->last_err_msg) - 1, + &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); + /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), + einfo->last_error, + "SQLSTATE[%s] %s: %d %s", + *pdo_err, + what, + einfo->last_error, + einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, discard_buf, sizeof(discard_buf) - 1, &errmsgsize); + } +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void *) SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t +*quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) { + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error( + dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = + SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error(dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" + : "SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) { + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER) buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) { + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL /* get_gc */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void *) pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) (intptr_t) (dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void *) cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + /* Force UID and PWD to be set in the DSN */ + bool is_uid_set = + dbh->username && *dbh->username && !strstr(dbh->data_source, "uid=") && !strstr(dbh->data_source, "UID="); + bool is_pwd_set = + dbh->password && *dbh->password && !strstr(dbh->data_source, "pwd=") && !strstr(dbh->data_source, "PWD="); + if (is_uid_set && is_pwd_set) { + char *uid = NULL, *pwd = NULL; + bool should_quote_uid = + !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + bool should_quote_pwd = + !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + size_t new_dsn_size = strlen(dbh->data_source) + strlen(uid) + strlen(pwd) + strlen(";UID=;PWD=") + 1; + char *dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s", dbh->data_source, uid, pwd); + pefree((char *) dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + } + + rc = SQLDriverConnect(H->dbc, + NULL, + (SQLCHAR *) dbh->data_source, + strlen(dbh->data_source), + dsnbuf, + sizeof(dsnbuf) - 1, + &dsnbuflen, + SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, + (SQLCHAR *) dbh->data_source, + SQL_NTS, + (SQLCHAR *) dbh->username, + SQL_NTS, + (SQLCHAR *) dbh->password, + SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = {PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory}; +#endif diff --git a/thirdparty/php82/pdo_odbc/odbc_stmt.c b/thirdparty/php82/pdo_odbc/odbc_stmt.c new file mode 100644 index 0000000000..398e66ef68 --- /dev/null +++ b/thirdparty/php82/pdo_odbc/odbc_stmt.c @@ -0,0 +1,866 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, PDO_ODBC_CONV_OK, PDO_ODBC_CONV_FAIL }; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2( + pdo_stmt_t *stmt, int is_unicode, const char *buf, zend_ulong buflen, zend_ulong *outlen) { +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR) S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) { +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte( + CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result) / sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, + 0, + (LPCWSTR) Z_STRVAL_P(result), + Z_STRLEN_P(result) / sizeof(WCHAR), + ZSTR_VAL(str), + ZSTR_LEN(str), + NULL, + NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) { + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) { + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER *) ¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param *) param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + RETCODE rc; + SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno + 1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2 : 1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, + (SQLUSMALLINT) param->paramno + 1, + P->paramtype, + ctype, + sqltype, + precision, + scale, + P->paramtype == SQL_PARAM_INPUT ? (SQLPOINTER) param : P->outbuf, + P->len, + &P->len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch ( + pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + odbcori = SQL_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + odbcori = SQL_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + odbcori = SQL_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + odbcori = SQL_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + odbcori = SQL_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + odbcori = SQL_FETCH_RELATIVE; + break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SQLSMALLINT colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, + colno + 1, + (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname) - 1, + &colnamelen, + &S->cols[colno].coltype, + &colsize, + NULL, + NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && (S->cols[colno].coltype == SQL_VARCHAR || S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize + 1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, + colno + 1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen + 1, + &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2 = emalloc(256); + zend_string *str = zend_string_init(C->data, 256, 0); + size_t used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData( + S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* adjust `used` in case we have proper length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + if (fixed_used <= used + 1) { + used = fixed_used; + } + } + + /* resize output buffer and reassemble block */ + if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in + http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx states that if + SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) (if a driver fails to + follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); + used = used + 255; + } else if (rc == SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) { + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = {odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor}; +#endif diff --git a/thirdparty/php82/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php82/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 0000000000..6fac35495c --- /dev/null +++ b/thirdparty/php82/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,183 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +#define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +#warning Please fix configure to give your ODBC libraries a name +#define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +#include +#if defined(DB268K) && HAVE_LIBRARYMANAGER_H +#include +#endif +#endif + +#ifdef HAVE_ODBC_H +#include +#endif + +#ifdef HAVE_IODBC_H +#include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +#include +#endif + +#ifdef HAVE_SQLTYPES_H +#include +#endif + +#ifdef HAVE_SQLUCODE_H +#include +#endif + +#ifdef HAVE_SQL_H +#include +#endif + +#ifdef HAVE_ISQL_H +#include +#endif + +#ifdef HAVE_SQLEXT_H +#include +#endif + +#ifdef HAVE_ISQLEXT_H +#include +#endif + +#ifdef HAVE_UDBCEXT_H +#include +#endif + +#ifdef HAVE_CLI0CORE_H +#include +#endif + +#ifdef HAVE_CLI0EXT1_H +#include +#endif + +#ifdef HAVE_CLI0CLI_H +#include +#endif + +#ifdef HAVE_CLI0DEFS_H +#include +#endif + +#ifdef HAVE_CLI0ENV_H +#include +#endif + +#ifdef HAVE_ODBCSDK_H +#include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +#define PDO_ODBC_HENV SQLHANDLE +#define PDO_ODBC_HDBC SQLHANDLE +#define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +#define PDO_ODBC_HENV SQLHENV +#define PDO_ODBC_HDBC SQLHDBC +#define PDO_ODBC_HSTMT SQLHSTMT +#else +#define PDO_ODBC_HENV HENV +#define PDO_ODBC_HDBC HDBC +#define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8 : 1; + unsigned _spare : 31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SQLSMALLINT coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long : 1; + unsigned assume_utf8 : 1; + signed col_count : 16; + unsigned _spare : 14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php82/pdo_pgsql/pgsql_driver.c b/thirdparty/php82/pdo_pgsql/pgsql_driver.c new file mode 100644 index 0000000000..a2fcc9cd62 --- /dev/null +++ b/thirdparty/php82/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1329 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/standard/php_string.h" +#include "main/php_network.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char * _pdo_pgsql_trim_message(const char *message, int persistent) +{ + size_t i = strlen(message)-1; + char *tmp; + + if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { + --i; + } + while (i>0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string* _pdo_pgsql_escape_credentials(char *str) +{ + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } + else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } + else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ +{ +/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_write(self->conn, self->lfd, (char*)buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) +{ + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, + zend_off_t *newoffset) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; +#if defined(HAVE_PG_LO64) && defined(ZEND_ENABLE_ZVAL_LONG64) + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const php_stream_ops pdo_pgsql_lob_stream_ops = { + pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid) +{ + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + Z_ADDREF_P(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) +{ + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, + PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (!emulate && PQprotocolVersion(H->server) <= 2) { + emulate = 1; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + size_t tmp_len; + int err; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *)ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + if (escaped == NULL) { + return NULL; + } + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted+1, escaped, quotedlen-2); + quoted[0] = '\''; + quoted[quotedlen-1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &err); + if (err) { + efree(quoted); + return NULL; + } + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *)PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) +{ + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + if (PQprotocolVersion(H->server) >= 3) { /* PostgreSQL 7.4 or later */ + ZVAL_STRING(return_value, (char*)PQparameterStatus(H->server, "server_version")); + } else /* emulate above via a query */ + { + PGresult *res = PQexec(H->server, "SELECT VERSION()"); + if (res && PQresultStatus(res) == PGRES_TUPLES_OK) { + ZVAL_STRING(return_value, (char *)PQgetvalue(res, 0, 0)); + } + + if (res) { + PQclear(res); + } + } + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL(return_value, "Waiting for connection to be made.", sizeof("Waiting for connection to be made.")-1); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", sizeof("Connection OK; waiting to send.")-1); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, "Waiting for a response from the server.", sizeof("Waiting for a response from the server.")-1); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, "Received authentication; waiting for backend start-up to finish.", sizeof("Received authentication; waiting for backend start-up to finish.")-1); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", sizeof("Negotiating SSL encryption.")-1); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, "Negotiating environment-driven parameter settings.", sizeof("Negotiating environment-driven parameter settings.")-1); + break; + + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", sizeof("Bad connection.")-1); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char*)PQparameterStatus(H->server, "client_encoding"), + (char*)PQparameterStatus(H->server, "is_superuser"), + (char*)PQparameterStatus(H->server, "session_authorization"), + (char*)PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) +{ + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) +{ + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) +{ + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|sss!", + &table_name, &table_name_len, &pg_rows, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + memcpy(query, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + query_len = Z_STRLEN_P(tmp); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t)ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sss!", + &table_name, &table_name_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + lfd = lo_creat(H->server, INV_READ|INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", + &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ|INV_WRITE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", + &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int)ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} +/* }}} */ + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = { + pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL /* get_gc */ +}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *conn_str, *p, *e; + zend_string *tmp_user, *tmp_pass; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = + 1 << PDO_PARAM_EVT_EXEC_POST | + 1 << PDO_PARAM_EVT_FETCH_PRE | + 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = _pdo_pgsql_escape_credentials(dbh->username); + tmp_pass = _pdo_pgsql_escape_credentials(dbh->password); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user && tmp_pass) { + spprintf(&conn_str, 0, "%s user='%s' password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), ZSTR_VAL(tmp_pass), connect_timeout); + } else if (tmp_user) { + spprintf(&conn_str, 0, "%s user='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), connect_timeout); + } else if (tmp_pass) { + spprintf(&conn_str, 0, "%s password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_pass), connect_timeout); + } else { + spprintf(&conn_str, 0, "%s connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, connect_timeout); + } + + H->server = PQconnectdb(conn_str); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + efree(conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = { + PDO_DRIVER_HEADER(pgsql), + pdo_pgsql_handle_factory +}; +#endif diff --git a/thirdparty/php82/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php82/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 0000000000..a1c51dc0c4 --- /dev/null +++ b/thirdparty/php82/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,72 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 9bb79af98dbb7c171fd9533aeabece4937a06cd2 */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php82/pdo_pgsql/pgsql_statement.c b/thirdparty/php82/pdo_pgsql/pgsql_statement.c new file mode 100644 index 0000000000..5a76dde787 --- /dev/null +++ b/thirdparty/php82/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,699 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 + + + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle) + && IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) + && !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); + if (res) { + PQclear(res); + } + } + efree(S->stmt_name); + S->stmt_name = NULL; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if(S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* ensure that we free any previous unfetched results */ + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf(&q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else if (S->stmt_name) { + /* using a prepared statement */ + + if (!S->is_prepared) { +stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, S->stmt_name, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = 1; + PQclear(S->result); + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chance to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + PGresult *res; + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + S->result = PQexecPrepared(H->server, S->stmt_name, + stmt->bound_params ? + zend_hash_num_elements(stmt->bound_params) : + 0, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long)PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: + /* decode name from $1, $2 into 0, 1 etc. */ + if (param->name) { + if (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && (namevar = zend_hash_find_ptr(stmt->bound_param_map, + param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!stmt->bound_param_map) { + return 1; + } + if (!S->param_values) { + S->param_values = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(char*)); + S->param_lengths = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_formats = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_types = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(Oid)); + } + if (param->paramno >= 0) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && + Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char*)&P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || + Z_TYPE_P(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; + S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; + } + } + break; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: spprintf(&ori_str, 0, "NEXT"); break; + case PDO_FETCH_ORI_PRIOR: spprintf(&ori_str, 0, "BACKWARD"); break; + case PDO_FETCH_ORI_FIRST: spprintf(&ori_str, 0, "FIRST"); break; + case PDO_FETCH_ORI_LAST: spprintf(&ori_str, 0, "LAST"); break; + case PDO_FETCH_ORI_ABS: spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); break; + case PDO_FETCH_ORI_REL: spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); break; + default: + return 0; + } + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid)strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *)PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) +{ + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + PGresult *res; + char *q=NULL; + ExecStatusType status; + Oid table_oid; + char *table_name=NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + return 1; +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = { + pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer +}; +#endif diff --git a/thirdparty/php82/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php82/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 0000000000..3b2773298b --- /dev/null +++ b/thirdparty/php82/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,112 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached:1; + unsigned _reserved:31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + bool emulate_prepares; + bool disable_native_prepares; /* deprecated since 5.6 */ + bool disable_prepares; + HashTable *lob_streams; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s,e,z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php83/pdo_odbc/odbc_driver.c b/thirdparty/php83/pdo_odbc/odbc_driver.c new file mode 100644 index 0000000000..5c7cbe7689 --- /dev/null +++ b/thirdparty/php83/pdo_odbc/odbc_driver.c @@ -0,0 +1,605 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "zend_exceptions.h" +#include + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, + "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, + (long) einfo->last_error, + einfo->file, + einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + +void pdo_odbc_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, + eh, + recno++, + (SQLCHAR *) einfo->last_state, + &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, + sizeof(einfo->last_err_msg) - 1, + &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); + /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), + einfo->last_error, + "SQLSTATE[%s] %s: %d %s", + *pdo_err, + what, + einfo->last_error, + einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, discard_buf, sizeof(discard_buf) - 1, &errmsgsize); + } +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void *) SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t +*quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) { + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error( + dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = + SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error(dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" + : "SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) { + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER) buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) { + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL /* get_gc */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void *) pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) (intptr_t) (dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void *) cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + /* Force UID and PWD to be set in the DSN */ + bool is_uid_set = + dbh->username && *dbh->username && !strstr(dbh->data_source, "uid=") && !strstr(dbh->data_source, "UID="); + bool is_pwd_set = + dbh->password && *dbh->password && !strstr(dbh->data_source, "pwd=") && !strstr(dbh->data_source, "PWD="); + if (is_uid_set && is_pwd_set) { + char *uid = NULL, *pwd = NULL; + bool should_quote_uid = + !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + bool should_quote_pwd = + !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + size_t new_dsn_size = strlen(dbh->data_source) + strlen(uid) + strlen(pwd) + strlen(";UID=;PWD=") + 1; + char *dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s", dbh->data_source, uid, pwd); + pefree((char *) dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + } + + rc = SQLDriverConnect(H->dbc, + NULL, + (SQLCHAR *) dbh->data_source, + strlen(dbh->data_source), + dsnbuf, + sizeof(dsnbuf) - 1, + &dsnbuflen, + SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, + (SQLCHAR *) dbh->data_source, + SQL_NTS, + (SQLCHAR *) dbh->username, + SQL_NTS, + (SQLCHAR *) dbh->password, + SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = {PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory}; +#endif diff --git a/thirdparty/php83/pdo_odbc/odbc_stmt.c b/thirdparty/php83/pdo_odbc/odbc_stmt.c new file mode 100644 index 0000000000..da75f66841 --- /dev/null +++ b/thirdparty/php83/pdo_odbc/odbc_stmt.c @@ -0,0 +1,865 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, PDO_ODBC_CONV_OK, PDO_ODBC_CONV_FAIL }; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SWORD sqltype) { + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2( + pdo_stmt_t *stmt, int is_unicode, const char *buf, zend_ulong buflen, zend_ulong *outlen) { +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR) S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) { +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte( + CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result) / sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, + 0, + (LPCWSTR) Z_STRVAL_P(result), + Z_STRLEN_P(result) / sizeof(WCHAR), + ZSTR_VAL(str), + ZSTR_LEN(str), + NULL, + NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) { + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) { + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER *) ¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param *) param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + RETCODE rc; + SWORD sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno + 1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2 : 1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, + (SQLUSMALLINT) param->paramno + 1, + P->paramtype, + ctype, + sqltype, + precision, + scale, + P->paramtype == SQL_PARAM_INPUT ? (SQLPOINTER) param : P->outbuf, + P->len, + &P->len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch ( + pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + odbcori = SQL_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + odbcori = SQL_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + odbcori = SQL_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + odbcori = SQL_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + odbcori = SQL_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + odbcori = SQL_FETCH_RELATIVE; + break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SWORD colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, + colno + 1, + (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname) - 1, + &colnamelen, + &S->cols[colno].coltype, + &colsize, + NULL, + NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && (S->cols[colno].coltype == SQL_VARCHAR || S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize + 1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, + colno + 1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen + 1, + &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2 = emalloc(256); + zend_string *str = zend_string_init(C->data, 256, 0); + size_t used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData( + S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* adjust `used` in case we have length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + ZEND_ASSERT(fixed_used <= used + 1); + used = fixed_used; + } + + /* resize output buffer and reassemble block */ + if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in + http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx states that if + SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) (if a driver fails to + follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); + used = used + 255; + } else if (rc == SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) { + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = {odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor}; +#endif diff --git a/thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 0000000000..6f1b99b0c5 --- /dev/null +++ b/thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,183 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +# define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +# warning Please fix configure to give your ODBC libraries a name +# define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +# include +# if defined(DB268K) && HAVE_LIBRARYMANAGER_H +# include +# endif +#endif + +#ifdef HAVE_ODBC_H +# include +#endif + +#ifdef HAVE_IODBC_H +# include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +# include +#endif + +#ifdef HAVE_SQLTYPES_H +# include +#endif + +#ifdef HAVE_SQLUCODE_H +# include +#endif + +#ifdef HAVE_SQL_H +# include +#endif + +#ifdef HAVE_ISQL_H +# include +#endif + +#ifdef HAVE_SQLEXT_H +# include +#endif + +#ifdef HAVE_ISQLEXT_H +# include +#endif + +#ifdef HAVE_UDBCEXT_H +# include +#endif + +#ifdef HAVE_CLI0CORE_H +# include +#endif + +#ifdef HAVE_CLI0EXT1_H +# include +#endif + +#ifdef HAVE_CLI0CLI_H +# include +#endif + +#ifdef HAVE_CLI0DEFS_H +# include +#endif + +#ifdef HAVE_CLI0ENV_H +# include +#endif + +#ifdef HAVE_ODBCSDK_H +# include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +# define PDO_ODBC_HENV SQLHANDLE +# define PDO_ODBC_HDBC SQLHANDLE +# define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +# define PDO_ODBC_HENV SQLHENV +# define PDO_ODBC_HDBC SQLHDBC +# define PDO_ODBC_HSTMT SQLHSTMT +#else +# define PDO_ODBC_HENV HENV +# define PDO_ODBC_HDBC HDBC +# define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SDWORD last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8:1; + unsigned _spare:31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SWORD coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long:1; + unsigned assume_utf8:1; + signed col_count:16; + unsigned _spare:14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php83/pdo_pgsql/pgsql_driver.c b/thirdparty/php83/pdo_pgsql/pgsql_driver.c new file mode 100644 index 0000000000..86b05d37fc --- /dev/null +++ b/thirdparty/php83/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1341 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/standard/php_string.h" +#include "main/php_network.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char * _pdo_pgsql_trim_message(const char *message, int persistent) +{ + size_t i = strlen(message)-1; + char *tmp; + + if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { + --i; + } + while (i>0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string* _pdo_pgsql_escape_credentials(char *str) +{ + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } + else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } + else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ +{ +/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_write(self->conn, self->lfd, (char*)buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) +{ + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, + zend_off_t *newoffset) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; +#if defined(HAVE_PG_LO64) && defined(ZEND_ENABLE_ZVAL_LONG64) + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const php_stream_ops pdo_pgsql_lob_stream_ops = { + pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid) +{ + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + Z_ADDREF_P(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) +{ + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, + PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (!emulate && PQprotocolVersion(H->server) <= 2) { + emulate = 1; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + size_t tmp_len; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *)ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted+1, escaped, quotedlen-2); + quoted[0] = '\''; + quoted[quotedlen-1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *)PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) +{ + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + if (PQprotocolVersion(H->server) >= 3) { /* PostgreSQL 7.4 or later */ + ZVAL_STRING(return_value, (char*)PQparameterStatus(H->server, "server_version")); + } else /* emulate above via a query */ + { + PGresult *res = PQexec(H->server, "SELECT VERSION()"); + if (res && PQresultStatus(res) == PGRES_TUPLES_OK) { + ZVAL_STRING(return_value, (char *)PQgetvalue(res, 0, 0)); + } + + if (res) { + PQclear(res); + } + } + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL(return_value, "Waiting for connection to be made.", strlen("Waiting for connection to be made.")); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", strlen("Connection OK; waiting to send.")); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, "Waiting for a response from the server.", strlen("Waiting for a response from the server.")); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, "Received authentication; waiting for backend start-up to finish.", strlen("Received authentication; waiting for backend start-up to finish.")); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", strlen("Negotiating SSL encryption.")); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, "Negotiating environment-driven parameter settings.", strlen("Negotiating environment-driven parameter settings.")); + break; + +#ifdef CONNECTION_CONSUME + case CONNECTION_CONSUME: + ZVAL_STRINGL(return_value, "Flushing send queue/consuming extra data.", strlen("Flushing send queue/consuming extra data.")); + break; +#endif +#ifdef CONNECTION_GSS_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating GSSAPI.", strlen("Negotiating GSSAPI.")); + break; +#endif +#ifdef CONNECTION_CHECK_TARGET + case CONNECTION_CHECK_TARGET: + ZVAL_STRINGL(return_value, "Connection OK; checking target server properties.", strlen("Connection OK; checking target server properties.")); + break; +#endif +#ifdef CONNECTION_CHECK_STANDBY + case CONNECTION_CHECK_STANDBY: + ZVAL_STRINGL(return_value, "Connection OK; checking if server in standby.", strlen("Connection OK; checking if server in standby.")); + break; +#endif + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", strlen("Bad connection.")); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char*)PQparameterStatus(H->server, "client_encoding"), + (char*)PQparameterStatus(H->server, "is_superuser"), + (char*)PQparameterStatus(H->server, "session_authorization"), + (char*)PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) +{ + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) +{ + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) +{ + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|sss!", + &table_name, &table_name_len, &pg_rows, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + memcpy(query, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + query_len = Z_STRLEN_P(tmp); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t)ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sss!", + &table_name, &table_name_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + lfd = lo_creat(H->server, INV_READ|INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", + &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ|INV_WRITE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", + &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int)ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} +/* }}} */ + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = { + pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL /* get_gc */ +}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *conn_str, *p, *e; + zend_string *tmp_user, *tmp_pass; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = + 1 << PDO_PARAM_EVT_EXEC_POST | + 1 << PDO_PARAM_EVT_FETCH_PRE | + 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = _pdo_pgsql_escape_credentials(dbh->username); + tmp_pass = _pdo_pgsql_escape_credentials(dbh->password); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user && tmp_pass) { + spprintf(&conn_str, 0, "%s user='%s' password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), ZSTR_VAL(tmp_pass), connect_timeout); + } else if (tmp_user) { + spprintf(&conn_str, 0, "%s user='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), connect_timeout); + } else if (tmp_pass) { + spprintf(&conn_str, 0, "%s password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_pass), connect_timeout); + } else { + spprintf(&conn_str, 0, "%s connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, connect_timeout); + } + + H->server = PQconnectdb(conn_str); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + efree(conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = { + PDO_DRIVER_HEADER(pgsql), + pdo_pgsql_handle_factory +}; +#endif diff --git a/thirdparty/php83/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php83/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 0000000000..a1c51dc0c4 --- /dev/null +++ b/thirdparty/php83/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,72 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 9bb79af98dbb7c171fd9533aeabece4937a06cd2 */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php83/pdo_pgsql/pgsql_statement.c b/thirdparty/php83/pdo_pgsql/pgsql_statement.c new file mode 100644 index 0000000000..ae4e7e16fe --- /dev/null +++ b/thirdparty/php83/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,699 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 + + + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle) + && IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) + && !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); + if (res) { + PQclear(res); + } + } + efree(S->stmt_name); + S->stmt_name = NULL; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if(S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* ensure that we free any previous unfetched results */ + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf(&q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else if (S->stmt_name) { + /* using a prepared statement */ + + if (!S->is_prepared) { +stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, S->stmt_name, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = 1; + PQclear(S->result); + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chance to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + PGresult *res; + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + S->result = PQexecPrepared(H->server, S->stmt_name, + stmt->bound_params ? + zend_hash_num_elements(stmt->bound_params) : + 0, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long)PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: + /* decode name from $1, $2 into 0, 1 etc. */ + if (param->name) { + if (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && (namevar = zend_hash_find_ptr(stmt->bound_param_map, + param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!stmt->bound_param_map) { + return 1; + } + if (!S->param_values) { + S->param_values = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(char*)); + S->param_lengths = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_formats = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_types = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(Oid)); + } + if (param->paramno >= 0) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && + Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char*)&P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || + Z_TYPE_P(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; + S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; + } + } + break; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: spprintf(&ori_str, 0, "NEXT"); break; + case PDO_FETCH_ORI_PRIOR: spprintf(&ori_str, 0, "BACKWARD"); break; + case PDO_FETCH_ORI_FIRST: spprintf(&ori_str, 0, "FIRST"); break; + case PDO_FETCH_ORI_LAST: spprintf(&ori_str, 0, "LAST"); break; + case PDO_FETCH_ORI_ABS: spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); break; + case PDO_FETCH_ORI_REL: spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); break; + default: + return 0; + } + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid)strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *)PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) +{ + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + PGresult *res; + char *q=NULL; + ExecStatusType status; + Oid table_oid; + char *table_name=NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + return 1; +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = { + pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer +}; +#endif diff --git a/thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 0000000000..3b2773298b --- /dev/null +++ b/thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,112 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached:1; + unsigned _reserved:31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + bool emulate_prepares; + bool disable_native_prepares; /* deprecated since 5.6 */ + bool disable_prepares; + HashTable *lob_streams; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s,e,z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php84/curl/curl_arginfo.h b/thirdparty/php84/curl/curl_arginfo.h new file mode 100644 index 0000000000..8b3478716a --- /dev/null +++ b/thirdparty/php84/curl/curl_arginfo.h @@ -0,0 +1,159 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: ddfcdd8a0bf0ee6c338ec1689c6de5d7fd87303d */ +#include "swoole_curl_interface.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_close, 0, 1, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_swoole_native_curl_copy_handle, 0, 1, CurlHandle, MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_errno, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_error, 0, 1, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_escape, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_unescape arginfo_swoole_native_curl_escape + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_setopt, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_exec, 0, 1, MAY_BE_STRING|MAY_BE_BOOL) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_getinfo, 0, 1, IS_MIXED, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, option, IS_LONG, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_swoole_native_curl_init, 0, 0, CurlHandle, MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, url, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_upkeep, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() +#endif + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_add_handle, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_close, 0, 1, IS_VOID, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_errno, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_exec, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_INFO(1, still_running) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_getcontent, 0, 1, IS_STRING, 1) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_multi_info_read, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, queued_messages, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_swoole_native_curl_multi_init, 0, 0, CurlMultiHandle, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_multi_remove_handle arginfo_swoole_native_curl_multi_add_handle + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_select, 0, 1, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_DOUBLE, 0, "1.0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_multi_strerror, 0, 1, IS_STRING, 1) + ZEND_ARG_TYPE_INFO(0, error_code, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_pause, 0, 2, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, flags, IS_LONG, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_reset arginfo_swoole_native_curl_close + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_setopt_array, 0, 2, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, options, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_swoole_native_curl_setopt, 0, 3, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) + ZEND_ARG_TYPE_INFO(0, option, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) +ZEND_END_ARG_INFO() + +#define arginfo_swoole_native_curl_strerror arginfo_swoole_native_curl_multi_strerror + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_swoole_native_curl_version, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +static const zend_function_entry swoole_native_curl_functions[] = { + ZEND_FE(swoole_native_curl_close, arginfo_swoole_native_curl_close) + ZEND_FE(swoole_native_curl_copy_handle, arginfo_swoole_native_curl_copy_handle) + ZEND_FE(swoole_native_curl_errno, arginfo_swoole_native_curl_errno) + ZEND_FE(swoole_native_curl_error, arginfo_swoole_native_curl_error) + ZEND_FE(swoole_native_curl_escape, arginfo_swoole_native_curl_escape) + ZEND_FE(swoole_native_curl_unescape, arginfo_swoole_native_curl_unescape) + ZEND_FE(swoole_native_curl_multi_setopt, arginfo_swoole_native_curl_multi_setopt) + ZEND_FE(swoole_native_curl_exec, arginfo_swoole_native_curl_exec) + ZEND_FE(swoole_native_curl_getinfo, arginfo_swoole_native_curl_getinfo) + ZEND_FE(swoole_native_curl_init, arginfo_swoole_native_curl_init) +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + ZEND_FE(swoole_native_curl_upkeep, arginfo_swoole_native_curl_upkeep) +#endif + ZEND_FE(swoole_native_curl_multi_add_handle, arginfo_swoole_native_curl_multi_add_handle) + ZEND_FE(swoole_native_curl_multi_close, arginfo_swoole_native_curl_multi_close) + ZEND_FE(swoole_native_curl_multi_errno, arginfo_swoole_native_curl_multi_errno) + ZEND_FE(swoole_native_curl_multi_exec, arginfo_swoole_native_curl_multi_exec) + ZEND_FE(swoole_native_curl_multi_getcontent, arginfo_swoole_native_curl_multi_getcontent) + ZEND_FE(swoole_native_curl_multi_info_read, arginfo_swoole_native_curl_multi_info_read) + ZEND_FE(swoole_native_curl_multi_init, arginfo_swoole_native_curl_multi_init) + ZEND_FE(swoole_native_curl_multi_remove_handle, arginfo_swoole_native_curl_multi_remove_handle) + ZEND_FE(swoole_native_curl_multi_select, arginfo_swoole_native_curl_multi_select) + ZEND_FE(swoole_native_curl_pause, arginfo_swoole_native_curl_pause) + ZEND_FE(swoole_native_curl_reset, arginfo_swoole_native_curl_reset) + ZEND_FE(swoole_native_curl_setopt_array, arginfo_swoole_native_curl_setopt_array) + ZEND_FE(swoole_native_curl_setopt, arginfo_swoole_native_curl_setopt) + ZEND_FE_END +}; + +static const zend_function_entry class_CurlHandle_methods[] = { + ZEND_FE_END +}; + +static const zend_function_entry class_CurlMultiHandle_methods[] = { + ZEND_FE_END +}; + +static const zend_function_entry class_CurlShareHandle_methods[] = { + ZEND_FE_END +}; +#endif diff --git a/thirdparty/php84/curl/curl_private.h b/thirdparty/php84/curl/curl_private.h new file mode 100644 index 0000000000..d5d89f4193 --- /dev/null +++ b/thirdparty/php84/curl/curl_private.h @@ -0,0 +1,176 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#ifndef _PHP_CURL_PRIVATE_H +#define _PHP_CURL_PRIVATE_H + +#include "php_curl.h" + +#define PHP_CURL_DEBUG 0 + +#include "php_version.h" +#define PHP_CURL_VERSION PHP_VERSION + +#include +#include + +#define CURLOPT_RETURNTRANSFER 19913 +#define CURLOPT_BINARYTRANSFER 19914 /* For Backward compatibility */ +#define PHP_CURL_STDOUT 0 +#define PHP_CURL_FILE 1 +#define PHP_CURL_USER 2 +#define PHP_CURL_DIRECT 3 +#define PHP_CURL_RETURN 4 +#define PHP_CURL_IGNORE 7 + +#define SAVE_CURL_ERROR(__handle, __err) \ + do { \ + (__handle)->err.no = (int) __err; \ + } while (0) + +PHP_MINIT_FUNCTION(curl); +PHP_MSHUTDOWN_FUNCTION(curl); +PHP_MINFO_FUNCTION(curl); + +typedef struct { + zend_fcall_info_cache fcc; + FILE *fp; + smart_str buf; + int method; + zval stream; +} php_curl_write; + +typedef struct { + zend_fcall_info_cache fcc; + FILE *fp; + zend_resource *res; + int method; + zval stream; +} php_curl_read; + +typedef struct { + php_curl_write *write; + php_curl_write *write_header; + php_curl_read *read; + zval std_err; + zend_fcall_info_cache progress; + zend_fcall_info_cache xferinfo; + zend_fcall_info_cache fnmatch; + zend_fcall_info_cache debug; +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + zend_fcall_info_cache prereq; +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + zend_fcall_info_cache sshhostkey; +#endif +} php_curl_handlers; + +struct _php_curl_error { + char str[CURL_ERROR_SIZE + 1]; + int no; +}; + +struct _php_curl_send_headers { + zend_string *str; +}; + +struct _php_curl_free { + zend_llist post; + zend_llist stream; +#if PHP_VERSION_ID >= 80500 + HashTable slist; +#else + HashTable *slist; +#endif +}; + +typedef struct { + CURL *cp; + php_curl_handlers handlers; + struct _php_curl_free *to_free; + struct _php_curl_send_headers header; + struct _php_curl_error err; + bool in_callback; + uint32_t *clone; + zval postfields; + /* For CURLOPT_PRIVATE */ + zval private_data; + /* CurlShareHandle object set using CURLOPT_SHARE. */ + struct _php_curlsh *share; + zend_object std; +} php_curl; + +#define CURLOPT_SAFE_UPLOAD -1 + +typedef struct { + zend_fcall_info_cache server_push; +} php_curlm_handlers; + +namespace swoole { +namespace curl { +class Multi; +} +} // namespace swoole + +using swoole::curl::Multi; + +typedef struct { + Multi *multi; + zend_llist easyh; + php_curlm_handlers handlers; + struct { + int no; + } err; + zend_object std; +} php_curlm; + +typedef struct _php_curlsh { + CURLSH *share; + struct { + int no; + } err; + zend_object std; +} php_curlsh; + +php_curl *swoole_curl_init_handle_into_zval(zval *curl); +void swoole_curl_init_handle(php_curl *ch); +void swoole_curl_cleanup_handle(php_curl *); +void swoole_curl_multi_cleanup_list(void *data); +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror); +void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source); + +/* Consumes `zv` */ +zend_long swoole_curl_get_long(zval *zv); + +static inline php_curl *curl_from_obj(zend_object *obj) { + return (php_curl *) ((char *) (obj) -XtOffsetOf(php_curl, std)); +} + +#define Z_CURL_P(zv) curl_from_obj(Z_OBJ_P(zv)) + +static inline php_curlsh *curl_share_from_obj(zend_object *obj) { + return (php_curlsh *) ((char *) (obj) -XtOffsetOf(php_curlsh, std)); +} + +#define Z_CURL_SHARE_P(zv) curl_share_from_obj(Z_OBJ_P(zv)) +void curl_multi_register_class(const zend_function_entry *method_entries); +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type); + +#endif /* _PHP_CURL_PRIVATE_H */ +#endif diff --git a/thirdparty/php84/curl/interface.cc b/thirdparty/php84/curl/interface.cc new file mode 100644 index 0000000000..d3509c49ca --- /dev/null +++ b/thirdparty/php84/curl/interface.cc @@ -0,0 +1,2897 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole_cxx.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 +#include "php_swoole_curl.h" +using namespace swoole; + +SW_EXTERN_C_BEGIN +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS +#include "swoole_curl_interface.h" +#include "curl_arginfo.h" + +#include +#include + +#ifdef PHP_WIN32 +#include +#include +#endif + +#include +#include + +/* As of curl 7.11.1 this is no longer defined inside curl.h */ +#ifndef HttpPost +#define HttpPost curl_httppost +#endif + +#if PHP_VERSION_ID < 80500 +/* {{{ cruft for thread safe SSL crypto locks */ +#if defined(ZTS) && defined(HAVE_CURL_OLD_OPENSSL) +#if defined(HAVE_OPENSSL_CRYPTO_H) +#define PHP_CURL_NEED_OPENSSL_TSL +#include +#else +#warning "libcurl was compiled with OpenSSL support, but configure could not find " \ + "openssl/crypto.h; thus no SSL crypto locking callbacks will be set, which may " \ + "cause random crashes on SSL requests" +#endif +#endif /* ZTS && HAVE_CURL_OLD_OPENSSL */ +/* }}} */ +#endif + +#include "zend_smart_str.h" +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "ext/standard/url.h" +#include "curl_private.h" + +#ifdef __GNUC__ +/* don't complain about deprecated CURLOPT_* we're exposing to PHP; we + need to keep using those to avoid breaking PHP API compatibiltiy */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#if PHP_VERSION_ID < 80500 +#ifdef PHP_CURL_NEED_OPENSSL_TSL /* {{{ */ +static MUTEX_T *php_curl_openssl_tsl = NULL; + +/* Locking callbacks are no longer used since OpenSSL 1.1. Mark the functions as unused to + * avoid warnings due to this. */ +static ZEND_ATTRIBUTE_UNUSED void php_curl_ssl_lock(int mode, int n, const char *file, int line) { + if (mode & CRYPTO_LOCK) { + tsrm_mutex_lock(php_curl_openssl_tsl[n]); + } else { + tsrm_mutex_unlock(php_curl_openssl_tsl[n]); + } +} + +static ZEND_ATTRIBUTE_UNUSED unsigned long php_curl_ssl_id(void) { + return (unsigned long) tsrm_thread_id(); +} +#endif +/* }}} */ +#endif + +static zend_class_entry *swoole_native_curl_exception_ce; +static zend_object_handlers swoole_native_curl_exception_handlers; + +#define CAAL(s, v) add_assoc_long_ex(return_value, s, sizeof(s) - 1, (zend_long) v); +#define CAAD(s, v) add_assoc_double_ex(return_value, s, sizeof(s) - 1, (double) v); +#define CAAS(s, v) add_assoc_string_ex(return_value, s, sizeof(s) - 1, (char *) (v ? v : "")); +#define CAASTR(s, v) add_assoc_str_ex(return_value, s, sizeof(s) - 1, v ? zend_string_copy(v) : ZSTR_EMPTY_ALLOC()); +#define CAAZ(s, v) add_assoc_zval_ex(return_value, s, sizeof(s) - 1, (zval *) v); + +#if defined(PHP_WIN32) || defined(__GNUC__) +#define php_curl_ret(__ret) \ + RETVAL_FALSE; \ + return __ret; +#else +#define php_curl_ret(__ret) \ + RETVAL_FALSE; \ + return; +#endif + +static zend_result php_curl_option_str(php_curl *ch, zend_long option, const char *str, const size_t len) { + if (zend_char_has_nul_byte(str, len)) { + zend_value_error("%s(): cURL option must not contain any null bytes", get_active_function_name()); + return FAILURE; + } + + CURLcode error = curl_easy_setopt(ch->cp, (CURLoption) option, str); + SAVE_CURL_ERROR(ch, error); + + return error == CURLE_OK ? SUCCESS : FAILURE; +} + +static zend_result php_curl_option_url(php_curl *ch, const zend_string *url) /* {{{ */ +{ + /* Disable file:// if open_basedir are used */ + if (PG(open_basedir) && *PG(open_basedir)) { + curl_easy_setopt(ch->cp, CURLOPT_PROTOCOLS, CURLPROTO_ALL & ~CURLPROTO_FILE); + } + +#ifdef PHP_WIN32 + if (zend_string_starts_with_literal_ci(url, "file://") && '/' != ZSTR_VAL(url)[sizeof("file://") - 1] && + ZSTR_LEN(url) < MAXPATHLEN - 2) { + char _tmp[MAXPATHLEN] = {0}; + + memmove(_tmp, "file:///", sizeof("file:///") - 1); + memmove(_tmp + sizeof("file:///") - 1, + ZSTR_VAL(url) + sizeof("file://") - 1, + ZSTR_LEN(url) - sizeof("file://") + 1); + + return php_curl_option_str(ch, CURLOPT_URL, _tmp, ZSTR_LEN(url) + 1); + } +#endif + + return php_curl_option_str(ch, CURLOPT_URL, ZSTR_VAL(url), ZSTR_LEN(url)); +} +/* }}} */ + +void swoole_curl_verify_handlers(php_curl *ch, bool reporterror) /* {{{ */ +{ + php_stream *stream; + + ZEND_ASSERT(ch); + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_STDERR resource has gone away, resetting to stderr"); + } + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + + curl_easy_setopt(ch->cp, CURLOPT_STDERR, stderr); + } + } + if (ch->handlers.read && !Z_ISUNDEF(ch->handlers.read->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.read->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_INFILE resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + ch->handlers.read->res = NULL; + ch->handlers.read->fp = 0; + + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + } + } + if (ch->handlers.write_header && !Z_ISUNDEF(ch->handlers.write_header->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.write_header->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_WRITEHEADER resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = 0; + + ch->handlers.write_header->method = PHP_CURL_IGNORE; + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); + } + } + if (ch->handlers.write && !Z_ISUNDEF(ch->handlers.write->stream)) { + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.write->stream, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream == NULL) { + if (reporterror) { + php_error_docref(NULL, E_WARNING, "CURLOPT_FILE resource has gone away, resetting to default"); + } + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + ch->handlers.write->fp = 0; + + ch->handlers.write->method = PHP_CURL_STDOUT; + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + } + } + return; +} +/* }}} */ + +/* CurlHandle class */ +zend_class_entry *swoole_coroutine_curl_handle_ce; +static zend_object_handlers swoole_coroutine_curl_handle_handlers; + +static zend_object *swoole_curl_create_object(zend_class_entry *class_type); +static void swoole_curl_free_obj(zend_object *object); +static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n); +static zend_function *swoole_curl_get_constructor(zend_object *object); +static zend_object *swoole_curl_clone_obj(zend_object *object); +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields); +SW_EXTERN_C_END + +void swoole_native_curl_minit(int module_number) { + if (!SWOOLE_G(cli)) { + return; + } + +#if PHP_VERSION_ID < 80500 +#ifdef PHP_CURL_NEED_OPENSSL_TSL + if (!CRYPTO_get_id_callback()) { + int i, c = CRYPTO_num_locks(); + + php_curl_openssl_tsl = (MUTEX_T *) malloc(c * sizeof(MUTEX_T)); + if (!php_curl_openssl_tsl) { + return; + } + + for (i = 0; i < c; ++i) { + php_curl_openssl_tsl[i] = tsrm_mutex_alloc(); + } + + CRYPTO_set_id_callback(php_curl_ssl_id); + CRYPTO_set_locking_callback(php_curl_ssl_lock); + } +#endif +#endif + + swoole_coroutine_curl_handle_ce = curl_ce; + swoole_coroutine_curl_handle_ce->create_object = swoole_curl_create_object; + swoole_coroutine_curl_handle_ce->default_object_handlers = &swoole_coroutine_curl_handle_handlers; + + memcpy(&swoole_coroutine_curl_handle_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + swoole_coroutine_curl_handle_handlers.offset = XtOffsetOf(php_curl, std); + swoole_coroutine_curl_handle_handlers.free_obj = swoole_curl_free_obj; + swoole_coroutine_curl_handle_handlers.get_gc = swoole_curl_get_gc; + swoole_coroutine_curl_handle_handlers.get_constructor = swoole_curl_get_constructor; + swoole_coroutine_curl_handle_handlers.clone_obj = swoole_curl_clone_obj; + swoole_coroutine_curl_handle_handlers.cast_object = swoole_curl_cast_object; + swoole_coroutine_curl_handle_handlers.compare = [](zval *o1, zval *o2) { return ZEND_UNCOMPARABLE; }; + + swoole_coroutine_curl_handle_ce->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES; + + curl_multi_register_class(nullptr); + + zend_unregister_functions(swoole_native_curl_functions, -1, CG(function_table)); + zend_register_functions(NULL, swoole_native_curl_functions, NULL, MODULE_PERSISTENT); + + SW_INIT_CLASS_ENTRY_EX(swoole_native_curl_exception, + "Swoole\\Coroutine\\Curl\\Exception", + "Co\\Coroutine\\Curl\\Exception", + nullptr, + swoole_exception); +} +/* }}} */ + +/* CurlHandle class */ + +static zend_object *swoole_curl_create_object(zend_class_entry *class_type) { + php_curl *intern = (php_curl *) zend_object_alloc(sizeof(php_curl), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + + return &intern->std; +} + +static zend_function *swoole_curl_get_constructor(zend_object *object) { + zend_throw_error(NULL, "Cannot directly construct CurlHandle, use curl_init() instead"); + return NULL; +} + +static zend_object *swoole_curl_clone_obj(zend_object *object) { + php_curl *ch; + CURL *cp; + zval *postfields; + zend_object *clone_object; + php_curl *clone_ch; + + clone_object = swoole_curl_create_object(curl_ce); + clone_ch = curl_from_obj(clone_object); + swoole_curl_init_handle(clone_ch); + + ch = curl_from_obj(object); + cp = curl_easy_duphandle(ch->cp); + if (!cp) { + zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); + return &clone_ch->std; + } + + clone_ch->cp = cp; + swoole_setup_easy_copy_handlers(clone_ch, ch); + swoole::curl::create_handle(clone_ch->cp); + + postfields = &ch->postfields; + if (Z_TYPE_P(postfields) != IS_UNDEF) { + if (build_mime_structure_from_hash(clone_ch, postfields) == FAILURE) { + zend_throw_exception(NULL, "Failed to clone CurlHandle", 0); + return &clone_ch->std; + } + } + + return &clone_ch->std; +} + +static HashTable *swoole_curl_get_gc(zend_object *object, zval **table, int *n) { + php_curl *curl = curl_from_obj(object); + + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + zend_get_gc_buffer_add_zval(gc_buffer, &curl->postfields); + if (curl->handlers.read) { + if (ZEND_FCC_INITIALIZED(curl->handlers.read->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.read->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.read->stream); + } + + if (curl->handlers.write) { + if (ZEND_FCC_INITIALIZED(curl->handlers.write->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.write->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write->stream); + } + + if (curl->handlers.write_header) { + if (ZEND_FCC_INITIALIZED(curl->handlers.write_header->fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.write_header->fcc); + } + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.write_header->stream); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.progress)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.progress); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.xferinfo)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.xferinfo); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.fnmatch)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch); + } + + if (ZEND_FCC_INITIALIZED(curl->handlers.debug)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.debug); + } + +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq); + } +#endif + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey); + } +#endif + + zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.std_err); + zend_get_gc_buffer_add_zval(gc_buffer, &curl->private_data); + + zend_get_gc_buffer_use(gc_buffer, table, n); + +#if PHP_VERSION_ID >= 80500 + /* CurlHandle can never have properties as it's final and has strict-properties on. + * Avoid building a hash table. */ + return NULL; +#else + return zend_std_get_properties(object); +#endif +} + +zend_result swoole_curl_cast_object(zend_object *obj, zval *result, int type) { + if (type == IS_LONG) { + /* For better backward compatibility, make (int) $curl_handle return the object ID, + * similar to how it previously returned the resource ID. */ + ZVAL_LONG(result, obj->handle); + return SUCCESS; + } + + return zend_std_cast_object_tostring(obj, result, type); +} + +void swoole_native_curl_mshutdown() {} + +/* {{{ curl_write */ +static size_t fn_write(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_write *write_handler = ch->handlers.write; + size_t length = size * nmemb; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_write() called\n"); + fprintf(stderr, "data = %s, size = %d, nmemb = %d, ctx = %x\n", data, size, nmemb, ctx); +#endif + + switch (write_handler->method) { + case PHP_CURL_STDOUT: + PHPWRITE(data, length); + break; + case PHP_CURL_FILE: + return fwrite(data, size, nmemb, write_handler->fp); + case PHP_CURL_RETURN: + if (length > 0) { + smart_str_appendl(&write_handler->buf, data, (int) length); + } + break; + case PHP_CURL_USER: { + zval argv[2]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRINGL(&argv[1], data, length); + + ch->in_callback = true; + zend_call_known_fcc(&write_handler->fcc, &retval, /* param_count */ 2, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + length = swoole_curl_get_long(&retval); + } + + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + } + + return length; +} +/* }}} */ + +/* {{{ curl_fnmatch */ +static int fn_fnmatch(void *ctx, const char *pattern, const char *string) { + php_curl *ch = (php_curl *) ctx; + int rval = CURL_FNMATCHFUNC_FAIL; + zval argv[3]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRING(&argv[1], pattern); + ZVAL_STRING(&argv[2], string); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.fnmatch, &retval, /* param_count */ 3, argv, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + rval = swoole_curl_get_long(&retval); + } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + zval_ptr_dtor(&argv[2]); + return rval; +} +/* }}} */ + +/* {{{ curl_progress */ +static int fn_progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { + php_curl *ch = (php_curl *) clientp; + int rval = 0; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_progress() called\n"); + fprintf(stderr, + "clientp = %x, dltotal = %f, dlnow = %f, ultotal = %f, ulnow = %f\n", + clientp, + dltotal, + dlnow, + ultotal, + ulnow); +#endif + + zval args[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], (zend_long) dltotal); + ZVAL_LONG(&args[2], (zend_long) dlnow); + ZVAL_LONG(&args[3], (zend_long) ultotal); + ZVAL_LONG(&args[4], (zend_long) ulnow); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.progress, &retval, /* param_count */ 5, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + if (0 != swoole_curl_get_long(&retval)) { + rval = 1; + } + } + + zval_ptr_dtor(&args[0]); + return rval; +} +/* }}} */ + +/* {{{ curl_xferinfo */ +static int fn_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + php_curl *ch = (php_curl *) clientp; + int rval = 0; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_xferinfo() called\n"); + fprintf(stderr, + "clientp = %x, dltotal = %ld, dlnow = %ld, ultotal = %ld, ulnow = %ld\n", + clientp, + dltotal, + dlnow, + ultotal, + ulnow); +#endif + + zval argv[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_LONG(&argv[1], dltotal); + ZVAL_LONG(&argv[2], dlnow); + ZVAL_LONG(&argv[3], ultotal); + ZVAL_LONG(&argv[4], ulnow); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.xferinfo, &retval, /* param_count */ 5, argv, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + /* TODO Check callback returns an int or something castable to int */ + if (0 != swoole_curl_get_long(&retval)) { + rval = 1; + } + } + + zval_ptr_dtor(&argv[0]); + return rval; +} +/* }}} */ + +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ +static int fn_prereqfunction( + void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) { + php_curl *ch = (php_curl *) clientp; + int rval = CURL_PREREQFUNC_OK; + + // when CURLOPT_PREREQFUNCTION is set to null, curl_prereqfunction still + // gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid + // zend_call_known_fcc() with an uninitialized FCC. + if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + return rval; + } + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_prereqfunction() called\n"); + fprintf(stderr, + "conn_primary_ip = %s, conn_local_ip = %s, conn_primary_port = %d, conn_local_port = %d\n", + conn_primary_ip, + conn_local_ip, + conn_primary_port, + conn_local_port); +#endif + + zval args[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_STRING(&args[1], conn_primary_ip); + ZVAL_STRING(&args[2], conn_local_ip); + ZVAL_LONG(&args[3], conn_primary_port); + ZVAL_LONG(&args[4], conn_local_port); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.prereq, &retval, /* param_count */ 5, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_LONG) { + zend_long retval_long = Z_LVAL(retval); + if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) { + rval = retval_long; + } else { + zend_value_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or " + "CURL_PREREQFUNC_ABORT"); + } + } else { + zend_type_error( + "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT"); + } + } + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[1]); + zval_ptr_dtor(&args[2]); + + return rval; +} +#endif + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ +static int fn_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen) { + php_curl *ch = (php_curl *) clientp; + int rval = CURLKHMATCH_MISMATCH; /* cancel connection in case of an exception */ + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_ssh_hostkeyfunction() called\n"); + fprintf(stderr, "clientp = %x, keytype = %d, key = %s, keylen = %zu\n", clientp, keytype, key, keylen); +#endif + + zval args[4]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], keytype); + ZVAL_STRINGL(&args[2], key, keylen); + ZVAL_LONG(&args[3], keylen); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.sshhostkey, &retval, /* param_count */ 4, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_LONG) { + zend_long retval_long = Z_LVAL(retval); + if (retval_long == CURLKHMATCH_OK || retval_long == CURLKHMATCH_MISMATCH) { + rval = retval_long; + } else { + zend_throw_error(NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or " + "CURLKHMATCH_MISMATCH"); + } + } else { + zend_throw_error( + NULL, + "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or CURLKHMATCH_MISMATCH"); + zval_ptr_dtor(&retval); + } + } + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[2]); + return rval; +} +#endif + +/* {{{ curl_read */ +static size_t fn_read(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_read *read_handler = ch->handlers.read; + size_t length = 0; + + switch (read_handler->method) { + case PHP_CURL_DIRECT: + if (read_handler->fp) { + length = fread(data, size, nmemb, read_handler->fp); + } + break; + case PHP_CURL_USER: { + zval argv[3]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + if (read_handler->res) { + GC_ADDREF(read_handler->res); + ZVAL_RES(&argv[1], read_handler->res); + } else { + ZVAL_NULL(&argv[1]); + } + ZVAL_LONG(&argv[2], (int) size * nmemb); + + ch->in_callback = true; + zend_call_known_fcc(&read_handler->fcc, &retval, /* param_count */ 3, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + swoole_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_STRING) { + length = MIN((size * nmemb), Z_STRLEN(retval)); + memcpy(data, Z_STRVAL(retval), length); + } else if (Z_TYPE(retval) == IS_LONG) { + length = Z_LVAL_P(&retval); + } + // TODO Do type error if invalid type? + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + } + + return length; +} +/* }}} */ + +/* {{{ curl_write_header */ +static size_t fn_write_header(char *data, size_t size, size_t nmemb, void *ctx) { + php_curl *ch = (php_curl *) ctx; + php_curl_write *write_handler = ch->handlers.write_header; + size_t length = size * nmemb; + + switch (write_handler->method) { + case PHP_CURL_STDOUT: + /* Handle special case write when we're returning the entire transfer + */ + if (ch->handlers.write->method == PHP_CURL_RETURN && length > 0) { + smart_str_appendl(&ch->handlers.write->buf, data, (int) length); + } else { + PHPWRITE(data, length); + } + break; + case PHP_CURL_FILE: + return fwrite(data, size, nmemb, write_handler->fp); + case PHP_CURL_USER: { + zval argv[2]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&argv[0], &ch->std); + ZVAL_STRINGL(&argv[1], data, length); + + ch->in_callback = true; + zend_call_known_fcc(&write_handler->fcc, &retval, /* param_count */ 2, argv, /* named_params */ NULL); + ch->in_callback = false; + if (!Z_ISUNDEF(retval)) { + // TODO: Check for valid int type for return value + swoole_curl_verify_handlers(ch, /* reporterror */ true); + length = swoole_curl_get_long(&retval); + } + zval_ptr_dtor(&argv[0]); + zval_ptr_dtor(&argv[1]); + break; + } + + case PHP_CURL_IGNORE: + return length; + + default: + return -1; + } + + return length; +} +/* }}} */ + +static int fn_debug(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp) /* {{{ */ +{ + php_curl *ch = (php_curl *) clientp; + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_debug() called\n"); + fprintf(stderr, "type = %d, data = %s\n", type, data); +#endif + + // Implicitly store the headers for compatibility with CURLINFO_HEADER_OUT + // used as a Curl option. Previously, setting CURLINFO_HEADER_OUT set curl_debug + // as the CURLOPT_DEBUGFUNCTION and stored the debug data when type is set to + // CURLINFO_HEADER_OUT. For backward compatibility, we now store the headers + // but also call the user-callback function if available. + if (type == CURLINFO_HEADER_OUT) { + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + } + ch->header.str = zend_string_init(data, size, 0); + } + + if (!ZEND_FCC_INITIALIZED(ch->handlers.debug)) { + return 0; + } + + zval args[3]; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_LONG(&args[1], type); + ZVAL_STRINGL(&args[2], data, size); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.debug, NULL, /* param_count */ 3, args, /* named_params */ NULL); + ch->in_callback = false; + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[2]); + + return 0; +} +/* }}} */ + +/* {{{ curl_free_post */ +static void curl_free_post(void **post) { + curl_mime_free((curl_mime *) *post); +} +/* }}} */ + +struct mime_data_cb_arg { + zend_string *filename; + php_stream *stream; +}; + +/* {{{ curl_free_cb_arg */ +static void curl_free_cb_arg(void **cb_arg_p) { + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) *cb_arg_p; + + ZEND_ASSERT(cb_arg->stream == NULL); + zend_string_release(cb_arg->filename); + efree(cb_arg); +} +/* }}} */ + +/* {{{ curl_free_slist */ +static void curl_free_slist(zval *el) { + curl_slist_free_all(((struct curl_slist *) Z_PTR_P(el))); +} +/* }}} */ + +php_curl *swoole_curl_init_handle_into_zval(zval *curl) { + php_curl *ch; + + object_init_ex(curl, swoole_coroutine_curl_handle_ce); + ch = Z_CURL_P(curl); + + swoole_curl_init_handle(ch); + + return ch; +} + +void swoole_curl_init_handle(php_curl *ch) { + ch->to_free = (struct _php_curl_free *) ecalloc(1, sizeof(struct _php_curl_free)); + ch->handlers.write = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.write_header = (php_curl_write *) ecalloc(1, sizeof(php_curl_write)); + ch->handlers.read = (php_curl_read *) ecalloc(1, sizeof(php_curl_read)); + ch->handlers.progress = empty_fcall_info_cache; + ch->handlers.xferinfo = empty_fcall_info_cache; + ch->handlers.fnmatch = empty_fcall_info_cache; + ch->handlers.debug = empty_fcall_info_cache; +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + ch->handlers.prereq = empty_fcall_info_cache; +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + ch->handlers.sshhostkey = empty_fcall_info_cache; +#endif + ch->clone = (uint32_t *) emalloc(sizeof(uint32_t)); + *ch->clone = 1; + + memset(&ch->err, 0, sizeof(struct _php_curl_error)); + + zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t) curl_free_post, 0); + zend_llist_init(&ch->to_free->stream, sizeof(struct mime_data_cb_arg *), (llist_dtor_func_t) curl_free_cb_arg, 0); +#if PHP_VERSION_ID >= 80500 + zend_hash_init(&ch->to_free->slist, 4, NULL, curl_free_slist, 0); +#else + ch->to_free->slist = (HashTable *) emalloc(sizeof(HashTable)); + zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0); +#endif + ZVAL_UNDEF(&ch->postfields); +} + +/* }}} */ + +/* {{{ create_certinfo */ +static void create_certinfo(struct curl_certinfo *ci, zval *listcode) { + int i; + + if (ci) { + zval certhash; + + for (i = 0; i < ci->num_of_certs; i++) { + struct curl_slist *slist; + + array_init(&certhash); + for (slist = ci->certinfo[i]; slist; slist = slist->next) { + char s[64]; + char *tmp; + strncpy(s, slist->data, sizeof(s)); + s[sizeof(s) - 1] = '\0'; + tmp = (char *) memchr(s, ':', sizeof(s)); + if (tmp) { + *tmp = '\0'; + size_t len = strlen(s); + add_assoc_string(&certhash, s, &slist->data[len + 1]); + } else { + php_error_docref(NULL, E_WARNING, "Could not extract hash key from certificate info"); + } + } + add_next_index_zval(listcode, &certhash); + } + } +} +/* }}} */ + +/* {{{ _php_curl_set_default_options() + Set default options for a handle */ +static void _php_curl_set_default_options(php_curl *ch) { + char *cainfo; + + curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); + curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION, fn_write); + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_READFUNCTION, fn_read); + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION, fn_write_header); + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120L); + curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20L); /* prevent infinite redirects */ + + cainfo = INI_STR("openssl.cafile"); + if (!(cainfo && cainfo[0] != '\0')) { + cainfo = INI_STR("curl.cainfo"); + } + if (cainfo && cainfo[0] != '\0') { + curl_easy_setopt(ch->cp, CURLOPT_CAINFO, cainfo); + } + +#ifdef ZTS + curl_easy_setopt(ch->cp, CURLOPT_NOSIGNAL, 1L); +#endif +} +/* }}} */ + +/* {{{ Initialize a cURL session */ +PHP_FUNCTION(swoole_native_curl_init) { + php_curl *ch; + CURL *cp; + zend_string *url = NULL; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_NULL(url) + ZEND_PARSE_PARAMETERS_END(); + + cp = curl_easy_init(); + if (!cp) { + php_error_docref(NULL, E_WARNING, "Could not initialize a new cURL handle"); + RETURN_FALSE; + } + + ch = swoole_curl_init_handle_into_zval(return_value); + + ch->cp = cp; + + ch->handlers.write->method = PHP_CURL_STDOUT; + ch->handlers.read->method = PHP_CURL_DIRECT; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + + _php_curl_set_default_options(ch); + swoole::curl::create_handle(cp); + + if (url) { + if (php_curl_option_url(ch, url) == FAILURE) { + zval_ptr_dtor(return_value); + RETURN_FALSE; + } + } +} +/* }}} */ + +static void php_curl_copy_fcc_with_option(php_curl *ch, + CURLoption option, + zend_fcall_info_cache *target_fcc, + zend_fcall_info_cache *source_fcc) { + if (ZEND_FCC_INITIALIZED(*source_fcc)) { + zend_fcc_dup(target_fcc, source_fcc); + curl_easy_setopt(ch->cp, (CURLoption) option, (void *) ch); + } +} + +void swoole_setup_easy_copy_handlers(php_curl *ch, php_curl *source) { + if (!Z_ISUNDEF(source->handlers.write->stream)) { + Z_ADDREF(source->handlers.write->stream); + } + ch->handlers.write->stream = source->handlers.write->stream; + ch->handlers.write->method = source->handlers.write->method; + if (!Z_ISUNDEF(source->handlers.read->stream)) { + Z_ADDREF(source->handlers.read->stream); + } + ch->handlers.read->stream = source->handlers.read->stream; + ch->handlers.read->method = source->handlers.read->method; + ch->handlers.write_header->method = source->handlers.write_header->method; + if (!Z_ISUNDEF(source->handlers.write_header->stream)) { + Z_ADDREF(source->handlers.write_header->stream); + } + ch->handlers.write_header->stream = source->handlers.write_header->stream; + + ch->handlers.write->fp = source->handlers.write->fp; + ch->handlers.write_header->fp = source->handlers.write_header->fp; + ch->handlers.read->fp = source->handlers.read->fp; + ch->handlers.read->res = source->handlers.read->res; + + if (ZEND_FCC_INITIALIZED(source->handlers.read->fcc)) { + zend_fcc_dup(&source->handlers.read->fcc, &source->handlers.read->fcc); + } + if (ZEND_FCC_INITIALIZED(source->handlers.write->fcc)) { + zend_fcc_dup(&source->handlers.write->fcc, &source->handlers.write->fcc); + } + if (ZEND_FCC_INITIALIZED(source->handlers.write_header->fcc)) { + zend_fcc_dup(&source->handlers.write_header->fcc, &source->handlers.write_header->fcc); + } + + curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER, ch->err.str); + curl_easy_setopt(ch->cp, CURLOPT_FILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_INFILE, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); + + php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress); + php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo); + php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch); + php_curl_copy_fcc_with_option(ch, CURLOPT_DEBUGDATA, &ch->handlers.debug, &source->handlers.debug); +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq); +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey); +#endif + + ZVAL_COPY(&ch->private_data, &source->private_data); + +#if PHP_VERSION_ID < 80500 + efree(ch->to_free->slist); +#endif + efree(ch->to_free); + ch->to_free = source->to_free; + efree(ch->clone); + ch->clone = source->clone; + + /* Keep track of cloned copies to avoid invoking curl destructors for every clone */ + (*source->clone)++; +} + +zend_long swoole_curl_get_long(zval *zv) +{ + if (EXPECTED(Z_TYPE_P(zv) == IS_LONG)) { + return Z_LVAL_P(zv); + } else { + zend_long ret = zval_get_long(zv); + zval_ptr_dtor(zv); + return ret; + } +} + +static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + ssize_t numread; + + if (cb_arg->stream == NULL) { + if (!(cb_arg->stream = php_stream_open_wrapper(ZSTR_VAL(cb_arg->filename), "rb", IGNORE_PATH, NULL))) { + return CURL_READFUNC_ABORT; + } + } + numread = php_stream_read(cb_arg->stream, buffer, nitems * size); + if (numread < 0) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + return CURL_READFUNC_ABORT; + } + return numread; +} +/* }}} */ + +static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + int res; + + if (cb_arg->stream == NULL) { + return CURL_SEEKFUNC_CANTSEEK; + } + res = php_stream_seek(cb_arg->stream, offset, origin); + return res == SUCCESS ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK; +} +/* }}} */ + +static void free_cb(void *arg) /* {{{ */ +{ + struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; + + if (cb_arg->stream != NULL) { + php_stream_close(cb_arg->stream); + cb_arg->stream = NULL; + } +} +/* }}} */ + +static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key, zval *current) { + CURLcode error = CURLE_OK; + curl_mimepart *part; + CURLcode form_error; + zend_string *postval, *tmp_postval; + + postval = zval_get_tmp_string(current, &tmp_postval); + + part = curl_mime_addpart(mime); + if (part == NULL) { + zend_tmp_string_release(tmp_postval); + return CURLE_OUT_OF_MEMORY; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) { + error = form_error; + } + + zend_tmp_string_release(tmp_postval); + + return error; +} + +static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */ +{ + HashTable *postfields = Z_ARRVAL_P(zpostfields); + CURLcode error = CURLE_OK; + zval *current; + zend_string *string_key; + zend_ulong num_key; + curl_mime *mime = NULL; + curl_mimepart *part; + CURLcode form_error; + + if (zend_hash_num_elements(postfields) > 0) { + mime = curl_mime_init(ch->cp); + if (mime == NULL) { + return FAILURE; + } + } + + ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) { + zend_string *postval; + /* Pretend we have a string_key here */ + if (!string_key) { + string_key = zend_long_to_str(num_key); + } else { + zend_string_addref(string_key); + } + + ZVAL_DEREF(current); + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) { + /* new-style file upload */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + struct mime_data_cb_arg *cb_arg; + php_stream_statbuf ssb; + size_t filesize = -1; + curl_seek_callback seekfunc = seek_cb; + + prop = zend_read_property_ex( + curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) != IS_STRING) { + php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key)); + } else { + postval = Z_STR_P(prop); + + if (php_check_open_basedir(ZSTR_VAL(postval))) { + goto out_string; + } + + prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + type = Z_STRVAL_P(prop); + } + prop = zend_read_property( + curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + ZVAL_DEREF(prop); + if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) { + filename = Z_STRVAL_P(prop); + } + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + php_stream *stream; + if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) { + if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) { + filesize = ssb.sb.st_size; + } + } else { + seekfunc = NULL; + } + + part = curl_mime_addpart(mime); + if (part == NULL) { + if (stream) { + php_stream_close(stream); + } + goto out_string; + } + + cb_arg = (struct mime_data_cb_arg *) emalloc(sizeof *cb_arg); + cb_arg->filename = zend_string_copy(postval); + cb_arg->stream = stream; + + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK || + (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK || + (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) { + error = form_error; + } + zend_llist_add_element(&ch->to_free->stream, &cb_arg); + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) { + /* new-style file upload from string */ + zval *prop, rv; + char *type = NULL, *filename = NULL; + + prop = zend_read_property( + curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + filename = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + type = Z_STRVAL_P(prop); + + prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data") - 1, 0, &rv); + if (EG(exception)) { + goto out_string; + } + ZVAL_DEREF(prop); + ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING); + + postval = Z_STR_P(prop); + + zval_ptr_dtor(&ch->postfields); + ZVAL_COPY(&ch->postfields, zpostfields); + + part = curl_mime_addpart(mime); + if (part == NULL) { + goto out_string; + } + if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK || + (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK || + (form_error = curl_mime_filename(part, filename)) != CURLE_OK || + (form_error = curl_mime_type(part, type)) != CURLE_OK) { + error = form_error; + } + + zend_string_release_ex(string_key, 0); + continue; + } + + if (Z_TYPE_P(current) == IS_ARRAY) { + zval *current_element; + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(current), current_element) { + add_simple_field(mime, string_key, current_element); + } + ZEND_HASH_FOREACH_END(); + + zend_string_release_ex(string_key, 0); + continue; + } + + add_simple_field(mime, string_key, current); + + zend_string_release_ex(string_key, 0); + } + ZEND_HASH_FOREACH_END(); + + SAVE_CURL_ERROR(ch, error); + if (error != CURLE_OK) { + goto out_mime; + } + + if ((*ch->clone) == 1) { + zend_llist_clean(&ch->to_free->post); + } + zend_llist_add_element(&ch->to_free->post, &mime); + error = curl_easy_setopt(ch->cp, CURLOPT_MIMEPOST, mime); + + SAVE_CURL_ERROR(ch, error); + return error == CURLE_OK ? SUCCESS : FAILURE; + +out_string: + zend_string_release_ex(string_key, false); +out_mime: + curl_mime_free(mime); + return FAILURE; +} +/* }}} */ + +/* {{{ Copy a cURL handle along with all of it's preferences */ +PHP_FUNCTION(swoole_native_curl_copy_handle) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + zend_object *new_object = swoole_curl_clone_obj(Z_OBJ_P(zid)); +#if PHP_VERSION_ID >= 80500 + if (EG(exception)) { + if (new_object != NULL) { + OBJ_RELEASE(new_object); + } + zend_clear_exception(); + php_error_docref(NULL, E_WARNING, "Cannot duplicate cURL handle"); + RETURN_FALSE; + } +#endif + RETURN_OBJ(new_object); +} +/* }}} */ + +static bool php_curl_set_callable_handler(zend_fcall_info_cache *const handler_fcc, + zval *callable, + bool is_array_config, + const char *option_name) { + if (ZEND_FCC_INITIALIZED(*handler_fcc)) { + zend_fcc_dtor(handler_fcc); + } + + if (Z_TYPE_P(callable) == IS_NULL) { + return true; + } + + char *error = NULL; + if (UNEXPECTED(!zend_is_callable_ex(callable, + /* object */ NULL, + /* check_flags */ 0, + /* callable_name */ NULL, + handler_fcc, + /* error */ &error))) { + if (!EG(exception)) { + zend_argument_type_error( + 2 + !is_array_config, "must be a valid callback for option %s, %s", option_name, error); + } + efree(error); + return false; + } + zend_fcc_addref(handler_fcc); + return true; +} + +#define HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(curl_ptr, constant_no_function, handler_type, default_method) \ + case constant_no_function##FUNCTION: { \ + bool result = php_curl_set_callable_handler( \ + &curl_ptr->handlers.handler_type->fcc, zvalue, is_array_config, #constant_no_function "FUNCTION"); \ + if (!result) { \ + curl_ptr->handlers.handler_type->method = default_method; \ + return FAILURE; \ + } \ + if (!ZEND_FCC_INITIALIZED(curl_ptr->handlers.handler_type->fcc)) { \ + curl_ptr->handlers.handler_type->method = default_method; \ + return SUCCESS; \ + } \ + curl_ptr->handlers.handler_type->method = PHP_CURL_USER; \ + break; \ + } + +#define HANDLE_CURL_OPTION_CALLABLE(curl_ptr, constant_no_function, handler_fcc, c_callback) \ + case constant_no_function##FUNCTION: { \ + bool result = php_curl_set_callable_handler( \ + &curl_ptr->handler_fcc, zvalue, is_array_config, #constant_no_function "FUNCTION"); \ + if (!result) { \ + return FAILURE; \ + } \ + curl_easy_setopt(curl_ptr->cp, constant_no_function##FUNCTION, (c_callback)); \ + curl_easy_setopt(curl_ptr->cp, constant_no_function##DATA, curl_ptr); \ + break; \ + } + +static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue, bool is_array_config) /* {{{ */ +{ + CURLcode error = CURLE_OK; + zend_long lval; + + switch (option) { + /* Callable options */ + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_WRITE, write, PHP_CURL_STDOUT); + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_HEADER, write_header, PHP_CURL_IGNORE); + HANDLE_CURL_OPTION_CALLABLE_PHP_CURL_USER(ch, CURLOPT_READ, read, PHP_CURL_DIRECT); + + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, fn_progress); + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, fn_xferinfo); + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, fn_fnmatch); + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_DEBUG, handlers.debug, fn_debug); +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, fn_prereqfunction); +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, fn_ssh_hostkeyfunction); +#endif + + /* Long options */ + case CURLOPT_SSL_VERIFYHOST: + lval = zval_get_long(zvalue); + if (lval == 1) { + php_error_docref( + NULL, E_NOTICE, "CURLOPT_SSL_VERIFYHOST no longer accepts the value 1, value 2 will be used instead"); + error = curl_easy_setopt(ch->cp, (CURLoption) option, 2L); + break; + } + ZEND_FALLTHROUGH; + case CURLOPT_AUTOREFERER: + case CURLOPT_BUFFERSIZE: + case CURLOPT_CONNECTTIMEOUT: + case CURLOPT_COOKIESESSION: + case CURLOPT_CRLF: + case CURLOPT_DNS_CACHE_TIMEOUT: + case CURLOPT_FAILONERROR: + case CURLOPT_FILETIME: + case CURLOPT_FORBID_REUSE: + case CURLOPT_FRESH_CONNECT: + case CURLOPT_FTP_USE_EPRT: + case CURLOPT_FTP_USE_EPSV: + case CURLOPT_HEADER: + case CURLOPT_HTTPGET: + case CURLOPT_HTTPPROXYTUNNEL: + case CURLOPT_HTTP_VERSION: + case CURLOPT_INFILESIZE: + case CURLOPT_LOW_SPEED_LIMIT: + case CURLOPT_LOW_SPEED_TIME: + case CURLOPT_MAXCONNECTS: + case CURLOPT_MAXREDIRS: + case CURLOPT_NETRC: + case CURLOPT_NOBODY: + case CURLOPT_NOPROGRESS: + case CURLOPT_NOSIGNAL: + case CURLOPT_PORT: + case CURLOPT_POST: + case CURLOPT_PROXYPORT: + case CURLOPT_PROXYTYPE: + case CURLOPT_PUT: + case CURLOPT_RESUME_FROM: + case CURLOPT_SSLVERSION: + case CURLOPT_SSL_VERIFYPEER: + case CURLOPT_TIMECONDITION: + case CURLOPT_TIMEOUT: + case CURLOPT_TIMEVALUE: + case CURLOPT_TRANSFERTEXT: + case CURLOPT_UNRESTRICTED_AUTH: + case CURLOPT_UPLOAD: + case CURLOPT_VERBOSE: + case CURLOPT_HTTPAUTH: + case CURLOPT_FTP_CREATE_MISSING_DIRS: + case CURLOPT_PROXYAUTH: + case CURLOPT_SERVER_RESPONSE_TIMEOUT: + case CURLOPT_IPRESOLVE: + case CURLOPT_MAXFILESIZE: + case CURLOPT_TCP_NODELAY: + case CURLOPT_FTPSSLAUTH: + case CURLOPT_IGNORE_CONTENT_LENGTH: + case CURLOPT_FTP_SKIP_PASV_IP: + case CURLOPT_FTP_FILEMETHOD: + case CURLOPT_CONNECT_ONLY: + case CURLOPT_LOCALPORT: + case CURLOPT_LOCALPORTRANGE: + case CURLOPT_SSL_SESSIONID_CACHE: + case CURLOPT_FTP_SSL_CCC: + case CURLOPT_SSH_AUTH_TYPES: + case CURLOPT_CONNECTTIMEOUT_MS: + case CURLOPT_HTTP_CONTENT_DECODING: + case CURLOPT_HTTP_TRANSFER_DECODING: + case CURLOPT_TIMEOUT_MS: + case CURLOPT_NEW_DIRECTORY_PERMS: + case CURLOPT_NEW_FILE_PERMS: + case CURLOPT_USE_SSL: + case CURLOPT_APPEND: + case CURLOPT_DIRLISTONLY: + case CURLOPT_PROXY_TRANSFER_MODE: + case CURLOPT_ADDRESS_SCOPE: + case CURLOPT_CERTINFO: + case CURLOPT_PROTOCOLS: + case CURLOPT_REDIR_PROTOCOLS: + case CURLOPT_SOCKS5_GSSAPI_NEC: + case CURLOPT_TFTP_BLKSIZE: + case CURLOPT_FTP_USE_PRET: + case CURLOPT_RTSP_CLIENT_CSEQ: + case CURLOPT_RTSP_REQUEST: + case CURLOPT_RTSP_SERVER_CSEQ: + case CURLOPT_WILDCARDMATCH: + case CURLOPT_GSSAPI_DELEGATION: + case CURLOPT_ACCEPTTIMEOUT_MS: + case CURLOPT_SSL_OPTIONS: + case CURLOPT_TCP_KEEPALIVE: + case CURLOPT_TCP_KEEPIDLE: + case CURLOPT_TCP_KEEPINTVL: + case CURLOPT_SASL_IR: + case CURLOPT_EXPECT_100_TIMEOUT_MS: + case CURLOPT_SSL_ENABLE_ALPN: + case CURLOPT_SSL_ENABLE_NPN: + case CURLOPT_HEADEROPT: + case CURLOPT_SSL_VERIFYSTATUS: + case CURLOPT_PATH_AS_IS: + case CURLOPT_SSL_FALSESTART: + case CURLOPT_PIPEWAIT: + case CURLOPT_STREAM_WEIGHT: + case CURLOPT_TFTP_NO_OPTIONS: + case CURLOPT_TCP_FASTOPEN: + case CURLOPT_KEEP_SENDING_ON_ERROR: + case CURLOPT_PROXY_SSL_OPTIONS: + case CURLOPT_PROXY_SSL_VERIFYHOST: + case CURLOPT_PROXY_SSL_VERIFYPEER: + case CURLOPT_PROXY_SSLVERSION: + case CURLOPT_SUPPRESS_CONNECT_HEADERS: + case CURLOPT_SOCKS5_AUTH: + case CURLOPT_SSH_COMPRESSION: + case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS: + case CURLOPT_DNS_SHUFFLE_ADDRESSES: + case CURLOPT_HAPROXYPROTOCOL: + case CURLOPT_DISALLOW_USERNAME_IN_URL: +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + case CURLOPT_UPKEEP_INTERVAL_MS: + case CURLOPT_UPLOAD_BUFFERSIZE: +#endif +#if LIBCURL_VERSION_NUM >= 0x074000 /* Available since 7.64.0 */ + case CURLOPT_HTTP09_ALLOWED: +#endif +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074100 /* Available since 7.65.0 */ + case CURLOPT_MAXAGE_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x074500 /* Available since 7.69.0 */ + case CURLOPT_MAIL_RCPT_ALLLOWFAILS: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + case CURLOPT_HSTS_CTRL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074c00 /* Available since 7.76.0 */ + case CURLOPT_DOH_SSL_VERIFYHOST: + case CURLOPT_DOH_SSL_VERIFYPEER: + case CURLOPT_DOH_SSL_VERIFYSTATUS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + case CURLOPT_MAXLIFETIME_CONN: +#endif +#if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */ + case CURLOPT_MIME_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075600 /* Available since 7.86.0 */ + case CURLOPT_WS_OPTIONS: +#endif +#if LIBCURL_VERSION_NUM >= 0x075700 /* Available since 7.87.0 */ + case CURLOPT_CA_CACHE_TIMEOUT: + case CURLOPT_QUICK_EXIT: +#endif +#if LIBCURL_VERSION_NUM >= 0x080900 /* Available since 8.9.0 */ + case CURLOPT_TCP_KEEPCNT: +#endif +#if PHP_VERSION_ID >= 80500 + case CURLOPT_FOLLOWLOCATION: +#endif + lval = zval_get_long(zvalue); + if ((option == CURLOPT_PROTOCOLS || option == CURLOPT_REDIR_PROTOCOLS) && + (PG(open_basedir) && *PG(open_basedir)) && (lval & CURLPROTO_FILE)) { + php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set"); + return FAILURE; + } + error = curl_easy_setopt(ch->cp, (CURLoption) option, lval); + break; + case CURLOPT_SAFE_UPLOAD: + if (!zend_is_true(zvalue)) { + zend_value_error("%s(): Disabling safe uploads is no longer supported", get_active_function_name()); + return FAILURE; + } + break; + + /* String options */ + case CURLOPT_CAINFO: + case CURLOPT_CAPATH: + case CURLOPT_COOKIE: + case CURLOPT_EGDSOCKET: + case CURLOPT_INTERFACE: + case CURLOPT_PROXY: + case CURLOPT_PROXYUSERPWD: + case CURLOPT_REFERER: + case CURLOPT_SSLCERTTYPE: + case CURLOPT_SSLENGINE: + case CURLOPT_SSLENGINE_DEFAULT: + case CURLOPT_SSLKEY: + case CURLOPT_SSLKEYPASSWD: + case CURLOPT_SSLKEYTYPE: + case CURLOPT_SSL_CIPHER_LIST: + case CURLOPT_USERAGENT: + case CURLOPT_COOKIELIST: + case CURLOPT_FTP_ALTERNATIVE_TO_USER: + case CURLOPT_SSH_HOST_PUBLIC_KEY_MD5: + case CURLOPT_PROXYPASSWORD: + case CURLOPT_PROXYUSERNAME: + case CURLOPT_NOPROXY: + case CURLOPT_SOCKS5_GSSAPI_SERVICE: + case CURLOPT_MAIL_FROM: + case CURLOPT_RTSP_STREAM_URI: + case CURLOPT_RTSP_TRANSPORT: + case CURLOPT_TLSAUTH_TYPE: + case CURLOPT_TLSAUTH_PASSWORD: + case CURLOPT_TLSAUTH_USERNAME: + case CURLOPT_TRANSFER_ENCODING: + case CURLOPT_DNS_SERVERS: + case CURLOPT_MAIL_AUTH: + case CURLOPT_LOGIN_OPTIONS: + case CURLOPT_PINNEDPUBLICKEY: + case CURLOPT_PROXY_SERVICE_NAME: + case CURLOPT_SERVICE_NAME: + case CURLOPT_DEFAULT_PROTOCOL: + case CURLOPT_PRE_PROXY: + case CURLOPT_PROXY_CAINFO: + case CURLOPT_PROXY_CAPATH: + case CURLOPT_PROXY_CRLFILE: + case CURLOPT_PROXY_KEYPASSWD: + case CURLOPT_PROXY_PINNEDPUBLICKEY: + case CURLOPT_PROXY_SSL_CIPHER_LIST: + case CURLOPT_PROXY_SSLCERT: + case CURLOPT_PROXY_SSLCERTTYPE: + case CURLOPT_PROXY_SSLKEY: + case CURLOPT_PROXY_SSLKEYTYPE: + case CURLOPT_PROXY_TLSAUTH_PASSWORD: + case CURLOPT_PROXY_TLSAUTH_TYPE: + case CURLOPT_PROXY_TLSAUTH_USERNAME: + case CURLOPT_ABSTRACT_UNIX_SOCKET: + case CURLOPT_REQUEST_TARGET: + case CURLOPT_PROXY_TLS13_CIPHERS: + case CURLOPT_TLS13_CIPHERS: +#if LIBCURL_VERSION_NUM >= 0x074001 /* Available since 7.64.1 */ + case CURLOPT_ALTSVC: +#endif +#if LIBCURL_VERSION_NUM >= 0x074200 /* Available since 7.66.0 */ + case CURLOPT_SASL_AUTHZID: +#endif +#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ + case CURLOPT_PROXY_ISSUERCERT: +#endif +#if LIBCURL_VERSION_NUM >= 0x074900 /* Available since 7.73.0 */ + case CURLOPT_SSL_EC_CURVES: +#endif +#if LIBCURL_VERSION_NUM >= 0x074b00 /* Available since 7.75.0 */ + case CURLOPT_AWS_SIGV4: +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256: +#endif +#if LIBCURL_VERSION_NUM >= 0x075500 /* Available since 7.85.0 */ + case CURLOPT_PROTOCOLS_STR: + case CURLOPT_REDIR_PROTOCOLS_STR: +#endif + { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); +#if LIBCURL_VERSION_NUM >= 0x075500 /* Available since 7.85.0 */ + if ((option == CURLOPT_PROTOCOLS_STR || option == CURLOPT_REDIR_PROTOCOLS_STR) && + (PG(open_basedir) && *PG(open_basedir)) && + (php_memnistr(ZSTR_VAL(str), "file", sizeof("file") - 1, ZSTR_VAL(str) + ZSTR_LEN(str)) != NULL || + php_memnistr(ZSTR_VAL(str), "all", sizeof("all") - 1, ZSTR_VAL(str) + ZSTR_LEN(str)) != NULL)) { + zend_tmp_string_release(tmp_str); + php_error_docref(NULL, E_WARNING, "The FILE protocol cannot be activated when an open_basedir is set"); + return FAILURE; + } +#endif + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + + /* Curl nullable string options */ + case CURLOPT_CUSTOMREQUEST: + case CURLOPT_FTPPORT: + case CURLOPT_RANGE: + case CURLOPT_FTP_ACCOUNT: + case CURLOPT_RTSP_SESSION_ID: + case CURLOPT_ACCEPT_ENCODING: + case CURLOPT_DNS_INTERFACE: + case CURLOPT_DNS_LOCAL_IP4: + case CURLOPT_DNS_LOCAL_IP6: + case CURLOPT_XOAUTH2_BEARER: + case CURLOPT_UNIX_SOCKET_PATH: +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ + case CURLOPT_DOH_URL: +#endif +#if LIBCURL_VERSION_NUM >= 0x074a00 /* Available since 7.74.0 */ + case CURLOPT_HSTS: +#endif + case CURLOPT_KRBLEVEL: + // Authorization header would be implictly set + // with an empty string thus we explictly set the option + // to null to avoid this unwarranted side effect + case CURLOPT_USERPWD: + case CURLOPT_USERNAME: + case CURLOPT_PASSWORD: +#if LIBCURL_VERSION_NUM >= 0x080e00 && PHP_VERSION_ID >= 80500 /* Available since 8.14.0 */ + case CURLOPT_SSL_SIGNATURE_ALGORITHMS: +#endif + { + if (Z_ISNULL_P(zvalue)) { + error = curl_easy_setopt(ch->cp, (CURLoption) option, NULL); + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + break; + } + + /* Curl private option */ + case CURLOPT_PRIVATE: { + zval_ptr_dtor(&ch->private_data); + ZVAL_COPY(&ch->private_data, zvalue); + return SUCCESS; + } + + /* Curl url option */ + case CURLOPT_URL: { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret = php_curl_option_url(ch, str); + zend_tmp_string_release(tmp_str); + return ret; + } + + /* Curl file handle options */ + case CURLOPT_FILE: + case CURLOPT_INFILE: + case CURLOPT_STDERR: + case CURLOPT_WRITEHEADER: { + FILE *fp = NULL; + php_stream *what = NULL; + + if (Z_TYPE_P(zvalue) != IS_NULL) { + what = (php_stream *) zend_fetch_resource2_ex( + zvalue, "File-Handle", php_file_le_stream(), php_file_le_pstream()); + if (!what) { + return FAILURE; + } + + if (FAILURE == php_stream_cast(what, PHP_STREAM_AS_STDIO, (void **) &fp, REPORT_ERRORS)) { + return FAILURE; + } + + if (!fp) { + return FAILURE; + } + } + + error = CURLE_OK; + switch (option) { + case CURLOPT_FILE: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + } + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.write->stream); + ch->handlers.write->fp = fp; + ch->handlers.write->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write->stream, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + break; + case CURLOPT_WRITEHEADER: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + } + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ch->handlers.write_header->fp = fp; + ch->handlers.write_header->method = PHP_CURL_FILE; + ZVAL_COPY(&ch->handlers.write_header->stream, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + break; + case CURLOPT_INFILE: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + } + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; + } else { + zval_ptr_dtor(&ch->handlers.read->stream); + ch->handlers.read->fp = fp; + ch->handlers.read->res = Z_RES_P(zvalue); + ZVAL_COPY(&ch->handlers.read->stream, zvalue); + } + break; + case CURLOPT_STDERR: + if (!what) { + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + } + } else if (what->mode[0] != 'r' || what->mode[1] == '+') { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_COPY(&ch->handlers.std_err, zvalue); + } else { + zend_value_error("%s(): The provided file handle must be writable", get_active_function_name()); + return FAILURE; + } + ZEND_FALLTHROUGH; + default: + error = curl_easy_setopt(ch->cp, (CURLoption) option, fp); + break; + } + break; + } + + /* Curl linked list options */ + case CURLOPT_HTTP200ALIASES: + case CURLOPT_HTTPHEADER: + case CURLOPT_POSTQUOTE: + case CURLOPT_PREQUOTE: + case CURLOPT_QUOTE: + case CURLOPT_TELNETOPTIONS: + case CURLOPT_MAIL_RCPT: + case CURLOPT_RESOLVE: + case CURLOPT_PROXYHEADER: + case CURLOPT_CONNECT_TO: { + zval *current; + HashTable *ph; + zend_string *val, *tmp_val; + struct curl_slist *slist = NULL; + + if (Z_TYPE_P(zvalue) != IS_ARRAY) { + const char *name = NULL; + switch (option) { + case CURLOPT_HTTPHEADER: + name = "CURLOPT_HTTPHEADER"; + break; + case CURLOPT_QUOTE: + name = "CURLOPT_QUOTE"; + break; + case CURLOPT_HTTP200ALIASES: + name = "CURLOPT_HTTP200ALIASES"; + break; + case CURLOPT_POSTQUOTE: + name = "CURLOPT_POSTQUOTE"; + break; + case CURLOPT_PREQUOTE: + name = "CURLOPT_PREQUOTE"; + break; + case CURLOPT_TELNETOPTIONS: + name = "CURLOPT_TELNETOPTIONS"; + break; + case CURLOPT_MAIL_RCPT: + name = "CURLOPT_MAIL_RCPT"; + break; + case CURLOPT_RESOLVE: + name = "CURLOPT_RESOLVE"; + break; + case CURLOPT_PROXYHEADER: + name = "CURLOPT_PROXYHEADER"; + break; + case CURLOPT_CONNECT_TO: + name = "CURLOPT_CONNECT_TO"; + break; + } + + zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name); + return FAILURE; + } + + ph = Z_ARRVAL_P(zvalue); + ZEND_HASH_FOREACH_VAL(ph, current) { + ZVAL_DEREF(current); + val = zval_get_tmp_string(current, &tmp_val); + struct curl_slist *new_slist = curl_slist_append(slist, ZSTR_VAL(val)); + zend_tmp_string_release(tmp_val); + if (!new_slist) { + curl_slist_free_all(slist); + php_error_docref(NULL, E_WARNING, "Could not build curl_slist"); + return FAILURE; + } + slist = new_slist; + } + ZEND_HASH_FOREACH_END(); + + if (slist) { +#if PHP_VERSION_ID >= 80500 + if ((*ch->clone) == 1) { + zend_hash_index_update_ptr(&ch->to_free->slist, option, slist); + } else { + zend_hash_next_index_insert_ptr(&ch->to_free->slist, slist); + } +#else + if ((*ch->clone) == 1) { + zend_hash_index_update_ptr(ch->to_free->slist, option, slist); + } else { + zend_hash_next_index_insert_ptr(ch->to_free->slist, slist); + } +#endif + } + + error = curl_easy_setopt(ch->cp, (CURLoption) option, slist); + + break; + } + + case CURLOPT_BINARYTRANSFER: + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* Do nothing, just backward compatibility */ + break; + +#if PHP_VERSION_ID < 80500 + case CURLOPT_FOLLOWLOCATION: + lval = zend_is_true(zvalue); + error = curl_easy_setopt(ch->cp, (CURLoption) option, (long) lval); + break; +#endif + + case CURLOPT_POSTFIELDS: + if (Z_TYPE_P(zvalue) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(zvalue)) == 0) { + /* no need to build the mime structure for empty hashtables; + also works around https://github.com/curl/curl/issues/6455 */ + curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDS, ""); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, 0L); + } else { + return build_mime_structure_from_hash(ch, zvalue); + } + } else { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + /* with curl 7.17.0 and later, we can use COPYPOSTFIELDS, but we have to provide size before */ + error = curl_easy_setopt(ch->cp, CURLOPT_POSTFIELDSIZE, ZSTR_LEN(str)); + error = curl_easy_setopt(ch->cp, CURLOPT_COPYPOSTFIELDS, ZSTR_VAL(str)); + zend_tmp_string_release(tmp_str); + } + break; + + case CURLOPT_RETURNTRANSFER: + if (zend_is_true(zvalue)) { + ch->handlers.write->method = PHP_CURL_RETURN; + } else { + ch->handlers.write->method = PHP_CURL_STDOUT; + } + break; + + /* Curl off_t options */ +#if PHP_VERSION_ID >= 80500 + case CURLOPT_INFILESIZE_LARGE: +#endif + case CURLOPT_MAX_RECV_SPEED_LARGE: + case CURLOPT_MAX_SEND_SPEED_LARGE: + case CURLOPT_MAXFILESIZE_LARGE: + case CURLOPT_TIMEVALUE_LARGE: + lval = zval_get_long(zvalue); + error = curl_easy_setopt(ch->cp, (CURLoption) option, (curl_off_t) lval); + break; + + case CURLOPT_POSTREDIR: + lval = zval_get_long(zvalue); + error = curl_easy_setopt(ch->cp, CURLOPT_POSTREDIR, (long) (lval & CURL_REDIR_POST_ALL)); + break; + + /* the following options deal with files, therefore the open_basedir check + * is required. + */ + case CURLOPT_COOKIEFILE: + case CURLOPT_COOKIEJAR: + case CURLOPT_RANDOM_FILE: + case CURLOPT_SSLCERT: + case CURLOPT_NETRC_FILE: + case CURLOPT_SSH_PRIVATE_KEYFILE: + case CURLOPT_SSH_PUBLIC_KEYFILE: + case CURLOPT_CRLFILE: + case CURLOPT_ISSUERCERT: + case CURLOPT_SSH_KNOWNHOSTS: { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + zend_result ret; + + if (ZSTR_LEN(str) && php_check_open_basedir(ZSTR_VAL(str))) { + zend_tmp_string_release(tmp_str); + return FAILURE; + } + + ret = php_curl_option_str(ch, option, ZSTR_VAL(str), ZSTR_LEN(str)); + zend_tmp_string_release(tmp_str); + return ret; + } + + case CURLINFO_HEADER_OUT: + if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) { + zend_value_error("CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set"); + return FAILURE; + } + if (zend_is_true(zvalue)) { + curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, fn_debug); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *) ch); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 1L); + } else { + curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, NULL); + curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, NULL); + curl_easy_setopt(ch->cp, CURLOPT_VERBOSE, 0L); + } + break; + + case CURLOPT_SHARE: { +#if PHP_VERSION_ID >= 80500 + if (Z_TYPE_P(zvalue) != IS_OBJECT) { + break; + } + + if (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_share_persistent_ce) { + break; + } +#else + if (Z_TYPE_P(zvalue) == IS_OBJECT && Z_OBJCE_P(zvalue) == curl_share_ce) { +#endif + php_curlsh *sh = Z_CURL_SHARE_P(zvalue); + curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share); + + if (ch->share) { + OBJ_RELEASE(&ch->share->std); + } + GC_ADDREF(&sh->std); + ch->share = sh; +#if PHP_VERSION_ID < 80500 + } +#endif + } break; + + /* Curl blob options */ +#if LIBCURL_VERSION_NUM >= 0x074700 /* Available since 7.71.0 */ + case CURLOPT_ISSUERCERT_BLOB: + case CURLOPT_PROXY_ISSUERCERT_BLOB: + case CURLOPT_PROXY_SSLCERT_BLOB: + case CURLOPT_PROXY_SSLKEY_BLOB: + case CURLOPT_SSLCERT_BLOB: + case CURLOPT_SSLKEY_BLOB: +#if LIBCURL_VERSION_NUM >= 0x074d00 /* Available since 7.77.0 */ + case CURLOPT_CAINFO_BLOB: + case CURLOPT_PROXY_CAINFO_BLOB: +#endif + { + zend_string *tmp_str; + zend_string *str = zval_get_tmp_string(zvalue, &tmp_str); + + struct curl_blob stblob; + stblob.data = ZSTR_VAL(str); + stblob.len = ZSTR_LEN(str); + stblob.flags = CURL_BLOB_COPY; + error = curl_easy_setopt(ch->cp, (CURLoption) option, &stblob); + + zend_tmp_string_release(tmp_str); + } break; +#endif + + default: + if (is_array_config) { + zend_argument_value_error(2, "must contain only valid cURL options"); + } else { + zend_argument_value_error(2, "is not a valid cURL option"); + } + error = CURLE_UNKNOWN_OPTION; + break; + } + + SAVE_CURL_ERROR(ch, error); + if (error != CURLE_OK) { + return FAILURE; + } else { + return SUCCESS; + } +} +/* }}} */ + +/* {{{ Set an option for a cURL transfer */ +PHP_FUNCTION(swoole_native_curl_setopt) { + zval *zid, *zvalue; + zend_long options; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + Z_PARAM_LONG(options) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid, false)) == NULL) { + RETURN_FALSE; + } + + if (_php_curl_setopt(ch, options, zvalue, 0) == SUCCESS) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Set an array of option for a cURL transfer */ +PHP_FUNCTION(swoole_native_curl_setopt_array) { + zval *zid, *arr, *entry; + php_curl *ch; + zend_ulong option; + zend_string *string_key; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_ARRAY(arr) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid, false)) == NULL) { + RETURN_FALSE; + } + + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(arr), option, string_key, entry) { + if (UNEXPECTED(string_key)) { + zend_argument_value_error(2, "contains an invalid cURL option"); + RETURN_THROWS(); + } + + ZVAL_DEREF(entry); + if (_php_curl_setopt(ch, (zend_long) option, entry, 1) == FAILURE) { + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ _php_curl_cleanup_handle(ch) + Cleanup an execution phase */ +void swoole_curl_cleanup_handle(php_curl *ch) { + smart_str_free(&ch->handlers.write->buf); + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + ch->header.str = NULL; + } + + memset(ch->err.str, 0, CURL_ERROR_SIZE + 1); + ch->err.no = 0; +} +/* }}} */ + +/* {{{ Perform a cURL session */ +PHP_FUNCTION(swoole_native_curl_exec) { + CURLcode error; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + swoole_curl_verify_handlers(ch, 1); + + swoole_curl_cleanup_handle(ch); + + error = swoole_curl_easy_perform(ch->cp); + + SAVE_CURL_ERROR(ch, error); + + if (error != CURLE_OK) { + smart_str_free(&ch->handlers.write->buf); + RETURN_FALSE; + } + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + php_stream *stream; + stream = (php_stream *) zend_fetch_resource2_ex( + &ch->handlers.std_err, NULL, php_file_le_stream(), php_file_le_pstream()); + if (stream) { + php_stream_flush(stream); + } + } + + if (ch->handlers.write->method == PHP_CURL_RETURN && ch->handlers.write->buf.s) { + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); + } + + /* flush the file handle, so any remaining data is synched to disk */ + if (ch->handlers.write->method == PHP_CURL_FILE && ch->handlers.write->fp) { + fflush(ch->handlers.write->fp); + } + if (ch->handlers.write_header->method == PHP_CURL_FILE && ch->handlers.write_header->fp) { + fflush(ch->handlers.write_header->fp); + } + + if (ch->handlers.write->method == PHP_CURL_RETURN) { + RETURN_EMPTY_STRING(); + } else { + RETURN_TRUE; + } +} +/* }}} */ + +/* {{{ Get information regarding a specific transfer */ +PHP_FUNCTION(swoole_native_curl_getinfo) { + zval *zid; + php_curl *ch; + zend_long option; + bool option_is_null = 1; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_LONG_OR_NULL(option, option_is_null) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (option_is_null) { + char *s_code; + /* libcurl expects long datatype. So far no cases are known where + it would be an issue. Using zend_long would truncate a 64-bit + var on Win64, so the exact long datatype fits everywhere, as + long as there's no 32-bit int overflow. */ + long l_code; + double d_code; + struct curl_certinfo *ci = NULL; + zval listcode; + curl_off_t co; + + array_init(return_value); + + if (curl_easy_getinfo(ch->cp, CURLINFO_EFFECTIVE_URL, &s_code) == CURLE_OK) { + CAAS("url", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_TYPE, &s_code) == CURLE_OK) { + if (s_code != NULL) { + CAAS("content_type", s_code); + } else { + zval retnull; + ZVAL_NULL(&retnull); + CAAZ("content_type", &retnull); + } + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HTTP_CODE, &l_code) == CURLE_OK) { + CAAL("http_code", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HEADER_SIZE, &l_code) == CURLE_OK) { + CAAL("header_size", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REQUEST_SIZE, &l_code) == CURLE_OK) { + CAAL("request_size", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_FILETIME, &l_code) == CURLE_OK) { + CAAL("filetime", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SSL_VERIFYRESULT, &l_code) == CURLE_OK) { + CAAL("ssl_verify_result", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_COUNT, &l_code) == CURLE_OK) { + CAAL("redirect_count", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_TOTAL_TIME, &d_code) == CURLE_OK) { + CAAD("total_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_NAMELOOKUP_TIME, &d_code) == CURLE_OK) { + CAAD("namelookup_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONNECT_TIME, &d_code) == CURLE_OK) { + CAAD("connect_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRETRANSFER_TIME, &d_code) == CURLE_OK) { + CAAD("pretransfer_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SIZE_UPLOAD, &d_code) == CURLE_OK) { + CAAD("size_upload", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SIZE_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("size_download", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SPEED_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("speed_download", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SPEED_UPLOAD, &d_code) == CURLE_OK) { + CAAD("speed_upload", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d_code) == CURLE_OK) { + CAAD("download_content_length", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CONTENT_LENGTH_UPLOAD, &d_code) == CURLE_OK) { + CAAD("upload_content_length", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_STARTTRANSFER_TIME, &d_code) == CURLE_OK) { + CAAD("starttransfer_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_TIME, &d_code) == CURLE_OK) { + CAAD("redirect_time", d_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_URL, &s_code) == CURLE_OK) { + CAAS("redirect_url", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRIMARY_IP, &s_code) == CURLE_OK) { + CAAS("primary_ip", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CERTINFO, &ci) == CURLE_OK) { + array_init(&listcode); + create_certinfo(ci, &listcode); + CAAZ("certinfo", &listcode); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRIMARY_PORT, &l_code) == CURLE_OK) { + CAAL("primary_port", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_LOCAL_IP, &s_code) == CURLE_OK) { + CAAS("local_ip", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_LOCAL_PORT, &l_code) == CURLE_OK) { + CAAL("local_port", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_HTTP_VERSION, &l_code) == CURLE_OK) { + CAAL("http_version", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PROTOCOL, &l_code) == CURLE_OK) { + CAAL("protocol", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PROXY_SSL_VERIFYRESULT, &l_code) == CURLE_OK) { + CAAL("ssl_verifyresult", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_SCHEME, &s_code) == CURLE_OK) { + CAAS("scheme", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_APPCONNECT_TIME_T, &co) == CURLE_OK) { + CAAL("appconnect_time_us", co); + } +#if LIBCURL_VERSION_NUM >= 0x080600 && PHP_VERSION_ID >= 80500 /* Available since 8.6.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_QUEUE_TIME_T, &co) == CURLE_OK) { + CAAL("queue_time_us", co); + } +#endif + if (curl_easy_getinfo(ch->cp, CURLINFO_CONNECT_TIME_T, &co) == CURLE_OK) { + CAAL("connect_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_NAMELOOKUP_TIME_T, &co) == CURLE_OK) { + CAAL("namelookup_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PRETRANSFER_TIME_T, &co) == CURLE_OK) { + CAAL("pretransfer_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_REDIRECT_TIME_T, &co) == CURLE_OK) { + CAAL("redirect_time_us", co); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_STARTTRANSFER_TIME_T, &co) == CURLE_OK) { + CAAL("starttransfer_time_us", co); + } +#if LIBCURL_VERSION_NUM >= 0x080a00 /* Available since 8.10.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_POSTTRANSFER_TIME_T, &co) == CURLE_OK) { + CAAL("posttransfer_time_us", co); + } +#endif + if (curl_easy_getinfo(ch->cp, CURLINFO_TOTAL_TIME_T, &co) == CURLE_OK) { + CAAL("total_time_us", co); + } + if (ch->header.str) { + CAASTR("request_header", ch->header.str); + } +#if LIBCURL_VERSION_NUM >= 0x074800 /* Available since 7.72.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_EFFECTIVE_METHOD, &s_code) == CURLE_OK) { + CAAS("effective_method", s_code); + } +#endif +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_CAPATH, &s_code) == CURLE_OK) { + CAAS("capath", s_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_CAINFO, &s_code) == CURLE_OK) { + CAAS("cainfo", s_code); + } +#endif +#if PHP_VERSION_ID >= 80500 +#if LIBCURL_VERSION_NUM >= 0x080700 /* Available since 8.7.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_USED_PROXY, &l_code) == CURLE_OK) { + CAAL("used_proxy", l_code); + } +#endif +#if LIBCURL_VERSION_NUM >= 0x080c00 /* Available since 8.12.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_HTTPAUTH_USED, &l_code) == CURLE_OK) { + CAAL("httpauth_used", l_code); + } + if (curl_easy_getinfo(ch->cp, CURLINFO_PROXYAUTH_USED, &l_code) == CURLE_OK) { + CAAL("proxyauth_used", l_code); + } +#endif +#if LIBCURL_VERSION_NUM >= 0x080200 /* Available since 8.2.0 */ + if (curl_easy_getinfo(ch->cp, CURLINFO_CONN_ID, &co) == CURLE_OK) { + CAAL("conn_id", co); + } +#endif +#endif + } else { + switch (option) { + case CURLINFO_HEADER_OUT: + if (ch->header.str) { + RETURN_STR_COPY(ch->header.str); + } else { + RETURN_FALSE; + } + case CURLINFO_CERTINFO: { + struct curl_certinfo *ci = NULL; + + array_init(return_value); + + if (curl_easy_getinfo(ch->cp, CURLINFO_CERTINFO, &ci) == CURLE_OK) { + create_certinfo(ci, return_value); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_PRIVATE: + if (!Z_ISUNDEF(ch->private_data)) { + RETURN_COPY(&ch->private_data); + } else { + RETURN_FALSE; + } + break; + default: { + int type = CURLINFO_TYPEMASK & option; + switch (type) { + case CURLINFO_STRING: { + char *s_code = NULL; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &s_code) == CURLE_OK && s_code) { + RETURN_STRING(s_code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_LONG: { + zend_long code = 0; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &code) == CURLE_OK) { + RETURN_LONG(code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_DOUBLE: { + double code = 0.0; + + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &code) == CURLE_OK) { + RETURN_DOUBLE(code); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_SLIST: { + struct curl_slist *slist; + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &slist) == CURLE_OK) { + struct curl_slist *current = slist; + array_init(return_value); +#if PHP_VERSION_ID >= 80500 + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); +#endif + while (current) { + add_next_index_string(return_value, current->data); + current = current->next; + } + curl_slist_free_all(slist); + } else { + RETURN_FALSE; + } + break; + } + case CURLINFO_OFF_T: { + curl_off_t c_off; + if (curl_easy_getinfo(ch->cp, (CURLINFO) option, &c_off) == CURLE_OK) { + RETURN_LONG((long) c_off); + } else { + RETURN_FALSE; + } + break; + } + default: + RETURN_FALSE; + } + } + } + } +} +/* }}} */ + +/* {{{ Return a string contain the last error for the current session */ +PHP_FUNCTION(swoole_native_curl_error) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ch->err.no) { + ch->err.str[CURL_ERROR_SIZE] = 0; + if (strlen(ch->err.str) > 0) { + RETURN_STRING(ch->err.str); + } else { + RETURN_STRING(curl_easy_strerror((CURLcode) ch->err.no)); + } + } else { + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + +/* {{{ Return an integer containing the last error number */ +PHP_FUNCTION(swoole_native_curl_errno) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(ch->err.no); +} +/* }}} */ + +/* {{{ Close a cURL session */ +PHP_FUNCTION(swoole_native_curl_close) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + return; + } + + if (ch->in_callback) { + zend_throw_error(NULL, "%s(): Attempt to close cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); + } +} +/* }}} */ + +static void swoole_curl_free_obj(zend_object *object) { + php_curl *ch = curl_from_obj(object); + +#if PHP_CURL_DEBUG + fprintf(stderr, "DTOR CALLED, ch = %x\n", ch); +#endif + + if (!ch->cp) { + /* Can happen if constructor throws. */ + zend_object_std_dtor(&ch->std); + return; + } + + swoole_curl_verify_handlers(ch, /* reporterror */ false); + + swoole::curl::Handle *handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + handle->multi->remove_handle(handle); + } + + /* cURL destructors should be invoked only by last curl handle */ + if (--(*ch->clone) == 0) { + zend_llist_clean(&ch->to_free->post); + zend_llist_clean(&ch->to_free->stream); +#if PHP_VERSION_ID >= 80500 + zend_hash_destroy(&ch->to_free->slist); +#else + zend_hash_destroy(ch->to_free->slist); + efree(ch->to_free->slist); +#endif + efree(ch->to_free); + efree(ch->clone); + swoole::curl::destroy_handle(ch->cp); + } + + if (ch->cp) { + curl_easy_cleanup(ch->cp); + } + + smart_str_free(&ch->handlers.write->buf); + if (ZEND_FCC_INITIALIZED(ch->handlers.write->fcc)) { + zend_fcc_dtor(&ch->handlers.write->fcc); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.write_header->fcc)) { + zend_fcc_dtor(&ch->handlers.write_header->fcc); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.read->fcc)) { + zend_fcc_dtor(&ch->handlers.read->fcc); + } + zval_ptr_dtor(&ch->handlers.std_err); + if (ch->header.str) { + zend_string_release_ex(ch->header.str, 0); + } + + zval_ptr_dtor(&ch->handlers.write_header->stream); + zval_ptr_dtor(&ch->handlers.write->stream); + zval_ptr_dtor(&ch->handlers.read->stream); + + efree(ch->handlers.write); + efree(ch->handlers.write_header); + efree(ch->handlers.read); + + if (ZEND_FCC_INITIALIZED(ch->handlers.progress)) { + zend_fcc_dtor(&ch->handlers.progress); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.xferinfo)) { + zend_fcc_dtor(&ch->handlers.xferinfo); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { + zend_fcc_dtor(&ch->handlers.fnmatch); + } + if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) { + zend_fcc_dtor(&ch->handlers.debug); + } +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + zend_fcc_dtor(&ch->handlers.prereq); + } +#endif + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { + zend_fcc_dtor(&ch->handlers.sshhostkey); + } +#endif + + zval_ptr_dtor(&ch->postfields); + zval_ptr_dtor(&ch->private_data); + + if (ch->share) { + OBJ_RELEASE(&ch->share->std); + } + + zend_object_std_dtor(&ch->std); +} +/* }}} */ + +/* {{{ _php_curl_reset_handlers() + Reset all handlers of a given php_curl */ +static void _php_curl_reset_handlers(php_curl *ch) { + if (!Z_ISUNDEF(ch->handlers.write->stream)) { + zval_ptr_dtor(&ch->handlers.write->stream); + ZVAL_UNDEF(&ch->handlers.write->stream); + } + ch->handlers.write->fp = NULL; + ch->handlers.write->method = PHP_CURL_STDOUT; + + if (!Z_ISUNDEF(ch->handlers.write_header->stream)) { + zval_ptr_dtor(&ch->handlers.write_header->stream); + ZVAL_UNDEF(&ch->handlers.write_header->stream); + } + ch->handlers.write_header->fp = NULL; + ch->handlers.write_header->method = PHP_CURL_IGNORE; + + if (!Z_ISUNDEF(ch->handlers.read->stream)) { + zval_ptr_dtor(&ch->handlers.read->stream); + ZVAL_UNDEF(&ch->handlers.read->stream); + } + ch->handlers.read->fp = NULL; + ch->handlers.read->res = NULL; + ch->handlers.read->method = PHP_CURL_DIRECT; + + if (!Z_ISUNDEF(ch->handlers.std_err)) { + zval_ptr_dtor(&ch->handlers.std_err); + ZVAL_UNDEF(&ch->handlers.std_err); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.progress)) { + zend_fcc_dtor(&ch->handlers.progress); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.xferinfo)) { + zend_fcc_dtor(&ch->handlers.xferinfo); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { + zend_fcc_dtor(&ch->handlers.fnmatch); + } + + if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) { + zend_fcc_dtor(&ch->handlers.debug); + } +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + zend_fcc_dtor(&ch->handlers.prereq); + } +#endif + +#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { + zend_fcc_dtor(&ch->handlers.sshhostkey); + } +#endif +} +/* }}} */ + +/* {{{ Reset all options of a libcurl session handle */ +PHP_FUNCTION(swoole_native_curl_reset) { + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ch->in_callback) { + zend_throw_error(NULL, "%s(): Attempt to reset cURL handle from a callback", get_active_function_name()); + RETURN_THROWS(); + } + + curl_easy_reset(ch->cp); + _php_curl_reset_handlers(ch); + _php_curl_set_default_options(ch); +} +/* }}} */ + +/* {{{ URL encodes the given string */ +PHP_FUNCTION(swoole_native_curl_escape) { + zend_string *str; + char *res; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) { + RETURN_FALSE; + } + + if ((res = curl_easy_escape(ch->cp, ZSTR_VAL(str), ZSTR_LEN(str)))) { + RETVAL_STRING(res); + curl_free(res); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ URL decodes the given string */ +PHP_FUNCTION(swoole_native_curl_unescape) { + char *out = NULL; + int out_len; + zval *zid; + zend_string *str; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, swoole_coroutine_curl_handle_ce) + Z_PARAM_STR(str) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + if (ZEND_SIZE_T_INT_OVFL(ZSTR_LEN(str))) { + RETURN_FALSE; + } + + if ((out = curl_easy_unescape(ch->cp, ZSTR_VAL(str), ZSTR_LEN(str), &out_len))) { + RETVAL_STRINGL(out, out_len); + curl_free(out); + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ pause and unpause a connection */ +PHP_FUNCTION(swoole_native_curl_pause) { + zend_long bitmask; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + Z_PARAM_LONG(bitmask) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + RETURN_LONG(curl_easy_pause(ch->cp, bitmask)); +} +/* }}} */ + +#if LIBCURL_VERSION_NUM >= 0x073E00 /* Available since 7.62.0 */ +/* {{{ perform connection upkeep checks */ +PHP_FUNCTION(swoole_native_curl_upkeep) { + CURLcode error; + zval *zid; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) + ZEND_PARSE_PARAMETERS_END(); + + if ((ch = swoole_curl_get_handle(zid)) == NULL) { + RETURN_FALSE; + } + + error = curl_easy_upkeep(ch->cp); + SAVE_CURL_ERROR(ch, error); + + RETURN_BOOL(error == CURLE_OK); +} +/*}}} */ +#endif +#endif diff --git a/thirdparty/php84/curl/multi.cc b/thirdparty/php84/curl/multi.cc new file mode 100644 index 0000000000..23a26261ec --- /dev/null +++ b/thirdparty/php84/curl/multi.cc @@ -0,0 +1,648 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole_cxx.h" +#include "zend_object_handlers.h" + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS +#include "php_swoole_curl.h" + +using swoole::curl::Multi; +using swoole::curl::Selector; + +SW_EXTERN_C_BEGIN +#include "swoole_curl_interface.h" +#include "curl_arginfo.h" + +#include +#include + +#include +#include + +#define SAVE_CURLM_ERROR(__handle, __err) (__handle)->err.no = (int) __err; + +void swoole_curl_multi_set_in_coroutine(php_curlm *mh, bool value) { + zend_update_property_bool(nullptr, &mh->std, ZEND_STRL("in_coroutine"), value); +} + +bool swoole_curl_multi_is_in_coroutine(php_curlm *mh) { + zval rv; + zval *zv = zend_read_property_ex(nullptr, &mh->std, SW_ZSTR_KNOWN(SW_ZEND_STR_IN_COROUTINE), 1, &rv); + return zval_is_true(zv); +} + +/* CurlMultiHandle class */ +zend_class_entry *swoole_coroutine_curl_multi_handle_ce; + +static inline php_curlm *curl_multi_from_obj(zend_object *obj) { + return (php_curlm *) ((char *) (obj) -XtOffsetOf(php_curlm, std)); +} + +#define Z_CURL_MULTI_P(zv) curl_multi_from_obj(Z_OBJ_P(zv)) + +static void _php_curl_multi_free(php_curlm *mh); + +SW_EXTERN_C_END + +/* {{{ Returns a new cURL multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_init) { + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_NONE(); + + object_init_ex(return_value, swoole_coroutine_curl_multi_handle_ce); + mh = Z_CURL_MULTI_P(return_value); + mh->multi = new Multi(); + + swoole_curl_multi_set_in_coroutine(mh, true); + zend_llist_init(&mh->easyh, sizeof(zval), swoole_curl_multi_cleanup_list, 0); +} +/* }}} */ + +/* {{{ Add a normal cURL handle to a cURL multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_add_handle) { + zval *z_mh; + zval *z_ch; + php_curlm *mh; + php_curl *ch; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + ch = swoole_curl_get_handle(z_ch); + + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + swoole_curl_verify_handlers(ch, /* reporterror */ true); + swoole_curl_cleanup_handle(ch); + + auto handle = swoole::curl::get_handle(ch->cp); + error = mh->multi->add_handle(handle); + SAVE_CURLM_ERROR(mh, error); + + if (error == CURLM_OK) { + Z_ADDREF_P(z_ch); + zend_llist_add_element(&mh->easyh, z_ch); + } + + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); + RETURN_LONG((zend_long) error); +} +/* }}} */ + +void swoole_curl_multi_cleanup_list(void *data) /* {{{ */ +{ + zval *z_ch = (zval *) data; + + zval_ptr_dtor(z_ch); +} +/* }}} */ + +/* Used internally as comparison routine passed to zend_list_del_element */ +static int curl_compare_objects(zval *z1, zval *z2) /* {{{ */ +{ + return (Z_TYPE_P(z1) == Z_TYPE_P(z2) && Z_TYPE_P(z1) == IS_OBJECT && Z_OBJ_P(z1) == Z_OBJ_P(z2)); +} +/* }}} */ + +/* Used to find the php_curl resource for a given curl easy handle */ +static zval *_php_curl_multi_find_easy_handle(php_curlm *mh, CURL *easy) /* {{{ */ +{ + php_curl *tmp_ch; + zend_llist_position pos; + zval *pz_ch_temp; + + for (pz_ch_temp = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch_temp; + pz_ch_temp = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + tmp_ch = swoole_curl_get_handle(pz_ch_temp, false, false); + + if (tmp_ch && tmp_ch->cp == easy) { + return pz_ch_temp; + } + } + + return NULL; +} +/* }}} */ + +/* {{{ Remove a multi handle from a set of cURL handles */ +PHP_FUNCTION(swoole_native_curl_multi_remove_handle) { + zval *z_mh; + zval *z_ch; + php_curlm *mh; + php_curl *ch; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + ch = Z_CURL_P(z_ch); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle && handle->multi) { + error = mh->multi->remove_handle(handle); + } else { + error = curl_multi_remove_handle(mh->multi, ch->cp); + } + + swoole_trace_log(SW_TRACE_CO_CURL, "multi=%p, cp=%p, handle=%p, error=%d", mh->multi, ch->cp, handle, error); + SAVE_CURLM_ERROR(mh, error); + if (error == CURLM_OK) { + zend_llist_del_element(&mh->easyh, z_ch, (int (*)(void *, void *)) curl_compare_objects); + } + + RETVAL_LONG((zend_long) error); +} +/* }}} */ + +/* {{{ Get all the sockets associated with the cURL extension, which can then be "selected" */ +PHP_FUNCTION(swoole_native_curl_multi_select) { + zval *z_mh; + php_curlm *mh; + double timeout = 1.0; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_DOUBLE(timeout) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + +#if PHP_VERSION_ID >= 80500 + if (!(timeout >= 0.0 && timeout <= (INT_MAX / 1000.0))) { + zend_argument_value_error(2, "must be between 0 and %f", INT_MAX / 1000.0); + RETURN_THROWS(); + } +#else + if (!(timeout >= 0.0 && timeout <= ((double) INT_MAX / 1000.0))) { + zend_argument_value_error(2, "must be between 0 and %d", (int) ceilf((double) INT_MAX / 1000)); + RETURN_THROWS(); + } +#endif + + RETURN_LONG(mh->multi->select(mh, timeout)); +} +/* }}} */ + +/* {{{ Run the sub-connections of the current cURL handle */ +PHP_FUNCTION(swoole_native_curl_multi_exec) { + zval *z_mh; + zval *z_still_running; + php_curlm *mh; + int still_running; + CURLMcode error = CURLM_OK; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_ZVAL(z_still_running) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + { + zend_llist_position pos; + php_curl *ch; + zval *pz_ch; + + for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + ch = Z_CURL_P(pz_ch); + swoole_curl_verify_handlers(ch, /* reporterror */ true); + } + } + + error = mh->multi->perform(); + still_running = mh->multi->get_running_handles(); + ZEND_TRY_ASSIGN_REF_LONG(z_still_running, still_running); + + SAVE_CURLM_ERROR(mh, error); + RETURN_LONG((zend_long) error); +} +/* }}} */ + +/* {{{ Return the content of a cURL handle if CURLOPT_RETURNTRANSFER is set */ +PHP_FUNCTION(swoole_native_curl_multi_getcontent) { + zval *z_ch; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_ch, swoole_coroutine_curl_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + ch = Z_CURL_P(z_ch); + + if (ch->handlers.write->method == PHP_CURL_RETURN) { + if (!ch->handlers.write->buf.s) { + RETURN_EMPTY_STRING(); + } + smart_str_0(&ch->handlers.write->buf); + RETURN_STR_COPY(ch->handlers.write->buf.s); + } + + RETURN_NULL(); +} +/* }}} */ + +/* {{{ Get information about the current transfers */ +PHP_FUNCTION(swoole_native_curl_multi_info_read) { + zval *z_mh; + php_curlm *mh; + CURLMsg *tmp_msg; + int queued_msgs; + zval *zmsgs_in_queue = NULL; + php_curl *ch; + + ZEND_PARSE_PARAMETERS_START(1, 2) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(zmsgs_in_queue) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + + tmp_msg = curl_multi_info_read(mh->multi->get_multi_handle(), &queued_msgs); + if (tmp_msg == NULL) { + RETURN_FALSE; + } + + if (zmsgs_in_queue) { + ZEND_TRY_ASSIGN_REF_LONG(zmsgs_in_queue, queued_msgs); + } + + array_init(return_value); + add_assoc_long(return_value, "msg", tmp_msg->msg); + add_assoc_long(return_value, "result", tmp_msg->data.result); + + /* find the original easy curl handle */ + { + zval *pz_ch = _php_curl_multi_find_easy_handle(mh, tmp_msg->easy_handle); + if (pz_ch != NULL) { + /* we must save result to be able to read error message */ + ch = swoole_curl_get_handle(pz_ch, false, false); + SAVE_CURL_ERROR(ch, tmp_msg->data.result); + + Z_ADDREF_P(pz_ch); + add_assoc_zval(return_value, "handle", pz_ch); + } + } +} +/* }}} */ + +/* {{{ Close a set of cURL handles */ +PHP_FUNCTION(swoole_native_curl_multi_close) { + php_curlm *mh; + zval *z_mh; + + zend_llist_position pos; + zval *pz_ch; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + + for (pz_ch = (zval *) zend_llist_get_first_ex(&mh->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&mh->easyh, &pos)) { + php_curl *ch = Z_CURL_P(pz_ch); + if (!ch) { + continue; + } + swoole_curl_verify_handlers(ch, 0); + auto handle = swoole::curl::get_handle(ch->cp); + if (handle) { + mh->multi->remove_handle(handle); + } else { + curl_multi_remove_handle(mh->multi, ch->cp); + } + } + zend_llist_clean(&mh->easyh); +} +/* }}} */ + +/* {{{ Return an integer containing the last multi curl error number */ +PHP_FUNCTION(swoole_native_curl_multi_errno) { + zval *z_mh; + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + + RETURN_LONG(mh->err.no); +} +/* }}} */ + +static int _php_server_push_callback( + CURL *parent_ch, CURL *easy, size_t num_headers, struct curl_pushheaders *push_headers, void *userp) /* {{{ */ +{ + php_curl *ch; + php_curl *parent; + php_curlm *mh = (php_curlm *) userp; + int rval = CURL_PUSH_DENY; + zval *pz_parent_ch = NULL; + zval pz_ch; + zval headers; + zval retval; + + pz_parent_ch = _php_curl_multi_find_easy_handle(mh, parent_ch); + if (pz_parent_ch == NULL) { + return rval; + } + + parent = Z_CURL_P(pz_parent_ch); + + ch = swoole_curl_init_handle_into_zval(&pz_ch); + ch->cp = easy; + swoole_setup_easy_copy_handlers(ch, parent); + + auto parent_handle = swoole::curl::get_handle(parent->cp); + if (parent_handle) { + auto handle = swoole::curl::create_handle(easy); + handle->multi = parent_handle->multi; + } + +#if PHP_VERSION_ID >= 80500 + array_init_size(&headers, num_headers); + zend_hash_real_init_packed(Z_ARRVAL(headers)); + for (size_t i = 0; i < num_headers; i++) { + char *header = curl_pushheader_bynum(push_headers, i); + add_index_string(&headers, i, header); + } +#else + array_init(&headers); + for (size_t i = 0; i < num_headers; i++) { + char *header = curl_pushheader_bynum(push_headers, i); + add_next_index_string(&headers, header); + } +#endif + + ZEND_ASSERT(pz_parent_ch); + zval call_args[3] = {*pz_parent_ch, pz_ch, headers}; + + zend_call_known_fcc(&mh->handlers.server_push, &retval, /* param_count */ 3, call_args, /* named_params */ NULL); + zval_ptr_dtor_nogc(&headers); + + if (!Z_ISUNDEF(retval)) { + if (CURL_PUSH_DENY != swoole_curl_get_long(&retval)) { + rval = CURL_PUSH_OK; + zend_llist_add_element(&mh->easyh, &pz_ch); + } else { + /* libcurl will free this easy handle, avoid double free */ + ch->cp = NULL; + } + } + + return rval; +} +/* }}} */ + +static bool _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ +{ + CURLMcode error = CURLM_OK; + + switch (option) { + case CURLMOPT_PIPELINING: + case CURLMOPT_MAXCONNECTS: + case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: + case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: + case CURLMOPT_MAX_HOST_CONNECTIONS: + case CURLMOPT_MAX_PIPELINE_LENGTH: + case CURLMOPT_MAX_TOTAL_CONNECTIONS: +#if LIBCURL_VERSION_NUM >= 0x074300 /* Available since 7.67.0 */ + case CURLMOPT_MAX_CONCURRENT_STREAMS: +#endif + { + zend_long lval = zval_get_long(zvalue); + + if (option == CURLMOPT_PIPELINING && (lval & 1)) { +#if LIBCURL_VERSION_NUM >= 0x073e00 /* Available since 7.62.0 */ + php_error_docref(NULL, E_WARNING, "CURLPIPE_HTTP1 is no longer supported"); +#else + php_error_docref(NULL, E_DEPRECATED, "CURLPIPE_HTTP1 is deprecated"); +#endif + } + error = curl_multi_setopt(mh->multi->get_multi_handle(), (CURLMoption) option, lval); + break; + } + case CURLMOPT_PUSHFUNCTION: { + /* See php_curl_set_callable_handler */ + if (ZEND_FCC_INITIALIZED(mh->handlers.server_push)) { + zend_fcc_dtor(&mh->handlers.server_push); + } + + char *error_str = NULL; + if (UNEXPECTED(!zend_is_callable_ex(zvalue, + /* object */ NULL, + /* check_flags */ 0, + /* callable_name */ NULL, + &mh->handlers.server_push, + /* error */ &error_str))) { + if (!EG(exception)) { + zend_argument_type_error(2, "must be a valid callback for option CURLMOPT_PUSHFUNCTION, %s", error_str); + } + efree(error_str); + return false; + } + zend_fcc_addref(&mh->handlers.server_push); + + error = curl_multi_setopt(mh->multi->get_multi_handle(), CURLMOPT_PUSHFUNCTION, _php_server_push_callback); + if (error != CURLM_OK) { + return false; + } + error = curl_multi_setopt(mh->multi->get_multi_handle(), CURLMOPT_PUSHDATA, mh); + break; + } + default: + zend_argument_value_error(2, "is not a valid cURL multi option"); + error = CURLM_UNKNOWN_OPTION; + break; + } + + SAVE_CURLM_ERROR(mh, error); + + return error == CURLM_OK; +} +/* }}} */ + +/* {{{ Set an option for the curl multi handle */ +PHP_FUNCTION(swoole_native_curl_multi_setopt) { + zval *z_mh, *zvalue; + zend_long options; + php_curlm *mh; + + ZEND_PARSE_PARAMETERS_START(3, 3) + Z_PARAM_OBJECT_OF_CLASS(z_mh, swoole_coroutine_curl_multi_handle_ce) + Z_PARAM_LONG(options) + Z_PARAM_ZVAL(zvalue) + ZEND_PARSE_PARAMETERS_END(); + + mh = Z_CURL_MULTI_P(z_mh); + if (!(swoole_curl_multi_is_in_coroutine(mh))) { + swoole_fatal_error(SW_ERROR_WRONG_OPERATION, + "The given object is not a valid coroutine CurlMultiHandle object"); + RETURN_FALSE; + } + if (_php_curl_multi_setopt(mh, (CURLMoption) options, zvalue, return_value)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } +} +/* }}} */ + +/* CurlMultiHandle class */ + +static zend_object_handlers swoole_coroutine_curl_multi_handle_handlers; + +static zend_object *swoole_curl_multi_create_object(zend_class_entry *class_type) { + php_curlm *intern = (php_curlm *) zend_object_alloc(sizeof(php_curlm), class_type); + + zend_object_std_init(&intern->std, class_type); + object_properties_init(&intern->std, class_type); + intern->std.handlers = &swoole_coroutine_curl_multi_handle_handlers; + + return &intern->std; +} + +static zend_function *swoole_curl_multi_get_constructor(zend_object *object) { + zend_throw_error(NULL, "Cannot directly construct CurlMultiHandle, use curl_multi_init() instead"); + return NULL; +} + +static void swoole_curl_multi_free_obj(zend_object *object) { + php_curlm *mh = curl_multi_from_obj(object); + + if (!mh->multi) { + /* Can happen if constructor throws. */ + zend_object_std_dtor(&mh->std); + return; + } + + _php_curl_multi_free(mh); + zend_object_std_dtor(&mh->std); +} + +static void _php_curl_multi_free(php_curlm *mh) { + bool is_in_coroutine = swoole_curl_multi_is_in_coroutine(mh); + for (zend_llist_element *element = mh->easyh.head; element; element = element->next) { + zval *z_ch = (zval *) element->data; + php_curl *ch; + if (OBJ_FLAGS(Z_OBJ_P(z_ch)) & IS_OBJ_FREE_CALLED) { + continue; + } + if ((ch = swoole_curl_get_handle(z_ch, true, false))) { + swoole_curl_verify_handlers(ch, 0); + auto handle = swoole::curl::get_handle(ch->cp); + if (is_in_coroutine && handle) { + mh->multi->remove_handle(handle); + } else { + curl_multi_remove_handle(mh->multi, ch->cp); + } + } + } + if (mh->multi) { + if (is_in_coroutine) { + delete mh->multi; + } else { + curl_multi_cleanup(mh->multi); + } + mh->multi = nullptr; + } + zend_llist_clean(&mh->easyh); + + if (ZEND_FCC_INITIALIZED(mh->handlers.server_push)) { + zend_fcc_dtor(&mh->handlers.server_push); + } +} + +static HashTable *swoole_curl_multi_get_gc(zend_object *object, zval **table, int *n) { + php_curlm *curl_multi = curl_multi_from_obj(object); + + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + if (ZEND_FCC_INITIALIZED(curl_multi->handlers.server_push)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl_multi->handlers.server_push); + } + + zend_llist_position pos; + for (zval *pz_ch = (zval *) zend_llist_get_first_ex(&curl_multi->easyh, &pos); pz_ch; + pz_ch = (zval *) zend_llist_get_next_ex(&curl_multi->easyh, &pos)) { + zend_get_gc_buffer_add_zval(gc_buffer, pz_ch); + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + +#if PHP_VERSION_ID >= 80500 + return NULL; +#else + return zend_std_get_properties(object); +#endif +} + +void curl_multi_register_class(const zend_function_entry *method_entries) { + swoole_coroutine_curl_multi_handle_ce = curl_multi_ce; + swoole_coroutine_curl_multi_handle_ce->create_object = swoole_curl_multi_create_object; + + memcpy(&swoole_coroutine_curl_multi_handle_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + swoole_coroutine_curl_multi_handle_handlers.offset = XtOffsetOf(php_curlm, std); + swoole_coroutine_curl_multi_handle_handlers.free_obj = swoole_curl_multi_free_obj; + swoole_coroutine_curl_multi_handle_handlers.get_gc = swoole_curl_multi_get_gc; + swoole_coroutine_curl_multi_handle_handlers.get_constructor = swoole_curl_multi_get_constructor; + swoole_coroutine_curl_multi_handle_handlers.clone_obj = NULL; + swoole_coroutine_curl_multi_handle_handlers.cast_object = swoole_curl_cast_object; + swoole_coroutine_curl_multi_handle_handlers.compare = [](zval *o1, zval *o2) { return ZEND_UNCOMPARABLE; }; + + zend_declare_property_bool(swoole_coroutine_curl_multi_handle_ce, ZEND_STRL("in_coroutine"), 0, ZEND_ACC_PUBLIC); +} +#endif diff --git a/thirdparty/php84/curl/php_curl.h b/thirdparty/php84/curl/php_curl.h new file mode 100644 index 0000000000..4bb3e1c226 --- /dev/null +++ b/thirdparty/php84/curl/php_curl.h @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sterling Hughes | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#if defined(SW_USE_CURL) && PHP_VERSION_ID >= 80400 + +#ifndef _PHP_CURL_H +#define _PHP_CURL_H + +#include "php.h" +#include "zend_smart_str.h" + +#define PHP_CURL_DEBUG 0 + +#ifdef PHP_WIN32 +#ifdef PHP_CURL_EXPORTS +#define PHP_CURL_API __declspec(dllexport) +#else +#define PHP_CURL_API __declspec(dllimport) +#endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +#define PHP_CURL_API __attribute__((visibility("default"))) +#else +#define PHP_CURL_API +#endif + +PHP_CURL_API extern zend_class_entry *curl_ce; +PHP_CURL_API extern zend_class_entry *curl_share_ce; +PHP_CURL_API extern zend_class_entry *curl_multi_ce; +#if PHP_VERSION_ID >= 80500 +PHP_CURL_API extern zend_class_entry *curl_share_persistent_ce; +#endif +PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_handle_ce; +PHP_CURL_API extern zend_class_entry *swoole_coroutine_curl_multi_handle_ce; +PHP_CURL_API extern zend_class_entry *curl_CURLFile_class; +PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class; + +#endif /* _PHP_CURL_H */ +#endif diff --git a/thirdparty/php84/ftp/CREDITS b/thirdparty/php84/ftp/CREDITS new file mode 100644 index 0000000000..bf0920ad80 --- /dev/null +++ b/thirdparty/php84/ftp/CREDITS @@ -0,0 +1,2 @@ +FTP +Stefan Esser, Andrew Skalski diff --git a/thirdparty/php84/ftp/ftp.c b/thirdparty/php84/ftp/ftp.c new file mode 100644 index 0000000000..78003df737 --- /dev/null +++ b/thirdparty/php84/ftp/ftp.c @@ -0,0 +1,2364 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andrew Skalski | + | Stefan Esser (resume functions) | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "php.h" +#include "php_swoole_api.h" +#include "swoole_socket_hook.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#ifdef PHP_WIN32 +#include +#else +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#include +#include +#include +#endif +#include + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#ifdef HAVE_SYS_SELECT_H +#include +#endif + +#ifdef SW_HAVE_FTP_SSL +#include +#include +#endif + +#include "ftp.h" +#include "ext/standard/fsock.h" + +#ifdef PHP_WIN32 +# undef ETIMEDOUT +# define ETIMEDOUT WSAETIMEDOUT +#endif + +/* sends an ftp command, returns true on success, false on error. + * it sends the string "cmd args\r\n" if args is non-null, or + * "cmd\r\n" if args is null + */ +static int ftp_putcmd( ftpbuf_t *ftp, + const char *cmd, + const size_t cmd_len, + const char *args, + const size_t args_len); + +/* wrapper around send/recv to handle timeouts */ +static int my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len); +static int my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len); +static int my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen); + +/* reads a line the socket , returns true on success, false on error */ +static int ftp_readline(ftpbuf_t *ftp); + +/* reads an ftp response, returns true on success, false on error */ +static int ftp_getresp(ftpbuf_t *ftp); + +/* sets the ftp transfer type */ +static int ftp_type(ftpbuf_t *ftp, ftptype_t type); + +/* opens up a data stream */ +static databuf_t* ftp_getdata(ftpbuf_t *ftp); + +/* accepts the data connection, returns updated data buffer */ +static databuf_t* data_accept(databuf_t *data, ftpbuf_t *ftp); + +/* closes the data connection, no-op if already closed */ +static void data_close(ftpbuf_t *ftp); + +/* generic file lister */ +static char** ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len); + +#ifdef SW_HAVE_FTP_SSL +/* shuts down a TLS/SSL connection */ +static void ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle); +#endif + +/* IP and port conversion box */ +union ipbox { + struct in_addr ia[2]; + unsigned short s[4]; + unsigned char c[8]; +}; + +/* {{{ ftp_open */ +ftpbuf_t* +ftp_open(const char *host, short port, zend_long timeout_sec) +{ + ftpbuf_t *ftp; + socklen_t size; + struct timeval tv; + + + /* alloc the ftp structure */ + ftp = ecalloc(1, sizeof(*ftp)); + + tv.tv_sec = timeout_sec; + tv.tv_usec = 0; + + ftp->fd = php_async_socket_connect_to_host(host, + (unsigned short) (port ? port : 21), SOCK_STREAM, + 0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE); + if (ftp->fd == -1) { + goto bail; + } + + /* Default Settings */ + ftp->timeout_sec = timeout_sec; + ftp->nb = 0; + + size = sizeof(ftp->localaddr); + memset(&ftp->localaddr, 0, size); + if (getsockname(ftp->fd, (struct sockaddr*) &ftp->localaddr, &size) != 0) { + php_error_docref(NULL, E_WARNING, "getsockname failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + if (!ftp_getresp(ftp) || ftp->resp != 220) { + goto bail; + } + + return ftp; + +bail: + if (ftp->fd != -1) { + closesocket(ftp->fd); + } + efree(ftp); + return NULL; +} +/* }}} */ + +/* {{{ ftp_close */ +ftpbuf_t* +ftp_close(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return NULL; + } +#ifdef SW_HAVE_FTP_SSL + if (ftp->last_ssl_session) { + SSL_SESSION_free(ftp->last_ssl_session); + } +#endif + data_close(ftp); + if (ftp->stream && ftp->closestream) { + php_stream_close(ftp->stream); + } + if (ftp->fd != -1) { +#ifdef SW_HAVE_FTP_SSL + if (ftp->ssl_active) { + ftp_ssl_shutdown(ftp, ftp->fd, ftp->ssl_handle); + } +#endif + closesocket(ftp->fd); + } + ftp_gc(ftp); + efree(ftp); + return NULL; +} +/* }}} */ + +/* {{{ ftp_gc */ +void +ftp_gc(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return; + } + if (ftp->pwd) { + efree(ftp->pwd); + ftp->pwd = NULL; + } + if (ftp->syst) { + efree(ftp->syst); + ftp->syst = NULL; + } +} +/* }}} */ + +/* {{{ ftp_quit */ +int +ftp_quit(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return 0; + } + + if (!ftp_putcmd(ftp, "QUIT", sizeof("QUIT")-1, NULL, (size_t) 0)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 221) { + return 0; + } + + if (ftp->pwd) { + efree(ftp->pwd); + ftp->pwd = NULL; + } + + return 1; +} +/* }}} */ + +#ifdef SW_HAVE_FTP_SSL +static int ftp_ssl_new_session_cb(SSL *ssl, SSL_SESSION *sess) +{ + ftpbuf_t *ftp = SSL_get_app_data(ssl); + + /* Technically there can be multiple sessions per connection, but we only care about the most recent one. */ + if (ftp->last_ssl_session) { + SSL_SESSION_free(ftp->last_ssl_session); + } + ftp->last_ssl_session = SSL_get1_session(ssl); + + /* Return 0 as we are not using OpenSSL's session cache. */ + return 0; +} +#endif + +/* {{{ ftp_login */ +int +ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const char *pass, const size_t pass_len) +{ +#ifdef SW_HAVE_FTP_SSL + SSL_CTX *ctx = NULL; + long ssl_ctx_options = SSL_OP_ALL; + int err, res; + bool retry; +#endif + if (ftp == NULL) { + return 0; + } + +#ifdef SW_HAVE_FTP_SSL + if (ftp->use_ssl && !ftp->ssl_active) { + if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "TLS", sizeof("TLS")-1)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + + if (ftp->resp != 234) { + if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "SSL", sizeof("SSL")-1)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + + if (ftp->resp != 334) { + return 0; + } else { + ftp->old_ssl = 1; + ftp->use_ssl_for_data = 1; + } + } + + ctx = SSL_CTX_new(SSLv23_client_method()); + if (ctx == NULL) { + php_error_docref(NULL, E_WARNING, "Failed to create the SSL context"); + return 0; + } + + ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + SSL_CTX_set_options(ctx, ssl_ctx_options); + + /* Allow SSL to re-use sessions. + * We're relying on our own session storage as only at most one session will ever be active per FTP connection. */ + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx, ftp_ssl_new_session_cb); + + ftp->ssl_handle = SSL_new(ctx); + SSL_set_app_data(ftp->ssl_handle, ftp); /* Needed for ftp_ssl_new_session_cb */ + SSL_CTX_free(ctx); + + if (ftp->ssl_handle == NULL) { + php_error_docref(NULL, E_WARNING, "Failed to create the SSL handle"); + return 0; + } + + SSL_set_fd(ftp->ssl_handle, ftp->fd); + + do { + res = SSL_connect(ftp->ssl_handle); + err = SSL_get_error(ftp->ssl_handle, res); + + /* TODO check if handling other error codes would make sense */ + switch (err) { + case SSL_ERROR_NONE: + retry = 0; + break; + + case SSL_ERROR_ZERO_RETURN: + retry = 0; + SSL_shutdown(ftp->ssl_handle); + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: { + php_pollfd p; + int i; + + p.fd = ftp->fd; + p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; + p.revents = 0; + + i = php_poll2(&p, 1, 300); + + retry = i > 0; + } + break; + + default: + php_error_docref(NULL, E_WARNING, "SSL/TLS handshake failed"); + SSL_shutdown(ftp->ssl_handle); + SSL_free(ftp->ssl_handle); + return 0; + } + } while (retry); + + ftp->ssl_active = 1; + + if (!ftp->old_ssl) { + + /* set protection buffersize to zero */ + if (!ftp_putcmd(ftp, "PBSZ", sizeof("PBSZ")-1, "0", sizeof("0")-1)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + + /* enable data conn encryption */ + if (!ftp_putcmd(ftp, "PROT", sizeof("PROT")-1, "P", sizeof("P")-1)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + + ftp->use_ssl_for_data = (ftp->resp >= 200 && ftp->resp <=299); + } + } +#endif + + if (!ftp_putcmd(ftp, "USER", sizeof("USER")-1, user, user_len)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + if (ftp->resp == 230) { + return 1; + } + if (ftp->resp != 331) { + return 0; + } + if (!ftp_putcmd(ftp, "PASS", sizeof("PASS")-1, pass, pass_len)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + return (ftp->resp == 230); +} +/* }}} */ + +/* {{{ ftp_reinit */ +int +ftp_reinit(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return 0; + } + + ftp_gc(ftp); + + ftp->nb = 0; + + if (!ftp_putcmd(ftp, "REIN", sizeof("REIN")-1, NULL, (size_t) 0)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 220) { + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ ftp_syst */ +const char* +ftp_syst(ftpbuf_t *ftp) +{ + char *syst, *end; + + if (ftp == NULL) { + return NULL; + } + + /* default to cached value */ + if (ftp->syst) { + return ftp->syst; + } + if (!ftp_putcmd(ftp, "SYST", sizeof("SYST")-1, NULL, (size_t) 0)) { + return NULL; + } + if (!ftp_getresp(ftp) || ftp->resp != 215) { + return NULL; + } + syst = ftp->inbuf; + while (*syst == ' ') { + syst++; + } + if ((end = strchr(syst, ' '))) { + *end = 0; + } + ftp->syst = estrdup(syst); + if (end) { + *end = ' '; + } + return ftp->syst; +} +/* }}} */ + +/* {{{ ftp_pwd */ +const char* +ftp_pwd(ftpbuf_t *ftp) +{ + char *pwd, *end; + + if (ftp == NULL) { + return NULL; + } + + /* default to cached value */ + if (ftp->pwd) { + return ftp->pwd; + } + if (!ftp_putcmd(ftp, "PWD", sizeof("PWD")-1, NULL, (size_t) 0)) { + return NULL; + } + if (!ftp_getresp(ftp) || ftp->resp != 257) { + return NULL; + } + /* copy out the pwd from response */ + if ((pwd = strchr(ftp->inbuf, '"')) == NULL) { + return NULL; + } + if ((end = strrchr(++pwd, '"')) == NULL) { + return NULL; + } + ftp->pwd = estrndup(pwd, end - pwd); + + return ftp->pwd; +} +/* }}} */ + +/* {{{ ftp_exec */ +int +ftp_exec(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len) +{ + if (ftp == NULL) { + return 0; + } + if (!ftp_putcmd(ftp, "SITE EXEC", sizeof("SITE EXEC")-1, cmd, cmd_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 200) { + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ ftp_raw */ +void +ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_value) +{ + if (ftp == NULL || cmd == NULL) { + RETURN_NULL(); + } + if (!ftp_putcmd(ftp, cmd, cmd_len, NULL, (size_t) 0)) { + RETURN_NULL(); + } + array_init(return_value); + while (ftp_readline(ftp)) { + add_next_index_string(return_value, ftp->inbuf); + if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { + return; + } + } +} +/* }}} */ + +/* {{{ ftp_chdir */ +int +ftp_chdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len) +{ + if (ftp == NULL) { + return 0; + } + + if (ftp->pwd) { + efree(ftp->pwd); + ftp->pwd = NULL; + } + + if (!ftp_putcmd(ftp, "CWD", sizeof("CWD")-1, dir, dir_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 250) { + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ ftp_cdup */ +int +ftp_cdup(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return 0; + } + + if (ftp->pwd) { + efree(ftp->pwd); + ftp->pwd = NULL; + } + + if (!ftp_putcmd(ftp, "CDUP", sizeof("CDUP")-1, NULL, (size_t) 0)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 250) { + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ ftp_mkdir */ +zend_string* +ftp_mkdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len) +{ + char *mkd, *end; + zend_string *ret; + + if (ftp == NULL) { + return NULL; + } + if (!ftp_putcmd(ftp, "MKD", sizeof("MKD")-1, dir, dir_len)) { + return NULL; + } + if (!ftp_getresp(ftp) || ftp->resp != 257) { + return NULL; + } + /* copy out the dir from response */ + if ((mkd = strchr(ftp->inbuf, '"')) == NULL) { + return zend_string_init(dir, dir_len, 0); + } + if ((end = strrchr(++mkd, '"')) == NULL) { + return NULL; + } + *end = 0; + ret = zend_string_init(mkd, end - mkd, 0); + *end = '"'; + + return ret; +} +/* }}} */ + +/* {{{ ftp_rmdir */ +int +ftp_rmdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len) +{ + if (ftp == NULL) { + return 0; + } + if (!ftp_putcmd(ftp, "RMD", sizeof("RMD")-1, dir, dir_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 250) { + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ ftp_chmod */ +int +ftp_chmod(ftpbuf_t *ftp, const int mode, const char *filename, const int filename_len) +{ + char *buffer; + size_t buffer_len; + + if (ftp == NULL || filename_len <= 0) { + return 0; + } + + buffer_len = spprintf(&buffer, 0, "CHMOD %o %s", mode, filename); + + if (!buffer) { + return 0; + } + + if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, buffer, buffer_len)) { + efree(buffer); + return 0; + } + + efree(buffer); + + if (!ftp_getresp(ftp) || ftp->resp != 200) { + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ ftp_alloc */ +int +ftp_alloc(ftpbuf_t *ftp, const zend_long size, zend_string **response) +{ + char buffer[64]; + int buffer_len; + + if (ftp == NULL || size <= 0) { + return 0; + } + + buffer_len = snprintf(buffer, sizeof(buffer) - 1, ZEND_LONG_FMT, size); + + if (buffer_len < 0) { + return 0; + } + + if (!ftp_putcmd(ftp, "ALLO", sizeof("ALLO")-1, buffer, buffer_len)) { + return 0; + } + + if (!ftp_getresp(ftp)) { + return 0; + } + + if (response) { + *response = zend_string_init(ftp->inbuf, strlen(ftp->inbuf), 0); + } + + if (ftp->resp < 200 || ftp->resp >= 300) { + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ ftp_nlist */ +char** +ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len) +{ + return ftp_genlist(ftp, "NLST", sizeof("NLST")-1, path, path_len); +} +/* }}} */ + +/* {{{ ftp_list */ +char** +ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive) +{ + return ftp_genlist(ftp, ((recursive) ? "LIST -R" : "LIST"), ((recursive) ? sizeof("LIST -R")-1 : sizeof("LIST")-1), path, path_len); +} +/* }}} */ + +/* {{{ ftp_mlsd */ +char** +ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len) +{ + return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len); +} +/* }}} */ + +/* {{{ ftp_mlsd_parse_line */ +int +ftp_mlsd_parse_line(HashTable *ht, const char *input) { + + zval zstr; + const char *end = input + strlen(input); + + const char *sp = memchr(input, ' ', end - input); + if (!sp) { + php_error_docref(NULL, E_WARNING, "Missing pathname in MLSD response"); + return FAILURE; + } + + /* Extract pathname */ + ZVAL_STRINGL(&zstr, sp + 1, end - sp - 1); + zend_hash_update(ht, ZSTR_KNOWN(ZEND_STR_NAME), &zstr); + end = sp; + + while (input < end) { + const char *semi, *eq; + + /* Find end of fact */ + semi = memchr(input, ';', end - input); + if (!semi) { + php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response"); + return FAILURE; + } + + /* Separate fact key and value */ + eq = memchr(input, '=', semi - input); + if (!eq) { + php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response"); + return FAILURE; + } + + ZVAL_STRINGL(&zstr, eq + 1, semi - eq - 1); + zend_hash_str_update(ht, input, eq - input, &zstr); + input = semi + 1; + } + + return SUCCESS; +} +/* }}} */ + +/* {{{ ftp_type */ +int +ftp_type(ftpbuf_t *ftp, ftptype_t type) +{ + const char *typechar; + + if (ftp == NULL) { + return 0; + } + if (type == ftp->type) { + return 1; + } + if (type == FTPTYPE_ASCII) { + typechar = "A"; + } else if (type == FTPTYPE_IMAGE) { + typechar = "I"; + } else { + return 0; + } + if (!ftp_putcmd(ftp, "TYPE", sizeof("TYPE")-1, typechar, 1)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 200) { + return 0; + } + ftp->type = type; + + return 1; +} +/* }}} */ + +/* {{{ ftp_pasv */ +int +ftp_pasv(ftpbuf_t *ftp, int pasv) +{ + char *ptr; + union ipbox ipbox; + unsigned long b[6]; + socklen_t n; + struct sockaddr *sa; + struct sockaddr_in *sin; + + if (ftp == NULL) { + return 0; + } + if (pasv && ftp->pasv == 2) { + return 1; + } + ftp->pasv = 0; + if (!pasv) { + return 1; + } + n = sizeof(ftp->pasvaddr); + memset(&ftp->pasvaddr, 0, n); + sa = (struct sockaddr *) &ftp->pasvaddr; + + if (getpeername(ftp->fd, sa, &n) < 0) { + return 0; + } + +#ifdef HAVE_IPV6 + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; + char *endptr, delimiter; + + /* try EPSV first */ + if (!ftp_putcmd(ftp, "EPSV", sizeof("EPSV")-1, NULL, (size_t) 0)) { + return 0; + } + if (!ftp_getresp(ftp)) { + return 0; + } + if (ftp->resp == 229) { + /* parse out the port */ + for (ptr = ftp->inbuf; *ptr && *ptr != '('; ptr++); + if (!*ptr) { + return 0; + } + delimiter = *++ptr; + for (n = 0; *ptr && n < 3; ptr++) { + if (*ptr == delimiter) { + n++; + } + } + + sin6->sin6_port = htons((unsigned short) strtoul(ptr, &endptr, 10)); + if (ptr == endptr || *endptr != delimiter) { + return 0; + } + ftp->pasv = 2; + return 1; + } + } + + /* fall back to PASV */ +#endif + + if (!ftp_putcmd(ftp, "PASV", sizeof("PASV")-1, NULL, (size_t) 0)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 227) { + return 0; + } + /* parse out the IP and port */ + for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); + n = sscanf(ptr, "%lu,%lu,%lu,%lu,%lu,%lu", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]); + if (n != 6) { + return 0; + } + for (n = 0; n < 6; n++) { + ipbox.c[n] = (unsigned char) b[n]; + } + sin = (struct sockaddr_in *) sa; + if (ftp->usepasvaddress) { + sin->sin_addr = ipbox.ia[0]; + } + sin->sin_port = ipbox.s[2]; + + ftp->pasv = 2; + + return 1; +} +/* }}} */ + +/* {{{ ftp_get */ +int +ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos) +{ + databuf_t *data = NULL; + size_t rcvd; + char arg[MAX_LENGTH_OF_LONG]; + + if (ftp == NULL) { + return 0; + } + if (!ftp_type(ftp, type)) { + goto bail; + } + + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + + if (resumepos > 0) { + int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos); + + if (arg_len < 0) { + goto bail; + } + if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 350)) { + goto bail; + } + } + + if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) { + goto bail; + } + + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + + while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) { + if (rcvd == (size_t)-1) { + goto bail; + } + + if (type == FTPTYPE_ASCII) { +#ifndef PHP_WIN32 + char *s; +#endif + char *ptr = data->buf; + char *e = ptr + rcvd; + /* logic depends on the OS EOL + * Win32 -> \r\n + * Everything Else \n + */ +#ifdef PHP_WIN32 + php_stream_write(outstream, ptr, (e - ptr)); + ptr = e; +#else + while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) { + php_stream_write(outstream, ptr, (s - ptr)); + if (*(s + 1) == '\n') { + s++; + php_stream_putc(outstream, '\n'); + } + ptr = s + 1; + } +#endif + if (ptr < e) { + php_stream_write(outstream, ptr, (e - ptr)); + } + } else if (rcvd != php_stream_write(outstream, data->buf, rcvd)) { + goto bail; + } + } + + data_close(ftp); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { + goto bail; + } + + return 1; +bail: + data_close(ftp); + return 0; +} +/* }}} */ + +static zend_result ftp_send_stream_to_data_socket(ftpbuf_t *ftp, databuf_t *data, php_stream *instream, ftptype_t type, bool send_once_and_return) +{ + if (type == FTPTYPE_ASCII) { + /* Change (and later restore) flags to make sure php_stream_get_line() searches '\n'. */ + const uint32_t flags_mask = PHP_STREAM_FLAG_EOL_UNIX | PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC; + uint32_t old_flags = instream->flags & flags_mask; + instream->flags = (instream->flags & ~flags_mask) | PHP_STREAM_FLAG_EOL_UNIX; + + char *ptr = data->buf; + const char *end = data->buf + FTP_BUFSIZE; + while (!php_stream_eof(instream)) { + size_t line_length; + if (!php_stream_get_line(instream, ptr, end - ptr, &line_length)) { + break; + } + + ZEND_ASSERT(line_length != 0); + + ptr += line_length - 1; + /* Replace \n with \r\n */ + if (*ptr == '\n') { + *ptr = '\r'; + /* The streams layer always puts a \0 byte at the end of a line, + * so there is always place to add an extra byte. */ + *++ptr = '\n'; + } + + ptr++; + + /* If less than 2 bytes remain, either the buffer is completely full or there is a single byte left to put a '\0' + * which isn't really useful, in this case send and reset the buffer. */ + if (end - ptr < 2) { + size_t send_size = FTP_BUFSIZE - (end - ptr); + if (UNEXPECTED(my_send(ftp, data->fd, data->buf, send_size) != send_size)) { + instream->flags = (instream->flags & ~flags_mask) | old_flags; + return FAILURE; + } + ptr = data->buf; + if (send_once_and_return) { + break; + } + } + } + + instream->flags = (instream->flags & ~flags_mask) | old_flags; + + if (end - ptr < FTP_BUFSIZE) { + size_t send_size = FTP_BUFSIZE - (end - ptr); + if (UNEXPECTED(my_send(ftp, data->fd, data->buf, send_size) != send_size)) { + return FAILURE; + } + } + } else { + while (!php_stream_eof(instream)) { + ssize_t size = php_stream_read(instream, data->buf, FTP_BUFSIZE); + if (size == 0) { + break; + } + if (UNEXPECTED(size < 0)) { + return FAILURE; + } + if (UNEXPECTED(my_send(ftp, data->fd, data->buf, size) != size)) { + return FAILURE; + } + if (send_once_and_return) { + break; + } + } + } + + return SUCCESS; +} + +/* {{{ ftp_put */ +int +ftp_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos) +{ + databuf_t *data = NULL; + char arg[MAX_LENGTH_OF_LONG]; + + if (ftp == NULL) { + return 0; + } + if (!ftp_type(ftp, type)) { + goto bail; + } + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + + if (startpos > 0) { + int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos); + + if (arg_len < 0) { + goto bail; + } + if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 350)) { + goto bail; + } + } + + if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) { + goto bail; + } + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + + if (ftp_send_stream_to_data_socket(ftp, data, instream, type, false) != SUCCESS) { + goto bail; + } + + data_close(ftp); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) { + goto bail; + } + return 1; +bail: + data_close(ftp); + return 0; +} +/* }}} */ + + +/* {{{ ftp_append */ +int +ftp_append(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type) +{ + databuf_t *data = NULL; + + if (ftp == NULL) { + return 0; + } + if (!ftp_type(ftp, type)) { + goto bail; + } + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + ftp->data = data; + + if (!ftp_putcmd(ftp, "APPE", sizeof("APPE")-1, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) { + goto bail; + } + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + + if (ftp_send_stream_to_data_socket(ftp, data, instream, type, false) != SUCCESS) { + goto bail; + } + + data_close(ftp); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) { + goto bail; + } + return 1; +bail: + data_close(ftp); + return 0; +} +/* }}} */ + +/* {{{ ftp_size */ +zend_long +ftp_size(ftpbuf_t *ftp, const char *path, const size_t path_len) +{ + if (ftp == NULL) { + return -1; + } + if (!ftp_type(ftp, FTPTYPE_IMAGE)) { + return -1; + } + if (!ftp_putcmd(ftp, "SIZE", sizeof("SIZE")-1, path, path_len)) { + return -1; + } + if (!ftp_getresp(ftp) || ftp->resp != 213) { + return -1; + } + return ZEND_ATOL(ftp->inbuf); +} +/* }}} */ + +/* {{{ ftp_mdtm */ +time_t +ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len) +{ + time_t stamp; + struct tm *gmt, tmbuf; + struct tm tm; + char *ptr; + int n; + + if (ftp == NULL) { + return -1; + } + if (!ftp_putcmd(ftp, "MDTM", sizeof("MDTM")-1, path, path_len)) { + return -1; + } + if (!ftp_getresp(ftp) || ftp->resp != 213) { + return -1; + } + /* parse out the timestamp */ + for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); + n = sscanf(ptr, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + if (n != 6) { + return -1; + } + tm.tm_year -= 1900; + tm.tm_mon--; + tm.tm_isdst = -1; + + /* figure out the GMT offset */ + stamp = time(NULL); + gmt = php_gmtime_r(&stamp, &tmbuf); + if (!gmt) { + return -1; + } + gmt->tm_isdst = -1; + + /* apply the GMT offset */ + tm.tm_sec += stamp - mktime(gmt); + tm.tm_isdst = gmt->tm_isdst; + + stamp = mktime(&tm); + + return stamp; +} +/* }}} */ + +/* {{{ ftp_delete */ +int +ftp_delete(ftpbuf_t *ftp, const char *path, const size_t path_len) +{ + if (ftp == NULL) { + return 0; + } + if (!ftp_putcmd(ftp, "DELE", sizeof("DELE")-1, path, path_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 250) { + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ ftp_rename */ +int +ftp_rename(ftpbuf_t *ftp, const char *src, const size_t src_len, const char *dest, const size_t dest_len) +{ + if (ftp == NULL) { + return 0; + } + if (!ftp_putcmd(ftp, "RNFR", sizeof("RNFR")-1, src, src_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 350) { + return 0; + } + if (!ftp_putcmd(ftp, "RNTO", sizeof("RNTO")-1, dest, dest_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp != 250) { + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ ftp_site */ +int +ftp_site(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len) +{ + if (ftp == NULL) { + return 0; + } + if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, cmd, cmd_len)) { + return 0; + } + if (!ftp_getresp(ftp) || ftp->resp < 200 || ftp->resp >= 300) { + return 0; + } + + return 1; +} +/* }}} */ + +/* static functions */ + +/* {{{ ftp_putcmd */ +int +ftp_putcmd(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *args, const size_t args_len) +{ + int size; + char *data; + + if (strpbrk(cmd, "\r\n")) { + return 0; + } + /* build the output buffer */ + if (args && args[0]) { + /* "cmd args\r\n\0" */ + if (cmd_len + args_len + 4 > FTP_BUFSIZE) { + return 0; + } + if (strpbrk(args, "\r\n")) { + return 0; + } + size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s %s\r\n", cmd, args); + } else { + /* "cmd\r\n\0" */ + if (cmd_len + 3 > FTP_BUFSIZE) { + return 0; + } + size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s\r\n", cmd); + } + + data = ftp->outbuf; + + /* Clear the inbuf and extra-lines buffer */ + ftp->inbuf[0] = '\0'; + ftp->extra = NULL; + + if (my_send(ftp, ftp->fd, data, size) != size) { + return 0; + } + return 1; +} +/* }}} */ + +/* {{{ ftp_readline */ +int +ftp_readline(ftpbuf_t *ftp) +{ + long size, rcvd; + char *data, *eol; + + /* shift the extra to the front */ + size = FTP_BUFSIZE; + rcvd = 0; + if (ftp->extra) { + memmove(ftp->inbuf, ftp->extra, ftp->extralen); + rcvd = ftp->extralen; + } + + data = ftp->inbuf; + + do { + size -= rcvd; + for (eol = data; rcvd; rcvd--, eol++) { + if (*eol == '\r') { + *eol = 0; + ftp->extra = eol + 1; + if (rcvd > 1 && *(eol + 1) == '\n') { + ftp->extra++; + rcvd--; + } + if ((ftp->extralen = --rcvd) == 0) { + ftp->extra = NULL; + } + return 1; + } else if (*eol == '\n') { + *eol = 0; + ftp->extra = eol + 1; + if ((ftp->extralen = --rcvd) == 0) { + ftp->extra = NULL; + } + return 1; + } + } + + data = eol; + if ((rcvd = my_recv(ftp, ftp->fd, data, size)) < 1) { + *data = 0; + return 0; + } + } while (size); + + *data = 0; + return 0; +} +/* }}} */ + +/* {{{ ftp_getresp */ +int +ftp_getresp(ftpbuf_t *ftp) +{ + if (ftp == NULL) { + return 0; + } + ftp->resp = 0; + + while (1) { + + if (!ftp_readline(ftp)) { + return 0; + } + + /* Break out when the end-tag is found */ + if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { + break; + } + } + + /* translate the tag */ + if (!isdigit(ftp->inbuf[0]) || !isdigit(ftp->inbuf[1]) || !isdigit(ftp->inbuf[2])) { + return 0; + } + + ftp->resp = 100 * (ftp->inbuf[0] - '0') + 10 * (ftp->inbuf[1] - '0') + (ftp->inbuf[2] - '0'); + + memmove(ftp->inbuf, ftp->inbuf + 4, FTP_BUFSIZE - 4); + + if (ftp->extra) { + ftp->extra -= 4; + } + return 1; +} +/* }}} */ + +static ssize_t my_send_wrapper_with_restart(php_socket_t fd, const void *buf, size_t size, int flags) { + ssize_t n; + do { + n = send(fd, buf, size, flags); + } while (n == -1 && php_socket_errno() == EINTR); + return n; +} + +static ssize_t my_recv_wrapper_with_restart(php_socket_t fd, void *buf, size_t size, int flags) { + ssize_t n; + do { + n = recv(fd, buf, size, flags); + } while (n == -1 && php_socket_errno() == EINTR); + return n; +} + +int single_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t size) { +#ifdef SW_HAVE_FTP_SSL + int err; + bool retry = 0; + SSL *handle = NULL; + php_socket_t fd; + size_t sent; + + if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) { + handle = ftp->ssl_handle; + fd = ftp->fd; + } else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) { + handle = ftp->data->ssl_handle; + fd = ftp->data->fd; + } else { + return my_send_wrapper_with_restart(s, buf, size, 0); + } + + do { + sent = SSL_write(handle, buf, size); + err = SSL_get_error(handle, sent); + + switch (err) { + case SSL_ERROR_NONE: + retry = 0; + break; + + case SSL_ERROR_ZERO_RETURN: + retry = 0; + SSL_shutdown(handle); + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_CONNECT: { + php_pollfd p; + int i; + + p.fd = fd; + p.events = POLLOUT; + p.revents = 0; + + i = php_poll2(&p, 1, 300); + + retry = i > 0; + } + break; + + default: + php_error_docref(NULL, E_WARNING, "SSL write failed"); + return -1; + } + } while (retry); + return sent; +#else + return my_send_wrapper_with_restart(s, buf, size, 0); +#endif +} + +#if PHP_VERSION_ID < 80300 +typedef uint64_t zend_hrtime_t; +#define ZEND_NANO_IN_SEC UINT64_C(1000000000) + +static zend_always_inline zend_hrtime_t zend_hrtime(void) { + struct timespec ts = { .tv_sec = 0, .tv_nsec = 0 }; + if (EXPECTED(0 == clock_gettime(CLOCK_MONOTONIC, &ts))) { + return ((zend_hrtime_t) ts.tv_sec * (zend_hrtime_t)ZEND_NANO_IN_SEC) + ts.tv_nsec; + } + return 0; +} +#endif + +static int my_poll(php_socket_t fd, int events, int timeout) { + int n; + zend_hrtime_t timeout_hr = (zend_hrtime_t) timeout * 1000000; + + while (true) { + zend_hrtime_t start_ns = zend_hrtime(); + n = php_async_socket_poll(fd, events, (int) (timeout_hr / 1000000)); + + if (n == -1 && php_socket_errno() == EINTR) { + zend_hrtime_t delta_ns = zend_hrtime() - start_ns; + /* delta_ns == 0 is only possible with a platform that does not support a high-res timer. */ + if (delta_ns > timeout_hr || UNEXPECTED(delta_ns == 0)) { +#ifndef PHP_WIN32 + errno = ETIMEDOUT; +#endif + break; + } + timeout_hr -= delta_ns; + } else { + break; + } + } + + return n; +} + +/* {{{ my_send */ +int +my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len) +{ + zend_long size, sent; + int n; + + size = len; + while (size) { + n = my_poll(s, POLLOUT, ftp->timeout_sec * 1000); + + if (n < 1) { + char buf[256]; + if (n == 0) { +#ifdef PHP_WIN32 + _set_errno(ETIMEDOUT); +#else + errno = ETIMEDOUT; +#endif + } + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf)); + return -1; + } + + sent = single_send(ftp, s, buf, size); + if (sent == -1) { + return -1; + } + + buf = (char*) buf + sent; + size -= sent; + } + + return len; +} +/* }}} */ + +/* {{{ my_recv */ +int +my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len) +{ + int n, nr_bytes; +#ifdef SW_HAVE_FTP_SSL + int err; + bool retry = 0; + SSL *handle = NULL; + php_socket_t fd; +#endif + n = my_poll(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000); + if (n < 1) { + char buf[256]; + if (n == 0) { +#ifdef PHP_WIN32 + _set_errno(ETIMEDOUT); +#else + errno = ETIMEDOUT; +#endif + } + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf)); + return -1; + } + +#ifdef SW_HAVE_FTP_SSL + if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) { + handle = ftp->ssl_handle; + fd = ftp->fd; + } else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) { + handle = ftp->data->ssl_handle; + fd = ftp->data->fd; + } + + if (handle) { + do { + nr_bytes = SSL_read(handle, buf, len); + err = SSL_get_error(handle, nr_bytes); + + switch (err) { + case SSL_ERROR_NONE: + retry = 0; + break; + + case SSL_ERROR_ZERO_RETURN: + retry = 0; + SSL_shutdown(handle); + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_CONNECT: { + int i = my_poll(fd, POLLIN, ftp->timeout_sec * 1000); + retry = i > 0; + break; + } + case SSL_ERROR_WANT_WRITE: { + int i = my_poll(fd, POLLOUT, ftp->timeout_sec * 1000); + retry = i > 0; + break; + } + default: + php_error_docref(NULL, E_WARNING, "SSL read failed"); + return -1; + } + } while (retry); + } else { +#endif + nr_bytes = my_recv_wrapper_with_restart(s, buf, len, 0); +#ifdef SW_HAVE_FTP_SSL + } +#endif + return (nr_bytes); +} +/* }}} */ + +/* {{{ data_available */ +int +data_available(ftpbuf_t *ftp, php_socket_t s) +{ + int n; + + n = my_poll(s, PHP_POLLREADABLE, 1000); + if (n < 1) { + char buf[256]; + if (n == 0) { +#ifdef PHP_WIN32 + _set_errno(ETIMEDOUT); +#else + errno = ETIMEDOUT; +#endif + } + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf)); + return 0; + } + + return 1; +} +/* }}} */ +/* {{{ data_writeable */ +int +data_writeable(ftpbuf_t *ftp, php_socket_t s) +{ + int n; + + n = my_poll(s, POLLOUT, 1000); + if (n < 1) { + char buf[256]; + if (n == 0) { +#ifdef PHP_WIN32 + _set_errno(ETIMEDOUT); +#else + errno = ETIMEDOUT; +#endif + } + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf)); + return 0; + } + + return 1; +} +/* }}} */ + +/* {{{ my_accept */ +int +my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen) +{ + int n; + + n = my_poll(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000); + if (n < 1) { + char buf[256]; + if (n == 0) { +#ifdef PHP_WIN32 + _set_errno(ETIMEDOUT); +#else + errno = ETIMEDOUT; +#endif + } + php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf)); + return -1; + } + + return accept(s, addr, addrlen); +} +/* }}} */ + +/* {{{ ftp_getdata */ +databuf_t* +ftp_getdata(ftpbuf_t *ftp) +{ + int fd = -1; + databuf_t *data; + php_sockaddr_storage addr; + struct sockaddr *sa; + socklen_t size; + union ipbox ipbox; + char arg[sizeof("255, 255, 255, 255, 255, 255")]; + struct timeval tv; + int arg_len; + + + /* ask for a passive connection if we need one */ + if (ftp->pasv && !ftp_pasv(ftp, 1)) { + return NULL; + } + /* alloc the data structure */ + data = ecalloc(1, sizeof(*data)); + data->listener = -1; + data->fd = -1; + data->type = ftp->type; + + sa = (struct sockaddr *) &ftp->localaddr; + /* bind/listen */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == SOCK_ERR) { + php_error_docref(NULL, E_WARNING, "socket() failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + /* passive connection handler */ + if (ftp->pasv) { + /* clear the ready status */ + ftp->pasv = 1; + + /* connect */ + /* Win 95/98 seems not to like size > sizeof(sockaddr_in) */ + size = php_sockaddr_size(&ftp->pasvaddr); + tv.tv_sec = ftp->timeout_sec; + tv.tv_usec = 0; + if (php_connect_nonb(fd, (struct sockaddr*) &ftp->pasvaddr, size, &tv) == -1) { + php_error_docref(NULL, E_WARNING, "php_connect_nonb() failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + data->fd = fd; + + ftp->data = data; + return data; + } + + + /* active (normal) connection */ + + /* bind to a local address */ + php_any_addr(sa->sa_family, &addr, 0); + size = php_sockaddr_size(&addr); + + if (bind(fd, (struct sockaddr*) &addr, size) != 0) { + php_error_docref(NULL, E_WARNING, "bind() failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + if (getsockname(fd, (struct sockaddr*) &addr, &size) != 0) { + php_error_docref(NULL, E_WARNING, "getsockname() failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + if (listen(fd, 5) != 0) { + php_error_docref(NULL, E_WARNING, "listen() failed: %s (%d)", strerror(errno), errno); + goto bail; + } + + data->listener = fd; + +#ifdef HAVE_IPV6 + if (sa->sa_family == AF_INET6) { + /* need to use EPRT */ + char eprtarg[INET6_ADDRSTRLEN + sizeof("|x||xxxxx|")]; + char out[INET6_ADDRSTRLEN]; + int eprtarg_len; + const char *r; + r = inet_ntop(AF_INET6, &((struct sockaddr_in6*) sa)->sin6_addr, out, sizeof(out)); + ZEND_ASSERT(r != NULL); + + eprtarg_len = snprintf(eprtarg, sizeof(eprtarg), "|2|%s|%hu|", out, ntohs(((struct sockaddr_in6 *) &addr)->sin6_port)); + + if (eprtarg_len < 0) { + goto bail; + } + + if (!ftp_putcmd(ftp, "EPRT", sizeof("EPRT")-1, eprtarg, eprtarg_len)) { + goto bail; + } + + if (!ftp_getresp(ftp) || ftp->resp != 200) { + goto bail; + } + + ftp->data = data; + return data; + } +#endif + + /* send the PORT */ + ipbox.ia[0] = ((struct sockaddr_in*) sa)->sin_addr; + ipbox.s[2] = ((struct sockaddr_in*) &addr)->sin_port; + arg_len = snprintf(arg, sizeof(arg), "%u,%u,%u,%u,%u,%u", ipbox.c[0], ipbox.c[1], ipbox.c[2], ipbox.c[3], ipbox.c[4], ipbox.c[5]); + + if (arg_len < 0) { + goto bail; + } + if (!ftp_putcmd(ftp, "PORT", sizeof("PORT")-1, arg, arg_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || ftp->resp != 200) { + goto bail; + } + + ftp->data = data; + return data; + +bail: + if (fd != -1) { + closesocket(fd); + } + efree(data); + return NULL; +} +/* }}} */ + +/* {{{ data_accept */ +databuf_t* +data_accept(databuf_t *data, ftpbuf_t *ftp) +{ + php_sockaddr_storage addr; + socklen_t size; + +#ifdef SW_HAVE_FTP_SSL + SSL_CTX *ctx; + SSL_SESSION *session; + int err, res; + bool retry; +#endif + + if (data->fd != -1) { + goto data_accepted; + } + size = sizeof(addr); + data->fd = my_accept(ftp, data->listener, (struct sockaddr*) &addr, &size); + closesocket(data->listener); + data->listener = -1; + + if (data->fd == -1) { + efree(data); + return NULL; + } + +data_accepted: +#ifdef SW_HAVE_FTP_SSL + + /* now enable ssl if we need to */ + if (ftp->use_ssl && ftp->use_ssl_for_data) { + ctx = SSL_get_SSL_CTX(ftp->ssl_handle); + if (ctx == NULL) { + php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL context"); + return 0; + } + + data->ssl_handle = SSL_new(ctx); + if (data->ssl_handle == NULL) { + php_error_docref(NULL, E_WARNING, "data_accept: failed to create the SSL handle"); + return 0; + } + + SSL_set_fd(data->ssl_handle, data->fd); + + if (ftp->old_ssl) { + SSL_copy_session_id(data->ssl_handle, ftp->ssl_handle); + } + + /* get the session from the control connection so we can re-use it */ + session = ftp->last_ssl_session; + if (session == NULL) { + php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL session"); + SSL_free(data->ssl_handle); + return 0; + } + + /* and set it on the data connection */ + SSL_set_app_data(data->ssl_handle, ftp); /* Needed for ftp_ssl_new_session_cb */ + res = SSL_set_session(data->ssl_handle, session); + if (res == 0) { + php_error_docref(NULL, E_WARNING, "data_accept: failed to set the existing SSL session"); + SSL_free(data->ssl_handle); + return 0; + } + + do { + res = SSL_connect(data->ssl_handle); + err = SSL_get_error(data->ssl_handle, res); + + switch (err) { + case SSL_ERROR_NONE: + retry = 0; + break; + + case SSL_ERROR_ZERO_RETURN: + retry = 0; + SSL_shutdown(data->ssl_handle); + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: { + php_pollfd p; + int i; + + p.fd = data->fd; + p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; + p.revents = 0; + + i = php_poll2(&p, 1, 300); + + retry = i > 0; + } + break; + + default: + php_error_docref(NULL, E_WARNING, "data_accept: SSL/TLS handshake failed"); + SSL_shutdown(data->ssl_handle); + SSL_free(data->ssl_handle); + return 0; + } + } while (retry); + + data->ssl_active = 1; + } + +#endif + + return data; +} +/* }}} */ + +/* {{{ ftp_ssl_shutdown */ +#ifdef SW_HAVE_FTP_SSL +static void ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle) { + /* In TLS 1.3 it's common to receive session tickets after the handshake has completed. We need to train + the socket (read the tickets until EOF/close_notify alert) before closing the socket. Otherwise the + server might get an ECONNRESET which might lead to data truncation on server side. + */ + char buf[256]; /* We will use this for the OpenSSL error buffer, so it has + to be at least 256 bytes long.*/ + int done = 1, err, nread; + unsigned long sslerror; + + err = SSL_shutdown(ssl_handle); + if (err < 0) { + php_error_docref(NULL, E_WARNING, "SSL_shutdown failed"); + } + else if (err == 0) { + /* The shutdown is not yet finished. Call SSL_read() to do a bidirectional shutdown. */ + done = 0; + } + + while (!done && data_available(ftp, fd)) { + ERR_clear_error(); + nread = SSL_read(ssl_handle, buf, sizeof(buf)); + if (nread <= 0) { + err = SSL_get_error(ssl_handle, nread); + switch (err) { + case SSL_ERROR_NONE: /* this is not an error */ + case SSL_ERROR_ZERO_RETURN: /* no more data */ + /* This is the expected response. There was no data but only + the close notify alert */ + done = 1; + break; + case SSL_ERROR_WANT_READ: + /* there's data pending, re-invoke SSL_read() */ + break; + case SSL_ERROR_WANT_WRITE: + /* SSL wants a write. Really odd. Let's bail out. */ + done = 1; + break; + case SSL_ERROR_SYSCALL: + /* most likely the peer closed the connection without + sending a close_notify shutdown alert; + bail out to avoid raising a spurious warning */ + done = 1; + break; + default: + if ((sslerror = ERR_get_error())) { + ERR_error_string_n(sslerror, buf, sizeof(buf)); + php_error_docref(NULL, E_WARNING, "SSL_read on shutdown: %s", buf); + } else if (errno) { + php_error_docref(NULL, E_WARNING, "SSL_read on shutdown: %s (%d)", strerror(errno), errno); + } + done = 1; + break; + } + } + } + (void)SSL_free(ssl_handle); +} +#endif +/* }}} */ + +/* {{{ data_close */ +void data_close(ftpbuf_t *ftp) +{ + ZEND_ASSERT(ftp != NULL); + databuf_t *data = ftp->data; + if (data == NULL) { + return; + } + if (data->listener != -1) { +#ifdef SW_HAVE_FTP_SSL + if (data->ssl_active) { + /* don't free the data context, it's the same as the control */ + ftp_ssl_shutdown(ftp, data->listener, data->ssl_handle); + data->ssl_active = 0; + } +#endif + closesocket(data->listener); + } + if (data->fd != -1) { +#ifdef SW_HAVE_FTP_SSL + if (data->ssl_active) { + /* don't free the data context, it's the same as the control */ + ftp_ssl_shutdown(ftp, data->fd, data->ssl_handle); + data->ssl_active = 0; + } +#endif + closesocket(data->fd); + } + ftp->data = NULL; + efree(data); +} +/* }}} */ + +/* {{{ ftp_genlist */ +char** +ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len) +{ + php_stream *tmpstream = NULL; + databuf_t *data = NULL; + char *ptr; + int ch, lastch; + size_t size, rcvd; + size_t lines; + char **ret = NULL; + char **entry; + char *text; + + + if ((tmpstream = php_stream_fopen_tmpfile()) == NULL) { + php_error_docref(NULL, E_WARNING, "Unable to create temporary file. Check permissions in temporary files directory."); + return NULL; + } + + if (!ftp_type(ftp, FTPTYPE_ASCII)) { + goto bail; + } + + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + ftp->data = data; + + if (!ftp_putcmd(ftp, cmd, cmd_len, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125 && ftp->resp != 226)) { + goto bail; + } + + /* some servers don't open a ftp-data connection if the directory is empty */ + if (ftp->resp == 226) { + data_close(ftp); + php_stream_close(tmpstream); + return ecalloc(1, sizeof(char*)); + } + + /* pull data buffer into tmpfile */ + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + size = 0; + lines = 0; + lastch = 0; + while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) { + if (rcvd == (size_t)-1 || rcvd > ((size_t)(-1))-size) { + goto bail; + } + + php_stream_write(tmpstream, data->buf, rcvd); + + size += rcvd; + for (ptr = data->buf; rcvd; rcvd--, ptr++) { + if (*ptr == '\n' && lastch == '\r') { + lines++; + } + lastch = *ptr; + } + } + + data_close(ftp); + + php_stream_rewind(tmpstream); + + ret = safe_emalloc((lines + 1), sizeof(char*), size); + + entry = ret; + text = (char*) (ret + lines + 1); + *entry = text; + lastch = 0; + while ((ch = php_stream_getc(tmpstream)) != EOF) { + if (ch == '\n' && lastch == '\r') { + *(text - 1) = 0; + *++entry = text; + } else { + *text++ = ch; + } + lastch = ch; + } + *entry = NULL; + + php_stream_close(tmpstream); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { + efree(ret); + return NULL; + } + + return ret; +bail: + data_close(ftp); + php_stream_close(tmpstream); + if (ret) + efree(ret); + return NULL; +} +/* }}} */ + +/* {{{ ftp_nb_get */ +int +ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos) +{ + databuf_t *data = NULL; + char arg[MAX_LENGTH_OF_LONG]; + + if (ftp == NULL) { + return PHP_FTP_FAILED; + } + + if (ftp->data != NULL) { + /* If there is a transfer in action, abort it. + * If we don't, we get an invalid state and memory leaks when the new connection gets opened. */ + data_close(ftp); + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { + goto bail; + } + } + + if (!ftp_type(ftp, type)) { + goto bail; + } + + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + + if (resumepos>0) { + int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos); + + if (arg_len < 0) { + goto bail; + } + if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 350)) { + goto bail; + } + } + + if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) { + goto bail; + } + + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + + ftp->data = data; + ftp->stream = outstream; + ftp->lastch = 0; + ftp->nb = 1; + + return (ftp_nb_continue_read(ftp)); + +bail: + data_close(ftp); + return PHP_FTP_FAILED; +} +/* }}} */ + +/* {{{ ftp_nb_continue_read */ +int +ftp_nb_continue_read(ftpbuf_t *ftp) +{ + databuf_t *data = NULL; + char *ptr; + char lastch; + size_t rcvd; + ftptype_t type; + + data = ftp->data; + + /* check if there is already more data */ + if (!data_available(ftp, data->fd)) { + return PHP_FTP_MOREDATA; + } + + type = ftp->type; + + lastch = ftp->lastch; + if ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) { + if (rcvd == (size_t)-1) { + goto bail; + } + + if (type == FTPTYPE_ASCII) { + for (ptr = data->buf; rcvd; rcvd--, ptr++) { + if (lastch == '\r' && *ptr != '\n') { + php_stream_putc(ftp->stream, '\r'); + } + if (*ptr != '\r') { + php_stream_putc(ftp->stream, *ptr); + } + lastch = *ptr; + } + } else if (rcvd != php_stream_write(ftp->stream, data->buf, rcvd)) { + goto bail; + } + + ftp->lastch = lastch; + return PHP_FTP_MOREDATA; + } + + if (type == FTPTYPE_ASCII && lastch == '\r') { + php_stream_putc(ftp->stream, '\r'); + } + + data_close(ftp); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { + goto bail; + } + + ftp->nb = 0; + return PHP_FTP_FINISHED; +bail: + ftp->nb = 0; + data_close(ftp); + return PHP_FTP_FAILED; +} +/* }}} */ + +/* {{{ ftp_nb_put */ +int +ftp_nb_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos) +{ + databuf_t *data = NULL; + char arg[MAX_LENGTH_OF_LONG]; + + if (ftp == NULL) { + return 0; + } + if (!ftp_type(ftp, type)) { + goto bail; + } + if ((data = ftp_getdata(ftp)) == NULL) { + goto bail; + } + if (startpos > 0) { + int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos); + + if (arg_len < 0) { + goto bail; + } + if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 350)) { + goto bail; + } + } + + if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) { + goto bail; + } + if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) { + goto bail; + } + if ((data = data_accept(data, ftp)) == NULL) { + goto bail; + } + ftp->data = data; + ftp->stream = instream; + ftp->lastch = 0; + ftp->nb = 1; + + return (ftp_nb_continue_write(ftp)); + +bail: + data_close(ftp); + return PHP_FTP_FAILED; +} +/* }}} */ + + +/* {{{ ftp_nb_continue_write */ +int +ftp_nb_continue_write(ftpbuf_t *ftp) +{ + /* check if we can write more data */ + if (!data_writeable(ftp, ftp->data->fd)) { + return PHP_FTP_MOREDATA; + } + + if (ftp_send_stream_to_data_socket(ftp, ftp->data, ftp->stream, ftp->type, true) != SUCCESS) { + goto bail; + } + + if (!php_stream_eof(ftp->stream)) { + return PHP_FTP_MOREDATA; + } + + data_close(ftp); + + if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { + goto bail; + } + ftp->nb = 0; + return PHP_FTP_FINISHED; +bail: + data_close(ftp); + ftp->nb = 0; + return PHP_FTP_FAILED; +} +/* }}} */ diff --git a/thirdparty/php84/ftp/ftp.h b/thirdparty/php84/ftp/ftp.h new file mode 100644 index 0000000000..004578e023 --- /dev/null +++ b/thirdparty/php84/ftp/ftp.h @@ -0,0 +1,233 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andrew Skalski | + | Stefan Esser (resume functions) | + +----------------------------------------------------------------------+ + */ + +#ifndef FTP_H +#define FTP_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php_network.h" + +#include +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#define FTP_DEFAULT_TIMEOUT 90 +#define FTP_DEFAULT_AUTOSEEK 1 +#define FTP_DEFAULT_USEPASVADDRESS 1 +#define PHP_FTP_FAILED 0 +#define PHP_FTP_FINISHED 1 +#define PHP_FTP_MOREDATA 2 + +/* XXX this should be configurable at runtime XXX */ +#define FTP_BUFSIZE 4096 + +typedef enum ftptype { + FTPTYPE_ASCII=1, + FTPTYPE_IMAGE +} ftptype_t; + +typedef struct databuf +{ + int listener; /* listener socket */ + php_socket_t fd; /* data connection */ + ftptype_t type; /* transfer type */ + char buf[FTP_BUFSIZE]; /* data buffer */ +#ifdef SW_HAVE_FTP_SSL + SSL *ssl_handle; /* ssl handle */ + int ssl_active; /* flag if ssl is active or not */ +#endif +} databuf_t; + +typedef struct ftpbuf +{ + php_socket_t fd; /* control connection */ + php_sockaddr_storage localaddr; /* local address */ + int resp; /* last response code */ + char inbuf[FTP_BUFSIZE]; /* last response text */ + char *extra; /* extra characters */ + int extralen; /* number of extra chars */ + char outbuf[FTP_BUFSIZE]; /* command output buffer */ + char *pwd; /* cached pwd */ + char *syst; /* cached system type */ + ftptype_t type; /* current transfer type */ + int pasv; /* 0=off; 1=pasv; 2=ready */ + php_sockaddr_storage pasvaddr; /* passive mode address */ + zend_long timeout_sec; /* User configurable timeout (seconds) */ + int autoseek; /* User configurable autoseek flag */ + int usepasvaddress; /* Use the address returned by the pasv command */ + + databuf_t *data; /* Data connection for "nonblocking" transfers */ + php_stream *stream; /* output stream for "nonblocking" transfers */ + bool nb; /* "nonblocking" transfer in progress */ + char lastch; /* last char of previous call */ + bool direction; /* recv = 0 / send = 1 */ + bool closestream;/* close or not close stream */ +#ifdef SW_HAVE_FTP_SSL + bool use_ssl; /* enable(1) or disable(0) ssl */ + bool use_ssl_for_data; /* en/disable ssl for the dataconnection */ + bool old_ssl; /* old mode = forced data encryption */ + bool ssl_active; /* ssl active on control conn */ + SSL *ssl_handle; /* handle for control connection */ + SSL_SESSION *last_ssl_session; /* last negotiated session */ +#endif + +} ftpbuf_t; + + + +/* open a FTP connection, returns ftpbuf (NULL on error) + * port is the ftp port in network byte order, or 0 for the default + */ +ftpbuf_t* ftp_open(const char *host, short port, zend_long timeout_sec); + +/* quits from the ftp session (it still needs to be closed) + * return true on success, false on error + */ +int ftp_quit(ftpbuf_t *ftp); + +/* frees up any cached data held in the ftp buffer */ +void ftp_gc(ftpbuf_t *ftp); + +/* close the FTP connection and return NULL */ +ftpbuf_t* ftp_close(ftpbuf_t *ftp); + +/* logs into the FTP server, returns true on success, false on error */ +int ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const char *pass, const size_t pass_len); + +/* reinitializes the connection, returns true on success, false on error */ +int ftp_reinit(ftpbuf_t *ftp); + +/* returns the remote system type (NULL on error) */ +const char* ftp_syst(ftpbuf_t *ftp); + +/* returns the present working directory (NULL on error) */ +const char* ftp_pwd(ftpbuf_t *ftp); + +/* exec a command [special features], return true on success, false on error */ +int ftp_exec(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len); + +/* send a raw ftp command, return response as a hashtable, NULL on error */ +void ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_value); + +/* changes directories, return true on success, false on error */ +int ftp_chdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len); + +/* changes to parent directory, return true on success, false on error */ +int ftp_cdup(ftpbuf_t *ftp); + +/* creates a directory, return the directory name on success, NULL on error. + * the return value must be freed + */ +zend_string* ftp_mkdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len); + +/* removes a directory, return true on success, false on error */ +int ftp_rmdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len); + +/* Set permissions on a file */ +int ftp_chmod(ftpbuf_t *ftp, const int mode, const char *filename, const int filename_len); + +/* Allocate space on remote server with ALLO command + * Many servers will respond with 202 Allocation not necessary, + * however some servers will not accept STOR or APPE until ALLO is confirmed. + * If response is passed, it is estrdup()ed from ftp->inbuf and must be freed + * or assigned to a zval returned to the user */ +int ftp_alloc(ftpbuf_t *ftp, const zend_long size, zend_string **response); + +/* returns a NULL-terminated array of filenames in the given path + * or NULL on error. the return array must be freed (but don't + * free the array elements) + */ +char** ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len); + +/* returns a NULL-terminated array of lines returned by the ftp + * LIST command for the given path or NULL on error. the return + * array must be freed (but don't + * free the array elements) + */ +char** ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive); + +/* populates a hashtable with the facts contained in one line of + * an MLSD response. + */ +int ftp_mlsd_parse_line(HashTable *ht, const char *input); + +/* returns a NULL-terminated array of lines returned by the ftp + * MLSD command for the given path or NULL on error. the return + * array must be freed (but don't + * free the array elements) + */ +char** ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len); + +/* switches passive mode on or off + * returns true on success, false on error + */ +int ftp_pasv(ftpbuf_t *ftp, int pasv); + +/* retrieves a file and saves its contents to outfp + * returns true on success, false on error + */ +int ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos); + +/* stores the data from a file, socket, or process as a file on the remote server + * returns true on success, false on error + */ +int ftp_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos); + +/* append the data from a file, socket, or process as a file on the remote server + * returns true on success, false on error + */ +int ftp_append(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type); + +/* returns the size of the given file, or -1 on error */ +zend_long ftp_size(ftpbuf_t *ftp, const char *path, const size_t path_len); + +/* returns the last modified time of the given file, or -1 on error */ +time_t ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len); + +/* renames a file on the server */ +int ftp_rename(ftpbuf_t *ftp, const char *src, const size_t src_len, const char *dest, const size_t dest_len); + +/* deletes the file from the server */ +int ftp_delete(ftpbuf_t *ftp, const char *path, const size_t path_len); + +/* sends a SITE command to the server */ +int ftp_site(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len); + +/* retrieves part of a file and saves its contents to outfp + * returns true on success, false on error + */ +int ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos); + +/* stores the data from a file, socket, or process as a file on the remote server + * returns true on success, false on error + */ +int ftp_nb_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos); + +/* continues a previous nb_(f)get command + */ +int ftp_nb_continue_read(ftpbuf_t *ftp); + +/* continues a previous nb_(f)put command + */ +int ftp_nb_continue_write(ftpbuf_t *ftp); + + +#endif diff --git a/thirdparty/php84/ftp/php_ftp.c b/thirdparty/php84/ftp/php_ftp.c new file mode 100644 index 0000000000..a7b2502571 --- /dev/null +++ b/thirdparty/php84/ftp/php_ftp.c @@ -0,0 +1,1320 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andrew Skalski | + | Stefan Esser (resume functions) | + +----------------------------------------------------------------------+ + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "php.h" +#include "php_swoole_api.h" + +#ifdef SW_HAVE_FTP_SSL +# include +#endif + +#ifdef SW_HAVE_FTP + +#include "ext/standard/info.h" +#include "ext/standard/file.h" +#include "Zend/zend_attributes.h" +#include "Zend/zend_exceptions.h" + +#include "php_ftp.h" +#include "ftp.h" + +static zend_class_entry *php_ftp_ce = NULL; +static zend_object_handlers ftp_object_handlers; + +static void register_ftp_symbols(int module_number) +{ + REGISTER_LONG_CONSTANT("FTP_ASCII", FTPTYPE_ASCII, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_TEXT", FTPTYPE_ASCII, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_BINARY", FTPTYPE_IMAGE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_IMAGE", FTPTYPE_IMAGE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_AUTORESUME", PHP_FTP_AUTORESUME, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_TIMEOUT_SEC", PHP_FTP_OPT_TIMEOUT_SEC, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_AUTOSEEK", PHP_FTP_OPT_AUTOSEEK, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_USEPASVADDRESS", PHP_FTP_OPT_USEPASVADDRESS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_FAILED", PHP_FTP_FAILED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_FINISHED", PHP_FTP_FINISHED, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("FTP_MOREDATA", PHP_FTP_MOREDATA, CONST_PERSISTENT); + +#if PHP_VERSION_ID >= 80300 + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "ftp_login", sizeof("ftp_login") - 1), 2, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); +#endif +} + +static zend_class_entry *register_class_FTP_Connection(void) +{ + zend_class_entry ce, *class_entry; + +#if PHP_VERSION_ID >= 80400 + INIT_NS_CLASS_ENTRY(ce, "FTP", "Connection", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); +#else + static const zend_function_entry class_FTP_Connection_methods[] = { + ZEND_FE_END + }; + INIT_NS_CLASS_ENTRY(ce, "FTP", "Connection", class_FTP_Connection_methods); + class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; +#endif + + return class_entry; +} + +typedef struct _php_ftp_object { + ftpbuf_t *ftp; + zend_object std; +} php_ftp_object; + +static inline zend_object *ftp_object_to_zend_object(php_ftp_object *obj) { + return ((zend_object*)(obj + 1)) - 1; +} + +static inline php_ftp_object *ftp_object_from_zend_object(zend_object *zobj) { + return ((php_ftp_object*)(zobj + 1)) - 1; +} + +static zend_object* ftp_object_create(zend_class_entry* ce) { + php_ftp_object *obj = zend_object_alloc(sizeof(php_ftp_object), ce); + zend_object *zobj = ftp_object_to_zend_object(obj); + obj->ftp = NULL; + zend_object_std_init(zobj, ce); + object_properties_init(zobj, ce); + zobj->handlers = &ftp_object_handlers; + + return zobj; +} + +static zend_function *ftp_object_get_constructor(zend_object *zobj) { + zend_throw_error(NULL, "Cannot directly construct FTP\\Connection, use ftp_connect() or ftp_ssl_connect() instead"); + return NULL; +} + +static void ftp_object_destroy(zend_object *zobj) { + php_ftp_object *obj = ftp_object_from_zend_object(zobj); + + if (obj->ftp) { + ftp_close(obj->ftp); + } + + zend_object_std_dtor(zobj); +} + +PHP_MINIT_FUNCTION(ftp) +{ + php_ftp_ce = register_class_FTP_Connection(); + php_ftp_ce->create_object = ftp_object_create; + + memcpy(&ftp_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + ftp_object_handlers.offset = XtOffsetOf(php_ftp_object, std); + ftp_object_handlers.get_constructor = ftp_object_get_constructor; + ftp_object_handlers.free_obj = ftp_object_destroy; + ftp_object_handlers.clone_obj = NULL; + + register_ftp_symbols(module_number); + + return SUCCESS; +} + +PHP_MINFO_FUNCTION(ftp) +{ + php_info_print_table_row(2, "FTP support", "enabled"); +#ifdef SW_HAVE_FTP_SSL + php_info_print_table_row(2, "FTPS support", "enabled"); +#else + php_info_print_table_row(2, "FTPS support", "disabled"); +#endif +} + +#define XTYPE(xtype, mode) { \ + if (mode != FTPTYPE_ASCII && mode != FTPTYPE_IMAGE) { \ + zend_argument_value_error(4, "must be either FTP_ASCII or FTP_BINARY"); \ + RETURN_THROWS(); \ + } \ + xtype = mode; \ +} + + +/* {{{ Opens a FTP stream */ +PHP_FUNCTION(ftp_connect) +{ + ftpbuf_t *ftp; + char *host; + size_t host_len; + zend_long port = 0; + zend_long timeout_sec = FTP_DEFAULT_TIMEOUT; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &host, &host_len, &port, &timeout_sec) == FAILURE) { + RETURN_THROWS(); + } + + if (timeout_sec <= 0) { + zend_argument_value_error(3, "must be greater than 0"); + RETURN_THROWS(); + } + + /* connect */ + if (!(ftp = ftp_open(host, (short)port, timeout_sec))) { + RETURN_FALSE; + } + + /* autoseek for resuming */ + ftp->autoseek = FTP_DEFAULT_AUTOSEEK; + ftp->usepasvaddress = FTP_DEFAULT_USEPASVADDRESS; +#ifdef SW_HAVE_FTP_SSL + /* disable ssl */ + ftp->use_ssl = 0; +#endif + + object_init_ex(return_value, php_ftp_ce); + ftp_object_from_zend_object(Z_OBJ_P(return_value))->ftp = ftp; +} +/* }}} */ + +#ifdef SW_HAVE_FTP_SSL +/* {{{ Opens a FTP-SSL stream */ +PHP_FUNCTION(ftp_ssl_connect) +{ + ftpbuf_t *ftp; + char *host; + size_t host_len; + zend_long port = 0; + zend_long timeout_sec = FTP_DEFAULT_TIMEOUT; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|ll", &host, &host_len, &port, &timeout_sec) == FAILURE) { + RETURN_THROWS(); + } + + if (timeout_sec <= 0) { + zend_argument_value_error(3, "must be greater than 0"); + RETURN_THROWS(); + } + + /* connect */ + if (!(ftp = ftp_open(host, (short)port, timeout_sec))) { + RETURN_FALSE; + } + + /* autoseek for resuming */ + ftp->autoseek = FTP_DEFAULT_AUTOSEEK; + ftp->usepasvaddress = FTP_DEFAULT_USEPASVADDRESS; + /* enable ssl */ + ftp->use_ssl = 1; + + object_init_ex(return_value, php_ftp_ce); + ftp_object_from_zend_object(Z_OBJ_P(return_value))->ftp = ftp; +} +/* }}} */ +#endif + +#define GET_FTPBUF(ftpbuf, zftp) \ + ftpbuf = ftp_object_from_zend_object(Z_OBJ_P(zftp))->ftp; \ + if (!ftpbuf) { \ + zend_throw_exception(zend_ce_value_error, "FTP\\Connection is already closed", 0); \ + RETURN_THROWS(); \ + } + +/* {{{ Logs into the FTP server */ +PHP_FUNCTION(ftp_login) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *user, *pass; + size_t user_len, pass_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oss", &z_ftp, php_ftp_ce, &user, &user_len, &pass, &pass_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* log in */ + if (!ftp_login(ftp, user, user_len, pass, pass_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Returns the present working directory */ +PHP_FUNCTION(ftp_pwd) +{ + zval *z_ftp; + ftpbuf_t *ftp; + const char *pwd; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &z_ftp, php_ftp_ce) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (!(pwd = ftp_pwd(ftp))) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_STRING((char*) pwd); +} +/* }}} */ + +/* {{{ Changes to the parent directory */ +PHP_FUNCTION(ftp_cdup) +{ + zval *z_ftp; + ftpbuf_t *ftp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &z_ftp, php_ftp_ce) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (!ftp_cdup(ftp)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Changes directories */ +PHP_FUNCTION(ftp_chdir) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *dir; + size_t dir_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &dir, &dir_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* change directories */ + if (!ftp_chdir(ftp, dir, dir_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Requests execution of a program on the FTP server */ +PHP_FUNCTION(ftp_exec) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *cmd; + size_t cmd_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &cmd, &cmd_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* execute serverside command */ + if (!ftp_exec(ftp, cmd, cmd_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Sends a literal command to the FTP server */ +PHP_FUNCTION(ftp_raw) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *cmd; + size_t cmd_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &cmd, &cmd_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* execute arbitrary ftp command */ + ftp_raw(ftp, cmd, cmd_len, return_value); +} +/* }}} */ + +/* {{{ Creates a directory and returns the absolute path for the new directory or false on error */ +PHP_FUNCTION(ftp_mkdir) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *dir; + zend_string *tmp; + size_t dir_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &dir, &dir_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* create directory */ + if (NULL == (tmp = ftp_mkdir(ftp, dir, dir_len))) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_STR(tmp); +} +/* }}} */ + +/* {{{ Removes a directory */ +PHP_FUNCTION(ftp_rmdir) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *dir; + size_t dir_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &dir, &dir_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* remove directorie */ + if (!ftp_rmdir(ftp, dir, dir_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Sets permissions on a file */ +PHP_FUNCTION(ftp_chmod) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *filename; + size_t filename_len; + zend_long mode; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Olp", &z_ftp, php_ftp_ce, &mode, &filename, &filename_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (!ftp_chmod(ftp, mode, filename, filename_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_LONG(mode); +} +/* }}} */ + +/* {{{ Attempt to allocate space on the remote FTP server */ +PHP_FUNCTION(ftp_alloc) +{ + zval *z_ftp, *zresponse = NULL; + ftpbuf_t *ftp; + zend_long size, ret; + zend_string *response = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ol|z", &z_ftp, php_ftp_ce, &size, &zresponse) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + ret = ftp_alloc(ftp, size, zresponse ? &response : NULL); + + if (response) { + ZEND_TRY_ASSIGN_REF_STR(zresponse, response); + } + + if (!ret) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Returns an array of filenames in the given directory */ +PHP_FUNCTION(ftp_nlist) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char **nlist, **ptr, *dir; + size_t dir_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Op", &z_ftp, php_ftp_ce, &dir, &dir_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* get list of files */ + if (NULL == (nlist = ftp_nlist(ftp, dir, dir_len))) { + RETURN_FALSE; + } + + array_init(return_value); + for (ptr = nlist; *ptr; ptr++) { + add_next_index_string(return_value, *ptr); + } + efree(nlist); +} +/* }}} */ + +/* {{{ Returns a detailed listing of a directory as an array of output lines */ +PHP_FUNCTION(ftp_rawlist) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char **llist, **ptr, *dir; + size_t dir_len; + bool recursive = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os|b", &z_ftp, php_ftp_ce, &dir, &dir_len, &recursive) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* get raw directory listing */ + if (NULL == (llist = ftp_list(ftp, dir, dir_len, recursive))) { + RETURN_FALSE; + } + + array_init(return_value); + for (ptr = llist; *ptr; ptr++) { + add_next_index_string(return_value, *ptr); + } + efree(llist); +} +/* }}} */ + +/* {{{ Returns a detailed listing of a directory as an array of parsed output lines */ +PHP_FUNCTION(ftp_mlsd) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char **llist, **ptr, *dir; + size_t dir_len; + zval entry; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &dir, &dir_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* get raw directory listing */ + if (NULL == (llist = ftp_mlsd(ftp, dir, dir_len))) { + RETURN_FALSE; + } + + array_init(return_value); + for (ptr = llist; *ptr; ptr++) { + array_init(&entry); + if (ftp_mlsd_parse_line(Z_ARRVAL_P(&entry), *ptr) == SUCCESS) { + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &entry); + } else { + zval_ptr_dtor(&entry); + } + } + + efree(llist); +} +/* }}} */ + +/* {{{ Returns the system type identifier */ +PHP_FUNCTION(ftp_systype) +{ + zval *z_ftp; + ftpbuf_t *ftp; + const char *syst; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &z_ftp, php_ftp_ce) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (NULL == (syst = ftp_syst(ftp))) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_STRING((char*) syst); +} +/* }}} */ + +/* {{{ Retrieves a file from the FTP server and writes it to an open file */ +PHP_FUNCTION(ftp_fget) +{ + zval *z_ftp, *z_file; + ftpbuf_t *ftp; + ftptype_t xtype; + php_stream *stream; + char *file; + size_t file_len; + zend_long mode=FTPTYPE_IMAGE, resumepos=0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ors|ll", &z_ftp, php_ftp_ce, &z_file, &file, &file_len, &mode, &resumepos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + php_stream_from_res(stream, Z_RES_P(z_file)); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && resumepos == PHP_FTP_AUTORESUME) { + resumepos = 0; + } + + if (ftp->autoseek && resumepos) { + /* if autoresume is wanted seek to end */ + if (resumepos == PHP_FTP_AUTORESUME) { + php_stream_seek(stream, 0, SEEK_END); + resumepos = php_stream_tell(stream); + } else { + php_stream_seek(stream, resumepos, SEEK_SET); + } + } + + if (!ftp_get(ftp, stream, file, file_len, xtype, resumepos)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Retrieves a file from the FTP server asynchronly and writes it to an open file */ +PHP_FUNCTION(ftp_nb_fget) +{ + zval *z_ftp, *z_file; + ftpbuf_t *ftp; + ftptype_t xtype; + php_stream *stream; + char *file; + size_t file_len; + zend_long mode=FTPTYPE_IMAGE, resumepos=0, ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ors|ll", &z_ftp, php_ftp_ce, &z_file, &file, &file_len, &mode, &resumepos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + php_stream_from_res(stream, Z_RES_P(z_file)); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && resumepos == PHP_FTP_AUTORESUME) { + resumepos = 0; + } + + if (ftp->autoseek && resumepos) { + /* if autoresume is wanted seek to end */ + if (resumepos == PHP_FTP_AUTORESUME) { + php_stream_seek(stream, 0, SEEK_END); + resumepos = php_stream_tell(stream); + } else { + php_stream_seek(stream, resumepos, SEEK_SET); + } + } + + /* configuration */ + ftp->direction = 0; /* recv */ + ftp->closestream = 0; /* do not close */ + + if ((ret = ftp_nb_get(ftp, stream, file, file_len, xtype, resumepos)) == PHP_FTP_FAILED) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_LONG(ret); + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ Turns passive mode on or off */ +PHP_FUNCTION(ftp_pasv) +{ + zval *z_ftp; + ftpbuf_t *ftp; + bool pasv; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ob", &z_ftp, php_ftp_ce, &pasv) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (!ftp_pasv(ftp, pasv ? 1 : 0)) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Retrieves a file from the FTP server and writes it to a local file */ +PHP_FUNCTION(ftp_get) +{ + zval *z_ftp; + ftpbuf_t *ftp; + ftptype_t xtype; + php_stream *outstream; + char *local, *remote; + size_t local_len, remote_len; + zend_long mode=FTPTYPE_IMAGE, resumepos=0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Opp|ll", &z_ftp, php_ftp_ce, &local, &local_len, &remote, &remote_len, &mode, &resumepos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && resumepos == PHP_FTP_AUTORESUME) { + resumepos = 0; + } + +#ifdef PHP_WIN32 + mode = FTPTYPE_IMAGE; +#endif + + if (ftp->autoseek && resumepos) { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "rt+" : "rb+", REPORT_ERRORS, NULL); + if (outstream == NULL) { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "wt" : "wb", REPORT_ERRORS, NULL); + } + if (outstream != NULL) { + /* if autoresume is wanted seek to end */ + if (resumepos == PHP_FTP_AUTORESUME) { + php_stream_seek(outstream, 0, SEEK_END); + resumepos = php_stream_tell(outstream); + } else { + php_stream_seek(outstream, resumepos, SEEK_SET); + } + } + } else { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "wt" : "wb", REPORT_ERRORS, NULL); + } + + if (outstream == NULL) { + php_error_docref(NULL, E_WARNING, "Error opening %s", local); + RETURN_FALSE; + } + + if (!ftp_get(ftp, outstream, remote, remote_len, xtype, resumepos)) { + php_stream_close(outstream); + VCWD_UNLINK(local); + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + php_stream_close(outstream); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Retrieves a file from the FTP server nbhronly and writes it to a local file */ +PHP_FUNCTION(ftp_nb_get) +{ + zval *z_ftp; + ftpbuf_t *ftp; + ftptype_t xtype; + php_stream *outstream; + char *local, *remote; + size_t local_len, remote_len; + int ret; + zend_long mode=FTPTYPE_IMAGE, resumepos=0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oss|ll", &z_ftp, php_ftp_ce, &local, &local_len, &remote, &remote_len, &mode, &resumepos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && resumepos == PHP_FTP_AUTORESUME) { + resumepos = 0; + } +#ifdef PHP_WIN32 + mode = FTPTYPE_IMAGE; +#endif + if (ftp->autoseek && resumepos) { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "rt+" : "rb+", REPORT_ERRORS, NULL); + if (outstream == NULL) { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "wt" : "wb", REPORT_ERRORS, NULL); + } + if (outstream != NULL) { + /* if autoresume is wanted seek to end */ + if (resumepos == PHP_FTP_AUTORESUME) { + php_stream_seek(outstream, 0, SEEK_END); + resumepos = php_stream_tell(outstream); + } else { + php_stream_seek(outstream, resumepos, SEEK_SET); + } + } + } else { + outstream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "wt" : "wb", REPORT_ERRORS, NULL); + } + + if (outstream == NULL) { + php_error_docref(NULL, E_WARNING, "Error opening %s", local); + RETURN_FALSE; + } + + /* configuration */ + ftp->direction = 0; /* recv */ + ftp->closestream = 1; /* do close */ + + if ((ret = ftp_nb_get(ftp, outstream, remote, remote_len, xtype, resumepos)) == PHP_FTP_FAILED) { + php_stream_close(outstream); + ftp->stream = NULL; + VCWD_UNLINK(local); + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_LONG(PHP_FTP_FAILED); + } + + if (ret == PHP_FTP_FINISHED){ + php_stream_close(outstream); + ftp->stream = NULL; + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ Continues retrieving/sending a file nbronously */ +PHP_FUNCTION(ftp_nb_continue) +{ + zval *z_ftp; + ftpbuf_t *ftp; + zend_long ret; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &z_ftp, php_ftp_ce) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + if (!ftp->nb) { + php_error_docref(NULL, E_WARNING, "No nbronous transfer to continue"); + RETURN_LONG(PHP_FTP_FAILED); + } + + if (ftp->direction) { + ret=ftp_nb_continue_write(ftp); + } else { + ret=ftp_nb_continue_read(ftp); + } + + if (ret != PHP_FTP_MOREDATA && ftp->closestream) { + php_stream_close(ftp->stream); + ftp->stream = NULL; + } + + if (ret == PHP_FTP_FAILED) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ Stores a file from an open file to the FTP server */ +PHP_FUNCTION(ftp_fput) +{ + zval *z_ftp, *z_file; + ftpbuf_t *ftp; + ftptype_t xtype; + size_t remote_len; + zend_long mode=FTPTYPE_IMAGE, startpos=0; + php_stream *stream; + char *remote; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Osr|ll", &z_ftp, php_ftp_ce, &remote, &remote_len, &z_file, &mode, &startpos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + php_stream_from_zval(stream, z_file); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && startpos == PHP_FTP_AUTORESUME) { + startpos = 0; + } + + if (ftp->autoseek && startpos) { + /* if autoresume is wanted ask for remote size */ + if (startpos == PHP_FTP_AUTORESUME) { + startpos = ftp_size(ftp, remote, remote_len); + if (startpos < 0) { + startpos = 0; + } + } + if (startpos) { + php_stream_seek(stream, startpos, SEEK_SET); + } + } + + if (!ftp_put(ftp, remote, remote_len, stream, xtype, startpos)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Stores a file from an open file to the FTP server nbronly */ +PHP_FUNCTION(ftp_nb_fput) +{ + zval *z_ftp, *z_file; + ftpbuf_t *ftp; + ftptype_t xtype; + size_t remote_len; + int ret; + zend_long mode=FTPTYPE_IMAGE, startpos=0; + php_stream *stream; + char *remote; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Osr|ll", &z_ftp, php_ftp_ce, &remote, &remote_len, &z_file, &mode, &startpos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + php_stream_from_res(stream, Z_RES_P(z_file)); + XTYPE(xtype, mode); + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && startpos == PHP_FTP_AUTORESUME) { + startpos = 0; + } + + if (ftp->autoseek && startpos) { + /* if autoresume is wanted ask for remote size */ + if (startpos == PHP_FTP_AUTORESUME) { + startpos = ftp_size(ftp, remote, remote_len); + if (startpos < 0) { + startpos = 0; + } + } + if (startpos) { + php_stream_seek(stream, startpos, SEEK_SET); + } + } + + /* configuration */ + ftp->direction = 1; /* send */ + ftp->closestream = 0; /* do not close */ + + if (((ret = ftp_nb_put(ftp, remote, remote_len, stream, xtype, startpos)) == PHP_FTP_FAILED)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_LONG(ret); + } + + RETURN_LONG(ret); +} +/* }}} */ + + +/* {{{ Stores a file on the FTP server */ +PHP_FUNCTION(ftp_put) +{ + zval *z_ftp; + ftpbuf_t *ftp; + ftptype_t xtype; + char *remote, *local; + size_t remote_len, local_len; + zend_long mode=FTPTYPE_IMAGE, startpos=0; + php_stream *instream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Opp|ll", &z_ftp, php_ftp_ce, &remote, &remote_len, &local, &local_len, &mode, &startpos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + XTYPE(xtype, mode); + + if (!(instream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "rt" : "rb", REPORT_ERRORS, NULL))) { + RETURN_FALSE; + } + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && startpos == PHP_FTP_AUTORESUME) { + startpos = 0; + } + + if (ftp->autoseek && startpos) { + /* if autoresume is wanted ask for remote size */ + if (startpos == PHP_FTP_AUTORESUME) { + startpos = ftp_size(ftp, remote, remote_len); + if (startpos < 0) { + startpos = 0; + } + } + if (startpos) { + php_stream_seek(instream, startpos, SEEK_SET); + } + } + + if (!ftp_put(ftp, remote, remote_len, instream, xtype, startpos)) { + php_stream_close(instream); + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + php_stream_close(instream); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Append content of a file a another file on the FTP server */ +PHP_FUNCTION(ftp_append) +{ + zval *z_ftp; + ftpbuf_t *ftp; + ftptype_t xtype; + char *remote, *local; + size_t remote_len, local_len; + zend_long mode=FTPTYPE_IMAGE; + php_stream *instream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Opp|l", &z_ftp, php_ftp_ce, &remote, &remote_len, &local, &local_len, &mode) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + XTYPE(xtype, mode); + + if (!(instream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "rt" : "rb", REPORT_ERRORS, NULL))) { + RETURN_FALSE; + } + + if (!ftp_append(ftp, remote, remote_len, instream, xtype)) { + php_stream_close(instream); + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + php_stream_close(instream); + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Stores a file on the FTP server */ +PHP_FUNCTION(ftp_nb_put) +{ + zval *z_ftp; + ftpbuf_t *ftp; + ftptype_t xtype; + char *remote, *local; + size_t remote_len, local_len; + zend_long mode=FTPTYPE_IMAGE, startpos=0, ret; + php_stream *instream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Opp|ll", &z_ftp, php_ftp_ce, &remote, &remote_len, &local, &local_len, &mode, &startpos) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + XTYPE(xtype, mode); + + if (!(instream = php_stream_open_wrapper(local, mode == FTPTYPE_ASCII ? "rt" : "rb", REPORT_ERRORS, NULL))) { + RETURN_FALSE; + } + + /* ignore autoresume if autoseek is switched off */ + if (!ftp->autoseek && startpos == PHP_FTP_AUTORESUME) { + startpos = 0; + } + + if (ftp->autoseek && startpos) { + /* if autoresume is wanted ask for remote size */ + if (startpos == PHP_FTP_AUTORESUME) { + startpos = ftp_size(ftp, remote, remote_len); + if (startpos < 0) { + startpos = 0; + } + } + if (startpos) { + php_stream_seek(instream, startpos, SEEK_SET); + } + } + + /* configuration */ + ftp->direction = 1; /* send */ + ftp->closestream = 1; /* do close */ + + ret = ftp_nb_put(ftp, remote, remote_len, instream, xtype, startpos); + + if (ret != PHP_FTP_MOREDATA) { + php_stream_close(instream); + ftp->stream = NULL; + } + + if (ret == PHP_FTP_FAILED) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + + RETURN_LONG(ret); +} +/* }}} */ + +/* {{{ Returns the size of the file, or -1 on error */ +PHP_FUNCTION(ftp_size) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *file; + size_t file_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Op", &z_ftp, php_ftp_ce, &file, &file_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* get file size */ + RETURN_LONG(ftp_size(ftp, file, file_len)); +} +/* }}} */ + +/* {{{ Returns the last modification time of the file, or -1 on error */ +PHP_FUNCTION(ftp_mdtm) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *file; + size_t file_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Op", &z_ftp, php_ftp_ce, &file, &file_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* get file mod time */ + RETURN_LONG(ftp_mdtm(ftp, file, file_len)); +} +/* }}} */ + +/* {{{ Renames the given file to a new path */ +PHP_FUNCTION(ftp_rename) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *src, *dest; + size_t src_len, dest_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Oss", &z_ftp, php_ftp_ce, &src, &src_len, &dest, &dest_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* rename the file */ + if (!ftp_rename(ftp, src, src_len, dest, dest_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Deletes a file */ +PHP_FUNCTION(ftp_delete) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *file; + size_t file_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &file, &file_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* delete the file */ + if (!ftp_delete(ftp, file, file_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Sends a SITE command to the server */ +PHP_FUNCTION(ftp_site) +{ + zval *z_ftp; + ftpbuf_t *ftp; + char *cmd; + size_t cmd_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Os", &z_ftp, php_ftp_ce, &cmd, &cmd_len) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + /* send the site command */ + if (!ftp_site(ftp, cmd, cmd_len)) { + if (*ftp->inbuf) { + php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Closes the FTP stream */ +PHP_FUNCTION(ftp_close) +{ + zval *z_ftp; + php_ftp_object *obj; + bool success = true; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &z_ftp, php_ftp_ce) == FAILURE) { + RETURN_THROWS(); + } + + obj = ftp_object_from_zend_object(Z_OBJ_P(z_ftp)); + if (obj->ftp) { + success = ftp_quit(obj->ftp); + ftp_close(obj->ftp); + obj->ftp = NULL; + } + + RETURN_BOOL(success); +} +/* }}} */ + +/* {{{ Sets an FTP option */ +PHP_FUNCTION(ftp_set_option) +{ + zval *z_ftp, *z_value; + zend_long option; + ftpbuf_t *ftp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Olz", &z_ftp, php_ftp_ce, &option, &z_value) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + switch (option) { + case PHP_FTP_OPT_TIMEOUT_SEC: + if (Z_TYPE_P(z_value) != IS_LONG) { + zend_argument_type_error(3, "must be of type int for the FTP_TIMEOUT_SEC option, %s given", zend_zval_value_name(z_value)); + RETURN_THROWS(); + } + if (Z_LVAL_P(z_value) <= 0) { + zend_argument_value_error(3, "must be greater than 0 for the FTP_TIMEOUT_SEC option"); + RETURN_THROWS(); + } + ftp->timeout_sec = Z_LVAL_P(z_value); + RETURN_TRUE; + break; + case PHP_FTP_OPT_AUTOSEEK: + if (Z_TYPE_P(z_value) != IS_TRUE && Z_TYPE_P(z_value) != IS_FALSE) { + zend_argument_type_error(3, "must be of type bool for the FTP_AUTOSEEK option, %s given", zend_zval_value_name(z_value)); + RETURN_THROWS(); + } + ftp->autoseek = Z_TYPE_P(z_value) == IS_TRUE ? 1 : 0; + RETURN_TRUE; + break; + case PHP_FTP_OPT_USEPASVADDRESS: + if (Z_TYPE_P(z_value) != IS_TRUE && Z_TYPE_P(z_value) != IS_FALSE) { + zend_argument_type_error(3, "must be of type bool for the FTP_USEPASVADDRESS option, %s given", zend_zval_value_name(z_value)); + RETURN_THROWS(); + } + ftp->usepasvaddress = Z_TYPE_P(z_value) == IS_TRUE ? 1 : 0; + RETURN_TRUE; + break; + default: + zend_argument_value_error(2, "must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS"); + RETURN_THROWS(); + break; + } +} +/* }}} */ + +/* {{{ Gets an FTP option */ +PHP_FUNCTION(ftp_get_option) +{ + zval *z_ftp; + zend_long option; + ftpbuf_t *ftp; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Ol", &z_ftp, php_ftp_ce, &option) == FAILURE) { + RETURN_THROWS(); + } + GET_FTPBUF(ftp, z_ftp); + + switch (option) { + case PHP_FTP_OPT_TIMEOUT_SEC: + RETURN_LONG(ftp->timeout_sec); + break; + case PHP_FTP_OPT_AUTOSEEK: + RETURN_BOOL(ftp->autoseek); + break; + case PHP_FTP_OPT_USEPASVADDRESS: + RETURN_BOOL(ftp->usepasvaddress); + break; + default: + zend_argument_value_error(2, "must be one of FTP_TIMEOUT_SEC, FTP_AUTOSEEK, or FTP_USEPASVADDRESS"); + RETURN_THROWS(); + } +} +/* }}} */ + +#endif /* SW_HAVE_FTP */ diff --git a/thirdparty/php84/ftp/php_ftp.h b/thirdparty/php84/ftp/php_ftp.h new file mode 100644 index 0000000000..4ab34b511d --- /dev/null +++ b/thirdparty/php84/ftp/php_ftp.h @@ -0,0 +1,30 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andrew Skalski | + | Stefan Esser (resume functions) | + +----------------------------------------------------------------------+ + */ + +#ifndef _INCLUDED_FTP_H +#define _INCLUDED_FTP_H + +#include "php_version.h" +#define PHP_FTP_VERSION PHP_VERSION + +#define PHP_FTP_OPT_TIMEOUT_SEC 0 +#define PHP_FTP_OPT_AUTOSEEK 1 +#define PHP_FTP_OPT_USEPASVADDRESS 2 +#define PHP_FTP_AUTORESUME -1 + + +#endif diff --git a/thirdparty/php84/pdo_firebird/CREDITS b/thirdparty/php84/pdo_firebird/CREDITS new file mode 100644 index 0000000000..60b917415d --- /dev/null +++ b/thirdparty/php84/pdo_firebird/CREDITS @@ -0,0 +1,2 @@ +Firebird driver for PDO +Ard Biesheuvel diff --git a/thirdparty/php84/pdo_firebird/firebird_driver.c b/thirdparty/php84/pdo_firebird/firebird_driver.c new file mode 100644 index 0000000000..0d6377189a --- /dev/null +++ b/thirdparty/php84/pdo_firebird/firebird_driver.c @@ -0,0 +1,1442 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID < 80500 + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include "php.h" +#include "zend_exceptions.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*, + HashTable*); +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh); + +const char CHR_LETTER = 1; +const char CHR_DIGIT = 2; +const char CHR_IDENT = 4; +const char CHR_QUOTE = 8; +const char CHR_WHITE = 16; +const char CHR_HEX = 32; +const char CHR_INTRODUCER = 64; + +static const char classes_array[] = { + /* 000 */ 0, + /* 001 */ 0, + /* 002 */ 0, + /* 003 */ 0, + /* 004 */ 0, + /* 005 */ 0, + /* 006 */ 0, + /* 007 */ 0, + /* 008 */ 0, + /* 009 */ 16, /* CHR_WHITE */ + /* 010 */ 16, /* CHR_WHITE */ + /* 011 */ 0, + /* 012 */ 0, + /* 013 */ 16, /* CHR_WHITE */ + /* 014 */ 0, + /* 015 */ 0, + /* 016 */ 0, + /* 017 */ 0, + /* 018 */ 0, + /* 019 */ 0, + /* 020 */ 0, + /* 021 */ 0, + /* 022 */ 0, + /* 023 */ 0, + /* 024 */ 0, + /* 025 */ 0, + /* 026 */ 0, + /* 027 */ 0, + /* 028 */ 0, + /* 029 */ 0, + /* 030 */ 0, + /* 031 */ 0, + /* 032 */ 16, /* CHR_WHITE */ + /* 033 ! */ 0, + /* 034 " */ 8, /* CHR_QUOTE */ + /* 035 # */ 0, + /* 036 $ */ 4, /* CHR_IDENT */ + /* 037 % */ 0, + /* 038 & */ 0, + /* 039 ' */ 8, /* CHR_QUOTE */ + /* 040 ( */ 0, + /* 041 ) */ 0, + /* 042 * */ 0, + /* 043 + */ 0, + /* 044 , */ 0, + /* 045 - */ 0, + /* 046 . */ 0, + /* 047 / */ 0, + /* 048 0 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 049 1 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 050 2 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 051 3 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 052 4 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 053 5 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 054 6 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 055 7 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 056 8 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 057 9 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 058 : */ 0, + /* 059 ; */ 0, + /* 060 < */ 0, + /* 061 = */ 0, + /* 062 > */ 0, + /* 063 ? */ 0, + /* 064 @ */ 0, + /* 065 A */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 066 B */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 067 C */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 068 D */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 069 E */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 070 F */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 071 G */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 072 H */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 073 I */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 074 J */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 075 K */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 076 L */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 077 M */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 078 N */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 079 O */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 080 P */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 081 Q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 082 R */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 083 S */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 084 T */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 085 U */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 086 V */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 087 W */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 088 X */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 089 Y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 090 Z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 091 [ */ 0, + /* 092 \ */ 0, + /* 093 ] */ 0, + /* 094 ^ */ 0, + /* 095 _ */ 68, /* CHR_IDENT | CHR_INTRODUCER */ + /* 096 ` */ 0, + /* 097 a */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 098 b */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 099 c */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 100 d */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 101 e */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 102 f */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 103 g */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 104 h */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 105 i */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 106 j */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 107 k */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 108 l */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 109 m */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 110 n */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 111 o */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 112 p */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 113 q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 114 r */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 115 s */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 116 t */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 117 u */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 118 v */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 119 w */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 120 x */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 121 y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 122 z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 123 { */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 124 | */ 0, + /* 125 } */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 126 ~ */ 0, + /* 127 */ 0 +}; + +static inline char php_firebird_classes(char idx) +{ + unsigned char uidx = (unsigned char) idx; + if (uidx > 127) return 0; + return classes_array[uidx]; +} + +typedef enum { + ttNone, + ttWhite, + ttComment, + ttBrokenComment, + ttString, + ttParamMark, + ttIdent, + ttOther +} FbTokenType; + +static FbTokenType php_firebird_get_token(const char** begin, const char* end) +{ + FbTokenType ret = ttNone; + const char* p = *begin; + + char c = *p++; + switch (c) + { + case ':': + case '?': + ret = ttParamMark; + break; + + case '\'': + case '"': + while (p < end) + { + if (*p++ == c) + { + ret = ttString; + break; + } + } + break; + + case '/': + if (p < end && *p == '*') + { + ret = ttBrokenComment; + p++; + while (p < end) + { + if (*p++ == '*' && p < end && *p == '/') + { + p++; + ret = ttComment; + break; + } + } + } + else { + ret = ttOther; + } + break; + + case '-': + if (p < end && *p == '-') + { + while (++p < end) + { + if (*p == '\r') + { + p++; + if (p < end && *p == '\n') + p++; + break; + } + else if (*p == '\n') + break; + } + + ret = ttComment; + } + else + ret = ttOther; + break; + + default: + if (php_firebird_classes(c) & CHR_DIGIT) + { + while (p < end && (php_firebird_classes(*p) & CHR_DIGIT)) + p++; + ret = ttOther; + } + else if (php_firebird_classes(c) & CHR_IDENT) + { + while (p < end && (php_firebird_classes(*p) & CHR_IDENT)) + p++; + ret = ttIdent; + } + else if (php_firebird_classes(c) & CHR_WHITE) + { + while (p < end && (php_firebird_classes(*p) & CHR_WHITE)) + p++; + ret = ttWhite; + } + else + { + while (p < end && !(php_firebird_classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) && + (*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') && + (*p != '\'') && (*p != '"')) + { + p++; + } + ret = ttOther; + } + } + + *begin = p; + return ret; +} + +static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTable* named_params) +{ + bool passAsIs = 1, execBlock = 0; + zend_long pindex = -1; + char pname[254], ident[253], ident2[253]; + unsigned int l; + const char* p = ZSTR_VAL(sql), * end = ZSTR_VAL(sql) + ZSTR_LEN(sql); + const char* start = p; + FbTokenType tok = php_firebird_get_token(&p, end); + + const char* i = start; + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i = p; + tok = php_firebird_get_token(&p, end); + } + + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + /* skip leading comments ?? */ + start = i; + l = p - i; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, i, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "EXECUTE")) + { + /* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */ + /* However, in EXECUTE BLOCK this is done in a special way. */ + const char* i2 = p; + tok = php_firebird_get_token(&p, end); + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i2 = p; + tok = php_firebird_get_token(&p, end); + } + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + l = p - i2; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident2, i2, l); + ident2[l] = '\0'; + execBlock = !strcasecmp(ident2, "BLOCK"); + passAsIs = 0; + } + else + { + /* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */ + /* If CTEs are present in the query, they begin with the WITH keyword. */ + passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") && + strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") && + strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH"); + } + + if (passAsIs) + { + strcpy(sql_out, ZSTR_VAL(sql)); + return 1; + } + + strncat(sql_out, start, p - start); + + while (p < end) + { + start = p; + tok = php_firebird_get_token(&p, end); + switch (tok) + { + case ttParamMark: + tok = php_firebird_get_token(&p, end); + if (tok == ttIdent /*|| tok == ttString*/) + { + ++pindex; + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + /* + symbol ":" */ + if (l > 253) { + return 0; + } + strncpy(pname, start, l); + pname[l] = '\0'; + + if (named_params) { + zval tmp; + ZVAL_LONG(&tmp, pindex); + zend_hash_str_update(named_params, pname, l, &tmp); + } + + strcat(sql_out, "?"); + } + else + { + if (strncmp(start, "?", 1)) { + /* Execute statement preprocess SQL error */ + /* Parameter name expected */ + return 0; + } + ++pindex; + strncat(sql_out, start, p - start); + } + break; + + case ttIdent: + if (execBlock) + { + /* In the EXECUTE BLOCK statement, processing must be */ + /* carried out up to the keyword AS. */ + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, start, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "AS")) + { + strncat(sql_out, start, end - start); + return 1; + } + } + /* TODO Check this is correct? */ + ZEND_FALLTHROUGH; + + case ttWhite: + case ttComment: + case ttString: + case ttOther: + strncat(sql_out, start, p - start); + break; + + case ttBrokenComment: + { + /* Execute statement preprocess SQL error */ + /* Unclosed comment found near ''@1'' */ + return 0; + } + break; + + + case ttNone: + /* Execute statement preprocess SQL error */ + return 0; + break; + } + } + return 1; +} + +#if FB_API_VER >= 40 +/* set coercing a data type */ +static void set_coercing_output_data_types(XSQLDA* sqlda) +{ + /* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */ + /* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)). */ + /* In any case, at this data types can only be mapped to strings. */ + /* This function allows you to ensure minimal performance of queries if they contain columns of the above types. */ + unsigned int i; + short dtype; + short nullable; + XSQLVAR* var; + unsigned fb_client_version = fb_get_client_version(); + unsigned fb_client_major_version = (fb_client_version >> 8) & 0xFF; + for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) { + dtype = (var->sqltype & ~1); /* drop flag bit */ + nullable = (var->sqltype & 1); + switch(dtype) { + case SQL_INT128: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + var->sqlscale = 0; + break; + + case SQL_DEC16: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 24; + break; + + case SQL_DEC34: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 43; + break; + + case SQL_TIMESTAMP_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 58; + } + break; + + case SQL_TIME_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + } + break; + + default: + break; + } + } +} +#endif + +/* map driver specific error message to PDO error */ +void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len) /* {{{ */ +{ + pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code; + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_error_info *einfo = &H->einfo; + int sqlcode = -999; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + einfo->errmsg_length = 0; + } + + if (H->isc_status && (H->isc_status[0] == 1 && H->isc_status[1] > 0)) { + char buf[512]; + size_t buf_size = sizeof(buf), read_len = 0; + ssize_t tmp_len; + const ISC_STATUS *s = H->isc_status; + sqlcode = isc_sqlcode(s); + + while ((buf_size > (read_len + 1)) && (tmp_len = fb_interpret(&buf[read_len], (buf_size - read_len - 1), &s)) && tmp_len > 0) { + read_len += tmp_len; + buf[read_len++] = ' '; + } + + /* remove last space */ + if (read_len) { + buf[read_len--] = '\0'; + } + + einfo->errmsg_length = read_len; + einfo->errmsg = pestrndup(buf, read_len, dbh->is_persistent); + + char sqlstate[sizeof(pdo_error_type)]; + fb_sqlstate(sqlstate, H->isc_status); + if (sqlstate != NULL && strlen(sqlstate) < sizeof(pdo_error_type)) { + strcpy(*error_code, sqlstate); + goto end; + } + } else if (msg && msg_len) { + einfo->errmsg_length = msg_len; + einfo->errmsg = pestrndup(msg, einfo->errmsg_length, dbh->is_persistent); + } + + if (state && state_len && state_len < sizeof(pdo_error_type)) { + memcpy(*error_code, state, state_len + 1); + } else { + memcpy(*error_code, "HY000", sizeof("HY000")); + } + +end: + einfo->sqlcode = sqlcode; + if (!dbh->methods) { + pdo_throw_exception(0, einfo->errmsg, error_code); + } +} +/* }}} */ + +/* called by PDO to close a db handle */ +static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (H->tr) { + if (dbh->auto_commit) { + php_firebird_commit_transaction(dbh, /* retain */ false); + } else { + php_firebird_rollback_transaction(dbh); + } + } + H->in_manually_txn = 0; + + /* isc_detach_database returns 0 on success, 1 on failure. */ + if (H->db && isc_detach_database(H->isc_status, &H->db)) { + php_firebird_error(dbh); + } + + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +/* called by PDO to prepare an SQL query */ +static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ + pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_stmt *S = NULL; + HashTable *np; + + do { + isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER; + XSQLDA num_sqlda; + static char const info[] = { isc_info_sql_stmt_type }; + char result[8]; + + num_sqlda.version = PDO_FB_SQLDA_VERSION; + num_sqlda.sqln = 1; + + ALLOC_HASHTABLE(np); + zend_hash_init(np, 8, NULL, NULL, 0); + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &num_sqlda, &s, np)) { + break; + } + + /* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */ + S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld)); + S->H = H; + S->stmt = s; + S->out_sqlda.version = PDO_FB_SQLDA_VERSION; + S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld; + S->named_params = np; + + /* determine the statement type */ + if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result), + result)) { + break; + } + S->statement_type = result[3]; + + /* fill the output sqlda with information about the prepared query */ + if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + php_firebird_error(dbh); + break; + } + +#if FB_API_VER >= 40 + /* set coercing a data type */ + set_coercing_output_data_types(&S->out_sqlda); +#endif + + /* allocate the input descriptors */ + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) { + break; + } + + if (num_sqlda.sqld) { + S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld)); + S->in_sqlda->version = PDO_FB_SQLDA_VERSION; + S->in_sqlda->sqln = num_sqlda.sqld; + + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* make all parameters nullable */ + unsigned int i; + XSQLVAR* var; + for (i = 0, var = S->in_sqlda->sqlvar; i < S->in_sqlda->sqld; i++, var++) { + /* The low bit of sqltype indicates that the parameter can take a NULL value */ + var->sqltype |= 1; + } + } + + stmt->driver_data = S; + stmt->methods = &firebird_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + + return true; + + } while (0); + + php_firebird_error(dbh); + + zend_hash_destroy(np); + FREE_HASHTABLE(np); + + if (S) { + if (S->in_sqlda) { + efree(S->in_sqlda); + } + efree(S); + } + + return false; +} +/* }}} */ + +/* called by PDO to execute a statement that doesn't produce a result set */ +static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER; + static char const info_count[] = { isc_info_sql_records }; + char result[64]; + int ret = 0; + XSQLDA in_sqlda, out_sqlda; + + /* TODO no placeholders in exec() for now */ + in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION; + in_sqlda.sqld = out_sqlda.sqld = 0; + out_sqlda.sqln = 1; + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &out_sqlda, &stmt, 0)) { + return -1; + } + + /* execute the statement */ + if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + /* find out how many rows were affected */ + if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count), + sizeof(result), result)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1],2); + + if (result_size > sizeof(result)) { + ret = -1; + goto free_statement; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short)isc_vax_integer(&result[i+1],2); + /* bail out on bad len */ + if (len != 1 && len != 2 && len != 4) { + ret = -1; + goto free_statement; + } + if (result[i] != isc_info_req_select_count) { + ret += isc_vax_integer(&result[i+3],len); + } + i += len+3; + } + } + + if (dbh->auto_commit && !H->in_manually_txn) { + if (!php_firebird_commit_transaction(dbh, /* retain */ true)) { + ret = -1; + } + } + +free_statement: + + if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) { + php_firebird_error(dbh); + } + + return ret; +} +/* }}} */ + +/* called by the PDO SQL parser to add quotes to values that are copied into SQL */ +static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + size_t qcount = 0; + char const *co, *l, *r; + char *c; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* Firebird only requires single quotes to be doubled if string lengths are used */ + /* count the number of ' characters */ + for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + + if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { + return NULL; + } + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted_str = zend_string_alloc(quotedlen, 0); + c = ZSTR_VAL(quoted_str); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = ZSTR_VAL(unquoted); (r = strchr(l,'\'')); l = r+1) { + strncpy(c, l, r-l+1); + c += (r-l+1); + /* add the second quote */ + *c++ = '\''; + } + + /* copy the remainder */ + strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1); + ZSTR_VAL(quoted_str)[quotedlen-1] = '\''; + ZSTR_VAL(quoted_str)[quotedlen] = '\0'; + + return quoted_str; +} +/* }}} */ + +/* php_firebird_begin_transaction */ +static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* isc_xxx are all 1 byte. */ + char tpb[4] = { isc_tpb_version3 }; + size_t tpb_size; + + /* access mode. writable or readonly */ + tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read; + + if (is_auto_commit_txn) { + /* + * In autocommit mode, we need to always read the latest information, so we set `read committed`. + */ + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + } else { + switch (H->txn_isolation_level) { + /* + * firebird's `read committed` has the option to wait until other transactions + * commit or rollback if there is indeterminate data. + * Introducing too many configuration values at once can cause confusion, so + * we don't support in PDO that feature yet. + */ + case PDO_FB_READ_COMMITTED: + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + break; + + case PDO_FB_SERIALIZABLE: + tpb[2] = isc_tpb_consistency; + tpb_size = 3; + break; + + case PDO_FB_REPEATABLE_READ: + default: + tpb[2] = isc_tpb_concurrency; + tpb_size = 3; + break; + } + } + + if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to start a transaction */ +static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * If in autocommit mode and in transaction, we will need to close the transaction once. + */ + if (dbh->auto_commit && H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) { + return false; + } + H->in_manually_txn = 1; + return true; +} +/* }}} */ + +/* php_firebird_commit_transaction */ +bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * `retaining` keeps the transaction open without closing it. + * + * firebird needs to always have a transaction open to emulate autocommit mode, + * and in autocommit mode it keeps the transaction open. + * + * Same as close and then begin again, but use retain to save overhead. + */ + if (retain) { + if (isc_commit_retaining(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } else { + if (isc_commit_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } + return true; +} +/* }}} */ + +/* called by PDO to commit a transaction */ +static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (!php_firebird_commit_transaction(dbh, /*release*/ false)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = 0; + return true; +} +/* }}} */ + +/* php_firebird_rollback_transaction */ +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (isc_rollback_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to rollback a transaction */ +static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (!php_firebird_rollback_transaction(dbh)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = 0; + return true; +} +/* }}} */ + +/* used by prepare and exec to allocate a statement handle and prepare the SQL */ +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql, + XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + char *new_sql; + + /* allocate the statement */ + if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { + php_firebird_error(dbh); + return 0; + } + + /* in order to support named params, which Firebird itself doesn't, + we need to replace :foo by ?, and store the name we just replaced */ + new_sql = emalloc(ZSTR_LEN(sql)+1); + new_sql[0] = '\0'; + if (!php_firebird_preprocess(sql, new_sql, named_params)) { + php_firebird_error_with_info(dbh, "07000", strlen("07000"), NULL, 0); + efree(new_sql); + return 0; + } + + /* prepare the statement */ + if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) { + php_firebird_error(dbh); + efree(new_sql); + return 0; + } + + efree(new_sql); + return 1; +} + +/* called by PDO to set a driver-specific dbh attribute */ +static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + bool bval; + zend_long lval; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + /* change auto commit mode with an open transaction is illegal, because + we won't know what to do with it */ + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (dbh->auto_commit ^ bval) { + if (bval) { + /* + * change to auto commit mode. + * If the transaction is not started, start it. + */ + if (!H->tr) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + } else { + /* + * change to not auto commit mode. + * close the transaction if exists. + */ + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + } + dbh->auto_commit = bval; + } + } + return true; + + case PDO_ATTR_FETCH_TABLE_NAMES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->fetch_table_names = bval; + return true; + + case PDO_FB_ATTR_DATE_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + H->date_format = NULL; + } + H->date_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIME_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + H->time_format = NULL; + } + H->time_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + H->timestamp_format = NULL; + } + H->timestamp_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->txn_isolation_level != lval) { + if (lval == PDO_FB_READ_COMMITTED || + lval == PDO_FB_REPEATABLE_READ || + lval == PDO_FB_SERIALIZABLE + ) { + /* + * Autocommit mode is always read-committed, so this setting is used the next time + * a manual transaction starts. Therefore, there is no need to immediately reopen the transaction. + */ + H->txn_isolation_level = lval; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + return false; + } + } + } + return true; + + case PDO_FB_WRITABLE_TRANSACTION: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->is_writable_txn != bval) { + H->is_writable_txn = bval; + if (dbh->auto_commit) { + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + } + } + return true; + } + return false; +} +/* }}} */ + +#define INFO_BUF_LEN 512 + +/* callback to used to report database server info */ +static void php_firebird_info_cb(void *arg, char const *s) /* {{{ */ +{ + if (arg) { + if (*(char*)arg) { /* second call */ + strlcat(arg, " ", INFO_BUF_LEN); + } + strlcat(arg, s, INFO_BUF_LEN); + } +} +/* }}} */ + +/* called by PDO to get a driver-specific dbh attribute */ +static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + switch (attr) { + char tmp[INFO_BUF_LEN]; + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val,dbh->auto_commit); + return 1; + + case PDO_ATTR_CONNECTION_STATUS: + ZVAL_BOOL(val, !isc_version(&H->db, php_firebird_info_cb, NULL)); + return 1; + + case PDO_ATTR_CLIENT_VERSION: { + isc_get_client_version(tmp); + ZVAL_STRING(val, tmp); + } + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + *tmp = 0; + + if (!isc_version(&H->db, php_firebird_info_cb, (void*)tmp)) { + ZVAL_STRING(val, tmp); + return 1; + } + return -1; + + case PDO_ATTR_FETCH_TABLE_NAMES: + ZVAL_BOOL(val, H->fetch_table_names); + return 1; + + case PDO_FB_ATTR_DATE_FORMAT: + ZVAL_STRING(val, H->date_format ? H->date_format : PDO_FB_DEF_DATE_FMT); + return 1; + + case PDO_FB_ATTR_TIME_FORMAT: + ZVAL_STRING(val, H->time_format ? H->time_format : PDO_FB_DEF_TIME_FMT); + return 1; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + ZVAL_STRING(val, H->timestamp_format ? H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT); + return 1; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + ZVAL_LONG(val, H->txn_isolation_level); + return 1; + + case PDO_FB_WRITABLE_TRANSACTION: + ZVAL_BOOL(val, H->is_writable_txn); + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to check liveness */ +static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* fb_ping return 0 if the connection is alive */ + return fb_ping(H->isc_status, &H->db) ? FAILURE : SUCCESS; +} +/* }}} */ + +/* called by PDO to retrieve driver-specific information about an error that has occurred */ +static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (H->einfo.sqlcode != IS_NULL) { + add_next_index_long(info, H->einfo.sqlcode); + } + if (H->einfo.errmsg && H->einfo.errmsg_length) { + add_next_index_stringl(info, H->einfo.errmsg, H->einfo.errmsg_length); + } +} +/* }}} */ + +/* {{{ firebird_in_manually_transaction */ +static bool pdo_firebird_in_manually_transaction(pdo_dbh_t *dbh) +{ + /** + * we can tell if a transaction exists now by checking H->tr, + * but which will always be true in autocommit mode. + * So this function checks if there is currently a "manually begun transaction". + */ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + return H->in_manually_txn; +} +/* }}} */ + +static const struct pdo_dbh_methods firebird_methods = { /* {{{ */ + firebird_handle_closer, + firebird_handle_preparer, + firebird_handle_doer, + firebird_handle_quoter, + firebird_handle_manually_begin, + firebird_handle_manually_commit, + firebird_handle_manually_rollback, + pdo_firebird_set_attribute, + NULL, /* last_id not supported */ + pdo_firebird_fetch_error_func, + pdo_firebird_get_attribute, + pdo_firebird_check_liveness, + NULL, /* get driver methods */ + NULL, /* request shutdown */ + pdo_firebird_in_manually_transaction, + NULL, /* get gc */ +#if PHP_VERSION_ID >= 80400 + NULL /* scanner */ +#endif +}; +/* }}} */ + +/* the driver-specific PDO handle constructor */ +static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + struct pdo_data_src_parser vars[] = { + { "dbname", NULL, 0 }, + { "charset", NULL, 0 }, + { "role", NULL, 0 }, + { "dialect", "3", 0 }, + { "user", NULL, 0 }, + { "password", NULL, 0 } + }; + int i, ret = 0; + short buf_len = 256, dpb_len; + + pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent); + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6); + + if (!dbh->username && vars[4].optval) { + dbh->username = pestrdup(vars[4].optval, dbh->is_persistent); + } + + if (!dbh->password && vars[5].optval) { + dbh->password = pestrdup(vars[5].optval, dbh->is_persistent); + } + + H->in_manually_txn = 0; + H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1); + zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ); + if (txn_isolation_level == PDO_FB_READ_COMMITTED || + txn_isolation_level == PDO_FB_REPEATABLE_READ || + txn_isolation_level == PDO_FB_SERIALIZABLE + ) { + H->txn_isolation_level = txn_isolation_level; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + ret = 0; + } + + do { + static char const dpb_flags[] = { + isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name }; + char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval }; + char dpb_buffer[256] = { isc_dpb_version1 }, *dpb; + + dpb = dpb_buffer + 1; + + /* loop through all the provided arguments and set dpb fields accordingly */ + for (i = 0; i < sizeof(dpb_flags); ++i) { + if (dpb_values[i] && buf_len > 0) { + dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]), + dpb_values[i]); + dpb += dpb_len; + buf_len -= dpb_len; + } + } + + H->sql_dialect = PDO_FB_DIALECT; + if (vars[3].optval) { + H->sql_dialect = atoi(vars[3].optval); + } + + /* fire it up baby! */ + if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) { + break; + } + + dbh->methods = &firebird_methods; + dbh->native_case = PDO_CASE_UPPER; + dbh->alloc_own_columns = 1; + + ret = 1; + + } while (0); + + for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!dbh->methods) { + char errmsg[512]; + const ISC_STATUS *s = H->isc_status; + fb_interpret(errmsg, sizeof(errmsg),&s); + zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%ld] %s", + "HY000", H->isc_status[1], errmsg); + } + + if (ret && dbh->auto_commit && !H->tr) { + ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true); + } + + if (!ret) { + firebird_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + + +const pdo_driver_t swoole_pdo_firebird_driver = { /* {{{ */ + PDO_DRIVER_HEADER(firebird), + pdo_firebird_handle_factory +}; +/* }}} */ + +#endif diff --git a/thirdparty/php84/pdo_firebird/firebird_statement.c b/thirdparty/php84/pdo_firebird/firebird_statement.c new file mode 100644 index 0000000000..51f8707abb --- /dev/null +++ b/thirdparty/php84/pdo_firebird/firebird_statement.c @@ -0,0 +1,962 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID < 80500 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +#include + +#define READ_AND_RETURN_USING_MEMCPY(type, sqldata) do { \ + type ret; \ + memcpy(&ret, sqldata, sizeof(ret)); \ + return ret; \ + } while (0); + +static zend_always_inline ISC_INT64 php_get_isc_int64_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_INT64, sqldata); +} + +static zend_always_inline ISC_LONG php_get_isc_long_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_LONG, sqldata); +} + +static zend_always_inline double php_get_double_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(double, sqldata); +} + +static zend_always_inline float php_get_float_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(float, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP php_get_isc_timestamp_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP, sqldata); +} + +static zend_always_inline ISC_QUAD php_get_isc_quad_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_QUAD, sqldata); +} + +#if FB_API_VER >= 40 + +static zend_always_inline ISC_TIME_TZ php_get_isc_time_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIME_TZ, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP_TZ php_get_isc_timestamp_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP_TZ, sqldata); +} + +/* fetch formatted time with time zone */ +static int get_formatted_time_tz(pdo_stmt_t *stmt, const ISC_TIME_TZ* timeTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned hours = 0, minutes = 0, seconds = 0, fractions = 0; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIME time; + char timeBuf[80] = {0}; + char timeTzBuf[124] = {0}; + if (fb_decode_time_tz(S->H->isc_status, timeTz, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_sql_time(&time, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + + size_t len = strftime(timeBuf, sizeof(timeBuf), fmt, &t); + if (len == 0) { + return 1; + } + + size_t time_tz_len = sprintf(timeTzBuf, "%s %s", timeBuf, timeZoneBuffer); + ZVAL_STRINGL(result, timeTzBuf, time_tz_len); + return 0; +} + +/* fetch formatted timestamp with time zone */ +static int get_formatted_timestamp_tz(pdo_stmt_t *stmt, const ISC_TIMESTAMP_TZ* timestampTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned year, month, day, hours, minutes, seconds, fractions; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIMESTAMP ts; + char timestampBuf[80] = {0}; + char timestampTzBuf[124] = {0}; + if (fb_decode_timestamp_tz(S->H->isc_status, timestampTz, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + ts.timestamp_date = fb_encode_date(year, month, day); + ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_timestamp(&ts, &t); + + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + + size_t len = strftime(timestampBuf, sizeof(timestampBuf), fmt, &t); + if (len == 0) { + return 1; + } + + size_t timestamp_tz_len = sprintf(timestampTzBuf, "%s %s", timestampBuf, timeZoneBuffer); + ZVAL_STRINGL(result, timestampTzBuf, timestamp_tz_len); + return 0; +} + +#endif + +/* free the allocated space for passing field values to the db and back */ +static void php_firebird_free_sqlda(XSQLDA const *sqlda) /* {{{ */ +{ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR const *var = &sqlda->sqlvar[i]; + + if (var->sqlind) { + efree(var->sqlind); + } + } +} +/* }}} */ + +/* called by PDO to clean up a statement handle */ +static int pdo_firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + int result = 1; + + /* TODO: for master, move this check to a separate function shared between pdo drivers. + * pdo_pgsql and pdo_mysql do this exact same thing */ + bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle) + && IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) + && !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + /* release the statement. + * Note: if the server object is already gone then the statement was closed already as well. */ + if (server_obj_usable && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { + php_firebird_error_stmt(stmt); + result = 0; + } + + zend_hash_destroy(S->named_params); + FREE_HASHTABLE(S->named_params); + + /* clean up the input descriptor */ + if (S->in_sqlda) { + php_firebird_free_sqlda(S->in_sqlda); + efree(S->in_sqlda); + } + + php_firebird_free_sqlda(&S->out_sqlda); + efree(S); + + return result; +} +/* }}} */ + +/* called by PDO to execute a prepared query */ +static int pdo_firebird_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + zend_ulong affected_rows = 0; + static char info_count[] = {isc_info_sql_records}; + char result[64]; + + do { + /* named or open cursors should be closed first */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(H->isc_status, &S->stmt, DSQL_close)) { + break; + } + S->cursor_open = 0; + + /* allocate storage for the output data */ + if (S->out_sqlda.sqld) { + unsigned int i; + for (i = 0; i < S->out_sqlda.sqld; i++) { + XSQLVAR *var = &S->out_sqlda.sqlvar[i]; + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)ecalloc(1, var->sqllen + 2 * sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + } + + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + if (isc_dsql_execute2(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda, &S->out_sqlda)) { + break; + } + } else if (isc_dsql_execute(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* Determine how many rows have changed. In this case we are + * only interested in rows changed, not rows retrieved. That + * should be handled by the client when fetching. */ + stmt->row_count = affected_rows; + + switch (S->statement_type) { + case isc_info_sql_stmt_insert: + case isc_info_sql_stmt_update: + case isc_info_sql_stmt_delete: + case isc_info_sql_stmt_exec_procedure: + if (isc_dsql_sql_info(H->isc_status, &S->stmt, sizeof ( info_count), + info_count, sizeof(result), result)) { + break; + } + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1], 2); + if (result_size > sizeof(result)) { + goto error; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short) isc_vax_integer(&result[i + 1], 2); + if (len != 1 && len != 2 && len != 4) { + goto error; + } + if (result[i] != isc_info_req_select_count) { + affected_rows += isc_vax_integer(&result[i + 3], len); + } + i += len + 3; + } + stmt->row_count = affected_rows; + } + /* TODO Dead code or assert one of the previous cases are hit? */ + default: + ; + } + + if (stmt->dbh->auto_commit && !S->H->in_manually_txn && !php_firebird_commit_transaction(stmt->dbh, /* retain */ true)) { + break; + } + + *S->name = 0; + S->cursor_open = S->out_sqlda.sqln && (S->statement_type != isc_info_sql_stmt_exec_procedure); + S->exhausted = !S->out_sqlda.sqln; /* There are data to fetch */ + + return 1; + } while (0); + +error: + php_firebird_error_stmt(stmt); + + return 0; +} +/* }}} */ + +/* called by PDO to fetch the next row from a statement */ +static int pdo_firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + + if (!stmt->executed) { + const char *msg = "Cannot fetch from a closed cursor"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } else if (!S->exhausted) { + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + stmt->row_count = 1; + S->exhausted = 1; + return 1; + } + if (isc_dsql_fetch(H->isc_status, &S->stmt, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + if (H->isc_status[0] && H->isc_status[1]) { + php_firebird_error_stmt(stmt); + } + S->exhausted = 1; + return 0; + } + stmt->row_count++; + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to retrieve information about the fields being returned */ +static int pdo_firebird_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + int colname_len; + char *cp; + + if ((var->sqltype & ~1) == SQL_TEXT) { + var->sqltype = SQL_VARYING | (var->sqltype & 1); + } + colname_len = (S->H->fetch_table_names && var->relname_length) + ? (var->aliasname_length + var->relname_length + 1) + : (var->aliasname_length); + col->precision = -var->sqlscale; + col->maxlen = var->sqllen; + col->name = zend_string_alloc(colname_len, 0); + cp = ZSTR_VAL(col->name); + if (colname_len > var->aliasname_length) { + memmove(cp, var->relname, var->relname_length); + cp += var->relname_length; + *cp++ = '.'; + } + memmove(cp, var->aliasname, var->aliasname_length); + *(cp+var->aliasname_length) = '\0'; + + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt *) stmt->driver_data; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + + enum pdo_param_type param_type; + if (var->sqlscale < 0) { + param_type = PDO_PARAM_STR; + } else { + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: +#if SIZEOF_ZEND_LONG >= 8 + case SQL_INT64: +#endif + param_type = PDO_PARAM_INT; + break; + case SQL_BOOLEAN: + param_type = PDO_PARAM_BOOL; + break; + default: + param_type = PDO_PARAM_STR; + break; + } + } + + array_init(return_value); + add_assoc_long(return_value, "pdo_type", param_type); + return 1; +} + +/* fetch a blob into a fetch buffer */ +static int php_firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QUAD *blob_id) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle blobh = PDO_FIREBIRD_HANDLE_INITIALIZER; + char const bl_item = isc_info_blob_total_length; + char bl_info[20]; + unsigned short i; + int retval = 0; + size_t len = 0; + + if (isc_open_blob(H->isc_status, &H->db, &H->tr, &blobh, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (isc_blob_info(H->isc_status, &blobh, 1, const_cast(&bl_item), + sizeof(bl_info), bl_info)) { + php_firebird_error_stmt(stmt); + goto fetch_blob_end; + } + + /* find total length of blob's data */ + for (i = 0; i < sizeof(bl_info); ) { + unsigned short item_len; + char item = bl_info[i++]; + + if (item == isc_info_end || item == isc_info_truncated || item == isc_info_error + || i >= sizeof(bl_info)) { + const char *msg = "Couldn't determine BLOB size"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + + item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + + if (item == isc_info_blob_total_length) { + len = isc_vax_integer(&bl_info[i+2], item_len); + break; + } + i += item_len+2; + } + + /* we've found the blob's length, now fetch! */ + + if (len) { + zend_ulong cur_len; + unsigned short seg_len; + ISC_STATUS stat; + zend_string *str; + + /* prevent overflow */ + if (len > ZSTR_MAX_LEN) { + result = 0; + goto fetch_blob_end; + } + + str = zend_string_alloc(len, 0); + + for (cur_len = stat = 0; (!stat || stat == isc_segment) && cur_len < len; cur_len += seg_len) { + + unsigned short chunk_size = (len - cur_len) > USHRT_MAX ? USHRT_MAX + : (unsigned short)(len - cur_len); + + stat = isc_get_segment(H->isc_status, &blobh, &seg_len, chunk_size, ZSTR_VAL(str) + cur_len); + } + + ZSTR_VAL(str)[len] = '\0'; + ZVAL_STR(result, str); + + if (H->isc_status[0] == 1 && (stat != 0 && stat != isc_segstr_eof && stat != isc_segment)) { + const char *msg = "Error reading from BLOB"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + } + retval = 1; + +fetch_blob_end: + if (isc_close_blob(H->isc_status, &blobh)) { + php_firebird_error_stmt(stmt); + return 0; + } + return retval; +} +/* }}} */ + +static int pdo_firebird_stmt_get_col( + pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLVAR const *var = &S->out_sqlda.sqlvar[colno]; + + if (*var->sqlind == -1) { + ZVAL_NULL(result); + } else { + if (var->sqlscale < 0) { + static ISC_INT64 const scales[] = { 1, 10, 100, 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + LL_LIT(10000000000), + LL_LIT(100000000000), + LL_LIT(1000000000000), + LL_LIT(10000000000000), + LL_LIT(100000000000000), + LL_LIT(1000000000000000), + LL_LIT(10000000000000000), + LL_LIT(100000000000000000), + LL_LIT(1000000000000000000) + }; + ISC_INT64 n, f = scales[-var->sqlscale]; + zend_string *str; + + switch (var->sqltype & ~1) { + case SQL_SHORT: + n = *(short*)var->sqldata; + break; + case SQL_LONG: + n = php_get_isc_long_from_sqldata(var->sqldata); + break; + case SQL_INT64: + n = php_get_isc_int64_from_sqldata(var->sqldata); + break; + case SQL_DOUBLE: + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + + if ((var->sqltype & ~1) == SQL_DOUBLE) { + str = zend_strpprintf(0, "%.*F", -var->sqlscale, php_get_double_from_sqldata(var->sqldata)); + } else if (n >= 0) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, n % f); + } else if (n <= -f) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, -n % f); + } else { + str = zend_strpprintf(0, "-0.%0*" LL_MASK "d", -var->sqlscale, -n % f); + } + ZVAL_STR(result, str); + } else { + switch (var->sqltype & ~1) { + struct tm t; + char *fmt; + + case SQL_VARYING: + ZVAL_STRINGL_FAST(result, &var->sqldata[2], *(short*)var->sqldata); + break; + case SQL_TEXT: + ZVAL_STRINGL_FAST(result, var->sqldata, var->sqllen); + break; + case SQL_SHORT: + ZVAL_LONG(result, *(short*)var->sqldata); + break; + case SQL_LONG: + ZVAL_LONG(result, php_get_isc_long_from_sqldata(var->sqldata)); + break; + case SQL_INT64: +#if SIZEOF_ZEND_LONG >= 8 + ZVAL_LONG(result, php_get_isc_int64_from_sqldata(var->sqldata)); +#else + ZVAL_STR(result, zend_strpprintf(0, "%" LL_MASK "d", php_get_isc_int64_from_sqldata(var->sqldata))); +#endif + break; + case SQL_FLOAT: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.8H", php_get_float_from_sqldata(var->sqldata))); + break; + case SQL_DOUBLE: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.16H", php_get_double_from_sqldata(var->sqldata))); + break; + case SQL_BOOLEAN: + ZVAL_BOOL(result, *(FB_BOOLEAN*)var->sqldata); + break; + case SQL_TYPE_DATE: + isc_decode_sql_date((ISC_DATE*)var->sqldata, &t); + fmt = S->H->date_format ? S->H->date_format : PDO_FB_DEF_DATE_FMT; + if (0) { + case SQL_TYPE_TIME: + isc_decode_sql_time((ISC_TIME*)var->sqldata, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + } else if (0) { + case SQL_TIMESTAMP: + { + ISC_TIMESTAMP timestamp = php_get_isc_timestamp_from_sqldata(var->sqldata); + isc_decode_timestamp(×tamp, &t); + } + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + } + /* convert the timestamp into a string */ + char buf[80]; + size_t len = strftime(buf, sizeof(buf), fmt, &t); + ZVAL_STRINGL(result, buf, len); + break; +#if FB_API_VER >= 40 + case SQL_TIME_TZ: { + ISC_TIME_TZ time = php_get_isc_time_tz_from_sqldata(var->sqldata); + return get_formatted_time_tz(stmt, &time, result); + } + case SQL_TIMESTAMP_TZ: { + ISC_TIMESTAMP_TZ ts = php_get_isc_timestamp_tz_from_sqldata(var->sqldata); + return get_formatted_timestamp_tz(stmt, &ts, result); + } +#endif + case SQL_BLOB: { + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + return php_firebird_fetch_blob(stmt, colno, result, &quad); + } + } + } + } + return 1; +} + +static int php_firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle h = PDO_FIREBIRD_HANDLE_INITIALIZER; + zval data; + zend_ulong put_cnt = 0, rem_cnt; + unsigned short chunk_size; + int result = 1; + + if (isc_create_blob(H->isc_status, &H->db, &H->tr, &h, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (Z_TYPE_P(param) != IS_STRING) { + ZVAL_STR(&data, zval_get_string_func(param)); + } else { + ZVAL_COPY_VALUE(&data, param); + } + + for (rem_cnt = Z_STRLEN(data); rem_cnt > 0; rem_cnt -= chunk_size) { + chunk_size = rem_cnt > USHRT_MAX ? USHRT_MAX : (unsigned short)rem_cnt; + if (isc_put_segment(H->isc_status, &h, chunk_size, &Z_STRVAL(data)[put_cnt])) { + php_firebird_error_stmt(stmt); + result = 0; + break; + } + put_cnt += chunk_size; + } + + if (Z_TYPE_P(param) != IS_STRING) { + zval_ptr_dtor_str(&data); + } + + if (isc_close_blob(H->isc_status, &h)) { + php_firebird_error_stmt(stmt); + return 0; + } + return result; +} + +static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, /* {{{ */ + enum pdo_param_event event_type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLDA *sqlda = param->is_param ? S->in_sqlda : &S->out_sqlda; + XSQLVAR *var; + + if (event_type == PDO_PARAM_EVT_FREE) { /* not used */ + return 1; + } + + if (!sqlda || param->paramno >= sqlda->sqld) { + const char *msg = "Invalid parameter index"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + if (param->is_param && param->paramno == -1) { + zval *index; + + /* try to determine the index by looking in the named_params hash */ + if ((index = zend_hash_find(S->named_params, param->name)) != NULL) { + param->paramno = Z_LVAL_P(index); + } else { + /* ... or by looking in the input descriptor */ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR *var = &sqlda->sqlvar[i]; + + if ((var->aliasname_length && !strncasecmp(ZSTR_VAL(param->name), var->aliasname, + min(ZSTR_LEN(param->name), var->aliasname_length))) + || (var->sqlname_length && !strncasecmp(ZSTR_VAL(param->name), var->sqlname, + min(ZSTR_LEN(param->name), var->sqlname_length)))) { + param->paramno = i; + break; + } + } + if (i >= sqlda->sqld) { + const char *msg = "Invalid parameter name"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + } + } + + var = &sqlda->sqlvar[param->paramno]; + + switch (event_type) { + zval *parameter; + + case PDO_PARAM_EVT_ALLOC: + if (param->is_param) { + /* allocate the parameter */ + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)emalloc(var->sqllen + 2*sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + break; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!param->is_param) { + break; + } + + *var->sqlind = 0; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } + + switch (var->sqltype & ~1) { + case SQL_ARRAY: + { + const char *msg = "Cannot bind to array field"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } + return 0; + + case SQL_BLOB: { + if (Z_TYPE_P(parameter) == IS_NULL) { + /* Check if field allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + return 1; + } + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + if (php_firebird_bind_blob(stmt, &quad, parameter) != 0) { + memcpy(var->sqldata, &quad, sizeof(quad)); + return 1; + } + return 0; + } + } + + /* keep native BOOLEAN type */ + if ((var->sqltype & ~1) == SQL_BOOLEAN) { + switch (Z_TYPE_P(parameter)) { + case IS_LONG: + case IS_DOUBLE: + case IS_TRUE: + case IS_FALSE: + *(FB_BOOLEAN*)var->sqldata = zend_is_true(parameter) ? FB_TRUE : FB_FALSE; + break; + case IS_STRING: + { + zend_long lval; + double dval; + + if (Z_STRLEN_P(parameter) == 0) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + break; + } + + switch (is_numeric_string(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &lval, &dval, 0)) { + case IS_LONG: + *(FB_BOOLEAN*)var->sqldata = (lval != 0) ? FB_TRUE : FB_FALSE; + break; + case IS_DOUBLE: + *(FB_BOOLEAN*)var->sqldata = (dval != 0) ? FB_TRUE : FB_FALSE; + break; + default: + if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "true", 4, 4)) { + *(FB_BOOLEAN*)var->sqldata = FB_TRUE; + } else if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "false", 5, 5)) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + } else { + const char *msg = "Cannot convert string to boolean"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + + } + } + break; + case IS_NULL: + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + } + + /* check if a NULL should be inserted */ + switch (Z_TYPE_P(parameter)) { + int force_null; + + case IS_LONG: + /* keep the allow-NULL flag */ + var->sqltype = (sizeof(zend_long) == 8 ? SQL_INT64 : SQL_LONG) | (var->sqltype & 1); + var->sqldata = (void*)&Z_LVAL_P(parameter); + var->sqllen = sizeof(zend_long); + break; + case IS_DOUBLE: + /* keep the allow-NULL flag */ + var->sqltype = SQL_DOUBLE | (var->sqltype & 1); + var->sqldata = (void*)&Z_DVAL_P(parameter); + var->sqllen = sizeof(double); + break; + case IS_STRING: + force_null = 0; + + /* for these types, an empty string can be handled like a NULL value */ + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: +#if FB_API_VER >= 40 + case SQL_INT128: + case SQL_DEC16: + case SQL_DEC34: + case SQL_TIMESTAMP_TZ: + case SQL_TIME_TZ: +#endif + force_null = (Z_STRLEN_P(parameter) == 0); + } + if (!force_null) { + /* keep the allow-NULL flag */ + var->sqltype = SQL_TEXT | (var->sqltype & 1); + var->sqldata = Z_STRVAL_P(parameter); + var->sqllen = Z_STRLEN_P(parameter); + break; + } + ZEND_FALLTHROUGH; + case IS_NULL: + /* complain if this field doesn't allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + + case PDO_PARAM_EVT_FETCH_POST: + if (param->paramno == -1) { + return 0; + } + if (param->is_param) { + break; + } + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + ZVAL_NULL(parameter); + return pdo_firebird_stmt_get_col(stmt, param->paramno, parameter, NULL); + default: + ; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + if (!try_convert_to_string(val)) { + return 0; + } + + if (isc_dsql_set_cursor_name(S->H->isc_status, &S->stmt, Z_STRVAL_P(val),0)) { + php_firebird_error_stmt(stmt); + return 0; + } + strlcpy(S->name, Z_STRVAL_P(val), sizeof(S->name)); + break; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + if (*S->name) { + ZVAL_STRING(val, S->name); + } else { + ZVAL_NULL(val); + } + break; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + /* close the statement handle */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_close)) { + php_firebird_error_stmt(stmt); + return 0; + } + *S->name = 0; + S->cursor_open = 0; + return 1; +} +/* }}} */ + + +const struct pdo_stmt_methods firebird_stmt_methods = { /* {{{ */ + pdo_firebird_stmt_dtor, + pdo_firebird_stmt_execute, + pdo_firebird_stmt_fetch, + pdo_firebird_stmt_describe, + pdo_firebird_stmt_get_col, + pdo_firebird_stmt_param_hook, + pdo_firebird_stmt_set_attribute, + pdo_firebird_stmt_get_attribute, + pdo_firebird_stmt_get_column_meta, + NULL, /* next_rowset_func */ + pdo_firebird_stmt_cursor_closer +}; +/* }}} */ + +#endif diff --git a/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp new file mode 100644 index 0000000000..0162fe30bf --- /dev/null +++ b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID < 80500 + +#include "pdo_firebird_utils.h" +#include +#include + +/* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ +extern "C" unsigned fb_get_client_version(void) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->getClientVersion(); +} + +extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeTime(hours, minutes, seconds, fractions); +} + +extern "C" ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeDate(year, month, day); +} + +#if FB_API_VER >= 40 +static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLength) +{ + for(size_t i=0; i < maxLength; ++i) { + memcpy(to + i, from + i, sizeof(ISC_STATUS)); + if (from[i] == isc_arg_end) { + break; + } + } +} + +/* Decodes a time with time zone into its time components. */ +extern "C" ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeTz(&st, timeTz, hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +/* Decodes a timestamp with time zone into its date and time components */ +extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeStampTz(&st, timestampTz, year, month, day, + hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +#endif +#endif diff --git a/thirdparty/php84/pdo_firebird/pdo_firebird_utils.h b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.h new file mode 100644 index 0000000000..19b0d0ab4b --- /dev/null +++ b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#ifndef PDO_FIREBIRD_UTILS_H +#define PDO_FIREBIRD_UTILS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned fb_get_client_version(void); + +ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions); + +ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day); + +#if FB_API_VER >= 40 + +ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h b/thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h new file mode 100644 index 0000000000..2b8d00a4e3 --- /dev/null +++ b/thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h @@ -0,0 +1,161 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#ifndef PHP_PDO_FIREBIRD_INT_H +#define PHP_PDO_FIREBIRD_INT_H + +#include + +#ifdef SQLDA_VERSION +#define PDO_FB_SQLDA_VERSION SQLDA_VERSION +#else +#define PDO_FB_SQLDA_VERSION 1 +#endif + +#define PDO_FB_DIALECT 3 + +#define PDO_FB_DEF_DATE_FMT "%Y-%m-%d" +#define PDO_FB_DEF_TIME_FMT "%H:%M:%S" +#define PDO_FB_DEF_TIMESTAMP_FMT PDO_FB_DEF_DATE_FMT " " PDO_FB_DEF_TIME_FMT + +#define SHORT_MAX (1 << (8*sizeof(short)-1)) + +#if SIZEOF_ZEND_LONG == 8 && !defined(PHP_WIN32) +# define LL_LIT(lit) lit ## L +#else +# define LL_LIT(lit) lit ## LL +#endif +#define LL_MASK "ll" + +/* Firebird API has a couple of missing const decls in its API */ +#define const_cast(s) ((char*)(s)) + +#ifdef PHP_WIN32 +typedef void (__stdcall *info_func_t)(char*); +#else +typedef void (*info_func_t)(char*); +#endif + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(_WIN64) +# define PDO_FIREBIRD_HANDLE_INITIALIZER 0U +#else +# define PDO_FIREBIRD_HANDLE_INITIALIZER NULL +#endif + +typedef struct { + int sqlcode; + char *errmsg; + size_t errmsg_length; +} pdo_firebird_error_info; + +typedef struct { + /* the result of the last API call */ + ISC_STATUS isc_status[20]; + + /* the connection handle */ + isc_db_handle db; + + /* the transaction handle */ + isc_tr_handle tr; + bool in_manually_txn; + bool is_writable_txn; + zend_ulong txn_isolation_level; + + /* date and time format strings, can be set by the set_attribute method */ + char *date_format; + char *time_format; + char *timestamp_format; + + unsigned sql_dialect:2; + + /* prepend table names on column names in fetch */ + unsigned fetch_table_names:1; + + unsigned _reserved:29; + + pdo_firebird_error_info einfo; +} pdo_firebird_db_handle; + + +typedef struct { + /* the link that owns this statement */ + pdo_firebird_db_handle *H; + + /* the statement handle */ + isc_stmt_handle stmt; + + /* the name of the cursor (if it has one) */ + char name[32]; + + /* the type of statement that was issued */ + char statement_type:8; + + /* whether EOF was reached for this statement */ + unsigned exhausted:1; + + /* successful isc_dsql_execute opens a cursor */ + unsigned cursor_open:1; + + unsigned _reserved:22; + + /* the named params that were converted to ?'s by the driver */ + HashTable *named_params; + + /* the input SQLDA */ + XSQLDA *in_sqlda; + + /* the output SQLDA */ + XSQLDA out_sqlda; /* last member */ +} pdo_firebird_stmt; + +extern const pdo_driver_t pdo_firebird_driver; + +extern const struct pdo_stmt_methods firebird_stmt_methods; + +extern void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len); +#define php_firebird_error(d) php_firebird_set_error(d, NULL, NULL, 0, NULL, 0) +#define php_firebird_error_stmt(s) php_firebird_set_error(s->dbh, s, NULL, 0, NULL, 0) +#define php_firebird_error_with_info(d,e,el,m,ml) php_firebird_set_error(d, NULL, e, el, m, ml) +#define php_firebird_error_stmt_with_info(s,e,el,m,ml) php_firebird_set_error(s->dbh, s, e, el, m, ml) + +extern bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); + +enum { + PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC, + PDO_FB_ATTR_TIME_FORMAT, + PDO_FB_ATTR_TIMESTAMP_FORMAT, + + /* + * transaction isolation level + * firebird does not have a level equivalent to read uncommited. + */ + PDO_FB_TRANSACTION_ISOLATION_LEVEL, + PDO_FB_READ_COMMITTED, + PDO_FB_REPEATABLE_READ, + PDO_FB_SERIALIZABLE, + + /* transaction access mode */ + PDO_FB_WRITABLE_TRANSACTION, +}; + +#endif /* PHP_PDO_FIREBIRD_INT_H */ diff --git a/thirdparty/php84/pdo_odbc/odbc_driver.c b/thirdparty/php84/pdo_odbc/odbc_driver.c new file mode 100644 index 0000000000..9978af9e35 --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_driver.c @@ -0,0 +1,638 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "zend_exceptions.h" +#include + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, + "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, + (long) einfo->last_error, + einfo->file, + einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + +void pdo_odbc_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, + eh, + recno++, + (SQLCHAR *) einfo->last_state, + &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, + sizeof(einfo->last_err_msg) - 1, + &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); + /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), + einfo->last_error, + "SQLSTATE[%s] %s: %d %s", + *pdo_err, + what, + einfo->last_error, + einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, discard_buf, sizeof(discard_buf) - 1, &errmsgsize); + } +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void *) SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t +*quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) { + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error( + dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = + SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error(dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" + : "SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) { + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER) buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) { + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL, /* get_gc */ + NULL /* scanner */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void *) pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) (intptr_t) (dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void *) cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + size_t db_len = strlen(dbh->data_source); + bool use_uid_arg = + dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + db_len); + bool use_pwd_arg = + dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + db_len); + + if (use_uid_arg || use_pwd_arg) { + char *db = (char *) emalloc(db_len + 1); + strcpy(db, dbh->data_source); + char *db_end = db + db_len; + db_end--; + if ((unsigned char) *(db_end) == ';') { + *db_end = '\0'; + } + + char *uid = NULL, *pwd = NULL, *dsn = NULL; + bool should_quote_uid, should_quote_pwd; + size_t new_dsn_size; + + if (use_uid_arg) { + should_quote_uid = + !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + + if (!use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(";UID=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;", db, uid); + } + } + + if (use_pwd_arg) { + should_quote_pwd = + !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + + if (!use_uid_arg) { + new_dsn_size = strlen(db) + strlen(pwd) + strlen(";PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;PWD=%s;", db, pwd); + } + } + + if (use_uid_arg && use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(pwd) + strlen(";UID=;PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s;", db, uid, pwd); + } + + pefree((char *) dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + efree(db); + } + + rc = SQLDriverConnect(H->dbc, + NULL, + (SQLCHAR *) dbh->data_source, + strlen(dbh->data_source), + dsnbuf, + sizeof(dsnbuf) - 1, + &dsnbuflen, + SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, + (SQLCHAR *) dbh->data_source, + SQL_NTS, + (SQLCHAR *) dbh->username, + SQL_NTS, + (SQLCHAR *) dbh->password, + SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = {PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_odbc/odbc_stmt.c b/thirdparty/php84/pdo_odbc/odbc_stmt.c new file mode 100644 index 0000000000..6bd65d205f --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_stmt.c @@ -0,0 +1,865 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, PDO_ODBC_CONV_OK, PDO_ODBC_CONV_FAIL }; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2( + pdo_stmt_t *stmt, int is_unicode, const char *buf, zend_ulong buflen, zend_ulong *outlen) { +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR) S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) { +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte( + CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result) / sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, + 0, + (LPCWSTR) Z_STRVAL_P(result), + Z_STRLEN_P(result) / sizeof(WCHAR), + ZSTR_VAL(str), + ZSTR_LEN(str), + NULL, + NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) { + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) { + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER *) ¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param *) param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + RETCODE rc; + SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno + 1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2 : 1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, + (SQLUSMALLINT) param->paramno + 1, + P->paramtype, + ctype, + sqltype, + precision, + scale, + P->paramtype == SQL_PARAM_INPUT ? (SQLPOINTER) param : P->outbuf, + P->len, + &P->len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch ( + pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + odbcori = SQL_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + odbcori = SQL_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + odbcori = SQL_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + odbcori = SQL_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + odbcori = SQL_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + odbcori = SQL_FETCH_RELATIVE; + break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SQLSMALLINT colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, + colno + 1, + (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname) - 1, + &colnamelen, + &S->cols[colno].coltype, + &colsize, + NULL, + NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && (S->cols[colno].coltype == SQL_VARCHAR || S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize + 1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, + colno + 1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen + 1, + &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2 = emalloc(256); + zend_string *str = zend_string_init(C->data, 256, 0); + size_t used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData( + S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* adjust `used` in case we have length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + ZEND_ASSERT(fixed_used <= used + 1); + used = fixed_used; + } + + /* resize output buffer and reassemble block */ + if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in + http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx states that if + SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) (if a driver fails to + follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); + used = used + 255; + } else if (rc == SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) { + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = {odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor}; +#endif diff --git a/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 0000000000..0893f4b824 --- /dev/null +++ b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,179 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +#define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +#warning Please fix configure to give your ODBC libraries a name +#define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +#include +#if defined(DB268K) && HAVE_LIBRARYMANAGER_H +#include +#endif +#endif + +#ifdef HAVE_ODBC_H +#include +#endif + +#ifdef HAVE_IODBC_H +#include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +#include +#endif + +#ifdef HAVE_SQLTYPES_H +#include +#endif + +#ifdef HAVE_SQLUCODE_H +#include +#endif + +#ifdef HAVE_SQL_H +#include +#endif + +#ifdef HAVE_ISQL_H +#include +#endif + +#ifdef HAVE_SQLEXT_H +#include +#endif + +#ifdef HAVE_ISQLEXT_H +#include +#endif + +#ifdef HAVE_UDBCEXT_H +#include +#endif + +#ifdef HAVE_CLI0CORE_H +#include +#endif + +#ifdef HAVE_CLI0EXT1_H +#include +#endif + +#ifdef HAVE_CLI0CLI_H +#include +#endif + +#ifdef HAVE_CLI0DEFS_H +#include +#endif + +#ifdef HAVE_CLI0ENV_H +#include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +#define PDO_ODBC_HENV SQLHANDLE +#define PDO_ODBC_HDBC SQLHANDLE +#define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +#define PDO_ODBC_HENV SQLHENV +#define PDO_ODBC_HDBC SQLHDBC +#define PDO_ODBC_HSTMT SQLHSTMT +#else +#define PDO_ODBC_HENV HENV +#define PDO_ODBC_HDBC HDBC +#define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8 : 1; + unsigned _spare : 31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SQLSMALLINT coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long : 1; + unsigned assume_utf8 : 1; + signed col_count : 16; + unsigned _spare : 14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver.c b/thirdparty/php84/pdo_pgsql/pgsql_driver.c new file mode 100644 index 0000000000..565f59d91e --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1458 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/php_string.h" /* For php_addcslashes_str() in _pdo_pgsql_escape_credentials() */ +#include "main/php_network.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "zend_smart_str.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char *_pdo_pgsql_trim_message(const char *message, int persistent) { + size_t i = strlen(message) - 1; + char *tmp; + + if (i > 1 && (message[i - 1] == '\r' || message[i - 1] == '\n') && message[i] == '.') { + --i; + } + while (i > 0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string *_pdo_pgsql_escape_credentials(char *str) { + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + int errcode, + const char *sqlstate, + const char *msg, + const char *file, + int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ +{ + pdo_dbh_t *dbh = (pdo_dbh_t *) context; + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *) dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); + zval_ptr_dtor_str(&zarg); + } +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zend_fcc_dtor(H->notice_callback); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + return lo_write(self->conn, self->lfd, (char *) buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) { + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const php_stream_ops pdo_pgsql_lob_stream_ops = {pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL}; + +php_stream *pdo_pgsql_create_lob_stream(zval *dbh, int lfd, Oid oid) { + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + Z_ADDREF_P(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) { + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } + ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + pdo_pgsql_cleanup_notice_callback(H); + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string *pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + size_t tmp_len; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *) ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted + 1, escaped, quotedlen - 2); + quoted[0] = '\''; + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *) PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) { + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *) PQparameterStatus(H->server, "server_version")); + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL( + return_value, "Waiting for connection to be made.", strlen("Waiting for connection to be made.")); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", strlen("Connection OK; waiting to send.")); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, + "Waiting for a response from the server.", + strlen("Waiting for a response from the server.")); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, + "Received authentication; waiting for backend start-up to finish.", + strlen("Received authentication; waiting for backend start-up to finish.")); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", strlen("Negotiating SSL encryption.")); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, + "Negotiating environment-driven parameter settings.", + strlen("Negotiating environment-driven parameter settings.")); + break; + +#ifdef CONNECTION_CONSUME + case CONNECTION_CONSUME: + ZVAL_STRINGL(return_value, + "Flushing send queue/consuming extra data.", + strlen("Flushing send queue/consuming extra data.")); + break; +#endif +#ifdef CONNECTION_GSS_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating GSSAPI.", strlen("Negotiating GSSAPI.")); + break; +#endif +#ifdef CONNECTION_CHECK_TARGET + case CONNECTION_CHECK_TARGET: + ZVAL_STRINGL(return_value, + "Connection OK; checking target server properties.", + strlen("Connection OK; checking target server properties.")); + break; +#endif +#ifdef CONNECTION_CHECK_STANDBY + case CONNECTION_CHECK_STANDBY: + ZVAL_STRINGL(return_value, + "Connection OK; checking if server in standby.", + strlen("Connection OK; checking if server in standby.")); + break; +#endif + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", strlen("Bad connection.")); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char *) PQparameterStatus(H->server, "client_encoding"), + (char *) PQparameterStatus(H->server, "is_superuser"), + (char *) PQparameterStatus(H->server, "session_authorization"), + (char *) PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) { + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) { + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) { + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sa|sss!", + &table_name, + &table_name_len, + &pg_rows, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + query_len = Z_STRLEN_P(tmp); + memcpy(query, Z_STRVAL_P(tmp), query_len); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) { + pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) { + pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t) ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) { + pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "s|sss!", + &table_name, + &table_name_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) { + pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + lfd = lo_creat(H->server, INV_READ | INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) { + pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + oid = (Oid) strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ | INV_WRITE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) { + pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid) strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) { + pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int) ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) { + pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) { + pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) { + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } + + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup); + + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + pdo_pgsql_cleanup_notice_callback(H); + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } + + return; + +cleanup: + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_fcc_dtor(&fcc); + } + RETURN_THROWS(); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) { + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = {pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL, /* get_gc */ + pdo_pgsql_scanner}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *p, *e; + zend_string *tmp_user, *tmp_pass; + smart_str conn_str = {0}; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_EXEC_POST | 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = !strstr((char *) dbh->data_source, "user=") ? _pdo_pgsql_escape_credentials(dbh->username) : NULL; + tmp_pass = !strstr((char *) dbh->data_source, "password=") ? _pdo_pgsql_escape_credentials(dbh->password) : NULL; + + smart_str_appends(&conn_str, dbh->data_source); + smart_str_append_printf(&conn_str, " connect_timeout=" ZEND_LONG_FMT, connect_timeout); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user) { + smart_str_append_printf(&conn_str, " user='%s'", ZSTR_VAL(tmp_user)); + } + + if (tmp_pass) { + smart_str_append_printf(&conn_str, " password='%s'", ZSTR_VAL(tmp_pass)); + } + smart_str_0(&conn_str); + + H->server = PQconnectdb(ZSTR_VAL(conn_str.s)); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + smart_str_free(&conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *) dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = {PDO_DRIVER_HEADER(pgsql), pdo_pgsql_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 0000000000..5bdea01edc --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,70 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: dd20abc5d8580d72b25bfb3c598b1ca54a501fcc */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c b/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c new file mode 100644 index 0000000000..5758b0920c --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c @@ -0,0 +1,525 @@ +/* Generated by re2c 3.1 */ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + + + +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy4; + case '$': goto yy6; + case '\'': goto yy7; + case '-': goto yy8; + case '/': goto yy9; + case ':': goto yy10; + case '?': goto yy11; + case 'E': + case 'e': goto yy13; + default: goto yy2; + } +yy1: + YYCURSOR = YYMARKER; + switch (yyaccept) { + case 0: goto yy5; + case 1: goto yy17; + case 2: goto yy23; + default: goto yy35; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case '"': + case '$': + case '\'': + case '-': + case '/': + case ':': + case '?': + case 'E': + case 'e': goto yy3; + default: goto yy2; + } +yy3: + { RET(PDO_PARSER_TEXT); } +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy15; +yy5: + { SKIP_ONE(PDO_PARSER_TEXT); } +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + switch (yych) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case ' ': + case '!': + case '"': + case '#': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '`': + case '{': + case '|': + case '}': + case '~': + case 0x7F: goto yy5; + case '$': goto yy18; + default: goto yy19; + } +yy7: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy21; +yy8: + yych = *++YYCURSOR; + switch (yych) { + case '-': goto yy24; + default: goto yy5; + } +yy9: + yych = *++YYCURSOR; + switch (yych) { + case '*': goto yy26; + default: goto yy5; + } +yy10: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy27; + case ':': goto yy29; + default: goto yy5; + } +yy11: + yych = *++YYCURSOR; + switch (yych) { + case '?': goto yy31; + default: goto yy12; + } +yy12: + { RET(PDO_PARSER_BIND_POS); } +yy13: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + switch (yych) { + case '\'': goto yy32; + default: goto yy5; + } +yy14: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy15: + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy16; + default: goto yy14; + } +yy16: + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '"': goto yy14; + default: goto yy17; + } +yy17: + { RET(PDO_PARSER_TEXT); } +yy18: + ++YYCURSOR; + { RET(PDO_PARSER_CUSTOM_QUOTE); } +yy19: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case ' ': + case '!': + case '"': + case '#': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '`': + case '{': + case '|': + case '}': + case '~': + case 0x7F: goto yy1; + case '$': goto yy18; + default: goto yy19; + } +yy20: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy21: + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy22; + default: goto yy20; + } +yy22: + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\'': goto yy20; + default: goto yy23; + } +yy23: + { RET(PDO_PARSER_TEXT); } +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\n': goto yy25; + default: goto yy24; + } +yy25: + { RET(PDO_PARSER_TEXT); } +yy26: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy33; + default: goto yy26; + } +yy27: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy27; + default: goto yy28; + } +yy28: + { RET(PDO_PARSER_BIND); } +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case ':': goto yy29; + default: goto yy30; + } +yy30: + { RET(PDO_PARSER_TEXT); } +yy31: + ++YYCURSOR; + { RET(PDO_PARSER_ESCAPED_QUESTION); } +yy32: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy34; + case '\\': goto yy36; + default: goto yy32; + } +yy33: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy33; + case '/': goto yy37; + default: goto yy26; + } +yy34: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\'': goto yy32; + default: goto yy35; + } +yy35: + { RET(PDO_PARSER_TEXT); } +yy36: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 0x00) goto yy1; + goto yy32; +yy37: + ++YYCURSOR; + goto yy25; +} + +} diff --git a/thirdparty/php84/pdo_pgsql/pgsql_statement.c b/thirdparty/php84/pdo_pgsql/pgsql_statement.c new file mode 100644 index 0000000000..0987b9825b --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,740 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 +#define FLOAT4LABEL "float4" +#define FLOAT4OID 700 +#define FLOAT8LABEL "float8" +#define FLOAT8OID 701 + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + bool server_obj_usable = + !Z_ISUNDEF(stmt->database_object_handle) && + IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) && + !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); + if (res) { + PQclear(res); + } + } + efree(S->stmt_name); + S->stmt_name = NULL; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if (S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* ensure that we free any previous unfetched results */ + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf( + &q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else if (S->stmt_name) { + /* using a prepared statement */ + + if (!S->is_prepared) { + stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, + S->stmt_name, + ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = 1; + PQclear(S->result); + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chance to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + PGresult *res; + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + S->result = PQexecPrepared(H->server, + S->stmt_name, + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + (const char **) S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, + ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char **) S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long) PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: + /* decode name from $1, $2 into 0, 1 etc. */ + if (param->name) { + if (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && + (namevar = zend_hash_find_ptr(stmt->bound_param_map, param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!stmt->bound_param_map) { + return 1; + } + if (!S->param_values) { + S->param_values = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(char *)); + S->param_lengths = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); + S->param_formats = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); + S->param_types = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(Oid)); + } + if (param->paramno >= 0) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char *) &P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || Z_TYPE_P(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; + S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; + } + } + break; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + spprintf(&ori_str, 0, "NEXT"); + break; + case PDO_FETCH_ORI_PRIOR: + spprintf(&ori_str, 0, "BACKWARD"); + break; + case PDO_FETCH_ORI_FIRST: + spprintf(&ori_str, 0, "FIRST"); + break; + case PDO_FETCH_ORI_LAST: + spprintf(&ori_str, 0, "LAST"); + break; + case PDO_FETCH_ORI_ABS: + spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); + break; + case PDO_FETCH_ORI_REL: + spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); + break; + default: + return 0; + } + + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + case FLOAT4OID: + case FLOAT8OID: + if (strncmp(ptr, "Infinity", len) == 0) { + ZVAL_DOUBLE(result, ZEND_INFINITY); + } else if (strncmp(ptr, "-Infinity", len) == 0) { + ZVAL_DOUBLE(result, -ZEND_INFINITY); + } else if (strncmp(ptr, "NaN", len) == 0) { + ZVAL_DOUBLE(result, ZEND_NAN); + } else { + ZVAL_DOUBLE(result, zend_strtod(ptr, NULL)); + } + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid) strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *) PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char *pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) { + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + PGresult *res; + char *q = NULL; + ExecStatusType status; + Oid table_oid; + char *table_name = NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case FLOAT4OID: + add_assoc_string(return_value, "native_type", FLOAT4LABEL); + break; + case FLOAT8OID: + add_assoc_string(return_value, "native_type", FLOAT8LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) { + return 1; +} + +static int pgsql_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + switch (attr) { +#ifdef HAVE_PG_RESULT_MEMORY_SIZE + case PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE: + if (stmt->executed) { + ZVAL_LONG(val, PQresultMemorySize(S->result)); + } else { + char *tmp; + spprintf(&tmp, 0, "statement '%s' has not been executed yet", S->stmt_name); + + pdo_pgsql_error_stmt_msg(stmt, 0, "HY000", tmp); + efree(tmp); + + ZVAL_NULL(val); + } + return 1; +#endif + + default: + (void) S; + return 0; + } +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = {pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + pgsql_stmt_get_attr, + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 0000000000..c48a6bd927 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,129 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached : 1; + unsigned _reserved : 31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + bool emulate_prepares; + bool disable_native_prepares; /* deprecated since 5.6 */ + bool disable_prepares; + HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + +extern int _pdo_pgsql_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d, e, z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d, e, m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s, e, z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, + PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H); + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php85/pdo_firebird/CREDITS b/thirdparty/php85/pdo_firebird/CREDITS new file mode 100644 index 0000000000..60b917415d --- /dev/null +++ b/thirdparty/php85/pdo_firebird/CREDITS @@ -0,0 +1,2 @@ +Firebird driver for PDO +Ard Biesheuvel diff --git a/thirdparty/php85/pdo_firebird/firebird_driver.c b/thirdparty/php85/pdo_firebird/firebird_driver.c new file mode 100644 index 0000000000..c8a6c186e7 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/firebird_driver.c @@ -0,0 +1,1439 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID >= 80500 + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include "php.h" +#include "zend_exceptions.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*, + HashTable*); +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh); + +const char CHR_LETTER = 1; +const char CHR_DIGIT = 2; +const char CHR_IDENT = 4; +const char CHR_QUOTE = 8; +const char CHR_WHITE = 16; +const char CHR_HEX = 32; +const char CHR_INTRODUCER = 64; + +static const char classes_array[] = { + /* 000 */ 0, + /* 001 */ 0, + /* 002 */ 0, + /* 003 */ 0, + /* 004 */ 0, + /* 005 */ 0, + /* 006 */ 0, + /* 007 */ 0, + /* 008 */ 0, + /* 009 */ 16, /* CHR_WHITE */ + /* 010 */ 16, /* CHR_WHITE */ + /* 011 */ 0, + /* 012 */ 0, + /* 013 */ 16, /* CHR_WHITE */ + /* 014 */ 0, + /* 015 */ 0, + /* 016 */ 0, + /* 017 */ 0, + /* 018 */ 0, + /* 019 */ 0, + /* 020 */ 0, + /* 021 */ 0, + /* 022 */ 0, + /* 023 */ 0, + /* 024 */ 0, + /* 025 */ 0, + /* 026 */ 0, + /* 027 */ 0, + /* 028 */ 0, + /* 029 */ 0, + /* 030 */ 0, + /* 031 */ 0, + /* 032 */ 16, /* CHR_WHITE */ + /* 033 ! */ 0, + /* 034 " */ 8, /* CHR_QUOTE */ + /* 035 # */ 0, + /* 036 $ */ 4, /* CHR_IDENT */ + /* 037 % */ 0, + /* 038 & */ 0, + /* 039 ' */ 8, /* CHR_QUOTE */ + /* 040 ( */ 0, + /* 041 ) */ 0, + /* 042 * */ 0, + /* 043 + */ 0, + /* 044 , */ 0, + /* 045 - */ 0, + /* 046 . */ 0, + /* 047 / */ 0, + /* 048 0 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 049 1 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 050 2 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 051 3 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 052 4 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 053 5 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 054 6 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 055 7 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 056 8 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 057 9 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 058 : */ 0, + /* 059 ; */ 0, + /* 060 < */ 0, + /* 061 = */ 0, + /* 062 > */ 0, + /* 063 ? */ 0, + /* 064 @ */ 0, + /* 065 A */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 066 B */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 067 C */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 068 D */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 069 E */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 070 F */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 071 G */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 072 H */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 073 I */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 074 J */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 075 K */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 076 L */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 077 M */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 078 N */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 079 O */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 080 P */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 081 Q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 082 R */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 083 S */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 084 T */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 085 U */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 086 V */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 087 W */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 088 X */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 089 Y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 090 Z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 091 [ */ 0, + /* 092 \ */ 0, + /* 093 ] */ 0, + /* 094 ^ */ 0, + /* 095 _ */ 68, /* CHR_IDENT | CHR_INTRODUCER */ + /* 096 ` */ 0, + /* 097 a */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 098 b */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 099 c */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 100 d */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 101 e */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 102 f */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 103 g */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 104 h */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 105 i */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 106 j */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 107 k */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 108 l */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 109 m */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 110 n */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 111 o */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 112 p */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 113 q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 114 r */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 115 s */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 116 t */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 117 u */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 118 v */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 119 w */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 120 x */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 121 y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 122 z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 123 { */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 124 | */ 0, + /* 125 } */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 126 ~ */ 0, + /* 127 */ 0 +}; + +static inline char php_firebird_classes(char idx) +{ + unsigned char uidx = (unsigned char) idx; + if (uidx > 127) return 0; + return classes_array[uidx]; +} + +typedef enum { + ttNone, + ttWhite, + ttComment, + ttBrokenComment, + ttString, + ttParamMark, + ttIdent, + ttOther +} FbTokenType; + +static FbTokenType php_firebird_get_token(const char** begin, const char* end) +{ + FbTokenType ret = ttNone; + const char* p = *begin; + + char c = *p++; + switch (c) + { + case ':': + case '?': + ret = ttParamMark; + break; + + case '\'': + case '"': + while (p < end) + { + if (*p++ == c) + { + ret = ttString; + break; + } + } + break; + + case '/': + if (p < end && *p == '*') + { + ret = ttBrokenComment; + p++; + while (p < end) + { + if (*p++ == '*' && p < end && *p == '/') + { + p++; + ret = ttComment; + break; + } + } + } + else { + ret = ttOther; + } + break; + + case '-': + if (p < end && *p == '-') + { + while (++p < end) + { + if (*p == '\r') + { + p++; + if (p < end && *p == '\n') + p++; + break; + } + else if (*p == '\n') + break; + } + + ret = ttComment; + } + else + ret = ttOther; + break; + + default: + if (php_firebird_classes(c) & CHR_DIGIT) + { + while (p < end && (php_firebird_classes(*p) & CHR_DIGIT)) + p++; + ret = ttOther; + } + else if (php_firebird_classes(c) & CHR_IDENT) + { + while (p < end && (php_firebird_classes(*p) & CHR_IDENT)) + p++; + ret = ttIdent; + } + else if (php_firebird_classes(c) & CHR_WHITE) + { + while (p < end && (php_firebird_classes(*p) & CHR_WHITE)) + p++; + ret = ttWhite; + } + else + { + while (p < end && !(php_firebird_classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) && + (*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') && + (*p != '\'') && (*p != '"')) + { + p++; + } + ret = ttOther; + } + } + + *begin = p; + return ret; +} + +static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTable* named_params) +{ + bool passAsIs = true, execBlock = false; + zend_long pindex = -1; + char pname[254], ident[253], ident2[253]; + unsigned int l; + const char* p = ZSTR_VAL(sql), * end = ZSTR_VAL(sql) + ZSTR_LEN(sql); + const char* start = p; + FbTokenType tok = php_firebird_get_token(&p, end); + + const char* i = start; + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i = p; + tok = php_firebird_get_token(&p, end); + } + + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + /* skip leading comments ?? */ + start = i; + l = p - i; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, i, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "EXECUTE")) + { + /* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */ + /* However, in EXECUTE BLOCK this is done in a special way. */ + const char* i2 = p; + tok = php_firebird_get_token(&p, end); + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i2 = p; + tok = php_firebird_get_token(&p, end); + } + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + l = p - i2; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident2, i2, l); + ident2[l] = '\0'; + execBlock = !strcasecmp(ident2, "BLOCK"); + passAsIs = false; + } + else + { + /* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */ + /* If CTEs are present in the query, they begin with the WITH keyword. */ + passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") && + strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") && + strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH"); + } + + if (passAsIs) + { + strcpy(sql_out, ZSTR_VAL(sql)); + return 1; + } + + strncat(sql_out, start, p - start); + + while (p < end) + { + start = p; + tok = php_firebird_get_token(&p, end); + switch (tok) + { + case ttParamMark: + tok = php_firebird_get_token(&p, end); + if (tok == ttIdent /*|| tok == ttString*/) + { + ++pindex; + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + /* + symbol ":" */ + if (l > 253) { + return 0; + } + strncpy(pname, start, l); + pname[l] = '\0'; + + if (named_params) { + zval tmp; + ZVAL_LONG(&tmp, pindex); + zend_hash_str_update(named_params, pname, l, &tmp); + } + + strcat(sql_out, "?"); + } + else + { + if (strncmp(start, "?", 1)) { + /* Execute statement preprocess SQL error */ + /* Parameter name expected */ + return 0; + } + ++pindex; + strncat(sql_out, start, p - start); + } + break; + + case ttIdent: + if (execBlock) + { + /* In the EXECUTE BLOCK statement, processing must be */ + /* carried out up to the keyword AS. */ + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, start, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "AS")) + { + strncat(sql_out, start, end - start); + return 1; + } + } + /* TODO Check this is correct? */ + ZEND_FALLTHROUGH; + + case ttWhite: + case ttComment: + case ttString: + case ttOther: + strncat(sql_out, start, p - start); + break; + + case ttBrokenComment: + { + /* Execute statement preprocess SQL error */ + /* Unclosed comment found near ''@1'' */ + return 0; + } + break; + + + case ttNone: + /* Execute statement preprocess SQL error */ + return 0; + break; + } + } + return 1; +} + +#if FB_API_VER >= 40 +/* set coercing a data type */ +static void set_coercing_output_data_types(XSQLDA* sqlda) +{ + /* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */ + /* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)). */ + /* In any case, at this data types can only be mapped to strings. */ + /* This function allows you to ensure minimal performance of queries if they contain columns of the above types. */ + unsigned int i; + short dtype; + short nullable; + XSQLVAR* var; + unsigned fb_client_version = fb_get_client_version(); + unsigned fb_client_major_version = (fb_client_version >> 8) & 0xFF; + for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) { + dtype = (var->sqltype & ~1); /* drop flag bit */ + nullable = (var->sqltype & 1); + switch(dtype) { + case SQL_INT128: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + var->sqlscale = 0; + break; + + case SQL_DEC16: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 24; + break; + + case SQL_DEC34: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 43; + break; + + case SQL_TIMESTAMP_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 58; + } + break; + + case SQL_TIME_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + } + break; + + default: + break; + } + } +} +#endif + +/* map driver specific error message to PDO error */ +void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len) /* {{{ */ +{ + pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code; + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_error_info *einfo = &H->einfo; + int sqlcode = -999; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + einfo->errmsg_length = 0; + } + + if (H->isc_status[0] == 1 && H->isc_status[1] > 0) { + char buf[512]; + size_t buf_size = sizeof(buf), read_len = 0; + ssize_t tmp_len; + const ISC_STATUS *s = H->isc_status; + sqlcode = isc_sqlcode(s); + + while ((buf_size > (read_len + 1)) && (tmp_len = fb_interpret(&buf[read_len], (buf_size - read_len - 1), &s)) && tmp_len > 0) { + read_len += tmp_len; + buf[read_len++] = ' '; + } + + /* remove last space */ + if (read_len) { + buf[read_len--] = '\0'; + } + + einfo->errmsg_length = read_len; + einfo->errmsg = pestrndup(buf, read_len, dbh->is_persistent); + + char sqlstate[sizeof(pdo_error_type)]; + fb_sqlstate(sqlstate, H->isc_status); + if (strlen(sqlstate) < sizeof(pdo_error_type)) { + strcpy(*error_code, sqlstate); + goto end; + } + } else if (msg && msg_len) { + einfo->errmsg_length = msg_len; + einfo->errmsg = pestrndup(msg, einfo->errmsg_length, dbh->is_persistent); + } + + if (state && state_len && state_len < sizeof(pdo_error_type)) { + memcpy(*error_code, state, state_len + 1); + } else { + memcpy(*error_code, "HY000", sizeof("HY000")); + } + +end: + einfo->sqlcode = sqlcode; + if (!dbh->methods) { + pdo_throw_exception(0, einfo->errmsg, error_code); + } +} +/* }}} */ + +/* called by PDO to close a db handle */ +static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (H->tr) { + if (dbh->auto_commit) { + php_firebird_commit_transaction(dbh, /* retain */ false); + } else { + php_firebird_rollback_transaction(dbh); + } + } + H->in_manually_txn = false; + + /* isc_detach_database returns 0 on success, 1 on failure. */ + if (H->db && isc_detach_database(H->isc_status, &H->db)) { + php_firebird_error(dbh); + } + + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +/* called by PDO to prepare an SQL query */ +static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ + pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_stmt *S = NULL; + HashTable *np; + + do { + isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER; + XSQLDA num_sqlda; + static char const info[] = { isc_info_sql_stmt_type }; + char result[8]; + + num_sqlda.version = PDO_FB_SQLDA_VERSION; + num_sqlda.sqln = 1; + + ALLOC_HASHTABLE(np); + zend_hash_init(np, 8, NULL, NULL, 0); + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &num_sqlda, &s, np)) { + break; + } + + /* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */ + S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld)); + S->H = H; + S->stmt = s; + S->out_sqlda.version = PDO_FB_SQLDA_VERSION; + S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld; + S->named_params = np; + + /* determine the statement type */ + if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result), + result)) { + break; + } + S->statement_type = result[3]; + + /* fill the output sqlda with information about the prepared query */ + if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + php_firebird_error(dbh); + break; + } + +#if FB_API_VER >= 40 + /* set coercing a data type */ + set_coercing_output_data_types(&S->out_sqlda); +#endif + + /* allocate the input descriptors */ + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) { + break; + } + + if (num_sqlda.sqld) { + S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld)); + S->in_sqlda->version = PDO_FB_SQLDA_VERSION; + S->in_sqlda->sqln = num_sqlda.sqld; + + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* make all parameters nullable */ + unsigned int i; + XSQLVAR* var; + for (i = 0, var = S->in_sqlda->sqlvar; i < S->in_sqlda->sqld; i++, var++) { + /* The low bit of sqltype indicates that the parameter can take a NULL value */ + var->sqltype |= 1; + } + } + + stmt->driver_data = S; + stmt->methods = &swoole_firebird_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + + return true; + + } while (0); + + php_firebird_error(dbh); + + zend_hash_destroy(np); + FREE_HASHTABLE(np); + + if (S) { + if (S->in_sqlda) { + efree(S->in_sqlda); + } + efree(S); + } + + return false; +} +/* }}} */ + +/* called by PDO to execute a statement that doesn't produce a result set */ +static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER; + static char const info_count[] = { isc_info_sql_records }; + char result[64]; + int ret = 0; + XSQLDA in_sqlda, out_sqlda; + + /* TODO no placeholders in exec() for now */ + in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION; + in_sqlda.sqld = out_sqlda.sqld = 0; + out_sqlda.sqln = 1; + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &out_sqlda, &stmt, 0)) { + return -1; + } + + /* execute the statement */ + if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + /* find out how many rows were affected */ + if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count), + sizeof(result), result)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1],2); + + if (result_size > sizeof(result)) { + ret = -1; + goto free_statement; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short)isc_vax_integer(&result[i+1],2); + /* bail out on bad len */ + if (len != 1 && len != 2 && len != 4) { + ret = -1; + goto free_statement; + } + if (result[i] != isc_info_req_select_count) { + ret += isc_vax_integer(&result[i+3],len); + } + i += len+3; + } + } + + if (dbh->auto_commit && !H->in_manually_txn) { + if (!php_firebird_commit_transaction(dbh, /* retain */ true)) { + ret = -1; + } + } + +free_statement: + + if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) { + php_firebird_error(dbh); + } + + return ret; +} +/* }}} */ + +/* called by the PDO SQL parser to add quotes to values that are copied into SQL */ +static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + size_t qcount = 0; + char const *co, *l, *r; + char *c; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* Firebird only requires single quotes to be doubled if string lengths are used */ + /* count the number of ' characters */ + for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + + if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { + return NULL; + } + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted_str = zend_string_alloc(quotedlen, 0); + c = ZSTR_VAL(quoted_str); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = ZSTR_VAL(unquoted); (r = strchr(l,'\'')); l = r+1) { + strncpy(c, l, r-l+1); + c += (r-l+1); + /* add the second quote */ + *c++ = '\''; + } + + /* copy the remainder */ + strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1); + ZSTR_VAL(quoted_str)[quotedlen-1] = '\''; + ZSTR_VAL(quoted_str)[quotedlen] = '\0'; + + return quoted_str; +} +/* }}} */ + +/* php_firebird_begin_transaction */ +static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* isc_xxx are all 1 byte. */ + char tpb[4] = { isc_tpb_version3 }; + size_t tpb_size; + + /* access mode. writable or readonly */ + tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read; + + if (is_auto_commit_txn) { + /* + * In autocommit mode, we need to always read the latest information, so we set `read committed`. + */ + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + } else { + switch (H->txn_isolation_level) { + /* + * firebird's `read committed` has the option to wait until other transactions + * commit or rollback if there is indeterminate data. + * Introducing too many configuration values at once can cause confusion, so + * we don't support in PDO that feature yet. + */ + case PDO_FB_READ_COMMITTED: + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + break; + + case PDO_FB_SERIALIZABLE: + tpb[2] = isc_tpb_consistency; + tpb_size = 3; + break; + + case PDO_FB_REPEATABLE_READ: + default: + tpb[2] = isc_tpb_concurrency; + tpb_size = 3; + break; + } + } + + if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to start a transaction */ +static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * If in autocommit mode and in transaction, we will need to close the transaction once. + */ + if (dbh->auto_commit && H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) { + return false; + } + H->in_manually_txn = true; + return true; +} +/* }}} */ + +/* php_firebird_commit_transaction */ +bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * `retaining` keeps the transaction open without closing it. + * + * firebird needs to always have a transaction open to emulate autocommit mode, + * and in autocommit mode it keeps the transaction open. + * + * Same as close and then begin again, but use retain to save overhead. + */ + if (retain) { + if (isc_commit_retaining(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } else { + if (isc_commit_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } + return true; +} +/* }}} */ + +/* called by PDO to commit a transaction */ +static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (!php_firebird_commit_transaction(dbh, /*release*/ false)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = false; + return true; +} +/* }}} */ + +/* php_firebird_rollback_transaction */ +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (isc_rollback_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to rollback a transaction */ +static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (!php_firebird_rollback_transaction(dbh)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = false; + return true; +} +/* }}} */ + +/* used by prepare and exec to allocate a statement handle and prepare the SQL */ +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql, + XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + char *new_sql; + + /* allocate the statement */ + if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { + php_firebird_error(dbh); + return 0; + } + + /* in order to support named params, which Firebird itself doesn't, + we need to replace :foo by ?, and store the name we just replaced */ + new_sql = emalloc(ZSTR_LEN(sql)+1); + new_sql[0] = '\0'; + if (!php_firebird_preprocess(sql, new_sql, named_params)) { + php_firebird_error_with_info(dbh, "07000", strlen("07000"), NULL, 0); + efree(new_sql); + return 0; + } + + /* prepare the statement */ + if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) { + php_firebird_error(dbh); + efree(new_sql); + return 0; + } + + efree(new_sql); + return 1; +} + +/* called by PDO to set a driver-specific dbh attribute */ +static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + bool bval; + zend_long lval; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + /* change auto commit mode with an open transaction is illegal, because + we won't know what to do with it */ + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (dbh->auto_commit ^ bval) { + if (bval) { + /* + * change to auto commit mode. + * If the transaction is not started, start it. + */ + if (!H->tr) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + } else { + /* + * change to not auto commit mode. + * close the transaction if exists. + */ + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + } + dbh->auto_commit = bval; + } + } + return true; + + case PDO_ATTR_FETCH_TABLE_NAMES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->fetch_table_names = bval; + return true; + + case PDO_FB_ATTR_DATE_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + H->date_format = NULL; + } + H->date_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIME_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + H->time_format = NULL; + } + H->time_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + H->timestamp_format = NULL; + } + H->timestamp_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->txn_isolation_level != lval) { + if (lval == PDO_FB_READ_COMMITTED || + lval == PDO_FB_REPEATABLE_READ || + lval == PDO_FB_SERIALIZABLE + ) { + /* + * Autocommit mode is always read-committed, so this setting is used the next time + * a manual transaction starts. Therefore, there is no need to immediately reopen the transaction. + */ + H->txn_isolation_level = lval; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + return false; + } + } + } + return true; + + case PDO_FB_WRITABLE_TRANSACTION: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->is_writable_txn != bval) { + H->is_writable_txn = bval; + if (dbh->auto_commit) { + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + } + } + return true; + } + return false; +} +/* }}} */ + +#define INFO_BUF_LEN 512 + +/* callback to used to report database server info */ +static void php_firebird_info_cb(void *arg, char const *s) /* {{{ */ +{ + if (arg) { + if (*(char*)arg) { /* second call */ + strlcat(arg, " ", INFO_BUF_LEN); + } + strlcat(arg, s, INFO_BUF_LEN); + } +} +/* }}} */ + +/* called by PDO to get a driver-specific dbh attribute */ +static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + switch (attr) { + char tmp[INFO_BUF_LEN]; + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val,dbh->auto_commit); + return 1; + + case PDO_ATTR_CONNECTION_STATUS: + ZVAL_BOOL(val, !isc_version(&H->db, php_firebird_info_cb, NULL)); + return 1; + + case PDO_ATTR_CLIENT_VERSION: + isc_get_client_version(tmp); + ZVAL_STRING(val, tmp); + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + *tmp = 0; + + if (!isc_version(&H->db, php_firebird_info_cb, (void*)tmp)) { + ZVAL_STRING(val, tmp); + return 1; + } + return -1; + + case PDO_ATTR_FETCH_TABLE_NAMES: + ZVAL_BOOL(val, H->fetch_table_names); + return 1; + + case PDO_FB_ATTR_DATE_FORMAT: + ZVAL_STRING(val, H->date_format ? H->date_format : PDO_FB_DEF_DATE_FMT); + return 1; + + case PDO_FB_ATTR_TIME_FORMAT: + ZVAL_STRING(val, H->time_format ? H->time_format : PDO_FB_DEF_TIME_FMT); + return 1; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + ZVAL_STRING(val, H->timestamp_format ? H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT); + return 1; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + ZVAL_LONG(val, H->txn_isolation_level); + return 1; + + case PDO_FB_WRITABLE_TRANSACTION: + ZVAL_BOOL(val, H->is_writable_txn); + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to check liveness */ +static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* fb_ping return 0 if the connection is alive */ + return fb_ping(H->isc_status, &H->db) ? FAILURE : SUCCESS; +} +/* }}} */ + +/* called by PDO to retrieve driver-specific information about an error that has occurred */ +static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (H->einfo.sqlcode != IS_NULL) { + add_next_index_long(info, H->einfo.sqlcode); + } + if (H->einfo.errmsg && H->einfo.errmsg_length) { + add_next_index_stringl(info, H->einfo.errmsg, H->einfo.errmsg_length); + } +} +/* }}} */ + +/* {{{ firebird_in_manually_transaction */ +static bool pdo_firebird_in_manually_transaction(pdo_dbh_t *dbh) +{ + /** + * we can tell if a transaction exists now by checking H->tr, + * but which will always be true in autocommit mode. + * So this function checks if there is currently a "manually begun transaction". + */ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + return H->in_manually_txn; +} +/* }}} */ + +static const struct pdo_dbh_methods firebird_methods = { /* {{{ */ + firebird_handle_closer, + firebird_handle_preparer, + firebird_handle_doer, + firebird_handle_quoter, + firebird_handle_manually_begin, + firebird_handle_manually_commit, + firebird_handle_manually_rollback, + pdo_firebird_set_attribute, + NULL, /* last_id not supported */ + pdo_firebird_fetch_error_func, + pdo_firebird_get_attribute, + pdo_firebird_check_liveness, + NULL, /* get driver methods */ + NULL, /* request shutdown */ + pdo_firebird_in_manually_transaction, + NULL, /* get gc */ + NULL /* scanner */ +}; +/* }}} */ + +/* the driver-specific PDO handle constructor */ +static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + struct pdo_data_src_parser vars[] = { + { "dbname", NULL, 0 }, + { "charset", NULL, 0 }, + { "role", NULL, 0 }, + { "dialect", "3", 0 }, + { "user", NULL, 0 }, + { "password", NULL, 0 } + }; + int i, ret = 0; + short buf_len = 256, dpb_len; + + pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent); + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6); + + if (!dbh->username && vars[4].optval) { + dbh->username = pestrdup(vars[4].optval, dbh->is_persistent); + } + + if (!dbh->password && vars[5].optval) { + dbh->password = pestrdup(vars[5].optval, dbh->is_persistent); + } + + H->in_manually_txn = false; + H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1); + zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ); + if (txn_isolation_level == PDO_FB_READ_COMMITTED || + txn_isolation_level == PDO_FB_REPEATABLE_READ || + txn_isolation_level == PDO_FB_SERIALIZABLE + ) { + H->txn_isolation_level = txn_isolation_level; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + ret = 0; + } + + do { + static char const dpb_flags[] = { + isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name }; + char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval }; + char dpb_buffer[256] = { isc_dpb_version1 }, *dpb; + + dpb = dpb_buffer + 1; + + /* loop through all the provided arguments and set dpb fields accordingly */ + for (i = 0; i < sizeof(dpb_flags); ++i) { + if (dpb_values[i] && buf_len > 0) { + dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]), + dpb_values[i]); + dpb += dpb_len; + buf_len -= dpb_len; + } + } + + H->sql_dialect = PDO_FB_DIALECT; + if (vars[3].optval) { + H->sql_dialect = atoi(vars[3].optval); + } + + /* fire it up baby! */ + if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) { + break; + } + + dbh->methods = &firebird_methods; + dbh->native_case = PDO_CASE_UPPER; + dbh->alloc_own_columns = 1; + + ret = 1; + + } while (0); + + for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!dbh->methods) { + char errmsg[512]; + const ISC_STATUS *s = H->isc_status; + fb_interpret(errmsg, sizeof(errmsg),&s); + zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%" PRIiPTR "] %s", + "HY000", H->isc_status[1], errmsg); + } + + if (ret && dbh->auto_commit && !H->tr) { + ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true); + } + + if (!ret) { + firebird_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + + +const pdo_driver_t swoole_pdo_firebird_driver = { /* {{{ */ + PDO_DRIVER_HEADER(firebird), + pdo_firebird_handle_factory +}; +/* }}} */ + +#endif diff --git a/thirdparty/php85/pdo_firebird/firebird_statement.c b/thirdparty/php85/pdo_firebird/firebird_statement.c new file mode 100644 index 0000000000..196b6fa509 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/firebird_statement.c @@ -0,0 +1,965 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID >= 80500 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +#include + +#define READ_AND_RETURN_USING_MEMCPY(type, sqldata) do { \ + type ret; \ + memcpy(&ret, sqldata, sizeof(ret)); \ + return ret; \ + } while (0); + +static zend_always_inline ISC_INT64 php_get_isc_int64_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_INT64, sqldata); +} + +static zend_always_inline ISC_LONG php_get_isc_long_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_LONG, sqldata); +} + +static zend_always_inline double php_get_double_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(double, sqldata); +} + +static zend_always_inline float php_get_float_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(float, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP php_get_isc_timestamp_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP, sqldata); +} + +static zend_always_inline ISC_QUAD php_get_isc_quad_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_QUAD, sqldata); +} + +#if FB_API_VER >= 40 + +static zend_always_inline ISC_TIME_TZ php_get_isc_time_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIME_TZ, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP_TZ php_get_isc_timestamp_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP_TZ, sqldata); +} + +/* fetch formatted time with time zone */ +static int get_formatted_time_tz(pdo_stmt_t *stmt, const ISC_TIME_TZ* timeTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned hours = 0, minutes = 0, seconds = 0, fractions = 0; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIME time; + char timeBuf[80] = {0}; + if (fb_decode_time_tz(S->H->isc_status, timeTz, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_sql_time(&time, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + + size_t len = strftime(timeBuf, sizeof(timeBuf), fmt, &t); + if (len == 0) { + return 1; + } + + zend_string *time_tz_str = zend_strpprintf(0, "%s %s", timeBuf, timeZoneBuffer); + ZVAL_NEW_STR(result, time_tz_str); + return 0; +} + +/* fetch formatted timestamp with time zone */ +static int get_formatted_timestamp_tz(pdo_stmt_t *stmt, const ISC_TIMESTAMP_TZ* timestampTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned year, month, day, hours, minutes, seconds, fractions; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIMESTAMP ts; + char timestampBuf[80] = {0}; + if (fb_decode_timestamp_tz(S->H->isc_status, timestampTz, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + ts.timestamp_date = fb_encode_date(year, month, day); + ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_timestamp(&ts, &t); + + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + + size_t len = strftime(timestampBuf, sizeof(timestampBuf), fmt, &t); + if (len == 0) { + return 1; + } + + zend_string *timestamp_tz_str = zend_strpprintf(0, "%s %s", timestampBuf, timeZoneBuffer); + ZVAL_NEW_STR(result, timestamp_tz_str); + return 0; +} + +#endif + +/* free the allocated space for passing field values to the db and back */ +static void php_firebird_free_sqlda(XSQLDA const *sqlda) /* {{{ */ +{ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR const *var = &sqlda->sqlvar[i]; + + if (var->sqlind) { + efree(var->sqlind); + } + } +} +/* }}} */ + +/* called by PDO to clean up a statement handle */ +static int pdo_firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + int result = 1; + + /* release the statement. + * Note: if the server object is already gone then the statement was closed already as well. */ + if (php_pdo_stmt_valid_db_obj_handle(stmt) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { + php_firebird_error_stmt(stmt); + result = 0; + } + + zend_hash_destroy(S->named_params); + FREE_HASHTABLE(S->named_params); + + /* clean up the input descriptor */ + if (S->in_sqlda) { + php_firebird_free_sqlda(S->in_sqlda); + efree(S->in_sqlda); + } + + php_firebird_free_sqlda(&S->out_sqlda); + efree(S); + + return result; +} +/* }}} */ + +/* called by PDO to execute a prepared query */ +static int pdo_firebird_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + zend_ulong affected_rows = 0; + static char info_count[] = {isc_info_sql_records}; + char result[64]; + + do { + /* named or open cursors should be closed first */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(H->isc_status, &S->stmt, DSQL_close)) { + break; + } + S->cursor_open = 0; + + /* allocate storage for the output data */ + if (S->out_sqlda.sqld) { + unsigned int i; + for (i = 0; i < S->out_sqlda.sqld; i++) { + XSQLVAR *var = &S->out_sqlda.sqlvar[i]; + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)ecalloc(1, var->sqllen + 2 * sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + } + + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + if (isc_dsql_execute2(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda, &S->out_sqlda)) { + break; + } + } else if (isc_dsql_execute(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* Determine how many rows have changed. In this case we are + * only interested in rows changed, not rows retrieved. That + * should be handled by the client when fetching. */ + stmt->row_count = affected_rows; + + switch (S->statement_type) { + case isc_info_sql_stmt_insert: + case isc_info_sql_stmt_update: + case isc_info_sql_stmt_delete: + case isc_info_sql_stmt_exec_procedure: + if (isc_dsql_sql_info(H->isc_status, &S->stmt, sizeof ( info_count), + info_count, sizeof(result), result)) { + break; + } + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1], 2); + if (result_size > sizeof(result)) { + goto error; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short) isc_vax_integer(&result[i + 1], 2); + if (len != 1 && len != 2 && len != 4) { + goto error; + } + if (result[i] != isc_info_req_select_count) { + affected_rows += isc_vax_integer(&result[i + 3], len); + } + i += len + 3; + } + stmt->row_count = affected_rows; + } + /* TODO Dead code or assert one of the previous cases are hit? */ + default: + ; + } + + if (stmt->dbh->auto_commit && !S->H->in_manually_txn && !php_firebird_commit_transaction(stmt->dbh, /* retain */ true)) { + break; + } + + *S->name = 0; + S->cursor_open = S->out_sqlda.sqln && (S->statement_type != isc_info_sql_stmt_exec_procedure); + S->exhausted = !S->out_sqlda.sqln; /* There are data to fetch */ + + return 1; + } while (0); + +error: + php_firebird_error_stmt(stmt); + + return 0; +} +/* }}} */ + +/* called by PDO to fetch the next row from a statement */ +static int pdo_firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + + if (!stmt->executed) { + const char *msg = "Cannot fetch from a closed cursor"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } else if (!S->exhausted) { + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + stmt->row_count = 1; + S->exhausted = 1; + return 1; + } + if (isc_dsql_fetch(H->isc_status, &S->stmt, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + if (H->isc_status[0] && H->isc_status[1]) { + php_firebird_error_stmt(stmt); + } + S->exhausted = 1; + return 0; + } + stmt->row_count++; + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to retrieve information about the fields being returned */ +static int pdo_firebird_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + int colname_len; + char *cp; + + if ((var->sqltype & ~1) == SQL_TEXT) { + var->sqltype = SQL_VARYING | (var->sqltype & 1); + } + colname_len = (S->H->fetch_table_names && var->relname_length) + ? (var->aliasname_length + var->relname_length + 1) + : (var->aliasname_length); + col->precision = -var->sqlscale; + col->maxlen = var->sqllen; + col->name = zend_string_alloc(colname_len, 0); + cp = ZSTR_VAL(col->name); + if (colname_len > var->aliasname_length) { + memmove(cp, var->relname, var->relname_length); + cp += var->relname_length; + *cp++ = '.'; + } + memmove(cp, var->aliasname, var->aliasname_length); + *(cp+var->aliasname_length) = '\0'; + + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt *) stmt->driver_data; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + + enum pdo_param_type param_type; + if (var->sqlscale < 0) { + param_type = PDO_PARAM_STR; + } else { + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: +#if SIZEOF_ZEND_LONG >= 8 + case SQL_INT64: +#endif + param_type = PDO_PARAM_INT; + break; + case SQL_BOOLEAN: + param_type = PDO_PARAM_BOOL; + break; + default: + param_type = PDO_PARAM_STR; + break; + } + } + + array_init(return_value); + add_assoc_long(return_value, "pdo_type", param_type); + return 1; +} + +/* fetch a blob into a fetch buffer */ +static int php_firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QUAD *blob_id) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle blobh = PDO_FIREBIRD_HANDLE_INITIALIZER; + char const bl_item = isc_info_blob_total_length; + char bl_info[20]; + unsigned short i; + int retval = 0; + size_t len = 0; + + if (isc_open_blob(H->isc_status, &H->db, &H->tr, &blobh, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (isc_blob_info(H->isc_status, &blobh, 1, const_cast(&bl_item), + sizeof(bl_info), bl_info)) { + php_firebird_error_stmt(stmt); + goto fetch_blob_end; + } + + /* find total length of blob's data */ + for (i = 0; i < sizeof(bl_info); ) { + unsigned short item_len; + char item = bl_info[i++]; + + if (item == isc_info_end || item == isc_info_truncated || item == isc_info_error + || i >= sizeof(bl_info)) { + const char *msg = "Couldn't determine BLOB size"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + + item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + + if (item == isc_info_blob_total_length) { + len = isc_vax_integer(&bl_info[i+2], item_len); + break; + } + i += item_len+2; + } + + /* we've found the blob's length, now fetch! */ + + if (len) { + zend_ulong cur_len; + unsigned short seg_len; + ISC_STATUS stat; + zend_string *str; + + /* prevent overflow */ + if (len > ZSTR_MAX_LEN) { + result = 0; + goto fetch_blob_end; + } + + str = zend_string_alloc(len, 0); + + for (cur_len = stat = 0; (!stat || stat == isc_segment) && cur_len < len; cur_len += seg_len) { + + unsigned short chunk_size = (len - cur_len) > USHRT_MAX ? USHRT_MAX + : (unsigned short)(len - cur_len); + + stat = isc_get_segment(H->isc_status, &blobh, &seg_len, chunk_size, ZSTR_VAL(str) + cur_len); + } + + ZSTR_VAL(str)[len] = '\0'; + ZVAL_STR(result, str); + + if (H->isc_status[0] == 1 && (stat != 0 && stat != isc_segstr_eof && stat != isc_segment)) { + const char *msg = "Error reading from BLOB"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + } + retval = 1; + +fetch_blob_end: + if (isc_close_blob(H->isc_status, &blobh)) { + php_firebird_error_stmt(stmt); + return 0; + } + return retval; +} +/* }}} */ + +static int pdo_firebird_stmt_get_col( + pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLVAR const *var = &S->out_sqlda.sqlvar[colno]; + + if (*var->sqlind == -1) { + ZVAL_NULL(result); + } else { + if (var->sqlscale < 0) { + static ISC_INT64 const scales[] = { 1, 10, 100, 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + LL_LIT(10000000000), + LL_LIT(100000000000), + LL_LIT(1000000000000), + LL_LIT(10000000000000), + LL_LIT(100000000000000), + LL_LIT(1000000000000000), + LL_LIT(10000000000000000), + LL_LIT(100000000000000000), + LL_LIT(1000000000000000000) + }; + ISC_INT64 n, f = scales[-var->sqlscale]; + zend_string *str; + + switch (var->sqltype & ~1) { + case SQL_SHORT: + n = *(short*)var->sqldata; + break; + case SQL_LONG: + n = php_get_isc_long_from_sqldata(var->sqldata); + break; + case SQL_INT64: + n = php_get_isc_int64_from_sqldata(var->sqldata); + break; + case SQL_DOUBLE: + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + + if ((var->sqltype & ~1) == SQL_DOUBLE) { + str = zend_strpprintf(0, "%.*F", -var->sqlscale, php_get_double_from_sqldata(var->sqldata)); + } else if (n >= 0) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, n % f); + } else if (n <= -f) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, -n % f); + } else { + str = zend_strpprintf(0, "-0.%0*" LL_MASK "d", -var->sqlscale, -n % f); + } + ZVAL_STR(result, str); + } else { + switch (var->sqltype & ~1) { + struct tm t; + char *fmt; + + case SQL_VARYING: + ZVAL_STRINGL_FAST(result, &var->sqldata[2], *(short*)var->sqldata); + break; + case SQL_TEXT: + ZVAL_STRINGL_FAST(result, var->sqldata, var->sqllen); + break; + case SQL_SHORT: + ZVAL_LONG(result, *(short*)var->sqldata); + break; + case SQL_LONG: + ZVAL_LONG(result, php_get_isc_long_from_sqldata(var->sqldata)); + break; + case SQL_INT64: +#if SIZEOF_ZEND_LONG >= 8 + ZVAL_LONG(result, php_get_isc_int64_from_sqldata(var->sqldata)); +#else + ZVAL_STR(result, zend_strpprintf(0, "%" LL_MASK "d", php_get_isc_int64_from_sqldata(var->sqldata))); +#endif + break; + case SQL_FLOAT: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.8H", php_get_float_from_sqldata(var->sqldata))); + break; + case SQL_DOUBLE: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.16H", php_get_double_from_sqldata(var->sqldata))); + break; + case SQL_BOOLEAN: + ZVAL_BOOL(result, *(FB_BOOLEAN*)var->sqldata); + break; + case SQL_TYPE_DATE: + isc_decode_sql_date((ISC_DATE*)var->sqldata, &t); + fmt = S->H->date_format ? S->H->date_format : PDO_FB_DEF_DATE_FMT; + if (0) { + case SQL_TYPE_TIME: + isc_decode_sql_time((ISC_TIME*)var->sqldata, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + } else if (0) { + case SQL_TIMESTAMP: + { + ISC_TIMESTAMP timestamp = php_get_isc_timestamp_from_sqldata(var->sqldata); + isc_decode_timestamp(×tamp, &t); + } + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + } + /* convert the timestamp into a string */ + char buf[80]; + size_t len = strftime(buf, sizeof(buf), fmt, &t); + ZVAL_STRINGL(result, buf, len); + break; +#if FB_API_VER >= 40 + case SQL_TIME_TZ: { + ISC_TIME_TZ time = php_get_isc_time_tz_from_sqldata(var->sqldata); + return get_formatted_time_tz(stmt, &time, result); + } + case SQL_TIMESTAMP_TZ: { + ISC_TIMESTAMP_TZ ts = php_get_isc_timestamp_tz_from_sqldata(var->sqldata); + return get_formatted_timestamp_tz(stmt, &ts, result); + } +#endif + case SQL_BLOB: { + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + return php_firebird_fetch_blob(stmt, colno, result, &quad); + } + } + } + } + return 1; +} + +static int php_firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle h = PDO_FIREBIRD_HANDLE_INITIALIZER; + zval data; + zend_ulong put_cnt = 0, rem_cnt; + unsigned short chunk_size; + int result = 1; + + if (isc_create_blob(H->isc_status, &H->db, &H->tr, &h, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (Z_TYPE_P(param) != IS_STRING) { + ZVAL_STR(&data, zval_get_string_func(param)); + } else { + ZVAL_COPY_VALUE(&data, param); + } + + for (rem_cnt = Z_STRLEN(data); rem_cnt > 0; rem_cnt -= chunk_size) { + chunk_size = rem_cnt > USHRT_MAX ? USHRT_MAX : (unsigned short)rem_cnt; + if (isc_put_segment(H->isc_status, &h, chunk_size, &Z_STRVAL(data)[put_cnt])) { + php_firebird_error_stmt(stmt); + result = 0; + break; + } + put_cnt += chunk_size; + } + + if (Z_TYPE_P(param) != IS_STRING) { + zval_ptr_dtor_str(&data); + } + + if (isc_close_blob(H->isc_status, &h)) { + php_firebird_error_stmt(stmt); + return 0; + } + return result; +} + +static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, /* {{{ */ + enum pdo_param_event event_type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLDA *sqlda = param->is_param ? S->in_sqlda : &S->out_sqlda; + XSQLVAR *var; + + if (event_type == PDO_PARAM_EVT_FREE) { /* not used */ + return 1; + } + + if (!sqlda || param->paramno >= sqlda->sqld) { + const char *msg = "Invalid parameter index"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + if (param->is_param && param->paramno == -1) { + zval *index; + + /* try to determine the index by looking in the named_params hash */ + if ((index = zend_hash_find(S->named_params, param->name)) != NULL) { + param->paramno = Z_LVAL_P(index); + } else { + /* ... or by looking in the input descriptor */ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR *var = &sqlda->sqlvar[i]; + + if ((var->aliasname_length && !strncasecmp(ZSTR_VAL(param->name), var->aliasname, + min(ZSTR_LEN(param->name), var->aliasname_length))) + || (var->sqlname_length && !strncasecmp(ZSTR_VAL(param->name), var->sqlname, + min(ZSTR_LEN(param->name), var->sqlname_length)))) { + param->paramno = i; + break; + } + } + if (i >= sqlda->sqld) { + const char *msg = "Invalid parameter name"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + } + } + + var = &sqlda->sqlvar[param->paramno]; + + switch (event_type) { + zval *parameter; + + case PDO_PARAM_EVT_ALLOC: + if (param->is_param) { + /* allocate the parameter */ + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)emalloc(var->sqllen + 2*sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + break; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!param->is_param) { + break; + } + + *var->sqlind = 0; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } + + switch (var->sqltype & ~1) { + case SQL_ARRAY: + { + const char *msg = "Cannot bind to array field"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } + return 0; + + case SQL_BLOB: { + if (Z_TYPE_P(parameter) == IS_NULL) { + /* Check if field allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + return 1; + } + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + if (php_firebird_bind_blob(stmt, &quad, parameter) != 0) { + memcpy(var->sqldata, &quad, sizeof(quad)); + return 1; + } + return 0; + } + } + + /* keep native BOOLEAN type */ + if ((var->sqltype & ~1) == SQL_BOOLEAN) { + switch (Z_TYPE_P(parameter)) { + case IS_LONG: + case IS_DOUBLE: + case IS_TRUE: + case IS_FALSE: + *(FB_BOOLEAN*)var->sqldata = zend_is_true(parameter) ? FB_TRUE : FB_FALSE; + break; + case IS_STRING: + { + zend_long lval; + double dval; + + if (Z_STRLEN_P(parameter) == 0) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + break; + } + + switch (is_numeric_string(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &lval, &dval, 0)) { + case IS_LONG: + *(FB_BOOLEAN*)var->sqldata = (lval != 0) ? FB_TRUE : FB_FALSE; + break; + case IS_DOUBLE: + *(FB_BOOLEAN*)var->sqldata = (dval != 0) ? FB_TRUE : FB_FALSE; + break; + default: + if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "true", 4, 4)) { + *(FB_BOOLEAN*)var->sqldata = FB_TRUE; + } else if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "false", 5, 5)) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + } else { + const char *msg = "Cannot convert string to boolean"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + + } + } + break; + case IS_NULL: + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + } + + /* check if a NULL should be inserted */ + switch (Z_TYPE_P(parameter)) { + int force_null; + + case IS_LONG: + /* keep the allow-NULL flag */ + var->sqltype = (sizeof(zend_long) == 8 ? SQL_INT64 : SQL_LONG) | (var->sqltype & 1); + var->sqldata = (void*)&Z_LVAL_P(parameter); + var->sqllen = sizeof(zend_long); + break; + case IS_DOUBLE: + /* keep the allow-NULL flag */ + var->sqltype = SQL_DOUBLE | (var->sqltype & 1); + var->sqldata = (void*)&Z_DVAL_P(parameter); + var->sqllen = sizeof(double); + break; + case IS_STRING: + force_null = 0; + + /* for these types, an empty string can be handled like a NULL value */ + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: +#if FB_API_VER >= 40 + case SQL_INT128: + case SQL_DEC16: + case SQL_DEC34: + case SQL_TIMESTAMP_TZ: + case SQL_TIME_TZ: +#endif + force_null = (Z_STRLEN_P(parameter) == 0); + } + if (!force_null) { + /* keep the allow-NULL flag */ + var->sqltype = SQL_TEXT | (var->sqltype & 1); + var->sqldata = Z_STRVAL_P(parameter); + var->sqllen = Z_STRLEN_P(parameter); + break; + } + ZEND_FALLTHROUGH; + case IS_NULL: + /* complain if this field doesn't allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + + case PDO_PARAM_EVT_FETCH_POST: + if (param->paramno == -1) { + return 0; + } + if (param->is_param) { + break; + } + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + ZVAL_NULL(parameter); + return pdo_firebird_stmt_get_col(stmt, param->paramno, parameter, NULL); + default: + ; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: { + zend_string *str_val = zval_try_get_string(val); + if (str_val == NULL) { + return 0; + } + // TODO Check cursor name does not have null bytes? + if (ZSTR_LEN(str_val) >= sizeof(S->name)) { + zend_value_error("Cursor name must not be longer than %zu bytes", sizeof(S->name) - 1); + zend_string_release(str_val); + return 0; + } + + if (isc_dsql_set_cursor_name(S->H->isc_status, &S->stmt, ZSTR_VAL(str_val), 0)) { + php_firebird_error_stmt(stmt); + zend_string_release(str_val); + return 0; + } + /* Include trailing nul byte */ + memcpy(S->name, ZSTR_VAL(str_val), ZSTR_LEN(str_val) + 1); + zend_string_release(str_val); + break; + } + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + if (*S->name) { + ZVAL_STRING(val, S->name); + } else { + ZVAL_NULL(val); + } + break; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + /* close the statement handle */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_close)) { + php_firebird_error_stmt(stmt); + return 0; + } + *S->name = 0; + S->cursor_open = 0; + return 1; +} +/* }}} */ + + +const struct pdo_stmt_methods swoole_firebird_stmt_methods = { /* {{{ */ + pdo_firebird_stmt_dtor, + pdo_firebird_stmt_execute, + pdo_firebird_stmt_fetch, + pdo_firebird_stmt_describe, + pdo_firebird_stmt_get_col, + pdo_firebird_stmt_param_hook, + pdo_firebird_stmt_set_attribute, + pdo_firebird_stmt_get_attribute, + pdo_firebird_stmt_get_column_meta, + NULL, /* next_rowset_func */ + pdo_firebird_stmt_cursor_closer +}; +/* }}} */ + +#endif diff --git a/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp new file mode 100644 index 0000000000..91973d8525 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp @@ -0,0 +1,97 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID >= 80500 + +#include "pdo_firebird_utils.h" +#include +#include + +/* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ +extern "C" unsigned fb_get_client_version(void) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->getClientVersion(); +} + +extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeTime(hours, minutes, seconds, fractions); +} + +extern "C" ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeDate(year, month, day); +} + +#if FB_API_VER >= 40 +static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLength) +{ + for(size_t i=0; i < maxLength; ++i) { + memcpy(to + i, from + i, sizeof(ISC_STATUS)); + if (from[i] == isc_arg_end) { + break; + } + } +} + +/* Decodes a time with time zone into its time components. */ +extern "C" ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeTz(&st, timeTz, hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +/* Decodes a timestamp with time zone into its date and time components */ +extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeStampTz(&st, timestampTz, year, month, day, + hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +#endif +#endif diff --git a/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h new file mode 100644 index 0000000000..19b0d0ab4b --- /dev/null +++ b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#ifndef PDO_FIREBIRD_UTILS_H +#define PDO_FIREBIRD_UTILS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned fb_get_client_version(void); + +ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions); + +ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day); + +#if FB_API_VER >= 40 + +ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h b/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h new file mode 100644 index 0000000000..f1dc9f625f --- /dev/null +++ b/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h @@ -0,0 +1,153 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#ifndef PHP_PDO_FIREBIRD_INT_H +#define PHP_PDO_FIREBIRD_INT_H + +#include + +#ifdef SQLDA_VERSION +#define PDO_FB_SQLDA_VERSION SQLDA_VERSION +#else +#define PDO_FB_SQLDA_VERSION 1 +#endif + +#define PDO_FB_DIALECT 3 + +#define PDO_FB_DEF_DATE_FMT "%Y-%m-%d" +#define PDO_FB_DEF_TIME_FMT "%H:%M:%S" +#define PDO_FB_DEF_TIMESTAMP_FMT PDO_FB_DEF_DATE_FMT " " PDO_FB_DEF_TIME_FMT + +#if SIZEOF_ZEND_LONG == 8 && !defined(PHP_WIN32) +# define LL_LIT(lit) lit ## L +#else +# define LL_LIT(lit) lit ## LL +#endif +#define LL_MASK "ll" + +/* Firebird API has a couple of missing const decls in its API */ +#define const_cast(s) ((char*)(s)) + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(_WIN64) +# define PDO_FIREBIRD_HANDLE_INITIALIZER 0U +#else +# define PDO_FIREBIRD_HANDLE_INITIALIZER NULL +#endif + +typedef struct { + int sqlcode; + char *errmsg; + size_t errmsg_length; +} pdo_firebird_error_info; + +typedef struct { + /* the result of the last API call */ + ISC_STATUS isc_status[20]; + + /* the connection handle */ + isc_db_handle db; + + /* the transaction handle */ + isc_tr_handle tr; + bool in_manually_txn; + bool is_writable_txn; + zend_ulong txn_isolation_level; + + /* date and time format strings, can be set by the set_attribute method */ + char *date_format; + char *time_format; + char *timestamp_format; + + unsigned sql_dialect:2; + + /* prepend table names on column names in fetch */ + unsigned fetch_table_names:1; + + unsigned _reserved:29; + + pdo_firebird_error_info einfo; +} pdo_firebird_db_handle; + + +typedef struct { + /* the link that owns this statement */ + pdo_firebird_db_handle *H; + + /* the statement handle */ + isc_stmt_handle stmt; + + /* the name of the cursor (if it has one) */ + char name[32]; + + /* the type of statement that was issued */ + char statement_type:8; + + /* whether EOF was reached for this statement */ + unsigned exhausted:1; + + /* successful isc_dsql_execute opens a cursor */ + unsigned cursor_open:1; + + unsigned _reserved:22; + + /* the named params that were converted to ?'s by the driver */ + HashTable *named_params; + + /* the input SQLDA */ + XSQLDA *in_sqlda; + + /* the output SQLDA */ + XSQLDA out_sqlda; /* last member */ +} pdo_firebird_stmt; + +extern const pdo_driver_t pdo_firebird_driver; + +extern const struct pdo_stmt_methods swoole_firebird_stmt_methods; + +extern void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len); +#define php_firebird_error(d) php_firebird_set_error(d, NULL, NULL, 0, NULL, 0) +#define php_firebird_error_stmt(s) php_firebird_set_error(s->dbh, s, NULL, 0, NULL, 0) +#define php_firebird_error_with_info(d,e,el,m,ml) php_firebird_set_error(d, NULL, e, el, m, ml) +#define php_firebird_error_stmt_with_info(s,e,el,m,ml) php_firebird_set_error(s->dbh, s, e, el, m, ml) + +extern bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); + +enum { + PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC, + PDO_FB_ATTR_TIME_FORMAT, + PDO_FB_ATTR_TIMESTAMP_FORMAT, + + /* + * transaction isolation level + * firebird does not have a level equivalent to read uncommited. + */ + PDO_FB_TRANSACTION_ISOLATION_LEVEL, + PDO_FB_READ_COMMITTED, + PDO_FB_REPEATABLE_READ, + PDO_FB_SERIALIZABLE, + + /* transaction access mode */ + PDO_FB_WRITABLE_TRANSACTION, +}; + +#endif /* PHP_PDO_FIREBIRD_INT_H */ diff --git a/thirdparty/php85/pdo_odbc/odbc_driver.c b/thirdparty/php85/pdo_odbc/odbc_driver.c new file mode 100644 index 0000000000..6a8cfc5e34 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/odbc_driver.c @@ -0,0 +1,632 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +/* this file actually lives in main/ */ +#include "php_odbc_utils.h" +#include "php_pdo_odbc.h" +//#include "php_pdo_odbc_int.h" +#include "zend_exceptions.h" + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + einfo = &S->einfo; + } + + /* If we don't have a driver error do not populate the info array */ + if (strlen(einfo->last_err_msg) == 0) { + return; + } + + message = strpprintf(0, "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, (long) einfo->last_error, + einfo->file, einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, eh, recno++, (SQLCHAR *) einfo->last_state, &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); +/* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error, "SQLSTATE[%s] %s: %d %s", + *pdo_err, what, einfo->last_error, einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, + discard_buf, sizeof(discard_buf)-1, &errmsgsize); + } + +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) +{ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &swoole_odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) +{ + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = SQLSetConnectAttr( + H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER + ); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error( + dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" : "SQLSetConnectAttr AUTOCOMMIT = OFF" + ); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) +{ + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER)buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) +{ + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, + sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL, /* get_gc */ + NULL /* scanner */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)(intptr_t)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + bool use_uid_arg = dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + dbh->data_source_len); + bool use_pwd_arg = dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + dbh->data_source_len); + + if (use_uid_arg || use_pwd_arg) { + char *db = (char*) estrndup(dbh->data_source, dbh->data_source_len); + char *db_end = db + dbh->data_source_len; + db_end--; + if ((unsigned char)*(db_end) == ';') { + *db_end = '\0'; + } + + char *uid = NULL, *pwd = NULL, *dsn = NULL; + bool should_quote_uid, should_quote_pwd; + size_t new_dsn_size; + + if (use_uid_arg) { + should_quote_uid = !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + + if (!use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(";UID=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;", db, uid); + } + } + + if (use_pwd_arg) { + should_quote_pwd = !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + + if (!use_uid_arg) { + new_dsn_size = strlen(db) + strlen(pwd) + strlen(";PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;PWD=%s;", db, pwd); + } + } + + if (use_uid_arg && use_pwd_arg) { + new_dsn_size = strlen(db) + + strlen(uid) + strlen(pwd) + + strlen(";UID=;PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s;", db, uid, pwd); + } + + pefree((char*)dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + dbh->data_source_len = strlen(dsn); + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + efree(db); + } + + rc = SQLDriverConnect(H->dbc, NULL, (SQLCHAR *) dbh->data_source, dbh->data_source_len, + dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, (SQLCHAR *) dbh->data_source, SQL_NTS, (SQLCHAR *) dbh->username, SQL_NTS, (SQLCHAR *) dbh->password, SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = { + PDO_DRIVER_HEADER(odbc), + pdo_odbc_handle_factory +}; +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_odbc/odbc_stmt.c b/thirdparty/php85/pdo_odbc/odbc_stmt.c new file mode 100644 index 0000000000..ae7a506609 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/odbc_stmt.c @@ -0,0 +1,898 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_odbc.h" +//#include "php_pdo_odbc_int.h" + +/* Buffer size; bigger columns than this become a "long column" */ +#define LONG_COLUMN_BUFFER_SIZE (ZEND_MM_PAGE_SIZE- ZSTR_MAX_OVERHEAD) + +enum pdo_odbc_conv_result { + PDO_ODBC_CONV_NOT_REQUIRED, + PDO_ODBC_CONV_OK, + PDO_ODBC_CONV_FAIL +}; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) +{ + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, + zend_ulong buflen, zend_ulong *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(int is_unicode, zval *result) +{ +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + DWORD ret; + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result)/sizeof(WCHAR), ZSTR_VAL(str), ZSTR_LEN(str), NULL, NULL); + if (ret == 0) { + zend_string_efree(str); + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) +{ + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE && php_pdo_stmt_valid_db_obj_handle(stmt)) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) +{ + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER*)¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param*)param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + RETCODE rc; + SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno+1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1, + P->paramtype, ctype, sqltype, precision, scale, + P->paramtype == SQL_PARAM_INPUT ? + (SQLPOINTER)param : + P->outbuf, + P->len, + &P->len + ); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) + amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: odbcori = SQL_FETCH_NEXT; break; + case PDO_FETCH_ORI_PRIOR: odbcori = SQL_FETCH_PRIOR; break; + case PDO_FETCH_ORI_FIRST: odbcori = SQL_FETCH_FIRST; break; + case PDO_FETCH_ORI_LAST: odbcori = SQL_FETCH_LAST; break; + case PDO_FETCH_ORI_ABS: odbcori = SQL_FETCH_ABSOLUTE; break; + case PDO_FETCH_ORI_REL: odbcori = SQL_FETCH_RELATIVE; break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SQLSMALLINT colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, colno+1, (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname)-1, &colnamelen, + &S->cols[colno].coltype, &colsize, NULL, NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && + (S->cols[colno].coltype == SQL_VARCHAR || + S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || + S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno+1, + SQL_DESC_DISPLAY_SIZE, + NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < LONG_COLUMN_BUFFER_SIZE && !S->going_long) { + S->cols[colno].data = emalloc(colsize+1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, colno+1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen+1, &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(LONG_COLUMN_BUFFER_SIZE); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of the page size minus zend_string overhead (LONG_COLUMN_BUFFER_SIZE); + * if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, + LONG_COLUMN_BUFFER_SIZE, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < LONG_COLUMN_BUFFER_SIZE) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* + * This is a long column. + * + * Try to get as much as we can at once. If the + * driver somehow has more for us, get more. We'll + * assemble it into one big buffer at the end. + * + * N.B. with n and n+1 mentioned in the comments: + * n is the size returned without null terminator. + * + * The extension previously tried getting it in 256 + * byte blocks, but this could have created trouble + * with some drivers. + * + * However, depending on the driver, fetched_len may + * not contain the number of bytes and SQL_NO_TOTAL + * may be passed. + * The behavior in this case is the same as before, + * dividing the data into blocks. However, it has been + * changed from 256 byte to LONG_COLUMN_BUFFER_SIZE. + */ + ssize_t to_fetch_len; + if (orig_fetched_len == SQL_NO_TOTAL) { + to_fetch_len = C->datalen > (LONG_COLUMN_BUFFER_SIZE - 1) ? (LONG_COLUMN_BUFFER_SIZE - 1) : C->datalen; + } else { + to_fetch_len = orig_fetched_len; + } + ssize_t to_fetch_byte = to_fetch_len + 1; + char *buf2 = emalloc(to_fetch_byte); + zend_string *str = zend_string_init(C->data, to_fetch_byte, 0); + size_t used = to_fetch_len; + + do { + C->fetched_len = 0; + /* read block. n + 1 bytes => n bytes are actually read, the last 1 is NULL */ + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, to_fetch_byte, &C->fetched_len); + + /* adjust `used` in case we have proper length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + if (fixed_used <= used + 1) { + used = fixed_used; + } + } + + /* resize output buffer and reassemble block */ + if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > to_fetch_len)) { + /* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx + states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > n (greater than buf2's size) + (if a driver fails to follow that and wrote less than n bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + to_fetch_byte, 0); + memcpy(ZSTR_VAL(str) + used, buf2, to_fetch_byte); + used = used + to_fetch_len; + } else if (rc==SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zend_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) +{ + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods swoole_odbc_stmt_methods = { + odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor +}; +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_odbc/php_pdo_odbc.h b/thirdparty/php85/pdo_odbc/php_pdo_odbc.h new file mode 100644 index 0000000000..3f04170297 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/php_pdo_odbc.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_ODBC_H +#define PHP_PDO_ODBC_H + +extern zend_module_entry pdo_odbc_module_entry; +#define phpext_pdo_odbc_ptr &pdo_odbc_module_entry + +#include "php_version.h" +#define PHP_PDO_ODBC_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_odbc); +PHP_MSHUTDOWN_FUNCTION(pdo_odbc); +PHP_RINIT_FUNCTION(pdo_odbc); +PHP_RSHUTDOWN_FUNCTION(pdo_odbc); +PHP_MINFO_FUNCTION(pdo_odbc); + +#endif /* PHP_PDO_ODBC_H */ diff --git a/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 0000000000..8b0ce31efa --- /dev/null +++ b/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,181 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#ifdef PHP_WIN32 +# define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +# warning Please fix configure to give your ODBC libraries a name +# define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +# include +# if defined(DB268K) && HAVE_LIBRARYMANAGER_H +# include +# endif +#endif + +#ifdef HAVE_ODBC_H +# include +#endif + +#ifdef HAVE_IODBC_H +# include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +# include +#endif + +#ifdef HAVE_SQLTYPES_H +# include +#endif + +#ifdef HAVE_SQLUCODE_H +# include +#endif + +#ifdef HAVE_SQL_H +# include +#endif + +#ifdef HAVE_ISQL_H +# include +#endif + +#ifdef HAVE_SQLEXT_H +# include +#endif + +#ifdef HAVE_ISQLEXT_H +# include +#endif + +#ifdef HAVE_UDBCEXT_H +# include +#endif + +#ifdef HAVE_CLI0CORE_H +# include +#endif + +#ifdef HAVE_CLI0EXT1_H +# include +#endif + +#ifdef HAVE_CLI0CLI_H +# include +#endif + +#ifdef HAVE_CLI0DEFS_H +# include +#endif + +#ifdef HAVE_CLI0ENV_H +# include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +# define PDO_ODBC_HENV SQLHANDLE +# define PDO_ODBC_HDBC SQLHANDLE +# define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +# define PDO_ODBC_HENV SQLHENV +# define PDO_ODBC_HDBC SQLHDBC +# define PDO_ODBC_HSTMT SQLHSTMT +#else +# define PDO_ODBC_HENV HENV +# define PDO_ODBC_HDBC HDBC +# define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8:1; + unsigned _spare:31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SQLSMALLINT coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long:1; + unsigned assume_utf8:1; + signed col_count:16; + unsigned _spare:14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods swoole_odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php85/pdo_pgsql/pdo_pgsql_arginfo.h b/thirdparty/php85/pdo_pgsql/pdo_pgsql_arginfo.h new file mode 100644 index 0000000000..296207471a --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pdo_pgsql_arginfo.h @@ -0,0 +1,168 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 0ea21010467d661416f0858f2bda095583ea3a36 */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_escapeIdentifier, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, input, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_copyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_copyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_copyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_Pdo_Pgsql_copyToFile arginfo_class_Pdo_Pgsql_copyFromFile + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_lobCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Pdo_Pgsql_lobOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_lobUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_getNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_getPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_setNoticeCallback, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) +ZEND_END_ARG_INFO() + +ZEND_METHOD(Pdo_Pgsql, escapeIdentifier); +ZEND_METHOD(Pdo_Pgsql, copyFromArray); +ZEND_METHOD(Pdo_Pgsql, copyFromFile); +ZEND_METHOD(Pdo_Pgsql, copyToArray); +ZEND_METHOD(Pdo_Pgsql, copyToFile); +ZEND_METHOD(Pdo_Pgsql, lobCreate); +ZEND_METHOD(Pdo_Pgsql, lobOpen); +ZEND_METHOD(Pdo_Pgsql, lobUnlink); +ZEND_METHOD(Pdo_Pgsql, getNotify); +ZEND_METHOD(Pdo_Pgsql, getPid); +ZEND_METHOD(Pdo_Pgsql, setNoticeCallback); + +static const zend_function_entry class_Pdo_Pgsql_methods[] = { + ZEND_ME(Pdo_Pgsql, escapeIdentifier, arginfo_class_Pdo_Pgsql_escapeIdentifier, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyFromArray, arginfo_class_Pdo_Pgsql_copyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyFromFile, arginfo_class_Pdo_Pgsql_copyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyToArray, arginfo_class_Pdo_Pgsql_copyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyToFile, arginfo_class_Pdo_Pgsql_copyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobCreate, arginfo_class_Pdo_Pgsql_lobCreate, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobOpen, arginfo_class_Pdo_Pgsql_lobOpen, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobUnlink, arginfo_class_Pdo_Pgsql_lobUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, getNotify, arginfo_class_Pdo_Pgsql_getNotify, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, getPid, arginfo_class_Pdo_Pgsql_getPid, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, setNoticeCallback, arginfo_class_Pdo_Pgsql_setNoticeCallback, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_Pdo_Pgsql(zend_class_entry *class_entry_PDO) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Pdo", "Pgsql", class_Pdo_Pgsql_methods); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_PDO, ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); + + zval const_ATTR_DISABLE_PREPARES_value; + ZVAL_LONG(&const_ATTR_DISABLE_PREPARES_value, PDO_PGSQL_ATTR_DISABLE_PREPARES); + zend_string *const_ATTR_DISABLE_PREPARES_name = zend_string_init_interned("ATTR_DISABLE_PREPARES", sizeof("ATTR_DISABLE_PREPARES") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_DISABLE_PREPARES_name, &const_ATTR_DISABLE_PREPARES_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_DISABLE_PREPARES_name, true); +#if defined(HAVE_PG_RESULT_MEMORY_SIZE) + + zval const_ATTR_RESULT_MEMORY_SIZE_value; + ZVAL_LONG(&const_ATTR_RESULT_MEMORY_SIZE_value, PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE); + zend_string *const_ATTR_RESULT_MEMORY_SIZE_name = zend_string_init_interned("ATTR_RESULT_MEMORY_SIZE", sizeof("ATTR_RESULT_MEMORY_SIZE") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_RESULT_MEMORY_SIZE_name, &const_ATTR_RESULT_MEMORY_SIZE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_RESULT_MEMORY_SIZE_name, true); +#endif + + zval const_TRANSACTION_IDLE_value; + ZVAL_LONG(&const_TRANSACTION_IDLE_value, PGSQL_TRANSACTION_IDLE); + zend_string *const_TRANSACTION_IDLE_name = zend_string_init_interned("TRANSACTION_IDLE", sizeof("TRANSACTION_IDLE") - 1, true); + zend_class_constant *const_TRANSACTION_IDLE = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_IDLE_name, &const_TRANSACTION_IDLE_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_IDLE_name, true); + + zval const_TRANSACTION_ACTIVE_value; + ZVAL_LONG(&const_TRANSACTION_ACTIVE_value, PGSQL_TRANSACTION_ACTIVE); + zend_string *const_TRANSACTION_ACTIVE_name = zend_string_init_interned("TRANSACTION_ACTIVE", sizeof("TRANSACTION_ACTIVE") - 1, true); + zend_class_constant *const_TRANSACTION_ACTIVE = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_ACTIVE_name, &const_TRANSACTION_ACTIVE_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_ACTIVE_name, true); + + zval const_TRANSACTION_INTRANS_value; + ZVAL_LONG(&const_TRANSACTION_INTRANS_value, PGSQL_TRANSACTION_INTRANS); + zend_string *const_TRANSACTION_INTRANS_name = zend_string_init_interned("TRANSACTION_INTRANS", sizeof("TRANSACTION_INTRANS") - 1, true); + zend_class_constant *const_TRANSACTION_INTRANS = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_INTRANS_name, &const_TRANSACTION_INTRANS_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_INTRANS_name, true); + + zval const_TRANSACTION_INERROR_value; + ZVAL_LONG(&const_TRANSACTION_INERROR_value, PGSQL_TRANSACTION_INERROR); + zend_string *const_TRANSACTION_INERROR_name = zend_string_init_interned("TRANSACTION_INERROR", sizeof("TRANSACTION_INERROR") - 1, true); + zend_class_constant *const_TRANSACTION_INERROR = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_INERROR_name, &const_TRANSACTION_INERROR_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_INERROR_name, true); + + zval const_TRANSACTION_UNKNOWN_value; + ZVAL_LONG(&const_TRANSACTION_UNKNOWN_value, PGSQL_TRANSACTION_UNKNOWN); + zend_string *const_TRANSACTION_UNKNOWN_name = zend_string_init_interned("TRANSACTION_UNKNOWN", sizeof("TRANSACTION_UNKNOWN") - 1, true); + zend_class_constant *const_TRANSACTION_UNKNOWN = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_UNKNOWN_name, &const_TRANSACTION_UNKNOWN_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_UNKNOWN_name, true); + + + zend_attribute *attribute_Deprecated_const_TRANSACTION_IDLE_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_IDLE, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_IDLE_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_IDLE_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + zend_string *attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str = zend_string_init("as it has no effect", strlen("as it has no effect"), 1); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_IDLE_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_IDLE_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_ACTIVE_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_ACTIVE, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_INTRANS_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_INTRANS, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_INERROR_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_INERROR, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_INERROR_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_INERROR_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_INERROR_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_INERROR_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_UNKNOWN_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_UNKNOWN, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + return class_entry; +} diff --git a/thirdparty/php85/pdo_pgsql/pgsql_driver.c b/thirdparty/php85/pdo_pgsql/pgsql_driver.c new file mode 100644 index 0000000000..2919afffd9 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1485 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/php_string.h" /* For php_addcslashes_str() in _pdo_pgsql_escape_credentials() */ +#include "main/php_network.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "zend_interfaces.h" +#include "zend_smart_str.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char * _pdo_pgsql_trim_message(const char *message, int persistent) +{ + size_t i = strlen(message); + char *tmp; + if (UNEXPECTED(i == 0)) { + tmp = pemalloc(1, persistent); + tmp[0] = '\0'; + return tmp; + } + --i; + + if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { + --i; + } + while (i>0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string* _pdo_pgsql_escape_credentials(char *str) +{ + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } + else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } + else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ +{ + pdo_dbh_t * dbh = (pdo_dbh_t *)context; + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); + zval_ptr_dtor_str(&zarg); + } +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zend_fcc_dtor(H->notice_callback); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_write(self->conn, self->lfd, (char*)buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) +{ + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, + zend_off_t *newoffset) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const php_stream_ops pdo_pgsql_lob_stream_ops = { + pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL +}; + +php_stream *pdo_pgsql_create_lob_stream(zend_object *dbh, int lfd, Oid oid) +{ + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(php_pdo_dbh_fetch_inner(dbh))->driver_data; + + ZVAL_OBJ(&self->dbh, dbh); + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + GC_ADDREF(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) +{ + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + pdo_pgsql_cleanup_notice_callback(H); + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + zval *val; + zend_long lval; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, + PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + S->is_unbuffered = + driver_options + && (val = zend_hash_index_find(Z_ARRVAL_P(driver_options), PDO_ATTR_PREFETCH)) + && pdo_get_long_param(&lval, val) + ? !lval + : H->default_fetching_laziness + ; + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + size_t tmp_len; + int err; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *)ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + if (escaped == NULL) { + return NULL; + } + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted+1, escaped, quotedlen-2); + quoted[0] = '\''; + quoted[quotedlen-1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &err); + if (err) { + efree(quoted); + return NULL; + } + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *)PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) +{ + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char*)PQparameterStatus(H->server, "server_version")); + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL(return_value, "Waiting for connection to be made.", strlen("Waiting for connection to be made.")); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", strlen("Connection OK; waiting to send.")); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, "Waiting for a response from the server.", strlen("Waiting for a response from the server.")); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, "Received authentication; waiting for backend start-up to finish.", strlen("Received authentication; waiting for backend start-up to finish.")); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", strlen("Negotiating SSL encryption.")); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, "Negotiating environment-driven parameter settings.", strlen("Negotiating environment-driven parameter settings.")); + break; + +#ifdef CONNECTION_CONSUME + case CONNECTION_CONSUME: + ZVAL_STRINGL(return_value, "Flushing send queue/consuming extra data.", strlen("Flushing send queue/consuming extra data.")); + break; +#endif +#ifdef CONNECTION_GSS_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating GSSAPI.", strlen("Negotiating GSSAPI.")); + break; +#endif +#ifdef CONNECTION_CHECK_TARGET + case CONNECTION_CHECK_TARGET: + ZVAL_STRINGL(return_value, "Connection OK; checking target server properties.", strlen("Connection OK; checking target server properties.")); + break; +#endif +#ifdef CONNECTION_CHECK_STANDBY + case CONNECTION_CHECK_STANDBY: + ZVAL_STRINGL(return_value, "Connection OK; checking if server in standby.", strlen("Connection OK; checking if server in standby.")); + break; +#endif + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", strlen("Bad connection.")); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char*)PQparameterStatus(H->server, "client_encoding"), + (char*)PQparameterStatus(H->server, "is_superuser"), + (char*)PQparameterStatus(H->server, "session_authorization"), + (char*)PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) +{ + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) +{ + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) +{ + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static bool _pdo_pgsql_send_copy_data(pdo_pgsql_db_handle *H, zval *line) { + size_t query_len; + zend_string *query; + + if (!try_convert_to_string(line)) { + return false; + } + + query_len = Z_STRLEN_P(line); + query = zend_string_alloc(query_len + 2, false); /* room for \n\0 */ + memcpy(ZSTR_VAL(query), Z_STRVAL_P(line), query_len + 1); + ZSTR_LEN(query) = query_len; + + if (query_len > 0 && ZSTR_VAL(query)[query_len - 1] != '\n') { + ZSTR_VAL(query)[query_len] = '\n'; + ZSTR_VAL(query)[query_len + 1] = '\0'; + ZSTR_LEN(query) ++; + } + + if (PQputCopyData(H->server, ZSTR_VAL(query), ZSTR_LEN(query)) != 1) { + zend_string_release_ex(query, false); + return false; + } + + zend_string_release_ex(query, false); + return true; +} + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + ZEND_PARSE_PARAMETERS_START(2, 5) + Z_PARAM_STRING(table_name, table_name_len) + Z_PARAM_ITERABLE(pg_rows) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(pg_delim, pg_delim_len) + Z_PARAM_STRING(pg_null_as, pg_null_as_len) + Z_PARAM_STRING_OR_NULL(pg_fields, pg_fields_len) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + zval *tmp; + zend_object_iterator *iter; + + PQclear(pgsql_result); + + if (Z_TYPE_P(pg_rows) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + if (!_pdo_pgsql_send_copy_data(H, tmp)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + } else { + iter = Z_OBJCE_P(pg_rows)->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0); + if (iter == NULL || EG(exception)) { + RETURN_THROWS(); + } + + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (EG(exception)) { + RETURN_THROWS(); + } + } + + for (; iter->funcs->valid(iter) == SUCCESS && EG(exception) == NULL; iter->funcs->move_forward(iter)) { + tmp = iter->funcs->get_current_data(iter); + if (!_pdo_pgsql_send_copy_data(H, tmp)) { + zend_iterator_dtor(iter); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + zend_iterator_dtor(iter); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) +{ + pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) +{ + pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t)ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) +{ + pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); + +} +/* }}} */ + +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sss!", + &table_name, &table_name_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) +{ + pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + lfd = lo_creat(H->server, INV_READ|INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) +{ + pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", + &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ|INV_WRITE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(Z_OBJ_P(ZEND_THIS), lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) +{ + pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) +{ + pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", + &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int)ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) +{ + pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) +{ + pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) +{ + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } + + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup); + + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + pdo_pgsql_cleanup_notice_callback(H); + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } + + return; + +cleanup: + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_fcc_dtor(&fcc); + } + RETURN_THROWS(); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + case PDO_ATTR_PREFETCH: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->default_fetching_laziness = !bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = { + pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL, /* get_gc */ + pdo_pgsql_scanner +}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *p, *e; + zend_string *tmp_user, *tmp_pass; + smart_str conn_str = {0}; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = + 1 << PDO_PARAM_EVT_EXEC_POST | + 1 << PDO_PARAM_EVT_FETCH_PRE | + 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + dbh->data_source_len; + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = !strstr((char *) dbh->data_source, "user=") ? _pdo_pgsql_escape_credentials(dbh->username) : NULL; + tmp_pass = !strstr((char *) dbh->data_source, "password=") ? _pdo_pgsql_escape_credentials(dbh->password) : NULL; + + smart_str_appendl(&conn_str, dbh->data_source, dbh->data_source_len); + smart_str_append_printf(&conn_str, " connect_timeout=" ZEND_LONG_FMT, connect_timeout); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user) { + smart_str_append_printf(&conn_str, " user='%s'", ZSTR_VAL(tmp_user)); + } + + if (tmp_pass) { + smart_str_append_printf(&conn_str, " password='%s'", ZSTR_VAL(tmp_pass)); + } + smart_str_0(&conn_str); + + H->server = PQconnectdb(ZSTR_VAL(conn_str.s)); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + smart_str_free(&conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *)dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = { + PDO_DRIVER_HEADER(pgsql), + pdo_pgsql_handle_factory +}; +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 0000000000..cd01e2e8e7 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,70 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 30c01b4d2e7f836b81a31dc0c1a115883eb41568 */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_OBJ_TYPE_MASK(0, rows, Traversable, MAY_BE_ARRAY, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php85/pdo_pgsql/pgsql_sql_parser.c b/thirdparty/php85/pdo_pgsql/pgsql_sql_parser.c new file mode 100644 index 0000000000..ba40f6ed22 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_sql_parser.c @@ -0,0 +1,322 @@ +/* Generated by re2c 4.3 on Fri Jul 18 21:15:02 2025 */ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + + + +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '.') { + if (yych <= '$') { + if (yych <= '!') { + if (yych >= 0x01) goto yy2; + } else { + if (yych <= '"') goto yy4; + if (yych <= '#') goto yy2; + goto yy6; + } + } else { + if (yych <= '\'') { + if (yych <= '&') goto yy2; + goto yy7; + } else { + if (yych == '-') goto yy8; + goto yy2; + } + } + } else { + if (yych <= '?') { + if (yych <= '9') { + if (yych <= '/') goto yy9; + goto yy2; + } else { + if (yych <= ':') goto yy10; + if (yych <= '>') goto yy2; + goto yy11; + } + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy2; + goto yy12; + } else { + if (yych == 'e') goto yy12; + goto yy2; + } + } + } +yy1: + YYCURSOR = YYMARKER; + if (yyaccept <= 1) { + if (yyaccept == 0) goto yy5; + else goto yy15; + } else { + if (yyaccept == 2) goto yy20; + else goto yy31; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '.') { + if (yych <= '$') { + if (yych <= '!') { + if (yych >= 0x01) goto yy2; + } else { + if (yych == '#') goto yy2; + } + } else { + if (yych <= '\'') { + if (yych <= '&') goto yy2; + } else { + if (yych != '-') goto yy2; + } + } + } else { + if (yych <= '?') { + if (yych <= '9') { + if (yych >= '0') goto yy2; + } else { + if (yych <= ':') goto yy3; + if (yych <= '>') goto yy2; + } + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy2; + } else { + if (yych != 'e') goto yy2; + } + } + } +yy3: + { RET(PDO_PARSER_TEXT); } +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy14; +yy5: + { SKIP_ONE(PDO_PARSER_TEXT); } +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= '^') { + if (yych <= '$') { + if (yych <= '#') goto yy5; + goto yy16; + } else { + if (yych <= '@') goto yy5; + if (yych <= 'Z') goto yy17; + goto yy5; + } + } else { + if (yych <= '`') { + if (yych <= '_') goto yy17; + goto yy5; + } else { + if (yych <= 'z') goto yy17; + if (yych <= 0x7F) goto yy5; + goto yy17; + } + } +yy7: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy19; +yy8: + yych = *++YYCURSOR; + if (yych == '-') goto yy21; + goto yy5; +yy9: + yych = *++YYCURSOR; + if (yych == '*') goto yy23; + goto yy5; +yy10: + yych = *++YYCURSOR; + if (yych <= 'Z') { + if (yych <= '9') { + if (yych <= '/') goto yy5; + goto yy24; + } else { + if (yych <= ':') goto yy26; + if (yych <= '@') goto yy5; + goto yy24; + } + } else { + if (yych <= '_') { + if (yych <= '^') goto yy5; + goto yy24; + } else { + if (yych <= '`') goto yy5; + if (yych <= 'z') goto yy24; + goto yy5; + } + } +yy11: + yych = *++YYCURSOR; + if (yych == '?') goto yy27; + { RET(PDO_PARSER_BIND_POS); } +yy12: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych == '\'') goto yy28; + goto yy5; +yy13: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy14: + if (yych <= 0x00) goto yy1; + if (yych != '"') goto yy13; + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '"') goto yy13; +yy15: + { RET(PDO_PARSER_TEXT); } +yy16: + ++YYCURSOR; + { RET(PDO_PARSER_CUSTOM_QUOTE); } +yy17: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 'Z') { + if (yych <= '/') { + if (yych == '$') goto yy16; + goto yy1; + } else { + if (yych <= '9') goto yy17; + if (yych <= '@') goto yy1; + goto yy17; + } + } else { + if (yych <= '`') { + if (yych == '_') goto yy17; + goto yy1; + } else { + if (yych <= 'z') goto yy17; + if (yych <= 0x7F) goto yy1; + goto yy17; + } + } +yy18: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy19: + if (yych <= 0x00) goto yy1; + if (yych != '\'') goto yy18; + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '\'') goto yy18; +yy20: + { RET(PDO_PARSER_TEXT); } +yy21: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych != '\n') goto yy21; +yy22: + { RET(PDO_PARSER_TEXT); } +yy23: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '*') goto yy29; + goto yy23; +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 'Z') { + if (yych <= '/') goto yy25; + if (yych <= '9') goto yy24; + if (yych >= 'A') goto yy24; + } else { + if (yych <= '_') { + if (yych >= '_') goto yy24; + } else { + if (yych <= '`') goto yy25; + if (yych <= 'z') goto yy24; + } + } +yy25: + { RET(PDO_PARSER_BIND); } +yy26: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == ':') goto yy26; + { RET(PDO_PARSER_TEXT); } +yy27: + ++YYCURSOR; + { RET(PDO_PARSER_ESCAPED_QUESTION); } +yy28: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '\'') { + if (yych <= 0x00) goto yy1; + if (yych <= '&') goto yy28; + goto yy30; + } else { + if (yych == '\\') goto yy32; + goto yy28; + } +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '*') goto yy29; + if (yych == '/') goto yy33; + goto yy23; +yy30: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '\'') goto yy28; +yy31: + { RET(PDO_PARSER_TEXT); } +yy32: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 0x00) goto yy1; + goto yy28; +yy33: + ++YYCURSOR; + goto yy22; +} + +} diff --git a/thirdparty/php85/pdo_pgsql/pgsql_statement.c b/thirdparty/php85/pdo_pgsql/pgsql_statement.c new file mode 100644 index 0000000000..ab442a2aed --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,874 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80500 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_pgsql.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 +#define FLOAT4LABEL "float4" +#define FLOAT4OID 700 +#define FLOAT8LABEL "float8" +#define FLOAT8OID 701 + +#define FIN_DISCARD 0x1 +#define FIN_CLOSE 0x2 +#define FIN_ABORT 0x4 + + + +static void pgsql_stmt_finish(pdo_pgsql_stmt *S, int fin_mode) +{ + pdo_pgsql_db_handle *H = S->H; + + if (S->is_running_unbuffered && S->result && (fin_mode & FIN_ABORT)) { + PGcancel *cancel = PQgetCancel(H->server); + char errbuf[256]; + PQcancel(cancel, errbuf, 256); + PQfreeCancel(cancel); + S->is_running_unbuffered = false; + } + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->is_running_unbuffered) { + /* https://postgresql.org/docs/current/libpq-async.html: + * "PQsendQuery cannot be called again until PQgetResult has returned NULL" + * And as all single-row functions are connection-wise instead of statement-wise, + * any new single-row query has to make sure no preceding one is still running. + */ + // @todo Implement !(fin_mode & FIN_DISCARD) + // instead of discarding results we could store them to their statement + // so that their fetch() will get them (albeit not in lazy mode anymore). + while ((S->result = PQgetResult(H->server))) { + PQclear(S->result); + S->result = NULL; + } + S->is_running_unbuffered = false; + } + + if (S->stmt_name && S->is_prepared && (fin_mode & FIN_CLOSE)) { + PGresult *res; +#ifndef HAVE_PQCLOSEPREPARED + // TODO (??) libpq does not support close statement protocol < postgres 17 + // check if we can circumvent this. + char *q = NULL; + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); +#else + res = PQclosePrepared(H->server, S->stmt_name); +#endif + if (res) { + PQclear(res); + } + + S->is_prepared = false; + if (H->running_stmt == S) { + H->running_stmt = NULL; + } + } +} + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + bool server_obj_usable = php_pdo_stmt_valid_db_obj_handle(stmt); + + pgsql_stmt_finish(S, FIN_DISCARD|(server_obj_usable ? FIN_CLOSE|FIN_ABORT : 0)); + + if (S->stmt_name) { + efree(S->stmt_name); + S->stmt_name = NULL; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if(S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + int dispatch_result = 1; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* in unbuffered mode, finish any running statement: libpq explicitely prohibits this + * and returns a PGRES_FATAL_ERROR when PQgetResult gets called for stmt 2 if DEALLOCATE + * was called for stmt 1 inbetween + * (maybe it will change with pipeline mode in libpq 14?) */ + if (S->is_unbuffered && H->running_stmt) { + pgsql_stmt_finish(H->running_stmt, FIN_CLOSE); + H->running_stmt = NULL; + } + /* ensure that we free any previous unfetched results */ + pgsql_stmt_finish(S, 0); + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf(&q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = true; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else if (S->stmt_name) { + /* using a prepared statement */ + + if (!S->is_prepared) { +stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, S->stmt_name, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = true; + PQclear(S->result); + S->result = NULL; + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chance to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (sqlstate && !strcmp(sqlstate, "42P05")) { + PGresult *res; +#ifndef HAVE_PQCLOSEPREPARED + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); +#else + res = PQclosePrepared(H->server, S->stmt_name); +#endif + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + if (S->is_unbuffered) { + dispatch_result = PQsendQueryPrepared(H->server, S->stmt_name, + stmt->bound_params ? + zend_hash_num_elements(stmt->bound_params) : + 0, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + S->result = PQexecPrepared(H->server, S->stmt_name, + stmt->bound_params ? + zend_hash_num_elements(stmt->bound_params) : + 0, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + if (S->is_unbuffered) { + dispatch_result = PQsendQueryParams(H->server, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + S->result = PQexecParams(H->server, ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } + } else { + /* execute plain query (with embedded parameters) */ + if (S->is_unbuffered) { + dispatch_result = PQsendQuery(H->server, ZSTR_VAL(stmt->active_query_string)); + } else { + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + } + + H->running_stmt = S; + + if (S->is_unbuffered) { + if (!dispatch_result) { + pdo_pgsql_error_stmt(stmt, 0, NULL); + H->running_stmt = NULL; + return 0; + } + S->is_running_unbuffered = true; + (void)PQsetSingleRowMode(H->server); + /* no matter if it returns 0: PQ then transparently fallbacks to full result fetching */ + + /* try a first fetch to at least have column names and so on */ + S->result = PQgetResult(S->H->server); + } + + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long)PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: + /* decode name from $1, $2 into 0, 1 etc. */ + if (param->name) { + if (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && (namevar = zend_hash_find_ptr(stmt->bound_param_map, + param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!stmt->bound_param_map) { + return 1; + } + if (!S->param_values) { + S->param_values = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(char*)); + S->param_lengths = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_formats = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + S->param_types = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(Oid)); + } + if (param->paramno >= 0) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && + Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char*)&P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || + Z_TYPE_P(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; + S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; + } + } + break; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: ori_str = "NEXT"; break; + case PDO_FETCH_ORI_PRIOR: ori_str = "BACKWARD"; break; + case PDO_FETCH_ORI_FIRST: ori_str = "FIRST"; break; + case PDO_FETCH_ORI_LAST: ori_str = "LAST"; break; + case PDO_FETCH_ORI_ABS: spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); break; + case PDO_FETCH_ORI_REL: spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); break; + default: + return 0; + } + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + if (ori == PDO_FETCH_ORI_ABS || ori == PDO_FETCH_ORI_REL) { + efree(ori_str); + } + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->is_running_unbuffered && S->current_row >= stmt->row_count) { + ExecStatusType status; + + /* @todo in unbuffered mode, PQ allows multiple queries to be passed: + * column_count should be recomputed on each iteration */ + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->result = PQgetResult(S->H->server); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->row_count = (zend_long)PQntuples(S->result); + S->current_row = 0; + + if (!stmt->row_count) { + S->is_running_unbuffered = false; + /* libpq requires looping until getResult returns null */ + pgsql_stmt_finish(S, 0); + } + } + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + case FLOAT4OID: + case FLOAT8OID: + if (strncmp(ptr, "Infinity", len) == 0) { + ZVAL_DOUBLE(result, ZEND_INFINITY); + } else if (strncmp(ptr, "-Infinity", len) == 0) { + ZVAL_DOUBLE(result, -ZEND_INFINITY); + } else if (strncmp(ptr, "NaN", len) == 0) { + ZVAL_DOUBLE(result, ZEND_NAN); + } else { + ZVAL_DOUBLE(result, zend_strtod(ptr, NULL)); + } + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid)strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *)PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) +{ + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + PGresult *res; + char *q=NULL; + ExecStatusType status; + Oid table_oid; + char *table_name=NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case FLOAT4OID: + add_assoc_string(return_value, "native_type", FLOAT4LABEL); + break; + case FLOAT8OID: + add_assoc_string(return_value, "native_type", FLOAT8LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + return 1; +} + +static int pgsql_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + switch (attr) { +#ifdef HAVE_PG_RESULT_MEMORY_SIZE + case PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE: + if(stmt->executed) { + ZVAL_LONG(val, PQresultMemorySize(S->result)); + } else { + char *tmp; + spprintf(&tmp, 0, "statement '%s' has not been executed yet", S->stmt_name); + + pdo_pgsql_error_stmt_msg(stmt, 0, "HY000", tmp); + efree(tmp); + + ZVAL_NULL(val); + } + return 1; +#endif + + default: + (void)S; + return 0; + } +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = { + pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + pgsql_stmt_get_attr, + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer +}; +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h new file mode 100644 index 0000000000..fa28214f71 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Edin Kadribasic | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_H +#define PHP_PDO_PGSQL_H + +#include + +extern zend_module_entry pdo_pgsql_module_entry; +#define phpext_pdo_pgsql_ptr &pdo_pgsql_module_entry + +#include "php_version.h" +#define PHP_PDO_PGSQL_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_pgsql); +PHP_MSHUTDOWN_FUNCTION(pdo_pgsql); +PHP_MINFO_FUNCTION(pdo_pgsql); + +#endif /* PHP_PDO_PGSQL_H */ diff --git a/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 0000000000..dadc2c240f --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,133 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +typedef struct pdo_pgsql_stmt pdo_pgsql_stmt; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached:1; + unsigned _reserved:31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + bool emulate_prepares; + bool disable_prepares; + HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; + bool default_fetching_laziness; + pdo_pgsql_stmt *running_stmt; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +struct pdo_pgsql_stmt { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; + bool is_unbuffered; + bool is_running_unbuffered; +}; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + +extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s,e,z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, + PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zend_object *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H); + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/swoole_http_parser.c b/thirdparty/swoole_http_parser.c deleted file mode 100644 index f0dfe8e055..0000000000 --- a/thirdparty/swoole_http_parser.c +++ /dev/null @@ -1,1575 +0,0 @@ -/* Copyright 2009,2010 Ryan Dahl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#include -#include -#include "swoole_http_parser.h" - - -#ifndef MIN -# define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif - - -#define CALLBACK2(FOR) \ -do { \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser)) return (p - data); \ - } \ -} while (0) - -#define CALLBACK_MESSAGE_COMPLETE() \ -do { \ - if (settings->on_message_complete) { \ - int ret = settings->on_message_complete(parser); \ - if (0 != ret) return (p - data + (ret > 0 ? 1 : 0)); \ - } \ -} while (0) - - -#define MARK(FOR) \ -do { \ - FOR##_mark = p; \ -} while (0) - -#define CALLBACK_NOCLEAR(FOR) \ -do { \ - if (FOR##_mark) { \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser, \ - FOR##_mark, \ - p - FOR##_mark)) \ - { \ - return (p - data); \ - } \ - } \ - } \ -} while (0) - -#ifdef PHP_WIN32 -# undef CALLBACK -#endif -#define CALLBACK(FOR) \ -do { \ - CALLBACK_NOCLEAR(FOR); \ - FOR##_mark = NULL; \ -} while (0) - - -#define PROXY_CONNECTION "proxy-connection" -#define CONNECTION "connection" -#define CONTENT_LENGTH "content-length" -#define TRANSFER_ENCODING "transfer-encoding" -#define UPGRADE "upgrade" -#define CHUNKED "chunked" -#define KEEP_ALIVE "keep-alive" -#define CLOSE "close" - - -static const char *method_strings[] = - { "DELETE" - , "GET" - , "HEAD" - , "POST" - , "PUT" - , "PATCH" - , "CONNECT" - , "OPTIONS" - , "TRACE" - , "COPY" - , "LOCK" - , "MKCOL" - , "MOVE" - , "MKCALENDAR" - , "PROPFIND" - , "PROPPATCH" - , "SEARCH" - , "UNLOCK" - , "REPORT" - , "MKACTIVITY" - , "CHECKOUT" - , "MERGE" - , "M-SEARCH" - , "NOTIFY" - , "SUBSCRIBE" - , "UNSUBSCRIBE" - , "PURGE" - , "NOTIMPLEMENTED" - }; - - -/* Tokens as defined by rfc 2616. Also lowercases them. - * token = 1* - * separators = "(" | ")" | "<" | ">" | "@" - * | "," | ";" | ":" | "\" | <"> - * | "/" | "[" | "]" | "?" | "=" - * | "{" | "}" | SP | HT - */ -static const char tokens[256] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - ' ', '!', '"', '#', '$', '%', '&', '\'', -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 0, 0, '*', '+', 0, '-', '.', '/', -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - '0', '1', '2', '3', '4', '5', '6', '7', -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - '8', '9', 0, 0, 0, 0, 0, 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 'x', 'y', 'z', 0, 0, 0, '^', '_', -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 'x', 'y', 'z', 0, '|', '}', '~', 0 }; - - -static const int8_t unhex[256] = - {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 - ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 - }; - - -static const uint8_t normal_url_char[256] = { -/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, -/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, 1, 1, 0, 1, 1, 1, 1, -/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - 1, 1, 1, 1, 1, 1, 1, 0, -/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 1, 1, 1, 1, 1, 1, 1, 1, -/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 1, 1, 1, 1, 1, 1, 1, 0 }; - - -#define PARSING_HEADER(state) (state <= s_headers_almost_done && 0 == (parser->flags & F_TRAILING)) - - -enum header_states - { h_general = 0 - , h_C - , h_CO - , h_CON - - , h_matching_connection - , h_matching_proxy_connection - , h_matching_content_length - , h_matching_transfer_encoding - , h_matching_upgrade - - , h_connection - , h_content_length - , h_transfer_encoding - , h_upgrade - - , h_matching_transfer_encoding_chunked - , h_matching_connection_keep_alive - , h_matching_connection_close - - , h_transfer_encoding_chunked - , h_connection_keep_alive - , h_connection_close - }; - - -enum flags - { F_CHUNKED = 1 << 0 - , F_CONNECTION_KEEP_ALIVE = 1 << 1 - , F_CONNECTION_CLOSE = 1 << 2 - , F_TRAILING = 1 << 3 - , F_UPGRADE = 1 << 4 - , F_SKIPBODY = 1 << 5 - }; - - -#define CR '\r' -#define LF '\n' -#define LOWER(c) (unsigned char)(c | 0x20) -#define TOKEN(c) tokens[(unsigned char)c] - - -#define start_state (parser->type == PHP_HTTP_REQUEST ? s_start_req : s_start_res) - - -#if HTTP_PARSER_STRICT -# define STRICT_CHECK(cond) if (cond) goto error -# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) -#else -# define STRICT_CHECK(cond) -# define NEW_MESSAGE() start_state -#endif - - -size_t swoole_http_parser_execute (swoole_http_parser *parser, - const swoole_http_parser_settings *settings, - const char *data, - size_t len) -{ - char ch; - signed char c; - const char *p = data, *pe; - size_t to_read; - - enum state state = (enum state) parser->state; - enum header_states header_state = (enum header_states) parser->header_state; - uint32_t index = parser->index; - uint32_t nread = parser->nread; - - /* technically we could combine all of these (except for url_mark) into one - variable, saving stack space, but it seems more clear to have them - separated. */ - const char *header_field_mark = 0; - const char *header_value_mark = 0; - const char *fragment_mark = 0; - const char *query_string_mark = 0; - const char *path_mark = 0; - const char *url_mark = 0; - - if (len == 0) { - if (state == s_body_identity_eof) { - CALLBACK_MESSAGE_COMPLETE(); - } - return 0; - } - - if (state == s_header_field) - header_field_mark = data; - if (state == s_header_value) - header_value_mark = data; - if (state == s_req_fragment) - fragment_mark = data; - if (state == s_req_query_string) - query_string_mark = data; - if (state == s_req_path) - path_mark = data; - if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash - || state == s_req_schema_slash_slash || state == s_req_port - || state == s_req_query_string_start || state == s_req_query_string - || state == s_req_host - || state == s_req_fragment_start || state == s_req_fragment) - url_mark = data; - - for (p=data, pe=data+len; p != pe; p++) { - ch = *p; - - if (PARSING_HEADER(state)) { - ++nread; - /* Buffer overflow attack */ - if (nread > PHP_HTTP_MAX_HEADER_SIZE) goto error; - } - - switch (state) { - - case s_dead: - /* this state is used after a 'Connection: close' message - * the parser will error out if it reads another message - */ - goto error; - - case s_start_req_or_res: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = -1; - - CALLBACK2(message_begin); - - if (ch == 'H') - state = s_res_or_resp_H; - else { - parser->type = PHP_HTTP_REQUEST; - goto start_req_method_assign; - } - break; - } - - case s_res_or_resp_H: - if (ch == 'T') { - parser->type = PHP_HTTP_RESPONSE; - state = s_res_HT; - } else { - if (ch != 'E') goto error; - parser->type = PHP_HTTP_REQUEST; - parser->method = PHP_HTTP_HEAD; - index = 2; - state = s_req_method; - } - break; - - case s_start_res: - { - parser->flags = 0; - parser->content_length = -1; - - CALLBACK2(message_begin); - - switch (ch) { - case 'H': - state = s_res_H; - break; - - case CR: - case LF: - break; - - default: - goto error; - } - break; - } - - case s_res_H: - STRICT_CHECK(ch != 'T'); - state = s_res_HT; - break; - - case s_res_HT: - STRICT_CHECK(ch != 'T'); - state = s_res_HTT; - break; - - case s_res_HTT: - STRICT_CHECK(ch != 'P'); - state = s_res_HTTP; - break; - - case s_res_HTTP: - STRICT_CHECK(ch != '/'); - state = s_res_first_http_major; - break; - - case s_res_first_http_major: - if (ch < '1' || ch > '9') goto error; - parser->http_major = ch - '0'; - state = s_res_http_major; - break; - - /* major HTTP version or dot */ - case s_res_http_major: - { - if (ch == '.') { - state = s_res_first_http_minor; - break; - } - - if (ch < '0' || ch > '9') goto error; - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (parser->http_major > 999) goto error; - break; - } - - /* first digit of minor HTTP version */ - case s_res_first_http_minor: - if (ch < '0' || ch > '9') goto error; - parser->http_minor = ch - '0'; - state = s_res_http_minor; - break; - - /* minor HTTP version or end of request line */ - case s_res_http_minor: - { - if (ch == ' ') { - state = s_res_first_status_code; - break; - } - - if (ch < '0' || ch > '9') goto error; - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (parser->http_minor > 999) goto error; - break; - } - - case s_res_first_status_code: - { - if (ch < '0' || ch > '9') { - if (ch == ' ') { - break; - } - goto error; - } - parser->status_code = ch - '0'; - state = s_res_status_code; - break; - } - - case s_res_status_code: - { - if (ch < '0' || ch > '9') { - switch (ch) { - case ' ': - state = s_res_status; - break; - case CR: - state = s_res_line_almost_done; - break; - case LF: - state = s_header_field_start; - break; - default: - goto error; - } - break; - } - - parser->status_code *= 10; - parser->status_code += ch - '0'; - - if (parser->status_code > 999) goto error; - break; - } - - case s_res_status: - /* the human readable status. e.g. "NOT FOUND" - * we are not humans so just ignore this */ - if (ch == CR) { - state = s_res_line_almost_done; - break; - } - - if (ch == LF) { - state = s_header_field_start; - break; - } - break; - - case s_res_line_almost_done: - STRICT_CHECK(ch != LF); - state = s_header_field_start; - break; - - case s_start_req: - { - if (ch == CR || ch == LF) - break; - parser->flags = 0; - parser->content_length = -1; - - CALLBACK2(message_begin); - - if (ch < 'A' || 'Z' < ch) goto error; - - start_req_method_assign: - parser->method = (enum swoole_http_method) 0; - index = 1; - switch (ch) { - case 'C': parser->method = PHP_HTTP_CONNECT; /* or COPY, CHECKOUT */ break; - case 'D': parser->method = PHP_HTTP_DELETE; break; - case 'G': parser->method = PHP_HTTP_GET; break; - case 'H': parser->method = PHP_HTTP_HEAD; break; - case 'L': parser->method = PHP_HTTP_LOCK; break; - case 'M': parser->method = PHP_HTTP_MKCOL; /* or MOVE, MKCALENDAR, MKACTIVITY, MERGE, M-SEARCH */ break; - case 'N': parser->method = PHP_HTTP_NOTIFY; break; - case 'O': parser->method = PHP_HTTP_OPTIONS; break; - case 'P': parser->method = PHP_HTTP_POST; /* or PROPFIND, PROPPATCH, PUT, PATCH, PURGE */ break; - case 'R': parser->method = PHP_HTTP_REPORT; break; - case 'S': parser->method = PHP_HTTP_SUBSCRIBE; /* or SEARCH */ break; - case 'T': parser->method = PHP_HTTP_TRACE; break; - case 'U': parser->method = PHP_HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; - default: parser->method = PHP_HTTP_NOT_IMPLEMENTED; break; - } - state = s_req_method; - break; - } - case s_req_method: - { - const char *matcher; - if (ch == '\0') - goto error; - - matcher = method_strings[parser->method]; - if (ch == ' ') { - if (parser->method != PHP_HTTP_NOT_IMPLEMENTED && matcher[index] != '\0') { - parser->method = PHP_HTTP_NOT_IMPLEMENTED; - } - state = s_req_spaces_before_url; - } else if (parser->method == PHP_HTTP_NOT_IMPLEMENTED || ch == matcher[index]) { - ; /* nada */ - } else if (parser->method == PHP_HTTP_CONNECT) { - if (index == 1 && ch == 'H') { - parser->method = PHP_HTTP_CHECKOUT; - } else if (index == 2 && ch == 'P') { - parser->method = PHP_HTTP_COPY; - } else { - parser->method = PHP_HTTP_NOT_IMPLEMENTED; - } - } else if (parser->method == PHP_HTTP_MKCOL) { - if (index == 1 && ch == 'O') { - parser->method = PHP_HTTP_MOVE; - } else if (index == 3 && ch == 'A') { - parser->method = PHP_HTTP_MKCALENDAR; - } else if (index == 1 && ch == 'E') { - parser->method = PHP_HTTP_MERGE; - } else if (index == 1 && ch == '-') { - parser->method = PHP_HTTP_MSEARCH; - } else if (index == 2 && ch == 'A') { - parser->method = PHP_HTTP_MKACTIVITY; - } else { - parser->method = PHP_HTTP_NOT_IMPLEMENTED; - } - } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'R') { - parser->method = PHP_HTTP_PROPFIND; /* or HTTP_PROPPATCH */ - } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'U') { - parser->method = PHP_HTTP_PUT; /* or PHP_HTTP_PURGE */ - } else if (index == 1 && parser->method == PHP_HTTP_POST && ch == 'A') { - parser->method = PHP_HTTP_PATCH; - } else if (index == 1 && parser->method == PHP_HTTP_SUBSCRIBE && ch == 'E') { - parser->method = PHP_HTTP_SEARCH; - } else if (index == 2 && parser->method == PHP_HTTP_UNLOCK && ch == 'S') { - parser->method = PHP_HTTP_UNSUBSCRIBE; - } else if (index == 2 && parser->method == PHP_HTTP_PUT && ch == 'R') { - parser->method = PHP_HTTP_PURGE; - } else if (index == 4 && parser->method == PHP_HTTP_PROPFIND && ch == 'P') { - parser->method = PHP_HTTP_PROPPATCH; - } else { - parser->method = PHP_HTTP_NOT_IMPLEMENTED; - } - - ++index; - break; - } - case s_req_spaces_before_url: - { - if (ch == ' ') break; - - if (ch == '/' || ch == '*') { - MARK(url); - MARK(path); - state = s_req_path; - break; - } - - c = LOWER(ch); - - if (c >= 'a' && c <= 'z') { - MARK(url); - state = s_req_schema; - break; - } - - goto error; - } - - case s_req_schema: - { - c = LOWER(ch); - - if (c >= 'a' && c <= 'z') break; - - if (ch == ':') { - state = s_req_schema_slash; - break; - } else if (ch == '.') { - state = s_req_host; - break; - } else if ('0' <= ch && ch <= '9') { - state = s_req_host; - break; - } - - goto error; - } - - case s_req_schema_slash: - STRICT_CHECK(ch != '/'); - state = s_req_schema_slash_slash; - break; - - case s_req_schema_slash_slash: - STRICT_CHECK(ch != '/'); - state = s_req_host; - break; - - case s_req_host: - { - c = LOWER(ch); - if (c >= 'a' && c <= 'z') break; - if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break; - switch (ch) { - case ':': - state = s_req_port; - break; - case '/': - MARK(path); - state = s_req_path; - break; - case ' ': - /* The request line looks like: - * "GET http://foo.bar.com HTTP/1.1" - * That is, there is no path. - */ - CALLBACK(url); - state = s_req_http_start; - break; - default: - goto error; - } - break; - } - - case s_req_port: - { - if (ch >= '0' && ch <= '9') break; - switch (ch) { - case '/': - MARK(path); - state = s_req_path; - break; - case ' ': - /* The request line looks like: - * "GET http://foo.bar.com:1234 HTTP/1.1" - * That is, there is no path. - */ - CALLBACK(url); - state = s_req_http_start; - break; - default: - goto error; - } - break; - } - - case s_req_path: - { - if (normal_url_char[(unsigned char)ch]) break; - - switch (ch) { - case ' ': - CALLBACK(url); - CALLBACK(path); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - CALLBACK(path); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - CALLBACK(path); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - CALLBACK(path); - state = s_req_query_string_start; - break; - case '#': - CALLBACK(path); - state = s_req_fragment_start; - break; - default: - goto error; - } - break; - } - - case s_req_query_string_start: - { - if (normal_url_char[(unsigned char)ch]) { - MARK(query_string); - state = s_req_query_string; - break; - } - - switch (ch) { - case '?': - break; /* XXX ignore extra '?' ... is this right? */ - case ' ': - CALLBACK(url); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '#': - state = s_req_fragment_start; - break; - default: - goto error; - } - break; - } - - case s_req_query_string: - { - if (normal_url_char[(unsigned char)ch]) break; - - switch (ch) { - case '?': - /* allow extra '?' in query string */ - break; - case ' ': - CALLBACK(url); - CALLBACK(query_string); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - CALLBACK(query_string); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - CALLBACK(query_string); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '#': - CALLBACK(query_string); - state = s_req_fragment_start; - break; - default: - goto error; - } - break; - } - - case s_req_fragment_start: - { - if (normal_url_char[(unsigned char)ch]) { - MARK(fragment); - state = s_req_fragment; - break; - } - - switch (ch) { - case ' ': - CALLBACK(url); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - MARK(fragment); - state = s_req_fragment; - break; - case '#': - break; - default: - goto error; - } - break; - } - - case s_req_fragment: - { - if (normal_url_char[(unsigned char)ch]) break; - - switch (ch) { - case ' ': - CALLBACK(url); - CALLBACK(fragment); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - CALLBACK(fragment); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - CALLBACK(fragment); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - case '#': - break; - default: - goto error; - } - break; - } - - case s_req_http_start: - switch (ch) { - case 'H': - state = s_req_http_H; - break; - case ' ': - break; - default: - goto error; - } - break; - - case s_req_http_H: - STRICT_CHECK(ch != 'T'); - state = s_req_http_HT; - break; - - case s_req_http_HT: - STRICT_CHECK(ch != 'T'); - state = s_req_http_HTT; - break; - - case s_req_http_HTT: - STRICT_CHECK(ch != 'P'); - state = s_req_http_HTTP; - break; - - case s_req_http_HTTP: - STRICT_CHECK(ch != '/'); - state = s_req_first_http_major; - break; - - /* first digit of major HTTP version */ - case s_req_first_http_major: - if (ch < '1' || ch > '9') goto error; - parser->http_major = ch - '0'; - state = s_req_http_major; - break; - - /* major HTTP version or dot */ - case s_req_http_major: - { - if (ch == '.') { - state = s_req_first_http_minor; - break; - } - - if (ch < '0' || ch > '9') goto error; - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (parser->http_major > 999) goto error; - break; - } - - /* first digit of minor HTTP version */ - case s_req_first_http_minor: - if (ch < '0' || ch > '9') goto error; - parser->http_minor = ch - '0'; - state = s_req_http_minor; - break; - - /* minor HTTP version or end of request line */ - case s_req_http_minor: - { - if (ch == CR) { - state = s_req_line_almost_done; - break; - } - - if (ch == LF) { - state = s_header_field_start; - break; - } - - /* XXX allow spaces after digit? */ - - if (ch < '0' || ch > '9') goto error; - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (parser->http_minor > 999) goto error; - break; - } - - /* end of request line */ - case s_req_line_almost_done: - { - if (ch != LF) goto error; - state = s_header_field_start; - break; - } - - case s_header_field_start: - { - if (ch == CR) { - state = s_headers_almost_done; - break; - } - - if (ch == LF) { - /* they might be just sending \n instead of \r\n so this would be - * the second \n to denote the end of headers*/ - state = s_headers_almost_done; - goto headers_almost_done; - } - - c = TOKEN(ch); - - if (!c) goto error; - - MARK(header_field); - - index = 0; - state = s_header_field; - - switch (c) { - case 'c': - header_state = h_C; - break; - - case 'p': - header_state = h_matching_proxy_connection; - break; - - case 't': - header_state = h_matching_transfer_encoding; - break; - - case 'u': - header_state = h_matching_upgrade; - break; - - default: - header_state = h_general; - break; - } - break; - } - - case s_header_field: - { - c = TOKEN(ch); - - if (c) { - switch (header_state) { - case h_general: - break; - - case h_C: - index++; - header_state = (c == 'o' ? h_CO : h_general); - break; - - case h_CO: - index++; - header_state = (c == 'n' ? h_CON : h_general); - break; - - case h_CON: - index++; - switch (c) { - case 'n': - header_state = h_matching_connection; - break; - case 't': - header_state = h_matching_content_length; - break; - default: - header_state = h_general; - break; - } - break; - - /* connection */ - - case h_matching_connection: - index++; - if (index > sizeof(CONNECTION)-1 - || c != CONNECTION[index]) { - header_state = h_general; - } else if (index == sizeof(CONNECTION)-2) { - header_state = h_connection; - } - break; - - /* proxy-connection */ - - case h_matching_proxy_connection: - index++; - if (index > sizeof(PROXY_CONNECTION)-1 - || c != PROXY_CONNECTION[index]) { - header_state = h_general; - } else if (index == sizeof(PROXY_CONNECTION)-2) { - header_state = h_connection; - } - break; - - /* content-length */ - - case h_matching_content_length: - index++; - if (index > sizeof(CONTENT_LENGTH)-1 - || c != CONTENT_LENGTH[index]) { - header_state = h_general; - } else if (index == sizeof(CONTENT_LENGTH)-2) { - header_state = h_content_length; - } - break; - - /* transfer-encoding */ - - case h_matching_transfer_encoding: - index++; - if (index > sizeof(TRANSFER_ENCODING)-1 - || c != TRANSFER_ENCODING[index]) { - header_state = h_general; - } else if (index == sizeof(TRANSFER_ENCODING)-2) { - header_state = h_transfer_encoding; - } - break; - - /* upgrade */ - - case h_matching_upgrade: - index++; - if (index > sizeof(UPGRADE)-1 - || c != UPGRADE[index]) { - header_state = h_general; - } else if (index == sizeof(UPGRADE)-2) { - header_state = h_upgrade; - } - break; - - case h_connection: - case h_content_length: - case h_transfer_encoding: - case h_upgrade: - if (ch != ' ') header_state = h_general; - break; - - default: - assert(0 && "Unknown header_state"); - break; - } - break; - } - - if (ch == ':') { - CALLBACK(header_field); - state = s_header_value_start; - break; - } - - if (ch == CR) { - state = s_header_almost_done; - CALLBACK(header_field); - break; - } - - if (ch == LF) { - CALLBACK(header_field); - state = s_header_field_start; - break; - } - - // WARNING: Swoole changes! - // header name allow all normal chars (else will lead to error) - if (normal_url_char[(unsigned char)ch]) - { - break; - } - goto error; - } - - case s_header_value_start: - { - if (ch == ' ') break; - - MARK(header_value); - - state = s_header_value; - index = 0; - - c = LOWER(ch); - - if (ch == CR) { - CALLBACK(header_value); - header_state = h_general; - state = s_header_almost_done; - break; - } - - if (ch == LF) { - CALLBACK(header_value); - state = s_header_field_start; - break; - } - - switch (header_state) { - case h_upgrade: - parser->flags |= F_UPGRADE; - header_state = h_general; - break; - - case h_transfer_encoding: - /* looking for 'Transfer-Encoding: chunked' */ - if ('c' == c) { - header_state = h_matching_transfer_encoding_chunked; - } else { - header_state = h_general; - } - break; - - case h_content_length: - if (ch < '0' || ch > '9') goto error; - parser->content_length = ch - '0'; - break; - - case h_connection: - /* looking for 'Connection: keep-alive' */ - if (c == 'k') { - header_state = h_matching_connection_keep_alive; - /* looking for 'Connection: close' */ - } else if (c == 'c') { - header_state = h_matching_connection_close; - } else { - header_state = h_general; - } - break; - - default: - header_state = h_general; - break; - } - break; - } - - case s_header_value: - { - c = LOWER(ch); - - if (ch == CR) { - CALLBACK(header_value); - state = s_header_almost_done; - break; - } - - if (ch == LF) { - CALLBACK(header_value); - goto header_almost_done; - } - - switch (header_state) { - case h_general: - break; - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here"); - break; - - case h_content_length: - if (ch == ' ') break; - if (ch < '0' || ch > '9') goto error; - parser->content_length *= 10; - parser->content_length += ch - '0'; - break; - - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - index++; - if (index > sizeof(CHUNKED)-1 - || c != CHUNKED[index]) { - header_state = h_general; - } else if (index == sizeof(CHUNKED)-2) { - header_state = h_transfer_encoding_chunked; - } - break; - - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - index++; - if (index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[index]) { - header_state = h_general; - } else if (index == sizeof(KEEP_ALIVE)-2) { - header_state = h_connection_keep_alive; - } - break; - - /* looking for 'Connection: close' */ - case h_matching_connection_close: - index++; - if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) { - header_state = h_general; - } else if (index == sizeof(CLOSE)-2) { - header_state = h_connection_close; - } - break; - - case h_transfer_encoding_chunked: - case h_connection_keep_alive: - case h_connection_close: - if (ch != ' ') header_state = h_general; - break; - - default: - state = s_header_value; - header_state = h_general; - break; - } - break; - } - - case s_header_almost_done: - header_almost_done: - { - STRICT_CHECK(ch != LF); - - state = s_header_field_start; - - switch (header_state) { - case h_connection_keep_alive: - parser->flags |= F_CONNECTION_KEEP_ALIVE; - break; - case h_connection_close: - parser->flags |= F_CONNECTION_CLOSE; - break; - case h_transfer_encoding_chunked: - parser->flags |= F_CHUNKED; - break; - default: - break; - } - break; - } - - case s_headers_almost_done: - headers_almost_done: - { - STRICT_CHECK(ch != LF); - - if (parser->flags & F_TRAILING) { - /* End of a chunked request */ - CALLBACK_MESSAGE_COMPLETE(); - state = NEW_MESSAGE(); - break; - } - - nread = 0; - - if (parser->flags & F_UPGRADE || parser->method == PHP_HTTP_CONNECT) { - parser->upgrade = 1; - } - - /* Here we call the headers_complete callback. This is somewhat - * different than other callbacks because if the user returns 1, we - * will interpret that as saying that this message has no body. This - * is needed for the annoying case of receiving a response to a HEAD - * request. - */ - if (settings->on_headers_complete) { - switch (settings->on_headers_complete(parser)) { - case 0: - break; - - case 1: - parser->flags |= F_SKIPBODY; - break; - - default: - return p - data; /* Error */ - } - } - - /* Exit, the rest of the connect is in a different protocol. */ - if (parser->upgrade) { - CALLBACK_MESSAGE_COMPLETE(); - // WARNING: Swoole changes! - // swoole only support websocket upgrade - // so we return 0 to continue but return else to finish it - // return (p - data); - } - - if (parser->flags & F_SKIPBODY) { - CALLBACK_MESSAGE_COMPLETE(); - state = NEW_MESSAGE(); - } else if (parser->flags & F_CHUNKED) { - /* chunked encoding - ignore Content-Length header */ - state = s_chunk_size_start; - } else { - if (parser->content_length == 0) { - /* Content-Length header given but zero: Content-Length: 0\r\n */ - CALLBACK_MESSAGE_COMPLETE(); - state = NEW_MESSAGE(); - } else if (parser->content_length > 0) { - /* Content-Length header given and non-zero */ - state = s_body_identity; - } else { - if (parser->type == PHP_HTTP_REQUEST || swoole_http_should_keep_alive(parser)) { - /* Assume content-length 0 - read the next */ - CALLBACK_MESSAGE_COMPLETE(); - state = NEW_MESSAGE(); - } else { - /* Read body until EOF */ - state = s_body_identity_eof; - } - } - } - - break; - } - - case s_body_identity: - assert(pe >= p); - - to_read = MIN((size_t)(pe - p), (size_t)parser->content_length); - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - parser->content_length -= to_read; - if (parser->content_length == 0) { - CALLBACK_MESSAGE_COMPLETE(); - state = NEW_MESSAGE(); - } - } - break; - - /* read until EOF */ - case s_body_identity_eof: - to_read = pe - p; - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - } - break; - - case s_chunk_size_start: - { - assert(parser->flags & F_CHUNKED); - - c = unhex[(unsigned char)ch]; - if (c == -1) goto error; - parser->content_length = c; - state = s_chunk_size; - break; - } - - case s_chunk_size: - { - assert(parser->flags & F_CHUNKED); - - if (ch == CR) { - state = s_chunk_size_almost_done; - break; - } - - c = unhex[(unsigned char)ch]; - - if (c == -1) { - if (ch == ';' || ch == ' ') { - state = s_chunk_parameters; - break; - } - goto error; - } - - parser->content_length *= 16; - parser->content_length += c; - break; - } - - case s_chunk_parameters: - { - assert(parser->flags & F_CHUNKED); - /* just ignore this shit. TODO check for overflow */ - if (ch == CR) { - state = s_chunk_size_almost_done; - break; - } - break; - } - - case s_chunk_size_almost_done: - { - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - - if (parser->content_length == 0) { - parser->flags |= F_TRAILING; - state = s_header_field_start; - } else { - state = s_chunk_data; - } - break; - } - - case s_chunk_data: - { - assert(parser->flags & F_CHUNKED); - assert(pe >= p); - - to_read = MIN((size_t)(pe - p), (size_t)(parser->content_length)); - - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - } - - if (to_read == (size_t)parser->content_length) { - state = s_chunk_data_almost_done; - } - - parser->content_length -= to_read; - break; - } - - case s_chunk_data_almost_done: - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != CR); - state = s_chunk_data_done; - break; - - case s_chunk_data_done: - assert(parser->flags & F_CHUNKED); - STRICT_CHECK(ch != LF); - state = s_chunk_size_start; - break; - - default: - assert(0 && "unhandled state"); - goto error; - } - } - - CALLBACK_NOCLEAR(header_field); - CALLBACK_NOCLEAR(header_value); - CALLBACK_NOCLEAR(fragment); - CALLBACK_NOCLEAR(query_string); - CALLBACK_NOCLEAR(path); - CALLBACK_NOCLEAR(url); - - parser->state = state; - parser->header_state = header_state; - parser->index = index; - parser->nread = nread; - - return len; - -error: - parser->state = s_dead; - return (p - data); -} - - -int -swoole_http_should_keep_alive (swoole_http_parser *parser) -{ - if (parser->http_major > 0 && parser->http_minor > 0) { - /* HTTP/1.1 */ - if (parser->flags & F_CONNECTION_CLOSE) { - return 0; - } else { - return 1; - } - } else { - /* HTTP/1.0 or earlier */ - if (parser->flags & F_CONNECTION_KEEP_ALIVE) { - return 1; - } else { - return 0; - } - } -} - - -const char * swoole_http_method_str (enum swoole_http_method m) -{ - return method_strings[m]; -} - - -void -swoole_http_parser_init (swoole_http_parser *parser, enum swoole_http_parser_type t) -{ - parser->type = t; - parser->state = (t == PHP_HTTP_REQUEST ? s_start_req : (t == PHP_HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); - parser->nread = 0; - parser->upgrade = 0; - parser->flags = 0; - parser->method = 0; -} diff --git a/thirdparty/swoole_http_parser.h b/thirdparty/swoole_http_parser.h deleted file mode 100644 index f87bca5a82..0000000000 --- a/thirdparty/swoole_http_parser.h +++ /dev/null @@ -1,241 +0,0 @@ -/* Copyright 2009,2010 Ryan Dahl - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -/* modified by Moriyoshi Koizumi to make it fit to PHP source tree. */ -#ifndef swoole_http_parser_h -#define swoole_http_parser_h -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/* Compile with -DPHP_HTTP_PARSER_STRICT=0 to make less checks, but run - * faster - */ -#ifndef PHP_HTTP_PARSER_STRICT -# define PHP_HTTP_PARSER_STRICT 1 -#else -# define PHP_HTTP_PARSER_STRICT 0 -#endif - - -/* Maximium header size allowed */ -#define PHP_HTTP_MAX_HEADER_SIZE (80*1024) - - -typedef struct swoole_http_parser swoole_http_parser; -typedef struct swoole_http_parser_settings swoole_http_parser_settings; - - -/* Callbacks should return non-zero to indicate an error. The parser will - * then halt execution. - * - * The one exception is on_headers_complete. In a PHP_HTTP_RESPONSE parser - * returning '1' from on_headers_complete will tell the parser that it - * should not expect a body. This is used when receiving a response to a - * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: - * chunked' headers that indicate the presence of a body. - * - * http_data_cb does not return data chunks. It will be call arbitrarally - * many times for each string. E.G. you might get 10 callbacks for "on_path" - * each providing just a few characters more data. - */ -typedef int (*swoole_http_data_cb) (swoole_http_parser*, const char *at, size_t length); -typedef int (*swoole_http_cb) (swoole_http_parser*); - - -/* Request Methods */ -enum swoole_http_method - { PHP_HTTP_DELETE = 0 - , PHP_HTTP_GET - , PHP_HTTP_HEAD - , PHP_HTTP_POST - , PHP_HTTP_PUT - , PHP_HTTP_PATCH - /* pathological */ - , PHP_HTTP_CONNECT - , PHP_HTTP_OPTIONS - , PHP_HTTP_TRACE - /* webdav */ - , PHP_HTTP_COPY - , PHP_HTTP_LOCK - , PHP_HTTP_MKCOL - , PHP_HTTP_MOVE - , PHP_HTTP_MKCALENDAR - , PHP_HTTP_PROPFIND - , PHP_HTTP_PROPPATCH - , PHP_HTTP_SEARCH - , PHP_HTTP_UNLOCK - /* subversion */ - , PHP_HTTP_REPORT - , PHP_HTTP_MKACTIVITY - , PHP_HTTP_CHECKOUT - , PHP_HTTP_MERGE - /* upnp */ - , PHP_HTTP_MSEARCH - , PHP_HTTP_NOTIFY - , PHP_HTTP_SUBSCRIBE - , PHP_HTTP_UNSUBSCRIBE - /* proxy */ - , PHP_HTTP_PURGE - /* unknown, not implemented */ - , PHP_HTTP_NOT_IMPLEMENTED - }; - - -enum swoole_http_parser_type { PHP_HTTP_REQUEST, PHP_HTTP_RESPONSE, PHP_HTTP_BOTH }; - -enum state - { s_dead = 1 /* important that this is > 0 */ - - , s_start_req_or_res - , s_res_or_resp_H - , s_start_res - , s_res_H - , s_res_HT - , s_res_HTT - , s_res_HTTP - , s_res_first_http_major - , s_res_http_major - , s_res_first_http_minor - , s_res_http_minor - , s_res_first_status_code - , s_res_status_code - , s_res_status - , s_res_line_almost_done - - , s_start_req - - , s_req_method - , s_req_spaces_before_url - , s_req_schema - , s_req_schema_slash - , s_req_schema_slash_slash - , s_req_host - , s_req_port - , s_req_path - , s_req_query_string_start - , s_req_query_string - , s_req_fragment_start - , s_req_fragment - , s_req_http_start - , s_req_http_H - , s_req_http_HT - , s_req_http_HTT - , s_req_http_HTTP - , s_req_first_http_major - , s_req_http_major - , s_req_first_http_minor - , s_req_http_minor - , s_req_line_almost_done - - , s_header_field_start - , s_header_field - , s_header_value_start - , s_header_value - - , s_header_almost_done - - , s_headers_almost_done - /* Important: 's_headers_almost_done' must be the last 'header' state. All - * states beyond this must be 'body' states. It is used for overflow - * checking. See the PARSING_HEADER() macro. - */ - , s_chunk_size_start - , s_chunk_size - , s_chunk_size_almost_done - , s_chunk_parameters - , s_chunk_data - , s_chunk_data_almost_done - , s_chunk_data_done - - , s_body_identity - , s_body_identity_eof - }; - -struct swoole_http_parser { - /** PRIVATE **/ - unsigned char type : 2; - unsigned char flags : 6; - unsigned char state; - unsigned char header_state; - unsigned char index; - - uint32_t nread; - ssize_t content_length; - - /** READ-ONLY **/ - unsigned short http_major; - unsigned short http_minor; - unsigned short status_code; /* responses only */ - enum swoole_http_method method; /* requests only */ - - /* 1 = Upgrade header was present and the parser has exited because of that. - * 0 = No upgrade header present. - * Should be checked when http_parser_execute() returns in addition to - * error checking. - */ - char upgrade; - - /** PUBLIC **/ - void *data; /* A pointer to get hook to the "connection" or "socket" object */ -}; - - -struct swoole_http_parser_settings { - swoole_http_cb on_message_begin; - swoole_http_data_cb on_path; - swoole_http_data_cb on_query_string; - swoole_http_data_cb on_url; - swoole_http_data_cb on_fragment; - swoole_http_data_cb on_header_field; - swoole_http_data_cb on_header_value; - swoole_http_cb on_headers_complete; - swoole_http_data_cb on_body; - swoole_http_cb on_message_complete; -}; - - -void swoole_http_parser_init(swoole_http_parser *parser, enum swoole_http_parser_type type); - - -size_t swoole_http_parser_execute(swoole_http_parser *parser, - const swoole_http_parser_settings *settings, - const char *data, - size_t len); - - -/* If swoole_http_should_keep_alive() in the on_headers_complete or - * on_message_complete callback returns true, then this will be should be - * the last message on the connection. - * If you are the server, respond with the "Connection: close" header. - * If you are the client, close the connection. - */ -int swoole_http_should_keep_alive(swoole_http_parser *parser); - -/* Returns a string version of the HTTP method. */ -const char *swoole_http_method_str(enum swoole_http_method); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/tools/bootstrap.php b/tools/bootstrap.php index 4d991f8fbf..d6ac2e1d39 100755 --- a/tools/bootstrap.php +++ b/tools/bootstrap.php @@ -10,7 +10,6 @@ define('ROOT_DIR', dirname(__DIR__)); const LIBRARY_DIR = ROOT_DIR . '/library'; const LIBRARY_SRC_DIR = LIBRARY_DIR . '/src'; -const PHP_TAG = ' ' . implode("\n> ", $output) . "" . PHP_EOL; @@ -126,7 +125,7 @@ function swoole_execute_and_check(array $commands): void echo "=========== Finish Done ============" . PHP_EOL . PHP_EOL; } -function scan_dir(string $dir, callable $filter = null): array +function scan_dir(string $dir, ?callable $filter = null): array { $files = array_filter(scandir($dir), function (string $file) { return $file[0] !== '.'; @@ -137,7 +136,7 @@ function scan_dir(string $dir, callable $filter = null): array return array_values($filter ? array_filter($files, $filter) : $files); } -function scan_dir_recursive(string $dir, callable $filter = null): array +function scan_dir_recursive(string $dir, ?callable $filter = null): array { $result = []; $files = scan_dir($dir, $filter); @@ -162,7 +161,7 @@ function file_size(string $filename, int $decimals = 2): string function swoole_git_files(): array { $root = SWOOLE_SOURCE_ROOT; - return explode(PHP_EOL, `cd {$root} && git ls-files`); + return explode(PHP_EOL, shell_exec("cd {$root} && git ls-files")); } function swoole_source_list(array $ext_list = [], array $excepts = []): array @@ -192,162 +191,3 @@ function swoole_source_list(array $ext_list = [], array $excepts = []): array return $source_list; } - -function swoole_library_files($librarySrcDir) -{ - $files = []; - - $file_spl_objects = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($librarySrcDir, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::LEAVES_ONLY - ); - - foreach ($file_spl_objects as $full_file_name => $file_spl_object) { - $files[] = str_replace($librarySrcDir . '/', '', $full_file_name); - } - - return $files; -} - -function swoole_remove_php_comments($code) -{ - $newCode = ''; - $commentTokens = [T_COMMENT]; - - if (defined('T_DOC_COMMENT')) { - $commentTokens[] = T_DOC_COMMENT; - } - - if (defined('T_ML_COMMENT')) { - $commentTokens[] = T_ML_COMMENT; - } - - $tokens = token_get_all($code); - foreach ($tokens as $token) { - if (is_array($token)) { - if (in_array($token[0], $commentTokens)) { - continue; - } - $token = $token[1]; - } - $newCode .= $token; - } - - return $newCode; -} - -class SwooleLibraryBuilder -{ - public $checkFileChange; - public $libraryDir; - public $librarySrcDir; - public $files; - public $srcPath; - public $stripComments = true; - public $symbolPrefix = 'swoole'; - public $outputFile; - - function make() - { - if ($this->checkFileChange) { - preg_match( - '/^(\d+)/', - trim(shell_exec('cd ' . $this->libraryDir . ' && git diff --shortstat') ?? ''), - $file_change - ); - $file_change = (int)($file_change[1] ?? 0); - if ($file_change > 0) { - swoole_error($file_change . ' file changed in [' . $this->libraryDir . ']'); - } - } - - $commit_id = trim(shell_exec('cd ' . $this->libraryDir . ' && git rev-parse HEAD')); - if (!$commit_id || strlen($commit_id) != 40) { - swoole_error('Unable to get commit id of library in [' . $this->libraryDir . ']'); - } - - $ignore_files = ['vendor_init.php',]; - - $diff_files = array_diff(swoole_library_files($this->librarySrcDir), $this->files); - foreach ($diff_files as $k => $f) { - if (in_array($f, $ignore_files)) { - unset($diff_files[$k]); - } - } - - if (!empty($diff_files)) { - swoole_error('Some files are not loaded: ', ...$diff_files); - } - - foreach ($this->files as $file) { - if (!file_exists($this->librarySrcDir . '/' . $file)) { - swoole_error("Unable to find source file [{$file}]"); - } - } - - $source_str = $eval_str = ''; - foreach ($this->files as $file) { - $php_file = $this->librarySrcDir . '/' . $file; - if (strpos(`/usr/bin/env php -n -l {$php_file} 2>&1`, 'No syntax errors detected') === false) { - swoole_error("Syntax error in file [{$php_file}]"); - } else { - swoole_ok("Syntax correct in [{$file}]"); - } - $code = file_get_contents($php_file); - if ($code === false) { - swoole_error("Can not read file [{$file}]"); - } - if (strpos($code, PHP_TAG) !== 0) { - swoole_error("File [{$file}] must start with \"stripComments) { - $code = swoole_remove_php_comments($code); - } - $name = unCamelize(str_replace(['/', '.php'], ['_', ''], $file)); - // keep line breaks to align line numbers - $code = rtrim(substr($code, strlen(PHP_TAG))); - $code = str_replace(['\\', '"', "\n"], ['\\\\', '\\"', "\\n\"\n\""], $code); - $code = implode("\n" . space(4), explode("\n", $code)); - $filename = "{$this->srcPath}/{$file}"; - $source_str .= "static const char* {$this->symbolPrefix}_library_source_{$name} =\n" . space(4) . "\"{$code}\\n\";\n\n"; - $eval_str .= space(4) . "zend::eval({$this->symbolPrefix}_library_source_{$name}, \"{$filename}\");\n"; - } - $source_str = rtrim($source_str); - $eval_str = rtrim($eval_str); - - global $argv; - $generator = $argv[0]; - $content = <<symbolPrefix}_load_library() -{ -{$eval_str} -} - -CODE; - - if (file_put_contents($this->outputFile, $content) != strlen($content)) { - swoole_error('Can not write source codes to ' . $this->outputFile); - } - swoole_success("Generated swoole php library successfully!"); - } -} diff --git a/tools/build-library.php b/tools/build-library.php index 13c1e39b04..13b00f83c0 100755 --- a/tools/build-library.php +++ b/tools/build-library.php @@ -1,103 +1,8 @@ #!/usr/bin/env php libraryDir = LIBRARY_DIR; -$builder->librarySrcDir = LIBRARY_SRC_DIR; -$builder->checkFileChange = !isset($argv[1]) or $argv[1] != 'dev'; -$builder->outputFile = ROOT_DIR . '/ext-src/php_swoole_library.h'; -$builder->stripComments = true; -$builder->symbolPrefix = 'swoole'; -$builder->srcPath = '@swoole-src/library'; - -/* Notice: Sort by dependency */ -$builder->files = [ - # # - 'constants.php', - # # - 'std/exec.php', - # # - 'core/Constant.php', - 'core/StringObject.php', - 'core/MultibyteStringObject.php', - 'core/Exception/ArrayKeyNotExists.php', - 'core/ArrayObject.php', - 'core/ObjectProxy.php', - 'core/Coroutine/WaitGroup.php', - 'core/Coroutine/Server.php', - 'core/Coroutine/Server/Connection.php', - 'core/Coroutine/Barrier.php', - 'core/Coroutine/Http/ClientProxy.php', - 'core/Coroutine/Http/functions.php', - # # - 'core/ConnectionPool.php', - 'core/Database/ObjectProxy.php', - 'core/Database/MysqliConfig.php', - 'core/Database/MysqliException.php', - 'core/Database/MysqliPool.php', - 'core/Database/MysqliProxy.php', - 'core/Database/MysqliStatementProxy.php', - 'core/Database/PDOConfig.php', - 'core/Database/PDOPool.php', - 'core/Database/PDOProxy.php', - 'core/Database/PDOStatementProxy.php', - 'core/Database/RedisConfig.php', - 'core/Database/RedisPool.php', - # # - 'core/Http/Status.php', - # # - 'core/Curl/Exception.php', - 'core/Curl/Handler.php', - # # - 'core/FastCGI.php', - 'core/FastCGI/Record.php', - 'core/FastCGI/Record/Params.php', - 'core/FastCGI/Record/AbortRequest.php', - 'core/FastCGI/Record/BeginRequest.php', - 'core/FastCGI/Record/Data.php', - 'core/FastCGI/Record/EndRequest.php', - 'core/FastCGI/Record/GetValues.php', - 'core/FastCGI/Record/GetValuesResult.php', - 'core/FastCGI/Record/Stdin.php', - 'core/FastCGI/Record/Stdout.php', - 'core/FastCGI/Record/Stderr.php', - 'core/FastCGI/Record/UnknownType.php', - 'core/FastCGI/FrameParser.php', - 'core/FastCGI/Message.php', - 'core/FastCGI/Request.php', - 'core/FastCGI/Response.php', - 'core/FastCGI/HttpRequest.php', - 'core/FastCGI/HttpResponse.php', - 'core/Coroutine/FastCGI/Client.php', - 'core/Coroutine/FastCGI/Client/Exception.php', - 'core/Coroutine/FastCGI/Proxy.php', - # # - 'core/Process/Manager.php', - # # - 'core/Server/Admin.php', - 'core/Server/Helper.php', - # # - 'core/NameResolver.php', - 'core/NameResolver/Exception.php', - 'core/NameResolver/Cluster.php', - 'core/NameResolver/Redis.php', - 'core/NameResolver/Nacos.php', - 'core/NameResolver/Consul.php', - # # - 'core/Coroutine/functions.php', - # # - 'ext/curl.php', - 'ext/sockets.php', - # # - 'functions.php', - 'alias.php', - 'alias_ns.php', -]; - -$builder->make(); +$argv[1] = realpath(__DIR__ . '/../library/src'); +putenv('SWOOLE_DIR=' . realpath(__DIR__ . '/..')); +require __DIR__ . '/vendor/bin/make-library.php'; diff --git a/tools/code-generator.php b/tools/code-generator.php index c32c15055c..2171f82765 100755 --- a/tools/code-generator.php +++ b/tools/code-generator.php @@ -63,7 +63,7 @@ // generate ERROR strings $swoole_error_cc = ROOT_DIR . '/src/core/error.cc'; $swoole_error_cc_content = file_get_contents($swoole_error_cc); -$swstrerror_output = space(4) . "switch(code) {\n"; +$swstrerror_output = space(4) . "switch (code) {\n"; foreach ($matches_error[0] as $match) { // convert error code to swstrerror $sw_error_str = implode(' ', explode('_', strtolower(str_replace('SW_ERROR_', '', $match)))); diff --git a/tools/composer.json b/tools/composer.json new file mode 100644 index 0000000000..55324d331a --- /dev/null +++ b/tools/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "swoole/make-library": "v1.0.0" + } +} diff --git a/tools/config-generator.php b/tools/config-generator.php deleted file mode 100755 index 7f83654a38..0000000000 --- a/tools/config-generator.php +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env php - false]); - -// config.m4 -$output = space(8) . implode(" \\\n" . space(8), $source_list); -$output = preg_replace('/(swoole_source_file=[^\n]+\n)[^"]+"/', "$1{$output}\"", $config_m4_content, 1, $count); -if ($count !== 1) { - swoole_error('Update source files in config.m4 error!'); -} -file_put_contents($config_m4, $output); -swoole_ok('Generate config.m4 ok!'); - -// cmake -// $cmake_lists = __DIR__ . '/../CMakeLists.txt'; -// $cmake_lists_content = file_get_contents($cmake_lists); -// $output = space(4) . implode("\n" . space(4), $source_list) . "\n"; -// $output = preg_replace('/(set\(SOURCE_FILES\n)[^)]+\)/', "$1{$output})", $cmake_lists_content, 1, $count); -// if ($count !== 1) { -// swoole_error('Update source files in CMakeLists.txt error!'); -// } -// file_put_contents($cmake_lists, $output); -// swoole_ok('Generate CMakeLists.txt ok!'); - -swoole_success('Config generator successfully done!'); diff --git a/tools/pecl-package.php b/tools/pecl-package.php index 03ee236b5b..d42274affd 100755 --- a/tools/pecl-package.php +++ b/tools/pecl-package.php @@ -87,7 +87,6 @@ function check_source_ver(string $expect_ver, $source_file) } // all check -swoole_execute_and_check(['php', __DIR__ . '/config-generator.php']); swoole_execute_and_check(['php', __DIR__ . '/arginfo-check.php']); swoole_execute_and_check(['php', __DIR__ . '/code-generator.php']); if (file_exists(LIBRARY_DIR)) { diff --git a/tools/templates/version.tpl.h b/tools/templates/version.tpl.h index 24ad9f888b..60e94c1e33 100644 --- a/tools/templates/version.tpl.h +++ b/tools/templates/version.tpl.h @@ -27,12 +27,7 @@ #define SWOOLE_API_VERSION_ID api."\n" ?> #define SWOOLE_BUG_REPORT \ - "A bug occurred in Swoole-v" SWOOLE_VERSION ", please report it.\n" \ - "The Swoole developers probably don't know about it,\n" \ - "and unless you report it, chances are it won't be fixed.\n" \ - "You can read How to report a bug doc before submitting any bug reports:\n" \ - ">> https://github.com/swoole/swoole-src/blob/master/.github/ISSUE.md \n" \ - "Please do not send bug reports in the mailing list or personal letters.\n" \ - "The issue page is also suitable to submit feature requests.\n" - + "A process crash occurred in Swoole-v" SWOOLE_VERSION ". Please report this issue.\n" \ + "You can refer to the documentation below, submit an issue to us on GitHub.\n" \ + ">> https://github.com/swoole/swoole-src/blob/master/docs/ISSUE.md\n" #endif diff --git a/travis/README.md b/travis/README.md deleted file mode 100644 index 07ef25edde..0000000000 --- a/travis/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Travis tests - - The automated test scripts in this directory can not only run on Travis CI. Powered by docker container technology, it can run on any systems. You only need to run the `route.sh` script to create containers of multiple PHP environments then it will run Swoole's build tests and unit tests on multiple systems automatically. - -### With special branch - -```shell -TRAVIS_BRANCH=alpine ./route.sh -``` - -### Enter the container - -> You can cancel the unit test by `CTRL+C` - -```shell -docker exec -it -e LINES=$(tput lines) -e COLUMNS=$(tput cols) swoole /bin/bash -``` diff --git a/travis/docker-compile.sh b/travis/docker-compile.sh deleted file mode 100755 index 73707cd9fe..0000000000 --- a/travis/docker-compile.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/sh -e -__CURRENT__=$(pwd) -__DIR__=$(cd "$(dirname "$0")";pwd) - -if [ ! -f "/.dockerenv" ]; then - echo "" && echo "❌ This script is just for Docker env!" - exit -fi - -cd "${__DIR__}" && cd .. -./clear.sh -phpize -./configure \ ---enable-openssl \ ---enable-sockets \ ---enable-mysqlnd \ ---enable-swoole-curl \ ---enable-cares \ ---enable-swoole-pgsql - -make -j$(cat /proc/cpuinfo | grep processor | wc -l) -make install -docker-php-ext-enable swoole -php -v -php -m -php --ri curl -php --ri swoole - diff --git a/travis/docker-compose.yml b/travis/docker-compose.yml deleted file mode 100755 index 75abd2ff4f..0000000000 --- a/travis/docker-compose.yml +++ /dev/null @@ -1,59 +0,0 @@ -version: '3.4' -services: - swoole: - container_name: "swoole" - image: "phpswoole/php:${PHP_VERSION}" - volumes: - - "${TRAVIS_BUILD_DIR}:/swoole-src:rw" - working_dir: /swoole-src - ulimits: - core: -1 - privileged: true - depends_on: - - mysql - - redis - - pgsql - dns: - - 8.8.8.8 - - 1.1.1.1 - environment: - SWOOLE_BRANCH: "${TRAVIS_BRANCH}" - command: tail -f /etc/group - mysql: - container_name: "mysql" - image: "twosee/swoole:mysql5" - volumes: - - ./data/mysql:/var/lib/mysql:rw - - ./data/run/mysqld:/var/run/mysqld:rw - environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: test - MYSQL_USER: swoole - MYSQL_PASSWORD: swoole - pgsql: - image: postgres:14 - container_name: "pgsql" - environment: - POSTGRES_USER: root - POSTGRES_DB: test - POSTGRES_PASSWORD: root - redis: - container_name: "redis" - image: "twosee/swoole:redis" - volumes: - - ./data/redis:/var/lib/redis:rw - - ./data/run/redis:/var/run/redis:rw - sysctls: - net.core.somaxconn: 65535 - httpbin: - container_name: "httpbin" - image: "kennethreitz/httpbin" - tinyproxy: - container_name: "tinyproxy" - image: "vimagick/tinyproxy" - golang-h2demo: - container_name: "golang-h2demo" - image: "phpswoole/golang-h2demo" - socks5: - container_name: "socks5" - image: "xkuma/socks5" diff --git a/travis/docker-route.sh b/travis/docker-route.sh deleted file mode 100755 index 80b890b889..0000000000 --- a/travis/docker-route.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -e -__CURRENT__=$(pwd) -__DIR__=$(cd "$(dirname "$0")";pwd) - -# enter the dir -cd "${__DIR__}" - -# show system info -date && echo "" -uname -a && echo "" - -# show php info -php -v && echo "" - -# compile in docker -echo "" && echo "📦 Compile test in docker..." && echo "" -./docker-compile.sh - -# run unit tests -echo "" && echo "📋 PHP unit tests in docker..." && echo "" -./run-tests.sh diff --git a/travis/pecl-install.sh b/travis/pecl-install.sh deleted file mode 100755 index 1e95efaab1..0000000000 --- a/travis/pecl-install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -e -__CURRENT__=`pwd` -__DIR__=$(cd "$(dirname "$0")";pwd) - -cd ${__DIR__} && cd ../ && \ -pecl config-show && \ -php ./tools/pecl-package.php && package_file="`ls | grep swoole-*tgz`" && \ -echo "\n" | pecl install -f ${package_file} | tee pecl.log && \ -cat pecl.log | grep "successfully" && \ -pecl uninstall swoole && \ -rm -f pecl.log diff --git a/travis/route.sh b/travis/route.sh deleted file mode 100755 index 1e7b092f62..0000000000 --- a/travis/route.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/sh -__CURRENT__=`pwd` -__DIR__=$(cd "$(dirname "$0")";pwd) - -export DOCKER_COMPOSE_VERSION="1.21.0" -[ -z "${TRAVIS_BRANCH}" ] && export TRAVIS_BRANCH="master" -[ -z "${TRAVIS_BUILD_DIR}" ] && export TRAVIS_BUILD_DIR=$(cd "$(dirname "$0")";cd ../;pwd) -[ -z "${PHP_VERSION_ID}" ] && export PHP_VERSION_ID=`php -r "echo PHP_VERSION_ID;"` -if [ ${PHP_VERSION_ID} -lt 80200 ]; then - export PHP_VERSION="`php -r "echo PHP_MAJOR_VERSION;"`.`php -r "echo PHP_MINOR_VERSION;"`" -else - export PHP_VERSION="rc" -fi -if [ "${TRAVIS_BRANCH}" = "alpine" ]; then - export PHP_VERSION="${PHP_VERSION}-alpine" -fi - -echo "\n🗻 With PHP version ${PHP_VERSION} on ${TRAVIS_BRANCH} branch" - -check_docker_dependency(){ - if [ "`docker -v 2>&1 | grep "version"`"x = ""x ]; then - echo "\n❌ Docker not found!" - exit 1 - elif [ "`docker ps 2>&1 | grep Cannot`"x != ""x ]; then - echo "\n❌ Docker is not running!" - exit 1 - else - which "docker-compose" > /dev/null - if [ $? -ne 0 ]; then - echo "\n🤔 Can not found docker-compose, try to install it now...\n" - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose && \ - chmod +x docker-compose && \ - sudo mv docker-compose /usr/local/bin - - which "docker-compose" > /dev/null - if [ $? -ne 0 ]; then - echo "\n❌ Install docker-compose failed!" - exit 1 - fi - - docker -v && docker-compose -v - fi - fi -} - -prepare_data_files(){ - cd ${__DIR__} && \ - remove_data_files && \ - mkdir -p \ - data \ - data/run \ - data/mysql data/run/mysqld \ - data/redis data/run/redis && \ - chmod -R 777 data - if [ $? -ne 0 ]; then - echo "\n❌ Prepare data files failed!" - exit 1 - fi -} - -remove_data_files(){ - cd ${__DIR__} && \ - rm -rf ../travis/data -} - -start_docker_containers(){ - remove_docker_containers - cd ${__DIR__} && \ - docker-compose up -d && \ - docker ps -a - if [ $? -ne 0 ]; then - echo "\n❌ Create containers failed!" - exit 1 - fi -} - -remove_docker_containers(){ - cd ${__DIR__} && \ - docker-compose kill > /dev/null 2>&1 && \ - docker-compose rm -f > /dev/null 2>&1 -} - -run_tests_in_docker(){ - docker exec swoole touch /.travisenv && \ - docker exec swoole /swoole-src/travis/docker-route.sh - if [ $? -ne 0 ]; then - echo "\n❌ Run tests failed!" - exit 1 - fi -} - -remove_tests_resources(){ - remove_docker_containers - remove_data_files -} - -check_docker_dependency - -echo "\n📖 Prepare for files...\n" -prepare_data_files - -echo "📦 Start docker containers...\n" -start_docker_containers # && trap "remove_tests_resources" - -echo "\n⏳ Run tests in docker...\n" -run_tests_in_docker - -echo "\n🚀🚀🚀Completed successfully🚀🚀🚀\n" diff --git a/travis/run-tests.sh b/travis/run-tests.sh deleted file mode 100755 index da9497b644..0000000000 --- a/travis/run-tests.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/sh -e -__CURRENT__=`pwd` -__DIR__=$(cd "$(dirname "$0")";pwd) - -[ -z "${SWOOLE_BRANCH}" ] && export SWOOLE_BRANCH="master" - -#-------------PHPT------------- -cd ${__DIR__} && cd ../tests/ - -# initialization -echo "" && echo "⭐️ Initialization for tests..." && echo "" -php ./init -cd ./include/lib -echo "composer update" -composer update -cd - -echo "" - -# debug -for debug_file in ${__DIR__}/debug/*.php -do - if test -f "${debug_file}";then - debug_file_basename="`basename ${debug_file}`" - echo "" && echo "====== RUN ${debug_file_basename} ======" && echo "" - php "${debug_file}" - echo "" && echo "========================================" && echo "" - fi -done - -# run tests @params($1=list_file, $2=options) -run_tests(){ - ./start.sh \ - "`tr '\n' ' ' < ${1} | xargs`" \ - -w ${1} \ - ${2} -} - -has_failures(){ - cat tests.list -} - -should_exit_with_error(){ - if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then - set +e - find ./ -type f -name "*.mem" - set -e - else - has_failures - fi -} - -touch tests.list -trap "rm -f tests.list; echo ''; echo '⌛ Done on '`date "+%Y-%m-%d %H:%M:%S"`;" EXIT - -cpu_num="$(/usr/bin/env php -r "echo swoole_cpu_num() * 2;")" -options="-j${cpu_num}" - -echo "" && echo "🌵️️ Current branch is ${SWOOLE_BRANCH}" && echo "" -if [ "${SWOOLE_BRANCH}" = "valgrind" ]; then - dir="base" - options="${options} -m" -else - dir="swoole_*" -fi -echo "${dir}" > tests.list -for i in 1 2 3 4 5 -do - if [ "`has_failures`" ]; then - if [ ${i} -gt "1" ]; then - sleep ${i} - echo "" && echo "😮 Retry failed tests#${i}:" && echo "" - fi - cat tests.list - timeout=`echo | expr ${i} \* 15 + 15` - options="${options} --set-timeout ${timeout}" - run_tests tests.list "${options}" - else - break - fi -done -if [ "`should_exit_with_error`" ]; then - exit 255 -fi